###############################################################################
#
## Desired directory layout for python package
##
## ${BLD_DIR}/
##    Wrapping/Modules/ITK${MODULE}/itk${MODULE_ITEM}Python.cpp # Generated by swig
##    Wrapping/Generator/Python/
##      - WrapITK.pth    # A path file that should point to this directory (cmake configured dynamically)
##      - itkConfig.py   # cmake configured file with paths, and dynamic compile time choices
##      - itk/
##        - __init__.py  # cmake copied file
##        - support/
##          - itk(Extras|Template|Base|LazyLoading|...).py # static python files cmake copied
##        - Configuration/
##          -- ITK${MODULE_ITEM}Config.py # igenerator.py output config database index files for .so
##          -- ITK${MODULE_ITEM}_snake_case.py # igenerator.py output config database index files for .so
##        - ITK${MODULE_ITEM}Python.py    # swig generated file
##        - _ITK${MODULE}.so              # swig python wrapped shared lib functions, copied here manually

include_directories("${Python3_INCLUDE_DIRS}")

include_directories("${CMAKE_CURRENT_SOURCE_DIR}")

include(itkTargetLinkLibrariesWithDynamicLookup)

###############################################################################
# store the current dir, so it can be reused later
set(ITK_WRAP_PYTHON_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL "python source dir")


###############################################################################
# create the python directory in the classindex dir
file(MAKE_DIRECTORY ${WRAPPER_MASTER_INDEX_OUTPUT_DIR}/python)


###############################################################################
# Configure Python wrapping installation
if(Python3_EXECUTABLE AND NOT PY_SITE_PACKAGES_PATH)
  set(python_check "try:\n    import sysconfig\n    print(sysconfig.get_path('platlib'))\nexcept:\n    pass")
  file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/detect_site_package_path.py ${python_check})
  execute_process(COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/detect_site_package_path.py"
    OUTPUT_VARIABLE py_spp
    ERROR_VARIABLE py_spp
  )

  execute_process(COMMAND "${Python3_EXECUTABLE}" -c "import sys\nprint(sys.prefix)"
    OUTPUT_VARIABLE py_prefix
    ERROR_VARIABLE py_prefix
    OUTPUT_STRIP_TRAILING_WHITESPACE
    )
  if(NOT "${CMAKE_INSTALL_PREFIX}" STREQUAL "${py_prefix}")
    install(CODE "message(WARNING \"CMAKE_INSTALL_PREFIX, ${CMAKE_INSTALL_PREFIX}, does not match Python's prefix, ${py_prefix}\")")
  endif()
  string(REGEX REPLACE "\n" "" py_spp_no_newline "${py_spp}")
  file(TO_CMAKE_PATH "${py_spp_no_newline}" py_spp_nobackslashes)

  set(PY_SITE_PACKAGES_PATH "${py_spp_nobackslashes}" CACHE STRING "Python site-packages directory to install Python bindings.")
  mark_as_advanced(PY_SITE_PACKAGES_PATH)
endif()

if(NOT PY_SITE_PACKAGES_PATH)
  message(SEND_ERROR "Please set PY_SITE_PACKAGES_PATH to the Python bindings installation directory.")
endif()

macro(WRAP_ITK_PYTHON_BINDINGS_INSTALL path wrapper_library_name)
  set(_component_module "")
  if(WRAP_ITK_INSTALL_COMPONENT_PER_MODULE)
    if("${wrapper_library_name}" MATCHES "^ITK(PyUtils|PyBase)$")
      set(_component_module "ITKCommon")
    else()
      set(_component_module "${wrapper_library_name}")
    endif()
  endif()
  install(FILES ${ARGN}
    DESTINATION "${PY_SITE_PACKAGES_PATH}/${path}"
    COMPONENT ${_component_module}${WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER}RuntimeLibraries
    )
  unset(_component_module)
endmacro()


###############################################################################
# Configure the path-dependent itkConfig.py

# we specify these directories with relative paths  so that the file can be
# bundled up into an install conventiently. Python will take care of turning
# the / path separator into \ on windows if needed.
if(NOT EXTERNAL_WRAP_ITK_PROJECT)

  # copy the files to expose build options in python
  set(ITK_WRAP_PYTHON_VECTOR_REAL)
  foreach(t ${WRAP_ITK_VECTOR_REAL})
    foreach(d ${ITK_WRAP_IMAGE_DIMS})
      list(APPEND ITK_WRAP_PYTHON_VECTOR_REAL ${ITKT_${t}${d}})
    endforeach()
  endforeach()
  set(ITK_WRAP_PYTHON_COV_VECTOR_REAL)
  foreach(t ${WRAP_ITK_COV_VECTOR_REAL})
    foreach(d ${ITK_WRAP_IMAGE_DIMS})
      list(APPEND ITK_WRAP_PYTHON_COV_VECTOR_REAL ${ITKT_${t}${d}})
    endforeach()
  endforeach()
  set(ITK_WRAP_PYTHON_RGB)
  foreach(t ${WRAP_ITK_RGB})
    list(APPEND ITK_WRAP_PYTHON_RGB ${ITKT_${t}})
  endforeach()
  set(ITK_WRAP_PYTHON_RGBA)
  foreach(t ${WRAP_ITK_RGBA})
    list(APPEND ITK_WRAP_PYTHON_RGBA ${ITKT_${t}})
  endforeach()
  set(ITK_WRAP_PYTHON_COMPLEX_REAL)
  foreach(t ${WRAP_ITK_COMPLEX_REAL})
    list(APPEND ITK_WRAP_PYTHON_COMPLEX_REAL ${ITKT_${t}})
  endforeach()

  file(RELATIVE_PATH CONFIG_PYTHON_CONFIGPY_DIR ${ITK_WRAP_PYTHON_ROOT_BINARY_DIR} ${ITK_WRAP_PYTHON_SWIG_CONFIGURATION_DIR})
  file(RELATIVE_PATH CONFIG_PYTHON_SWIGPY_DIR   ${ITK_WRAP_PYTHON_ROOT_BINARY_DIR} ${ITK_PYTHON_PACKAGE_DIR})
  file(RELATIVE_PATH CONFIG_PYTHON_SWIGLIB_DIR  ${ITK_WRAP_PYTHON_ROOT_BINARY_DIR} ${ITK_PYTHON_PACKAGE_DIR})

  configure_file("${CMAKE_CURRENT_SOURCE_DIR}/itkConfig.py.in"
                 "${ITK_WRAP_PYTHON_ROOT_BINARY_DIR}/itkConfig.py"
                 @ONLY)

  WRAP_ITK_PYTHON_BINDINGS_INSTALL(/ "ITKCommon" "${ITK_WRAP_PYTHON_ROOT_BINARY_DIR}/itkConfig.py")
endif()


###############################################################################
# Copy python files for out-of-source builds, and set up install of same.

if(NOT EXTERNAL_WRAP_ITK_PROJECT)
  set(ITK_PYTHON_SUPPORT_MODULES
    support/base
    support/template_class
    support/types
    support/extras
    support/xarray
    support/lazy
    support/helpers
    support/init_helpers
    support/build_options
    )
  set(ITK_INIT_MODULE "${CMAKE_CURRENT_SOURCE_DIR}/itk/__init__.py")
  set(ITK_CONFIGURATION_INIT_MODULE "${ITK_PYTHON_PACKAGE_DIR}/Configuration/__init__.py")
  # Done listing files.
  # Now copy these files if necessary.

  # If not an in-source build
  if(NOT "${WrapITK_BINARY_DIR}" MATCHES "^${WrapITK_SOURCE_DIR}$")
    set(ITK_WRAP_PYTHON_FILES)
    foreach(_file ${ITK_PYTHON_SUPPORT_MODULES})
      set(src "${CMAKE_CURRENT_SOURCE_DIR}/itk/${_file}.py")
      set(copy_tgt    "${ITK_PYTHON_PACKAGE_DIR}/${_file}.py")
      list(APPEND ITK_WRAP_PYTHON_FILES "${copy_tgt}")

      # In a multi-config build, libraries are generated in config subdirectories to keep them separate
      # We must copy those separate files to a common python compatible tree.
      # NOTE: THE LAST RUN Build Config will overwrite the current files in the python package tree.
      add_custom_command(OUTPUT ${copy_tgt}
        COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${copy_tgt}
        DEPENDS ${src}
        COMMENT "Copying ${_file}.py to ${copy_tgt}.")
    endforeach()
    set(src "${ITK_INIT_MODULE}")
    set(copy_tgt "${ITK_PYTHON_PACKAGE_DIR}/__init__.py")
    list(APPEND ITK_WRAP_PYTHON_FILES "${copy_tgt}")
    add_custom_command(OUTPUT ${copy_tgt}
      COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${copy_tgt}
      DEPENDS ${src}
      COMMENT "Copying __init__.py to ${copy_tgt}.")
    add_custom_target(copy_python_files ALL DEPENDS ${ITK_WRAP_PYTHON_FILES})
    file(WRITE "${ITK_CONFIGURATION_INIT_MODULE}" "")
  endif()

  # Install the package python files.
  set(ITK_WRAP_PYTHON_FILES)
  foreach(_file ${ITK_PYTHON_SUPPORT_MODULES})
    set(install_tgt "${CMAKE_CURRENT_SOURCE_DIR}/itk/${_file}.py")
    list(APPEND ITK_WRAP_PYTHON_FILES ${ITK_WRAP_PYTHON_FILES} "${install_tgt}")
  endforeach()
  WRAP_ITK_PYTHON_BINDINGS_INSTALL(/itk/support "ITKCommon" ${ITK_WRAP_PYTHON_FILES})
  WRAP_ITK_PYTHON_BINDINGS_INSTALL(/itk/Configuration "ITKCommon" "${ITK_CONFIGURATION_INIT_MODULE}")
  WRAP_ITK_PYTHON_BINDINGS_INSTALL(/itk "ITKCommon" ${ITK_INIT_MODULE})
  install(DIRECTORY ${ITK_STUB_DIR}/ DESTINATION ${PY_SITE_PACKAGES_PATH}/${ITK_STUB_BASENAME})
endif()


###############################################################################
# Configure and install the custom python .pth files

configure_file("${CMAKE_CURRENT_SOURCE_DIR}/WrapITK.pth.in"
               "${ITK_WRAP_PYTHON_ROOT_BINARY_DIR}/WrapITK.pth"
               @ONLY)

###############################################################################
# Define the wrapping macros

macro(itk_setup_swig_python type base_name interface_file python_file cpp_file doc_file)

  #############################################################################
  # Runs swig to produce the *Python.cpp and the *Python.py file
  # Called by itk_end_wrap_module_python and by itk_end_wrap_submodule_python
  # Type will then be either "Module" or "Submodule"
  #############################################################################

  set(swig_command ${SWIG_EXECUTABLE})
  if(ITK_USE_CCACHE)
    set(swig_command ${CCACHE_EXECUTABLE} ${swig_command})
  endif()

  set(_swig_depend)
  if(NOT ITK_USE_SYSTEM_SWIG)
    # The ExternalProject SWIG install.
    set(_swig_depend swig)
  endif()

  set(dependencies
    "${DEPS}"
    "${interface_file}"
    "${_swig_depend}")

  unset(_swig_depend)

  if(NOT EXTERNAL_WRAP_ITK_PROJECT)
    configure_file(${WrapITK_SOURCE_DIR}/Generators/Python/PyBase/pyBase.i
      "${WRAP_ITK_TYPEDEFS_DIRECTORY}" COPYONLY)
    list(APPEND dependencies "${WRAP_ITK_TYPEDEFS_DIRECTORY}/pyBase.i")
  endif()

  if("${type}" STREQUAL "Module")
    # Module
    list(APPEND dependencies
      "${ITK_WRAP_PYTHON_LIBRARY_DEPS}"
      "${WRAPPER_MASTER_INDEX_OUTPUT_DIR}/python/${WRAPPER_LIBRARY_NAME}_ext.i")
  else()
    # Submodule
    list(APPEND dependencies
      "${WRAPPER_MASTER_INDEX_OUTPUT_DIR}/python/${base_name}_ext.i"
      "${doc_file}")
  endif()

  add_custom_command(
    OUTPUT ${cpp_file} ${python_file}
    COMMAND ${swig_command} -c++ -python -O -features autodoc=2
    -py3
    -doxygen
    -Werror
    -w302 # Identifier 'name' redefined (ignored)
    -w303 # %extend defined for an undeclared class 'name' (to avoid warning about customization in pyBase.i)
    -w312 # Unnamed nested class not currently supported (ignored)
    -w314 # 'identifier' is a lang keyword
    -w361 # operator! ignored
    -w362 # operator= ignored
    -w350 # operator new ignored
    -w383 # operator++ ignored
    -w384 # operator-- ignored
    -w389 # operator[] ignored
    -w394 # operator new[] ignored
    -w395 # operator delete[] ignored
    -w467 # Overloaded declaration not supported (no type checking rule for 'type')
    -w508 # Declaration of 'name' shadows declaration accessible via operator->(), previous declaration of'declaration'
    -w509 # Overloaded method declaration effectively ignored, as it is shadowed by declaration
    -o ${cpp_file}
    # The order of this include folder and the next one matters as otherwise the following exception is thrown:
    # "This version of exception.i should not be used"
    -I${SWIG_DIR}/python
    -I${SWIG_DIR}
    -I${WRAP_ITK_CMAKE_DIR}/Generators
    -I${WRAP_ITK_TYPEDEFS_DIRECTORY}/python
    -I${WRAP_ITK_TYPEDEFS_DIRECTORY}
    ${WRAP_ITK_SWIG_ARGS_PYTHON}
    -outdir ${ITK_PYTHON_PACKAGE_DIR}
    ${interface_file}
    WORKING_DIRECTORY ${WRAPPER_MASTER_INDEX_OUTPUT_DIR}/python
    DEPENDS ${dependencies}
  )

  WRAP_ITK_PYTHON_BINDINGS_INSTALL(/itk "${WRAPPER_LIBRARY_NAME}" "${python_file}")

  unset(dependencies)
  unset(swig_command)
endmacro()


macro(itk_end_wrap_submodule_python group_name)

  # base_name may be used in other macros
  set(base_name ${group_name})

  if("${group_name}" STREQUAL "ITKQuadEdgeMeshBase")
    # add a template definition for the superclass which is not in ITK
    set(text)
    foreach(d ${ITK_WRAP_IMAGE_DIMS})
      set(text "${text}%template(mapULitkQuadEdgeMeshPointF${d}) std::map< unsigned long, itkQuadEdgeMeshPointF${d}, std::less< unsigned long > >;\n")
      ADD_PYTHON_CONFIG_TEMPLATE("map" "std::map" "mapULitkQuadEdgeMeshPointF${d}" "unsigned long, itk::QuadEdgeMeshPoint< float, ${d} >")

      # set(text "${text}%template(itkMapContainerMD${d}QBAIUL_Superclass) std::map< itkMeshD${d}Q::BoundaryAssignmentIdentifier, unsigned long, std::less< itkMeshD${d}Q::BoundaryAssignmentIdentifier > >;\n")
      # ADD_PYTHON_CONFIG_TEMPLATE("map" "std::map" "itkMapContainerMD${d}QBAIUL_Superclass" "itk::Mesh<double, ${d}u, itk::QuadEdgeMeshTraits<double, ${d}, bool, bool, float, float> >::BoundaryAssignmentIdentifier, unsigned long")

      set(text "${text}%traits_swigtype(itkCellInterfaceDQEMCTI${d});\n")
      set(text "${text}%fragment(SWIG_Traits_frag(itkCellInterfaceDQEMCTI${d}));\n")
      set(text "${text}%template(mapULitkCellInterfaceDQEMCTI${d}) std::map< unsigned long, itkCellInterfaceDQEMCTI${d} *, std::less< unsigned long > >;\n")
      ADD_PYTHON_CONFIG_TEMPLATE("map" "std::map" "mapULitkCellInterfaceDQEMCTI${d}" "unsigned long, itk::CellInterface< double, itk::QuadEdgeMeshCellTraitsInfo< ${d} > >*")
    endforeach()

    string(APPEND ITK_WRAP_PYTHON_SWIG_EXT "${text}")
    unset(text)
  endif()

  # is there a docstring file?
  set(doc_file)
  if(${module_prefix}_WRAP_DOC AND NOT ITK_WRAP_PYTHON_PROCESS_SWIG_INPUTS)
    # yes. Include the docstring file
    set(doc_file "${WRAPPER_MASTER_INDEX_OUTPUT_DIR}/${group_name}_doc.i")
    string(PREPEND ITK_WRAP_PYTHON_SWIG_EXT "%include ${group_name}_doc.i\n\n")
  else()
    # no. Clear the doc_file var
    set(doc_file "")
  endif()

  # the default typemaps, exception handler, and includes
  string(PREPEND ITK_WRAP_PYTHON_SWIG_EXT "%import pyBase.i\n\n")

  # create the swig interface for all the groups in the module
  set(interface_file "${WRAPPER_MASTER_INDEX_OUTPUT_DIR}/${base_name}.i")
  set(_swig_python_suffix "Python")
  set(python_file "${ITK_PYTHON_PACKAGE_DIR}/${base_name}${_swig_python_suffix}.py")
  set(cpp_file "${CMAKE_CURRENT_BINARY_DIR}/${base_name}${_swig_python_suffix}.cpp")
  unset(_swig_python_suffix)

  # create the python customization for that wrap_*.cmake file.
  configure_file("${ITK_WRAP_PYTHON_SOURCE_DIR}/module_ext.i.in"
  "${WRAPPER_MASTER_INDEX_OUTPUT_DIR}/python/${base_name}_ext.i"
  @ONLY)

  # prepare dependencies
  set(DEPS) # Used in itk_setup_swig_python macro (passed as global variable)
  foreach(dep ${WRAPPER_LIBRARY_DEPENDS})
    list(APPEND DEPS ${${dep}SwigFiles})
  endforeach()

  # Run swig to produce the *Python.cpp and the *Python.py file
  itk_setup_swig_python("Submodule" ${base_name} ${interface_file} ${python_file} ${cpp_file} "${doc_file}")

  # add the c++ files which will be generated by the swig command to the
  # list of python related c++ files, so they can be built at the end
  # of the current module.
  list(APPEND ITK_WRAP_PYTHON_CXX_FILES ${cpp_file})
  list(APPEND ITK_WRAP_PYTHON_FILES ${python_file})

  # add needed files to the deps list
  list(APPEND ITK_WRAP_PYTHON_LIBRARY_DEPS "${WRAPPER_MASTER_INDEX_OUTPUT_DIR}/python/${base_name}_ext.i" "${cpp_file}")

  # add this wrap_*.cmake stuff to the list of modules to init in the main module.
  # first the extern c declaration and then the call of the extern function
  set(ITK_WRAP_PYTHON_LIBRARY_DECLS "${ITK_WRAP_PYTHON_LIBRARY_DECLS} extern \"C\" PyMODINIT_FUNC PyInit__${base_name}Python();\n")
  set(ITK_WRAP_PYTHON_LIBRARY_CALLS "${ITK_WRAP_PYTHON_LIBRARY_CALLS}
  PyObject * ${base_name}AlreadyImported = PyDict_GetItemString(sysModules, \"itk._${base_name}Python\");
  if(${base_name}AlreadyImported == NULL)
    {
    PyImport_AddModule(\"itk._${base_name}Python\");
    PyObject * ${base_name}Module = PyInit__${base_name}Python();
    PyDict_SetItemString(sysModules, \"itk._${base_name}Python\", ${base_name}Module);
    Py_DECREF( ${base_name}Module);
    }
")

  unset(interface_file)
  unset(doc_file)
  unset(cpp_file)
  unset(python_file)
endmacro()


macro(ADD_PYTHON_CONFIG_TEMPLATE base_name wrap_class swig_name template_params)
  # Find if a header file corresponding to 'base_name' can be found in the current include directory
    set(_include ${${WRAPPER_LIBRARY_NAME}_SOURCE_DIR}/include/itk${base_name}.h)
    if(EXISTS ${_include})
      set(class_in_module "True")
    else()
      set(class_in_module "False")
    endif()

  # build the name - type association list used in *Config.py
  if("${template_params}" STREQUAL "")
    set(ITK_WRAP_PYTHON_CONFIGURATION_TEMPLATES "${ITK_WRAP_PYTHON_CONFIGURATION_TEMPLATES}  ('${base_name}', '${wrap_class}', '${swig_name}', ${class_in_module}),\n")
  else()
    set(ITK_WRAP_PYTHON_CONFIGURATION_TEMPLATES "${ITK_WRAP_PYTHON_CONFIGURATION_TEMPLATES}  ('${base_name}', '${wrap_class}', '${swig_name}', ${class_in_module}, '${template_params}'),\n")
  endif()

  unset(class_in_module)
  unset(_include)
endmacro()


macro(ADD_PYTHON_OUTPUT_RETURN_BY_VALUE_CLASS type function)
  string(APPEND ITK_WRAP_PYTHON_SWIG_EXT "DECL_PYTHON_OUTPUT_RETURN_BY_VALUE_CLASS(${type}, ${function})\n")
endmacro()


macro(ADD_PYTHON_SEQ_TYPEMAP swig_name dim)
  string(APPEND ITK_WRAP_PYTHON_SWIG_EXT "DECL_PYTHON_SEQ_TYPEMAP(${swig_name}, ${dim})\n")
endmacro()


macro(ADD_PYTHON_VEC_TYPEMAP swig_name template_params)
  string(REGEX REPLACE "(.*),(.*)" "\\1" type "${template_params}")
  string(REGEX REPLACE "(.*),(.*)" "\\2" dim "${template_params}")
  # Black listing all types that contain a comma which would create issues
  # in C/C++ macros
  string(REGEX MATCH "(.*,.*)" isTemplate "${type}")
  if(NOT isTemplate)
    string(APPEND ITK_WRAP_PYTHON_SWIG_EXT "DECL_PYTHON_VEC_TYPEMAP(${swig_name}, ${type}, ${dim})\n")
  endif()
endmacro()

macro(ADD_PYTHON_VARIABLE_LENGTH_SEQ_TYPEMAP type value_type)
  string(APPEND ITK_WRAP_PYTHON_SWIG_EXT "DECL_PYTHON_VARLEN_SEQ_TYPEMAP(${type}, ${value_type})\n")
endmacro()


macro(ADD_PYTHON_POINTER_TYPEMAP typemap_name)
  set(text "DECLARE_REF_COUNT_CLASS(${typemap_name})\n")

  string(PREPEND ITK_WRAP_PYTHON_SWIG_EXT "${text}")
endmacro()

###############################################################################
# Add the Python tests
if(BUILD_TESTING AND ITK_SOURCE_DIR AND NOT EXTERNAL_WRAP_ITK_PROJECT)
  add_subdirectory(Tests)
endif()
