# SPDX-FileCopyrightText: 2018-2026 Achilles Developers
# SPDX-License-Identifier: GPL-3.0-or-later

cmake_minimum_required(VERSION 3.17)

# Policy to address @foo@ variable expansion
if(POLICY CMP0053)
  cmake_policy(SET CMP0053 NEW)
endif()

# Set the project name and basic settings
project(ACHILLES LANGUAGES CXX VERSION 1.0.0)
include(CMake/StandardProjectSettings.cmake)

# RPATH handling
set(CMAKE_MACOSX_RPATH 1)
set(CMAKE_SKIP_BUILD_RPATH FALSE)
set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
# For a wheel we set an explicit relative $ORIGIN rpath below and co-locate
# every needed library in achilles/lib
if(SKBUILD)
  set(CMAKE_INSTALL_RPATH_USE_LINK_PATH FALSE)
else()
  set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
endif()

# For a wheel, install every shared library into achilles/lib so
# they are co-located and a single $ORIGIN rpath resolves them all.
# This must be set before the dependencies are configured.
if(SKBUILD)
  set(CMAKE_INSTALL_LIBDIR "achilles/lib" CACHE PATH "Wheel library install dir" FORCE)
endif()

# Set RPATH for the install location
if(SKBUILD)
  if(APPLE)
    set(CMAKE_INSTALL_RPATH "@loader_path;@loader_path/lib")
  else()
    set(CMAKE_INSTALL_RPATH "$ORIGIN;$ORIGIN/lib")
  endif()
else()
  LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" isSystemDir)
  IF("${isSystemDir}" STREQUAL "-1")
    if(APPLE)
      set(CMAKE_INSTALL_RPATH "@loader_path/../lib")
      list(APPEND CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
    elseif(UNIX)
      # Cover both lib and lib64
      set(CMAKE_INSTALL_RPATH "$ORIGIN/../lib;$ORIGIN/../lib64;${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}")
    endif()
  ENDIF()
endif()

# Link this 'library' to set the c++ standard / compile-time options requested
# Additionally, link to get include and external dependencies
add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_17)
target_include_directories(project_options INTERFACE
    "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
    "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)

enable_language(Fortran)
set(CMAKE_Fortran_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/modules)

# Link this 'library' to use the warnings specified in CompilerWarnings.cmake
add_library(project_warnings INTERFACE)

# Standard compiler warnings
include(CMake/CompilerWarnings.cmake)
set_project_warnings(project_warnings)

# Sanitizer options if supported by compiler
include(CMake/Sanitizers.cmake)
enable_sanitizers(project_options)

# Allow for static analysis options
include(CMake/StaticAnalyzers.cmake)
include(CMake/ProgramOptions.cmake)

# Test if C++ compiler supports math special functions
include(CheckCXXSourceCompiles)

check_cxx_source_compiles("
#include <cmath>
int main() {
    std::sph_bessel(1, 0.0);
    return 0;
}" ACHILLES_HAS_MATH_SPECIAL_FUNCTIONS)
if(ACHILLES_HAS_MATH_SPECIAL_FUNCTIONS)
  target_compile_definitions(project_options INTERFACE ACHILLES_HAS_MATH_SPECIAL_FUNCTIONS)
endif()

option(ENABLE_AUTODIFF "Enable Autodiff" OFF)

# Allow build to be made even if not a git repo
SET(GIT_FAIL_IF_NONZERO_EXIT FALSE)

# Very basic PCH example
if(ACHILLES_ENABLE_PCH)
  # This sets a global PCH parameter, each project will build its own PCH,
  # which is a good idea if any #define's change
  # These should be headers included in many places
  target_precompile_headers(project_options INTERFACE <vector> <string> <map>)
endif()

# Check for python development environment
# find_package(Python REQUIRED COMPONENTS Development)

# Add CMake modules to CMAKE_MODULE_PATH
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/CMake)

# Ensure compiler supports filesystem and needed compiler flags
find_package(Filesystem REQUIRED)

# Find ZLIB to read gzip files
if(ACHILLES_ENABLE_GZIP)
find_package(ZLIB REQUIRED)
endif()

if(ACHILLES_ENABLE_SHERPA)
# Find Sherpa
find_package(SHERPA REQUIRED)
find_package(ZLIB REQUIRED)
endif()

# Find ROOT if needed
if(ACHILLES_ENABLE_ROOT)
  MESSAGE(STATUS "Building with ROOT flux files")
  find_package(ROOT 6 REQUIRED)
  string(REPLACE "." ";" VERSION_LIST ${ROOT_VERSION})
  list(GET VERSION_LIST 1 ROOT_MINOR_VERSION)
  if(${ROOT_MINOR_VERSION} LESS 14)
    # ROOT targets are missing includes and flags in ROOT 6.10 and 6.12
    set_property(TARGET ROOT::Core PROPERTY
                 INTERFACE_INCLUDE_DIRECTORIES "${ROOT_INCLUDE_DIRS}")

    # Early ROOT does not include the flags required on targets
    add_library(ROOT::Flags_CXX IMPORTED INTERFACE)
  endif()

  if(${ROOT_MINOR_VERSION} LESS 16)
    # ROOT 6.14 and earlier have a spacing bug in the linker flags
    string(REPLACE "-L " "-L" ROOT_EXE_LINKER_FLAGS "${ROOT_EXE_LINKER_FLAGS}")

    # Fix for ROOT_CXX_FLAGS not actually being a CMake list
    separate_arguments(ROOT_CXX_FLAGS)
    set_property(TARGET ROOT::Flags_CXX APPEND PROPERTY
                 INTERFACE_COMPILE_OPTIONS ${ROOT_CXX_FLAGS})

    # Add definitions
    separate_arguments(ROOT_DEFINITIONS)
    foreach(flag ${ROOT_EXE_LINKER_FLAG_LIST})
      # Remove -D or /D if present
      string(REGEX REPLACE [=[^[-//]D]=] "" flag ${flag})
      set_property(TARGET ROOT::Flags
                   APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${flag})
    endforeach()

    # This also fixes a bug in the linker flags
    separate_arguments(ROOT_EXE_LINKER_FLAGS)
    set_property(TARGET ROOT::Flags_CXX APPEND PROPERTY
                 INTERFACE_LINK_LIBRARIES ${ROOT_EXE_LINKER_FLAGS})
  endif()
endif()

# Add dependencies
include(CMake/CPM.cmake)
include(external/CMakeLists.txt)

# Configure system specific variables for loading shared libraries
set(LIBPREFIX ${CMAKE_SHARED_LIBRARY_PREFIX})
set(LIBSUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX})
set(BUILDPATH ${CMAKE_CURRENT_BINARY_DIR})
set(INSTALLPATH ${CMAKE_INSTALL_PREFIX})
configure_file(include/Achilles/System.hh.in ${CMAKE_CURRENT_SOURCE_DIR}/include/Achilles/System.hh)

# Data is versioned independently of the code: bump this only when the data set itself
# changes, then publish a matching GitHub Release tagged with this value. Generated into
# the build tree and installed into the wheel below (see ACHILLES_ENABLE_PYTHON block).
set(ACHILLES_DATA_RELEASE "achilles-data-v1" CACHE STRING
    "GitHub Release tag of the Achilles data set downloaded on demand")
configure_file(python/achilles/_data_release.py.in
    ${CMAKE_CURRENT_BINARY_DIR}/python/achilles/_data_release.py @ONLY)

# Copy over data files. The heavy data/ tree is excluded from the sdist (it is
# downloaded on demand at runtime), so it may be absent when building from an sdist;
# guard on its presence. flux/ is small and always shipped.
if(EXISTS ${ACHILLES_SOURCE_DIR}/data)
    file(COPY ${ACHILLES_SOURCE_DIR}/data DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
    file(COPY ${ACHILLES_SOURCE_DIR}/data/default/run.yml DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
    file(COPY ${ACHILLES_SOURCE_DIR}/data/default/FormFactors.yml DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
endif()
file(COPY ${ACHILLES_SOURCE_DIR}/flux DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
file(COPY ${ACHILLES_SOURCE_DIR}/parameters.dat DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
file(COPY ${ACHILLES_SOURCE_DIR}/cascade.yml DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

# Copy over example folder
file(COPY ${ACHILLES_SOURCE_DIR}/examples DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

# Unzip if GZIP not turned on
if(NOT ACHILLES_ENABLE_GZIP)
  file(GLOB zipfiles "${CMAKE_CURRENT_BINARY_DIR}/data/configurations/*.gz")
  MESSAGE(STATUS "Extracting configurations from zip files")
  foreach(file ${zipfiles})
    execute_process(COMMAND gunzip "${file}"
                    WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/data/configurations/")
  endforeach()
endif()

# Build Plugin Library
# add_subdirectory(src/Plugins)

# Testing
if(ACHILLES_ENABLE_TESTING)
  if(ACHILLES_COVERAGE)
    target_compile_options(project_options INTERFACE --coverage)
    target_link_libraries(project_options INTERFACE gcov)
  endif()
  target_compile_definitions(project_options INTERFACE TESTING)
  enable_testing()
  message(STATUS "Building Tests.")
  add_subdirectory(test)
endif()

# Main code
add_subdirectory(src)

# Install CMake find_package files
include(CMakePackageConfigHelpers)

configure_package_config_file(
    ${CMAKE_SOURCE_DIR}/CMake/achilles-config.cmake.in
    ${CMAKE_BINARY_DIR}/CMake/achilles-config.cmake
    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/achilles
)

write_basic_package_version_file(
    ${CMAKE_BINARY_DIR}/CMake/achilles-config-version.cmake
    VERSION ${ACHILLES_VERSION}
    COMPATIBILITY AnyNewerVersion
)

install(
    FILES
        ${CMAKE_BINARY_DIR}/CMake/achilles-config.cmake
        ${CMAKE_BINARY_DIR}/CMake/achilles-config-version.cmake
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/achilles
)

# Install default run card and form factors card (only when data/ is available;
# absent for slim-sdist builds, where these come from the downloaded data set).
if(EXISTS ${CMAKE_BINARY_DIR}/data/default/run.yml)
install(
    FILES
        ${CMAKE_BINARY_DIR}/data/default/run.yml
        ${CMAKE_BINARY_DIR}/data/default/FormFactors.yml
    DESTINATION ${CMAKE_INSTALL_PREFIX}
)
endif()

# Install the data files to be found.
# Skipped for wheel builds (SKBUILD): the wheel ships no heavy data — the large
# data/ set is downloaded on demand into a user cache (see python/achilles/_data.py).
if(NOT SKBUILD)
install(DIRECTORY
    ${CMAKE_BINARY_DIR}/data
    ${CMAKE_BINARY_DIR}/flux
    DESTINATION share/Achilles
)
endif(NOT SKBUILD)

# Install examples into the install directory
install(DIRECTORY
    ${CMAKE_BINARY_DIR}/examples
    DESTINATION ${CMAKE_INSTALL_PREFIX}
)

if(ACHILLES_ENABLE_PYTHON)
# NOTE: data/ is deliberately NOT bundled here. It is 227 MB and would push the wheel
# past PyPI's 100 MB per-file limit, so it is downloaded on demand into a user cache
# via `achilles-data get` (python/achilles/_data.py).
install(DIRECTORY flux
        DESTINATION achilles)
install(FILES FormFactors.yml
        DESTINATION achilles)
# Baked-in achilles-data pin consumed by python/achilles/_data.py.
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/python/achilles/_data_release.py
        DESTINATION achilles)
endif(ACHILLES_ENABLE_PYTHON)
