#.rst:
#
# The following functions are defined:
#
# .. cmake:command:: add_python_library
#
# Add a library that contains a mix of C, C++, Fortran, Cython, F2PY, Template,
# and Tempita sources. The required targets are automatically generated to
# "lower" source files from their high-level representation to a file that the
# compiler can accept.
#
#
#   add_python_library(<Name>
#                      SOURCES [source1 [source2 ...]]
#                      [INCLUDE_DIRECTORIES [dir1 [dir2 ...]]
#                      [LINK_LIBRARIES [lib1 [lib2 ...]]
#                      [DEPENDS [source1 [source2 ...]]])
#
#
# Example usage
# ^^^^^^^^^^^^^
#
# .. code-block:: cmake
#
#   find_package(PythonExtensions)
#
#   file(GLOB arpack_sources ARPACK/SRC/*.f ARPACK/UTIL/*.f)
#
#    add_python_library(arpack_scipy
#      SOURCES ${arpack_sources}
#              ${g77_wrapper_sources}
#      INCLUDE_DIRECTORIES ARPACK/SRC
#    )
#
# .. cmake:command:: add_python_extension
#
# Add a extension that contains a mix of C, C++, Fortran, Cython, F2PY, Template,
# and Tempita sources. The required targets are automatically generated to
# "lower" source files from their high-level representation to a file that the
# compiler can accept.
#
#
#   add_python_extension(<Name>
#                        SOURCES [source1 [source2 ...]]
#                        [INCLUDE_DIRECTORIES [dir1 [dir2 ...]]
#                        [LINK_LIBRARIES [lib1 [lib2 ...]]
#                        [DEPENDS [source1 [source2 ...]]])
#
#
# Example usage
# ^^^^^^^^^^^^^
#
# .. code-block:: cmake
#
#   find_package(PythonExtensions)
#
#   file(GLOB arpack_sources ARPACK/SRC/*.f ARPACK/UTIL/*.f)
#
#    add_python_extension(arpack_scipy
#      SOURCES ${arpack_sources}
#              ${g77_wrapper_sources}
#      INCLUDE_DIRECTORIES ARPACK/SRC
#    )
#
#
#=============================================================================
# Copyright 2011 Kitware, Inc.
#
# 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
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
#=============================================================================

macro(_remove_whitespace _output)
  string(REGEX REPLACE "[ \r\n\t]+" " " ${_output} "${${_output}}")
  string(STRIP "${${_output}}" ${_output})
endmacro()

function(add_python_library _name)
  set(options STATIC SHARED MODULE)
  set(multiValueArgs SOURCES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS DEPENDS)
  cmake_parse_arguments(_args "${options}" "" "${multiValueArgs}" ${ARGN} )

  # Validate arguments to allow simpler debugging
  if(NOT _args_SOURCES)
    message(
      FATAL_ERROR
      "You have called add_python_library for library ${_name} without "
      "any source files. This typically indicates a problem with "
      "your CMakeLists.txt file"
    )
  endif()

  # Initialize the list of sources
  set(_sources ${_args_SOURCES})

  # Generate targets for all *.src files
  set(_processed )
  foreach(_source IN LISTS _sources)
    if(${_source} MATCHES ".pyf.src$" OR ${_source} MATCHES "\\.f\\.src$")
      if(NOT NumPy_FOUND)
        message(
          FATAL_ERROR
          "NumPy is required to process *.src Template files"
        )
      endif()
      string(REGEX REPLACE "\\.[^.]*$" "" _source_we ${_source})
      add_custom_command(
        OUTPUT ${_source_we}
        COMMAND ${NumPy_FROM_TEMPLATE_EXECUTABLE}
                ${CMAKE_CURRENT_SOURCE_DIR}/${_source}
                ${CMAKE_CURRENT_BINARY_DIR}/${_source_we}
        DEPENDS ${_source} ${_args_DEPENDS}
        COMMENT "Generating ${_source_we} from template ${_source}"
      )
      list(APPEND _processed ${_source_we})
    elseif(${_source} MATCHES "\\.c\\.src$")
      if(NOT NumPy_FOUND)
        message(
          FATAL_ERROR
          "NumPy is required to process *.src Template files"
        )
      endif()
      string(REGEX REPLACE "\\.[^.]*$" "" _source_we ${_source})
      add_custom_command(
        OUTPUT ${_source_we}
        COMMAND ${NumPy_CONV_TEMPLATE_EXECUTABLE}
                ${CMAKE_CURRENT_SOURCE_DIR}/${_source}
                ${CMAKE_CURRENT_BINARY_DIR}/${_source_we}
        DEPENDS ${_source} ${_args_DEPENDS}
        COMMENT "Generating ${_source_we} from template ${_source}"
      )
      list(APPEND _processed ${_source_we})
    elseif(${_source} MATCHES "\\.pyx\\.in$")
      if(NOT Cython_FOUND)
        message(
          FATAL_ERROR
          "Cython is required to process *.in Tempita files"
        )
      endif()
      string(REGEX REPLACE "\\.[^.]*$" "" _source_we ${_source})
      configure_file(
          ${CMAKE_CURRENT_SOURCE_DIR}/${_source}
          ${CMAKE_CURRENT_BINARY_DIR}/${_source}
          COPYONLY
      )
      set(_tempita_command
          "
            import os;
            import sys;
            from Cython.Tempita import Template;
            cwd = os.getcwd();
            open(os.path.join(cwd, '${_source_we}'), 'w+')
            .write(
                Template.from_filename(os.path.join(cwd, '${_source}'),
                encoding=sys.getdefaultencoding()).substitute()
            )
          "
      )
      _remove_whitespace(_tempita_command)
      add_custom_command(
        OUTPUT ${_source_we}
        COMMAND ${PYTHON_EXECUTABLE} -c "${_tempita_command}"
        DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${_source}"
                ${_args_DEPENDS}
      )
      list(APPEND _processed ${_source_we})
    else()
      list(APPEND _processed  ${_source})
    endif()
  endforeach()
  set(_sources ${_processed})

  # If we're building a Python extension and we're given only Fortran sources,
  # We can conclude that we need to generate a Fortran interface file
  list(FILTER _processed EXCLUDE REGEX "(\\.f|\\.f90)$")
  if(NOT _processed AND _args_MODULE)
    if(NOT NumPy_FOUND)
        message(
          FATAL_ERROR
          "NumPy is required to process *.pyf F2PY files"
        )
    endif()
    set(_sources_abs )
    foreach(_source IN LISTS _sources)
      if(NOT IS_ABSOLUTE ${_source})
        set(_source ${CMAKE_CURRENT_SOURCE_DIR}/${_source})
      endif()
      list(APPEND _sources_abs ${_source})
    endforeach()
    add_custom_command(
        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${_name}.pyf
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
        COMMAND ${F2PY_EXECUTABLE}
        ARGS -h ${_name}.pyf -m ${_name} --overwrite-signature
             ${_sources_abs}
        DEPENDS ${_sources} ${_args_DEPENDS}
        COMMENT "Generating ${_name} Fortran interface file"
    )
    list(APPEND _sources ${_name}.pyf)
  endif()

  # Are there F2PY targets?
  set(_has_f2py_targets OFF)
  set(_has_cython_targets OFF)

  # Generate targets for all *.pyx and *.pyf files
  set(_processed )
  foreach(_source IN LISTS _sources)
    if(${_source} MATCHES \\.pyx$)
      if(NOT Cython_FOUND)
        message(
          FATAL_ERROR
          "Cython is required to process *.pyx Cython files"
        )
      endif()
      string(REGEX REPLACE "\\.[^.]*$" "" _pyx_target_name ${_source})
      set(_has_cython_targets ON)
      add_cython_target(${_pyx_target_name}
          ${_source}
          OUTPUT_VAR _pyx_target_output
          DEPENDS ${_args_DEPENDS}
      )
      list(APPEND _processed ${_pyx_target_output})
    elseif(${_source} MATCHES \\.pyf$)
      if(NOT NumPy_FOUND)
          message(
            FATAL_ERROR
            "NumPy is required to process *.pyf F2PY files"
          )
      endif()
      string(REGEX REPLACE "\\.[^.]*$" "" _pyf_target_name ${_source})
      set(_has_f2py_targets ON)
      add_f2py_target(${_pyf_target_name}
          ${_source}
          OUTPUT_VAR _pyf_target_output
          DEPENDS ${_args_DEPENDS}
      )
      list(APPEND _processed  ${_pyf_target_output})
    else()
      list(APPEND _processed ${_source})
    endif()
  endforeach()
  set(_sources ${_processed})

  if(_args_SHARED)
    add_library(${_name} SHARED ${_sources})
  elseif(_args_MODULE)
    add_library(${_name} MODULE ${_sources})
  else()
    # Assume static
    add_library(${_name} STATIC ${_sources})
  endif()

  target_include_directories(${_name} PRIVATE ${_args_INCLUDE_DIRECTORIES})
  target_link_libraries(${_name} ${SKBUILD_LINK_LIBRARIES_KEYWORD} ${_args_LINK_LIBRARIES})

  if(_has_f2py_targets)
    target_include_directories(${_name} PRIVATE ${F2PY_INCLUDE_DIRS})
    target_link_libraries(${_name} ${SKBUILD_LINK_LIBRARIES_KEYWORD} ${F2PY_LIBRARIES})
  endif()

  if(_args_COMPILE_DEFINITIONS)
    target_compile_definitions(${_name} PRIVATE ${_args_COMPILE_DEFINITIONS})
  endif()

  if(_args_DEPENDS)
    add_custom_target(
      "${_name}_depends"
      DEPENDS ${_args_DEPENDS}
    )
    add_dependencies(${_name} "${_name}_depends")
  endif()
endfunction()

function(add_python_extension _name)
  # FIXME: make sure that extensions with the same name can happen
  # in multiple directories

  set(multiValueArgs SOURCES INCLUDE_DIRECTORIES LINK_LIBRARIES COMPILE_DEFINITIONS DEPENDS)
  cmake_parse_arguments(_args "" "" "${multiValueArgs}" ${ARGN} )

  # Validate arguments to allow simpler debugging
  if(NOT _args_SOURCES)
    message(
      FATAL_ERROR
      "You have called add_python_extension for library ${_name} without "
      "any source files. This typically indicates a problem with "
      "your CMakeLists.txt file"
    )
  endif()

  add_python_library(${_name} MODULE
    SOURCES ${_args_SOURCES}
    INCLUDE_DIRECTORIES ${_args_INCLUDE_DIRECTORIES}
    LINK_LIBRARIES ${_args_LINK_LIBRARIES}
    COMPILE_DEFINITIONS ${_args_COMPILE_DEFINITIONS}
    DEPENDS ${_args_DEPENDS}
  )
  python_extension_module(${_name})

  file(RELATIVE_PATH _relative "${CMAKE_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
  if(_relative STREQUAL "")
    set(_relative ".")
  endif()

  install(
    TARGETS ${_name}
    LIBRARY DESTINATION "${_relative}"
    RUNTIME DESTINATION "${_relative}"
  )
endfunction()
