CMAKE_MINIMUM_REQUIRED( VERSION 3.24 )

# include the CTest framework
include( CTest )

IF( SWIG_FOUND AND PYTHONLIBS_FOUND AND IS_NUMPY_FOUND )

    MESSAGE( STATUS "Configuring Python API" )

    if( NOT VSP_NO_PYDOC )
        MESSAGE( STATUS "Python API doc will be generated via venv sphinx." )
    endif()

    execute_process(
            COMMAND ${PYTHON_EXECUTABLE} -c "import numpy; print(numpy.__version__)"
            OUTPUT_VARIABLE NUMPY_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE
    )

    execute_process(
            COMMAND ${PYTHON_EXECUTABLE} -c "import numpy; print(numpy.get_include())"
            OUTPUT_VARIABLE NUMPY_INCLUDE_PATH OUTPUT_STRIP_TRAILING_WHITESPACE
    )

    MESSAGE( STATUS "Numpy found, vec3d to numpy API will be created." )
    MESSAGE( STATUS "  Numpy version: ${NUMPY_VERSION}" )
    MESSAGE( STATUS "  Numpy include path: ${NUMPY_INCLUDE_PATH}" )

    # Build include directories list
    set(INCLUDE_DIRS
        ${CLIPPER2_INCLUDE_DIR}
        ${CodeEli_INCLUDE_DIRS}
        ${CPPTEST_INCLUDE_DIR}
        ${CPPTEST_INCLUDE_DIR}
        ${DELABELLA_INCLUDE_DIR}
        ${FLTK_INCLUDE_DIR}
        ${GEOM_API_INCLUDE_DIR}
        ${GEOM_CORE_INCLUDE_DIR}
        ${GUI_AND_DRAW_INCLUDE_DIR}
        ${LIBIGES_INCLUDE_DIR}
        ${LIBXML2_INCLUDE_DIR}
        ${NANOFLANN_INCLUDE_DIR}
        ${PYTHON_INCLUDE_DIR}
        ${PYTHON_INCLUDE_PATH}
        ${STEPCODE_INCLUDE_DIR}
        ${TRIANGLE_INCLUDE_DIR}
        ${UTIL_INCLUDE_DIR}
        ${UTIL_API_INCLUDE_DIR}
        ${XMLVSP_INCLUDE_DIR}
        ${VSP_SOURCE_DIR}
    )

    # Add numpy include directory if numpy is found (needed for SWIG to process python.i)
    list(APPEND INCLUDE_DIRS ${NUMPY_INCLUDE_PATH})

    INCLUDE_DIRECTORIES(${INCLUDE_DIRS})

    SET( SWIG_MODULE_pyvsp_EXTRA_DEPS
        ${GEOM_API_INCLUDE_DIR}/APIDefines.h
        ${GEOM_API_INCLUDE_DIR}/APIErrorMgr.h
        ${GEOM_API_INCLUDE_DIR}/VSP_Geom_API.h
        ${GEOM_CORE_INCLUDE_DIR}/SWIGDefines.h
        ${UTIL_API_INCLUDE_DIR}/Vec3d.h
        # vsp_common.i holds the typemaps and the %extend blocks that bind the vec3d and vec2d
        # operators, and Vec2d.h and Matrix4d.h are wrapped just like Vec3d.h.  Without them here
        # SWIG is not re-run when they change, and the build quietly keeps the previous bindings.
        ${UTIL_API_INCLUDE_DIR}/Vec2d.h
        ${UTIL_API_INCLUDE_DIR}/Matrix4d.h
        ${GEOM_API_INCLUDE_DIR}/vsp_common.i
    )
    set(I_FILES
        ../geom_api/vsp_python.i
    )


    SET_SOURCE_FILES_PROPERTIES( ${I_FILES} PROPERTIES CPLUSPLUS ON )

    # Although not documented this way, CMAKE_SWIG_FLAGS is apparently a list and must be built up this way.
    SET( CMAKE_SWIG_FLAGS "-threads" )

    if( DOXYGEN_FOUND AND NOT VSP_NO_PYDOC )
        list( APPEND CMAKE_SWIG_FLAGS "-doxygen" )
    endif()

    SWIG_ADD_LIBRARY( pyvsp
        LANGUAGE python
        SOURCES ${I_FILES}
    )

    target_link_libraries( pyvsp Eigen3::Eigen )

    SET_TARGET_PROPERTIES( pyvsp PROPERTIES OUTPUT_NAME "vsp" )

    IF( APPLE )
        SET( PY_LIB "" )
        SET_TARGET_PROPERTIES( pyvsp PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" )
    ELSE()
        SET( PY_LIB ${PYTHON_LIBRARIES} )
    ENDIF()

    if( CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
        TARGET_COMPILE_OPTIONS( pyvsp PUBLIC -Wno-deprecated-declarations)
    endif()

    SWIG_LINK_LIBRARIES( pyvsp
        ${VSP_LIBRARIES_API_FIRST}
        ${PY_LIB}
    )
    target_include_directories(pyvsp PRIVATE ${NUMPY_INCLUDE_PATH})


    IF( NOT VSP_NO_GRAPHICS )

        SET( SWIG_MODULE_pyvsp_g_EXTRA_DEPS
            ${GEOM_API_INCLUDE_DIR}/APIDefines.h
            ${GEOM_API_INCLUDE_DIR}/APIErrorMgr.h
            ${GEOM_API_INCLUDE_DIR}/VSP_Geom_API.h
            ${GEOM_CORE_INCLUDE_DIR}/SWIGDefines.h
            ${UTIL_API_INCLUDE_DIR}/Vec3d.h
            ${UTIL_API_INCLUDE_DIR}/Vec2d.h
            ${UTIL_API_INCLUDE_DIR}/Matrix4d.h
            ${GEOM_API_INCLUDE_DIR}/vsp_common.i
        )

        set(I_FILES_g
            ../geom_api/vsp_g_python.i
        )

        SET_SOURCE_FILES_PROPERTIES( ${I_FILES_g} PROPERTIES CPLUSPLUS ON )

        SWIG_ADD_LIBRARY( pyvsp_g
            LANGUAGE python
            SOURCES ${I_FILES_g}
        )

        target_link_libraries( pyvsp_g Eigen3::Eigen )

        SET_TARGET_PROPERTIES( pyvsp_g PROPERTIES OUTPUT_NAME "vsp_g" )

        IF( APPLE )
            SET( PY_LIB "" )
            SET_TARGET_PROPERTIES( pyvsp_g PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" )
        ELSE()
            SET( PY_LIB ${PYTHON_LIBRARIES} )
        ENDIF()

        if( CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
            TARGET_COMPILE_OPTIONS( pyvsp_g PUBLIC -Wno-deprecated-declarations )
        endif()

        SWIG_LINK_LIBRARIES( pyvsp_g
            ${VSP_G_LIBRARIES_API_FIRST}
            ${PY_LIB}
        )
        target_include_directories(pyvsp_g PRIVATE ${NUMPY_INCLUDE_PATH})

        add_custom_command( TARGET pyvsp_g POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy
            $<TARGET_FILE:pyvsp_g>
            ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp/$<TARGET_FILE_NAME:pyvsp_g> )

        #copy vsp_g to folder and add "placeholder" facade functions
        add_custom_command( TARGET pyvsp_g POST_BUILD
            COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/auto_facade.py
            ${CMAKE_CURRENT_BINARY_DIR}/vsp_g.py False

            COMMAND ${CMAKE_COMMAND} -E copy
            ${CMAKE_CURRENT_BINARY_DIR}/vsp_g.py
            ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp/vsp_g.py
        )

        INSTALL( TARGETS pyvsp_g LIBRARY DESTINATION python/openvsp/openvsp )
        INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/vsp_g.py DESTINATION python/openvsp/openvsp )

        add_dependencies( pyvsp_g copy_package ) # Make pyvsp depend on copy_package.  Not sure this is required.

    ENDIF()

    CONFIGURE_FILE( packages/openvsp/setup.py.in python/packages/openvsp/setup.py )
    CONFIGURE_FILE( packages/openvsp/conda-recipe/meta.yaml.in python/packages/openvsp/conda-recipe/meta.yaml )
    CONFIGURE_FILE( packages/environment.yml.in python/packages/environment.yml )

    INSTALL( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/packages/ DESTINATION python
        PATTERN setup.py.in EXCLUDE
        PATTERN meta.yaml.in EXCLUDE
        PATTERN environment.yml.in EXCLUDE
        PATTERN conf.py EXCLUDE
        PATTERN index.rst EXCLUDE
        PATTERN openvsp.rst EXCLUDE )

    INSTALL( TARGETS pyvsp LIBRARY DESTINATION python/openvsp/openvsp )
    INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/vsp.py DESTINATION python/openvsp/openvsp )
    INSTALL( FILES ${CMAKE_CURRENT_SOURCE_DIR}/test.py DESTINATION python/openvsp/openvsp/tests )
    INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/python/packages/openvsp/setup.py DESTINATION python/openvsp)
    INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/python/packages/openvsp/conda-recipe/meta.yaml DESTINATION python/openvsp/conda-recipe )
    INSTALL( FILES ${CMAKE_CURRENT_BINARY_DIR}/python/packages/environment.yml DESTINATION python )
    INSTALL( FILES ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp/facade.py DESTINATION python/openvsp/openvsp )
    INSTALL( FILES ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp/facade_server.py DESTINATION python/openvsp/openvsp )

    if ( NOT VSP_NO_PYDOC )
        INSTALL( DIRECTORY ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/doc DESTINATION python/openvsp )
    endif()

    INSTALL( FILES ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp/tests/test_vsp_api.py DESTINATION python/openvsp/openvsp/tests )

    # Copy python package files to ${CMAKE_BINARY_DIR}/python_pseudo such that a virtual environment can be
    # built from there.  This brings both generated files (from BINARY_DIR) and source files (from SOURCE_DIR) together
    # and places them in the expected relative places.

    # Add copy_package target to copy full directory.  Unfortunately, will copy every build.  However, there is no
    # obvious way to have CMake track the contents of the directory for changes.
    add_custom_target( copy_package
        COMMAND ${CMAKE_COMMAND} -E copy_directory
        ${CMAKE_CURRENT_SOURCE_DIR}/packages/
        ${CMAKE_BINARY_DIR}/python_pseudo/
        # Declare the copied requirements-dev.txt as a byproduct so ninja knows this target
        # produces it.  Without this, the venv step's file-level DEPENDS on the copied file has
        # no producing rule on a clean tree ("no known rule to make it"), and it also forces the
        # full python_pseudo copy (the -e packages) to complete before the venv is built.
        BYPRODUCTS ${CMAKE_BINARY_DIR}/python_pseudo/requirements-dev.txt )
    add_dependencies( pyvsp copy_package ) # Make pyvsp depend on copy_package.  Not sure this is required.

    # Copy _vsp library post build.
    add_custom_command( TARGET pyvsp POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E remove
        ${CMAKE_BINARY_DIR}/python_pseudo/environment.yml.in )

    add_custom_command( TARGET pyvsp POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy
        $<TARGET_FILE:pyvsp>
        ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp/$<TARGET_FILE_NAME:pyvsp> )

    # Copy vsp.py wrapper post SWIG generation and run post copy commands.
    add_custom_command( TARGET pyvsp POST_BUILD
        COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/auto_facade.py
        ${CMAKE_CURRENT_BINARY_DIR}/vsp.py True

        COMMAND ${CMAKE_COMMAND} -E copy
        ${CMAKE_CURRENT_BINARY_DIR}/vsp.py
        ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp/vsp.py

        COMMAND ${CMAKE_COMMAND} -E copy
        ${CMAKE_CURRENT_BINARY_DIR}/facade_server.py
        ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp/facade_server.py

        COMMAND ${CMAKE_COMMAND} -E copy
        ${CMAKE_CURRENT_BINARY_DIR}/facade.py
        ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp/facade.py
    )

    # Write the per group reference pages from the API header, so a function added there turns up
    # in the documentation without a second place to remember.
    add_custom_command( TARGET pyvsp POST_BUILD
        COMMAND ${PYTHON_EXECUTABLE}
        ${CMAKE_CURRENT_SOURCE_DIR}/gen_api_docs.py
        ${VSP_SOURCE_DIR}
        ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp
    )

    # Both generators take the source directory rather than one header.  The API is spread over
    # several -- see the HEADERS list in api_headers.py, which tracks the INPUT list in Doxyfile.in.
    add_custom_command( TARGET pyvsp POST_BUILD
        COMMAND ${PYTHON_EXECUTABLE}
        ${CMAKE_CURRENT_SOURCE_DIR}/gen_unit_test.py
        ${CMAKE_BINARY_DIR}/python_pseudo/openvsp
        ${VSP_SOURCE_DIR}
    )

    # Configure files to pseudo install -- redundant to package configure above, but tracks dependencies best.
    # Unfortunately, it does not know if you delete the files from ${CMAKE_BINARY_DIR}.
    CONFIGURE_FILE( packages/openvsp/setup.py.in ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/setup.py )
    CONFIGURE_FILE( packages/openvsp/conda-recipe/meta.yaml.in ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/conda-recipe/meta.yaml )

    add_custom_command( TARGET pyvsp POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy
        ${CMAKE_CURRENT_BINARY_DIR}/python/packages/environment.yml
        ${CMAKE_BINARY_DIR}/python_pseudo/environment.yml )

    # Create virtual environment with openvsp package installed.
    # Must be called before pydoc target so PYTHON_VENV_EXE is set when pydoc is defined.
    CreateVirtualEnvironment( venv
        REQUIREMENTS_TXT ${CMAKE_BINARY_DIR}/python_pseudo/requirements-dev.txt
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/python_pseudo
        OUT_PYTHON_EXE PYTHON_VENV_EXE
        OUT_BINARY_DIR PYTHON_VENV_BIN_DIR
        OUT_VENV_DIR PYTHON_VENV_DIR )

    # MESSAGE( STATUS "PYTHON_VENV_EXE = ${PYTHON_VENV_EXE}")
    # MESSAGE( STATUS "PYTHON_VENV_BIN_DIR = ${PYTHON_VENV_BIN_DIR}")

    SET( PYTHON_VENV_EXE ${PYTHON_VENV_EXE} PARENT_SCOPE )
    SET( PYTHON_VENV_BIN_DIR ${PYTHON_VENV_BIN_DIR} PARENT_SCOPE )
    SET( PYTHON_VENV_DIR ${PYTHON_VENV_DIR} PARENT_SCOPE )

    ADD_CUSTOM_TARGET( run_pyvsp ${PYTHON_VENV_EXE} -m pyvsp.pyvsp )

    # Build a binary wheel of the openvsp package into ${CMAKE_BINARY_DIR}/wheel.
    #
    # A wheel takes its interpreter, ABI and platform tags from whichever Python builds it,
    # not from the module inside it, and nothing checks that the two agree.  Build this with
    # the wrong Python and the wheel is labelled for an interpreter that cannot import it, and
    # pip will install it anyway.  So it is pinned to the virtual environment's interpreter,
    # which is made from the Python this build compiled the extension against.
    #
    # --no-deps keeps the openvsp package's own requirements out of the output directory, and
    # --no-build-isolation uses the setuptools and wheel already in the environment rather than
    # fetching a fresh copy of them.
    ADD_CUSTOM_TARGET( wheel
        COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/wheel
        COMMAND ${PYTHON_VENV_EXE} -m pip wheel
        --no-deps
        --no-build-isolation
        --wheel-dir ${CMAKE_BINARY_DIR}/wheel
        ${CMAKE_BINARY_DIR}/python_pseudo/openvsp
        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/python_pseudo/openvsp
        DEPENDS pyvsp venv
        COMMENT "Building openvsp wheel with ${PYTHON_VENV_EXE}" )

    if ( NOT VSP_NO_PYDOC )
        add_custom_target( pydoc ALL
                COMMAND ${PYTHON_VENV_EXE} -m sphinx
                ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/openvsp
                ${CMAKE_BINARY_DIR}/python_pseudo/openvsp/doc
                DEPENDS pyvsp venv
        )

        add_custom_command( TARGET pydoc POST_BUILD
                COMMAND ${CMAKE_COMMAND} -E tar "cf"
                "${CMAKE_BINARY_DIR}/pydoc.zip"
                --format=zip
                -- .
                WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/python_pseudo/openvsp/doc"
        )

    endif()


ENDIF(SWIG_FOUND AND PYTHONLIBS_FOUND)
