###############################################################################
#
## 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 -fastdispatch -fvirtual -features autodoc=2 -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);
    SWIG_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()
