#==========================================================================
#
#   Copyright NumFOCUS
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#          https://www.apache.org/licenses/LICENSE-2.0.txt
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
#==========================================================================*/
cmake_minimum_required(VERSION 3.16.3...3.19.7 FATAL_ERROR)

project(WrapITK)

enable_testing()

# Set the module prefix once upon loading this files
# Case 1:  An external module is loading this file from
#          ${ITK_SOURCE_DIR}/CMake/ITKModuleExternal.cmake
# Case 2:  ITK is including this file from
#          ${ITK_SOURCE_DIR}/CMakeLists.txt
# module_prefix is a global variable
if(EXTERNAL_WRAP_ITK_PROJECT)
  set(module_prefix ${itk-module})
else()
  set(module_prefix ITK)
endif()

###############################################################################
# Configure installation
###############################################################################

if(ITK_INSTALL_PACKAGE_DIR)
  string(REGEX REPLACE "^/" "" path "${ITK_INSTALL_PACKAGE_DIR}/WrapITK/")
else()
  set(path "lib/InsightToolkit/WrapITK/")
endif()
set(
  WRAP_ITK_INSTALL_PREFIX
  "${path}"
  CACHE INTERNAL
  "subpath where where most of WrapITK files will be installed"
)

# Output directories.
set(WRAP_ITK_CONFIG_DIR "${WrapITK_SOURCE_DIR}/")
set(WRAP_ITK_CMAKE_DIR "${WrapITK_SOURCE_DIR}")
set(
  WRAPPER_MASTER_INDEX_OUTPUT_DIR
  "${ITK_DIR}/Wrapping/Typedefs"
  CACHE INTERNAL
  "typedefs dir"
)

if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
  # See ITK/WrappingConfigCommon.cmake for rational for overwriting multi-config default behavior of appending $<CONFIG>
  set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "$<1:${WrapITK_BINARY_DIR}/lib>")
endif()

# Set WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER to a non-null value, like
# "Wrapping", which will be inserted into the wrapping install component name.
# This can be used to split installation package components.
if(NOT WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER)
  set(WRAP_ITK_INSTALL_COMPONENT_IDENTIFIER "")
endif()

unset(GLOBAL_IdxFilesList)
unset(GLOBAL_IdxFilesList CACHE)

# Set WRAP_ITK_INSTALL_COMPONENT_PER_MODULE to 1 to have wrapping install
# component names prefixed with their respective module name.
# This can be used to have fine-control over the split of installation.
if(NOT DEFINED WRAP_ITK_INSTALL_COMPONENT_PER_MODULE)
  set(WRAP_ITK_INSTALL_COMPONENT_PER_MODULE 0)
endif()

mark_as_advanced(
  CMAKE_LIBRARY_OUTPUT_DIRECTORY
  WRAP_ITK_INSTALL_PREFIX
)

###############################################################################
# Additional files for installation
###############################################################################

# See https://peps.python.org/pep-0561/#stub-only-packages
# Packaging typehints with package so use "itk", if making separate typehint
# package use "itk-stubs"
set(ITK_STUB_BASENAME "itk")
# ITK_STUB_DIR: all stub files are stored in this directory
set(
  ITK_STUB_DIR
  "${ITK_DIR}/Wrapping/Generators/Python/${ITK_STUB_BASENAME}"
  CACHE INTERNAL
  "where python interface files are stored."
)
file(MAKE_DIRECTORY ${ITK_STUB_DIR})

# ITK_PKL_DIR: all temporary pickled object AST representations stored here
set(
  ITK_PKL_DIR
  "${ITK_DIR}/Wrapping/Generators/Python/itk-pkl"
  CACHE INTERNAL
  "where temp pkl files are stored"
)
file(MAKE_DIRECTORY ${ITK_PKL_DIR})
set(WRAP_ITK_TYPEDEFS_DIRECTORY "${ITK_DIR}/Wrapping/Typedefs")
set(WRAP_ITK_LIB_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")

###############################################################################
# Setup test driver
###############################################################################

set(ITK_TEST_DRIVER "itkTestDriver")

###############################################################################
# Check for IPO/LTO support once globally for all wrapper modules
###############################################################################

if(NOT MSVC)
  include(CheckIPOSupported)
  check_ipo_supported(RESULT ITK_WRAP_IPO_SUPPORTED OUTPUT ipo_check_output)
  if(ITK_WRAP_IPO_SUPPORTED)
    message(STATUS "IPO/LTO supported for ITK wrapper modules")
  else()
    message(
      STATUS
      "IPO/LTO not supported for ITK wrapper modules: ${ipo_check_output}"
    )
  endif()
  set(
    ITK_WRAP_IPO_SUPPORTED
    ${ITK_WRAP_IPO_SUPPORTED}
    CACHE INTERNAL
    "Whether IPO/LTO is supported for wrapper modules"
  )
  unset(ipo_check_output)
else()
  set(ITK_WRAP_IPO_SUPPORTED FALSE CACHE INTERNAL "IPO/LTO not checked on MSVC")
endif()

###############################################################################
# The real work on wrappers
###############################################################################

include(ConfigureWrapping.cmake)

###############################################################################
# Configure specific wrapper modules
###############################################################################

unset(WRAP_ITK_MODULES CACHE)

file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Modules/)
if(ITK_SOURCE_DIR)
  foreach(_module_enabled ${ITK_CONFIG_MODULES_ENABLED})
    if(EXISTS "${${_module_enabled}_SOURCE_DIR}/wrapping/CMakeLists.txt")
      list(APPEND WRAP_ITK_MODULES ${_module_enabled})
      set(itk-module_SOURCE_DIR ${${_module_enabled}_SOURCE_DIR})
      add_subdirectory(
        "${${_module_enabled}_SOURCE_DIR}/wrapping"
        ${CMAKE_CURRENT_BINARY_DIR}/Modules/${_module_enabled}
      )
      unset(itk-module_SOURCE_DIR)
    endif()
  endforeach()
else() # Building a module externally so itk-module variable must have been previously set
  if(EXISTS "${${itk-module}_SOURCE_DIR}/wrapping/CMakeLists.txt")
    foreach(_module_depend ${ITK_MODULE_${itk-module}_DEPENDS})
      if(EXISTS ${WRAPPER_MASTER_INDEX_OUTPUT_DIR}/${_module_depend}.mdx)
        list(APPEND WRAP_ITK_MODULES ${_module_depend})
      endif()
    endforeach()
    list(APPEND WRAP_ITK_MODULES ${itk-module})
    add_subdirectory(
      "${${itk-module}_SOURCE_DIR}/wrapping"
      ${CMAKE_CURRENT_BINARY_DIR}/Modules/${itk-module}
    )
  endif()
endif()
set(
  WRAP_ITK_MODULES
  ${WRAP_ITK_MODULES}
  CACHE INTERNAL
  "Internal library list."
)

###############################################################################
# let the different generators running some code after have parsed all the
# modules
###############################################################################

if(${module_prefix}_WRAP_PYTHON)
  if(NOT EXTERNAL_WRAP_ITK_PROJECT)
    add_subdirectory(${ITK_WRAP_PYTHON_SOURCE_DIR}/PyBase)
  endif()
endif()

###############################################################################
# Generate Python typehints when wrapped
###############################################################################
if(ITK_WRAP_PYTHON)
  # Generate .pyi files for each class
  # The intent is that the pyi_generator file is run after every occurrence of igenerator has ran.
  # The call to igenerator is contained within a macro that is called by each of the modules.
  # According to online documents CMAkE dependencies can only be created between
  # custom_targets and custom_commands contained within the same CMakeLists file. It is unknown if this
  # can be done since the macros are called from a variety of locations.

  # PYI_GENERATOR: The file location of the pyi_generator.py script
  #   This script is responsible for merging all of the pickle files together and
  #   generating the Template and Proxy files for each class. In addition this file
  #   generates the __init__.pyi and _proxies.pyi files used to interface between
  #   the individual .pyi files.
  set(
    PYI_GENERATOR
    "${CMAKE_CURRENT_SOURCE_DIR}/Generators/Python/itk/pyi_generator.py"
  )

  configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/GlobalIdxFilesList.txt.in
    ${CMAKE_CURRENT_BINARY_DIR}/GlobalIdxFilesList.txt
  )

  # Run pyi_generator
  # ${GLOBAL_IdxFilesList}: is described within SwigInterface/CMakeLists.txt
  #   The variable is passed to pyi_generator to make sure that the function only uses current index files
  #   All index files found that are not in the given list are assumed to be outdated and are removed.
  add_custom_target(
    itk-stub-files
    ALL
    BYPRODUCTS
      "${ITK_STUB_DIR}/_proxies.pyi"
      "${ITK_STUB_DIR}/__init__.pyi"
    COMMAND
      ${Python3_EXECUTABLE} ${PYI_GENERATOR} --pyi_dir "${ITK_STUB_DIR}"
      --pkl_dir "${ITK_PKL_DIR}" --index_list_file
      "${CMAKE_CURRENT_BINARY_DIR}/GlobalIdxFilesList.txt"
    DEPENDS
      ${PYI_GENERATOR}
      ${GLOBAL_IdxFilesList}
    COMMENT "Generating .pyi files for Python wrapper interface"
    VERBATIM
  )

  # Add module dependencies to ensure .index.txt files have been generated
  unique(WRAPPED_MODULE_LIST "${WRAP_ITK_MODULES}")
  foreach(WRAPPER_LIBRARY_NAME ${WRAPPED_MODULE_LIST})
    list(
      LENGTH
      ${WRAPPER_LIBRARY_NAME}PyiIdxFiles
      ${WRAPPER_LIBRARY_NAME}PyiIdxFiles_Len
    )
    if(${WRAPPER_LIBRARY_NAME}PyiIdxFiles_Len GREATER 0)
      # <module>Swig custom targets build .index.txt files as byproducts
      add_dependencies(itk-stub-files ${WRAPPER_LIBRARY_NAME}Swig)
    endif()
  endforeach()

  unset(ITK_STUB_DIR CACHE)
  unset(ITK_PKL_DIR CACHE)
endif()
