cmake_minimum_required(VERSION 3.14)
project(memilio-python)

# copy cpp files for building sdist's, then exit immediately
# Note: ${SKBUILD_STATE} is "wheel" while building from an sdist
if(SKBUILD_STATE STREQUAL "sdist")
    message(STATUS "Copying current C++ source tree for distribution")
    file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../../cpp DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}
         PATTERN "build" EXCLUDE
         PATTERN ".cache" EXCLUDE)
    return()
endif()

set(CMAKE_CXX_STANDARD "20")
set(CMAKE_CXX_STANDARD_REQUIRED "20")

option(MEMILIO_USE_BUNDLED_PYBIND11 "Use pybind11 bundled with this library." ON)
mark_as_advanced(MEMILIO_USE_BUNDLED_PYBIND11)

include(GNUInstallDirs) # set to gnu folders. No cache variable so this is not global

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")

# Windows has no RPATH (CMAKE_SKIP_RPATH defaults to ON there), so set this only on UNIX and APPLE.
if(APPLE)
    set(CMAKE_INSTALL_RPATH
        "@loader_path"
        "@loader_path/lib"
    )
elseif(UNIX)
    set(CMAKE_INSTALL_RPATH
        "$ORIGIN"
        "$ORIGIN/lib"
    )
endif()

# CMAKE_INSTALL_RPATH defaults to
# - Linux: $ORIGIN;$ORIGIN/lib
# - MacOS: @loader_path;@loader_path/lib
# - Windows: empty
set(PYMIO_INSTALL_RPATH "${CMAKE_INSTALL_RPATH}")

# keep build RPATH separate and avoid putting build paths into installed wheels
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)

# Use a static spdlog so the wheel stays self-contained.
set(SPDLOG_BUILD_SHARED OFF CACHE BOOL "" FORCE)

if(MEMILIO_USE_BUNDLED_PYBIND11)
    message(STATUS "Downloading pybind11 library")
    # Fetch pybind11
    include(FetchContent)
    FetchContent_Declare(pybind11
        GIT_REPOSITORY https://github.com/pybind/pybind11
        GIT_TAG v3.0.0
    )
    FetchContent_MakeAvailable(pybind11)
else()
    find_package(pybind11 REQUIRED)
endif()

# add in C++ library
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/cpp)
    # this is a sdist build, hence we use a copy C++ source tree, so that it is not out-of-tree wrt. pyproject.toml
    message(WARNING "Using ${CMAKE_CURRENT_SOURCE_DIR}/cpp as C++ source tree. This is intentional for sdist builds.")
    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/cpp ${CMAKE_CURRENT_BINARY_DIR}/cpp EXCLUDE_FROM_ALL)
else()
    # normal include using the main project tree
    add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../cpp ${CMAKE_CURRENT_BINARY_DIR}/cpp EXCLUDE_FROM_ALL)
endif()

# a list of all "LINKED_LIBRARIES" that are given to add_pymio_module. will contain duplicates
# used for wheel installation
set(PYMIO_MEMILIO_LIBS_LIST)

# Function to add a pybind11 module
function(add_pymio_module target_name)
    set(options)
    set(oneValueArgs)
    set(multiValueArgs LINKED_LIBRARIES SOURCES)
    cmake_parse_arguments(PYBIND11_MODULE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    pybind11_add_module(${target_name} MODULE ${PYBIND11_MODULE_SOURCES})
    target_link_libraries(${target_name} PRIVATE ${PYBIND11_MODULE_LINKED_LIBRARIES})
    target_include_directories(${target_name} PRIVATE memilio/simulation/bindings)

    # Collect linked libraries for later installation
    list(APPEND PYMIO_MEMILIO_LIBS_LIST "${PYBIND11_MODULE_LINKED_LIBRARIES}")
    set(PYMIO_MEMILIO_LIBS_LIST ${PYMIO_MEMILIO_LIBS_LIST} PARENT_SCOPE)

    # Set RPATH so extension can find the C++ libraries in lib subdirectory
    set_target_properties(${target_name} PROPERTIES
        INSTALL_RPATH "${PYMIO_INSTALL_RPATH}"
    )

    # Make sure to install shared and static libraries (LIBRARY and ARCHIVE) and the executables (RUNTIME).
    # Destination "." refers to the value of wheel.install-dir in the pyproject.toml.
    install(TARGETS ${target_name}
        LIBRARY DESTINATION .
        ARCHIVE DESTINATION .
        RUNTIME DESTINATION .
    )
endfunction()

# build python extensions
add_pymio_module(_simulation_abm
    LINKED_LIBRARIES memilio abm
    SOURCES memilio/simulation/bindings/models/abm.cpp
)

add_pymio_module(_simulation
    LINKED_LIBRARIES memilio
    SOURCES memilio/simulation/bindings/simulation.cpp
    memilio/simulation/bindings/epidemiology/damping_sampling.cpp
    memilio/simulation/bindings/epidemiology/uncertain_matrix.cpp
    memilio/simulation/bindings/mobility/metapopulation_mobility_instant.cpp
    memilio/simulation/bindings/utils/date.cpp
    memilio/simulation/bindings/utils/logging.cpp
    memilio/simulation/bindings/utils/time_series.cpp
    memilio/simulation/bindings/utils/parameter_distributions.cpp
    memilio/simulation/bindings/utils/uncertain_value.cpp
)

add_pymio_module(_simulation_osir
    LINKED_LIBRARIES memilio ode_sir
    SOURCES memilio/simulation/bindings/models/osir.cpp
)

add_pymio_module(_simulation_oseir
    LINKED_LIBRARIES memilio ode_seir
    SOURCES memilio/simulation/bindings/models/oseir.cpp
)

add_pymio_module(_simulation_oseir_metapop
    LINKED_LIBRARIES memilio ode_seir_metapop
    SOURCES memilio/simulation/bindings/models/oseir_metapop.cpp
)

add_pymio_module(_simulation_oseirdb
    LINKED_LIBRARIES memilio ode_seirdb
    SOURCES memilio/simulation/bindings/models/oseirdb.cpp
)

add_pymio_module(_simulation_osecir
    LINKED_LIBRARIES memilio ode_secir
    SOURCES memilio/simulation/bindings/models/osecir.cpp
)

add_pymio_module(_simulation_osecirvvs
    LINKED_LIBRARIES memilio ode_secirvvs
    SOURCES memilio/simulation/bindings/models/osecirvvs.cpp
)

add_pymio_module(_simulation_ssir
    LINKED_LIBRARIES memilio sde_sir
    SOURCES memilio/simulation/bindings/models/ssir.cpp
)

add_pymio_module(_simulation_ssirs
    LINKED_LIBRARIES memilio sde_sirs
    SOURCES memilio/simulation/bindings/models/ssirs.cpp
)
add_pymio_module(_simulation_omseirs4
    LINKED_LIBRARIES memilio ode_mseirs4
    SOURCES memilio/simulation/bindings/models/omseirs4.cpp
)

# install all shared memilio libraries, which were given as "LINKED_LIBRARIES" to add_pymio_module
list(REMOVE_DUPLICATES PYMIO_MEMILIO_LIBS_LIST)

# Set RPATH for all C++ libraries so they can find each other
foreach(target_name IN LISTS PYMIO_MEMILIO_LIBS_LIST)
    if(TARGET ${target_name})
        set_target_properties(${target_name} PROPERTIES
            INSTALL_RPATH "${PYMIO_INSTALL_RPATH}"
        )
    endif()
endforeach()

install(TARGETS ${PYMIO_MEMILIO_LIBS_LIST}
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION lib
)

# If jsoncpp comes from the bundled third-party dependencies, copy its shared
# library into the wheel under every file name importers might look for. Wheels
# drop symlinks, so place real copies for each alias.
if(TARGET jsoncpp_lib)
    set_target_properties(jsoncpp_lib PROPERTIES
        INSTALL_RPATH "${PYMIO_INSTALL_RPATH}"
    )
    install(FILES
        $<TARGET_FILE:jsoncpp_lib>
        $<TARGET_SONAME_FILE:jsoncpp_lib>
        $<TARGET_LINKER_FILE:jsoncpp_lib>
        DESTINATION lib
    )
endif()

if(TARGET spdlog)
    get_target_property(_SPDLOG_LIB_TYPE spdlog TYPE)

    if(_SPDLOG_LIB_TYPE STREQUAL "SHARED_LIBRARY")
        set_target_properties(spdlog PROPERTIES
            INSTALL_RPATH "${PYMIO_INSTALL_RPATH}"
        )
        install(FILES
            $<TARGET_FILE:spdlog>
            $<TARGET_SONAME_FILE:spdlog>
            $<TARGET_LINKER_FILE:spdlog>
            DESTINATION lib
        )
    endif()
endif()
