# Correlation - Liquid and Amorphous Solid Analysis Tool
# Copyright © 2013-2026 Isaías Rodríguez (isurwars@gmail.com)
# SPDX-License-Identifier: AGPL-3.0-only
# Full license: https://github.com/Isurwars/Correlation/blob/main/LICENSE

cmake_minimum_required(VERSION 3.25)

# -----------------------------------------------------------
# Out-of-source build guard
# -----------------------------------------------------------
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
    message(FATAL_ERROR "In-source builds are prohibited. Run cmake -B <build_dir> instead.")
endif()

project(Correlation VERSION 3.10.0 LANGUAGES CXX C)

# -----------------------------------------------------------
# Auto-generate packaging files from templates.
# Output to build directory to prevent source-tree pollution.
# -----------------------------------------------------------
configure_file(
    "${PROJECT_SOURCE_DIR}/packaging/correlation.iss.in"
    "${PROJECT_BINARY_DIR}/packaging/correlation.iss"
    @ONLY
)
configure_file(
    "${PROJECT_SOURCE_DIR}/packaging/resource.rc.in"
    "${PROJECT_BINARY_DIR}/packaging/resource.rc"
    @ONLY
)
configure_file(
    "${PROJECT_SOURCE_DIR}/packaging/PKGBUILD.in"
    "${PROJECT_BINARY_DIR}/packaging/PKGBUILD"
    @ONLY
)
configure_file(
    "${PROJECT_SOURCE_DIR}/recipe/meta.yaml.in"
    "${PROJECT_BINARY_DIR}/recipe/meta.yaml"
    @ONLY
)

add_compile_definitions(CORRELATION_VERSION_STRING="${PROJECT_VERSION}")

# -----------------------------------------------------------
# Build Options
# -----------------------------------------------------------
option(BUILD_GUI "Build the graphical user interface (requires Slint)" ON)
option(BUILD_WITH_HDF5 "Build with HDF5 support" OFF)
option(BUILD_WITH_ARROW "Build with Arrow/Parquet support" OFF)
option(BUILD_FUZZ_TESTS "Build libFuzzer fuzz testing targets (requires Clang)" OFF)
option(USE_SINGLE_PRECISION "Compile the core using single-precision floats instead of doubles" ON)
option(BUILD_PYTHON_BINDINGS "Build pybind11 bindings for Correlation" OFF)
option(BUILD_WASM "Build the WebAssembly target via Emscripten" OFF)
option(ENABLE_FIXUP_BUNDLE "Enable BundleUtilities fixup_bundle on macOS for standalone binaries" ON)
option(BUILD_WITH_CUDA "Enable GPU-accelerated calculators via CUDA" OFF)
option(BUILD_WITH_HIP "Enable GPU-accelerated calculators via HIP" OFF)

if(BUILD_WITH_CUDA AND BUILD_WITH_HIP)
    message(FATAL_ERROR "Cannot build with both BUILD_WITH_CUDA and BUILD_WITH_HIP enabled. Choose one GPU backend.")
endif()

if(BUILD_WITH_CUDA)
    enable_language(CUDA)
    find_package(CUDAToolkit REQUIRED)
    add_compile_definitions(CORRELATION_USE_CUDA CORRELATION_USE_GPU)
    message(STATUS "CUDA enabled — GPU calculators will be built")
endif()

if(BUILD_WITH_HIP)
    enable_language(HIP)
    add_compile_definitions(CORRELATION_USE_HIP CORRELATION_USE_GPU)
    message(STATUS "HIP enabled — GPU calculators will be built")
endif()

if(EMSCRIPTEN OR BUILD_WASM)
    set(BUILD_GUI OFF CACHE BOOL "Disable GUI for WASM" FORCE)
    set(BUILD_PYTHON_BINDINGS OFF CACHE BOOL "Disable Python bindings for WASM" FORCE)
    set(BUILD_TESTING OFF CACHE BOOL "Disable testing for WASM" FORCE)
    set(BUILD_SHARED_LIBS OFF CACHE BOOL "Disable shared libs for WASM" FORCE)
    set(BUILD_WITH_CUDA OFF CACHE BOOL "Disable CUDA for WASM" FORCE)
    set(BUILD_WITH_HIP OFF CACHE BOOL "Disable HIP for WASM" FORCE)
    set(ENABLE_COVERAGE OFF CACHE BOOL "Disable coverage for WASM" FORCE)
    option(WASM_ENABLE_THREADS "Enable pthreads for WASM multi-threading" OFF)
    if(NOT WASM_ENABLE_THREADS)
        set(EMSCRIPTEN_WITHOUT_PTHREAD ON CACHE BOOL "Disable pthreads for WASM" FORCE)
    endif()
    set(TBB_STRICT OFF CACHE BOOL "Disable TBB strict mode" FORCE)
    set(CMAKE_CXX_LINK_LIBRARY_USING_WHOLE_ARCHIVE
        "-Wl,--whole-archive"
        "<LINK_ITEM>"
        "-Wl,--no-whole-archive"
    )
    set(CMAKE_CXX_LINK_LIBRARY_USING_WHOLE_ARCHIVE_SUPPORTED TRUE)
endif()

if(USE_SINGLE_PRECISION)
    add_compile_definitions(CORRELATION_USE_SINGLE_PRECISION=1)
else()
    add_compile_definitions(CORRELATION_USE_SINGLE_PRECISION=0)
endif()

include(GNUInstallDirs)

# Set the C++ standard globally.
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# Enforce minimum compiler versions supporting C++23 features
if(NOT (EMSCRIPTEN OR BUILD_WASM))
    if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "16.0")
        message(FATAL_ERROR "Correlation requires Clang >= 16.0 for C++23 support. Detected: ${CMAKE_CXX_COMPILER_VERSION}")
    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "15.0")
        message(FATAL_ERROR "Correlation requires AppleClang >= 15.0 for C++23 support. Detected: ${CMAKE_CXX_COMPILER_VERSION}")
    elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "12.1")
        message(FATAL_ERROR "Correlation requires GCC >= 12.1 for C++23 support. Detected: ${CMAKE_CXX_COMPILER_VERSION}")
    elseif(MSVC AND MSVC_VERSION LESS 1933)
        message(FATAL_ERROR "Correlation requires MSVC >= 19.33 (Visual Studio 2022 17.3) for C++23 support. Detected: ${MSVC_VERSION}")
    endif()
endif()

# Workaround for Clang <= 18 with libstdc++: libstdc++ guards <expected> behind __cpp_concepts >= 202002L,
# while Clang <= 18 defaults __cpp_concepts to 201907L.
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.0")
    add_compile_definitions(__cpp_concepts=202002L)
    add_compile_options(-Wno-builtin-macro-redefined)
endif()

# Enable experimental C++ library features (needed for libc++ std::jthread starting in LLVM 18)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    include(CheckCXXCompilerFlag)
    check_cxx_compiler_flag("-fexperimental-library" COMPILER_SUPPORTS_EXPERIMENTAL_LIBRARY)
    if(COMPILER_SUPPORTS_EXPERIMENTAL_LIBRARY)
        add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fexperimental-library>)
        add_link_options($<$<COMPILE_LANGUAGE:CXX>:-fexperimental-library>)
    endif()
endif()

# Configure MSVC debug information format to allow sccache to cache objects
if(POLICY CMP0141)
    cmake_policy(SET CMP0141 NEW)
endif()
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "Embedded" CACHE STRING "MSVC debug info format" FORCE)

# Suppress MSVC warning C4624: 'destructor was implicitly defined as deleted'
# This is frequently triggered by Slint's internal headers.
if (MSVC)
    add_compile_options(/wd4624)
endif()

# Set default install path
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
    set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "Installation directory" FORCE)
endif()

# -----------------------------------------------------------
# Testing setup (early so BUILD_TESTING defaults to ON)
# -----------------------------------------------------------
include(CTest)
if(BUILD_FUZZ_TESTS AND NOT BUILD_TESTING)
    enable_testing()
endif()

# -----------------------------------------------------------
# Utils & Dependencies
# -----------------------------------------------------------
include(cmake/Utils.cmake)
include(cmake/Dependencies.cmake)

# -----------------------------------------------------------
# SIMD Feature Detection
# -----------------------------------------------------------
include(cmake/SIMD.cmake)

# -----------------------------------------------------------
# Code Coverage
# -----------------------------------------------------------
option(ENABLE_COVERAGE "Enable code coverage instrumentation" OFF)
if(ENABLE_COVERAGE)
    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        add_compile_options(--coverage -O0 -g)
        add_link_options(--coverage)
        message(STATUS "Code coverage instrumentation enabled")
    else()
        message(WARNING "Code coverage is only supported on GCC and Clang")
    endif()
endif()

# -----------------------------------------------------------
# Static Analysis (clang-tidy)
# -----------------------------------------------------------
option(ENABLE_CLANG_TIDY "Run clang-tidy on project targets during compilation" OFF)
find_program(CLANG_TIDY_EXE NAMES "clang-tidy")
if(CLANG_TIDY_EXE)
    if(ENABLE_CLANG_TIDY)
        set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE}")
        message(STATUS "clang-tidy enabled for project targets: ${CLANG_TIDY_EXE}")
    endif()

    find_program(RUN_CLANG_TIDY_EXE NAMES "run-clang-tidy")
    if(RUN_CLANG_TIDY_EXE)
        add_custom_target(clang-tidy
            COMMAND ${RUN_CLANG_TIDY_EXE} -p ${PROJECT_BINARY_DIR} "^${PROJECT_SOURCE_DIR}/(src|include|tests)/(?!plotters/vendor/|plotters/fonts/|plotters/pdfgen\\.c|plotters/RobotoData_).*"
            WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
            COMMENT "Running clang-tidy across all project source files..."
            VERBATIM
        )
    endif()
endif()

#-----------------------------------------------------------
# Targets setup
#-----------------------------------------------------------
add_subdirectory(src)

#-----------------------------------------------------------
# Testing
#-----------------------------------------------------------
if(BUILD_TESTING)
    add_subdirectory(tests)
endif()

if(BUILD_FUZZ_TESTS)
    add_subdirectory(tests/fuzz)
endif()

#-------------------------------------------
# Documentation
#-------------------------------------------
find_package(Doxygen)
if (DOXYGEN_FOUND)
    set(DOXYGEN_IN ${PROJECT_SOURCE_DIR}/docs/Doxyfile.in)
    set(DOXYGEN_OUT ${PROJECT_BINARY_DIR}/docs/Doxyfile)

    configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)

    add_custom_target(doxygen
        COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
        WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
        COMMENT "Generating API documentation with Doxygen"
        VERBATIM)
endif()

#-----------------------------------------------------------
# Install setup
#-----------------------------------------------------------

# Install the executable and the library.
if(BUILD_GUI)
    install(TARGETS correlation
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
        COMPONENT correlation
    )
endif()

# -----------------------------------------------------------
# Install setup for Linux shared libraries
# -----------------------------------------------------------
if (NOT WIN32)
    # Ensure correlation and correlation-cli find shared libraries in <prefix>/lib
    foreach(_target IN ITEMS correlation correlation-cli)
        if(TARGET ${_target})
            if(APPLE)
                set_target_properties(${_target} PROPERTIES
                    INSTALL_RPATH "@loader_path/../${CMAKE_INSTALL_LIBDIR}"
                )
            else()
                set_target_properties(${_target} PROPERTIES
                    INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
                )
            endif()
        endif()
    endforeach()
    # If Arrow/Parquet were built from source, install them
    if (TARGET arrow_shared AND TARGET parquet_shared AND NOT Arrow_FOUND)
        get_target_property(is_arrow_alias arrow_shared ALIASED_TARGET)
        if (is_arrow_alias STREQUAL "is_arrow_alias-NOTFOUND" OR NOT is_arrow_alias)
            install(TARGETS arrow_shared parquet_shared
                LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
                RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
                COMPONENT correlation
            )
        endif()
    endif()

    if (APPLE AND ENABLE_FIXUP_BUNDLE AND NOT BUILD_PYTHON_BINDINGS)
        # On macOS, fixup the bundle (embed dynamic libraries like TBB, HDF5, etc.)
        # BundleUtilities requires absolute paths; resolve CMAKE_INSTALL_PREFIX
        # in case the user passes a relative --prefix to cmake --install.
        # Force all dynamic libraries to be placed directly in the same bin/ folder
        # as the executables using gp_item_default_embedded_path_override.

        # Define the exact workspace directories where our compiled shared dependencies live
        set(MAC_DEPENDENCY_DIRS
            "${PROJECT_BINARY_DIR}/src"
            "${PROJECT_BINARY_DIR}/lib"
            "${PROJECT_BINARY_DIR}/_deps/nfd-build/src"
            "${PROJECT_BINARY_DIR}/_deps/arrow-build/release"
            "${PROJECT_BINARY_DIR}/_deps/arrow-build/release/cpp/src/arrow"
            "${PROJECT_BINARY_DIR}/_deps/arrow-build/release/cpp/src/arrow/array"
            "${PROJECT_BINARY_DIR}/_deps/arrow-build/release/cpp/src/arrow/compute"
            "${PROJECT_BINARY_DIR}/_deps/arrow-build/release/cpp/src/arrow/io"
            "${PROJECT_BINARY_DIR}/_deps/arrow-build/release/cpp/src/arrow/util"
            "${PROJECT_BINARY_DIR}/_deps/parquet-build/src"
            "${PROJECT_BINARY_DIR}/_deps/tbb-build"
            "${PROJECT_BINARY_DIR}/_deps/tbb-build/src"
            "${PROJECT_BINARY_DIR}/_deps/tbb-build/src/tbb"
            "${PROJECT_BINARY_DIR}/_deps/tbb-build/src/tbbmalloc"
            "/opt/homebrew/lib"
            "/usr/local/lib"
        )

        if(BUILD_GUI)
            install(CODE "
                function(gp_item_default_embedded_path_override item default_embedded_path_var)
                    set(\${default_embedded_path_var} \"@executable_path\" PARENT_SCOPE)
                endfunction()
                include(BundleUtilities)
                get_filename_component(_abs_prefix \"\${CMAKE_INSTALL_PREFIX}\" ABSOLUTE)
                fixup_bundle(\"\${_abs_prefix}/${CMAKE_INSTALL_BINDIR}/correlation\" \"\" \"${MAC_DEPENDENCY_DIRS}\")
            " COMPONENT correlation)
        endif()
        install(CODE "
            function(gp_item_default_embedded_path_override item default_embedded_path_var)
                set(\${default_embedded_path_var} \"@executable_path\" PARENT_SCOPE)
            endfunction()
            include(BundleUtilities)
            get_filename_component(_abs_prefix \"\${CMAKE_INSTALL_PREFIX}\" ABSOLUTE)
            fixup_bundle(\"\${_abs_prefix}/${CMAKE_INSTALL_BINDIR}/correlation-cli\" \"\" \"${MAC_DEPENDENCY_DIRS}\")
        " COMPONENT correlation)
    endif()
endif()

# -----------------------------------------------------------
# Install setup for Linux Desktop Integration
# -----------------------------------------------------------
if (NOT WIN32 AND BUILD_GUI)
    # 1. Install the Icon (adjust path if you use a different icon directory)
    install(FILES "${PROJECT_SOURCE_DIR}/Images/Logo.png"
            DESTINATION share/icons/hicolor/512x512/apps
            RENAME correlation.png
            COMPONENT correlation)

    # 2. Configure and Install the .desktop file
    configure_file("${PROJECT_SOURCE_DIR}/packaging/correlation.desktop"
                   "${PROJECT_BINARY_DIR}/correlation.desktop"
                   COPYONLY)
    install(FILES "${PROJECT_BINARY_DIR}/correlation.desktop"
            DESTINATION share/applications
            COMPONENT correlation)
endif()

# -----------------------------------------------------------
# CPack Packaging
# -----------------------------------------------------------
set(CPACK_PACKAGE_NAME "correlation")
set(CPACK_PACKAGE_VENDOR "Isaías Rodríguez")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Analysis tool for liquid and amorphous solid structures")
set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "correlation")
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_README "${PROJECT_SOURCE_DIR}/README.md")
set(CPACK_COMPONENTS_ALL correlation)

if (WIN32)
    set(CPACK_GENERATOR "NSIS")
    set(CPACK_PACKAGE_FILE_NAME "correlation-${PROJECT_VERSION}-win64")
    set(CPACK_NSIS_PACKAGE_NAME "Correlation Analysis Tool")
    set(CPACK_NSIS_DISPLAY_NAME "Correlation Analysis Tool")
    set(CPACK_PACKAGE_EXECUTABLES "correlation" "Correlation Analysis Tool")
    set(CPACK_CREATE_DESKTOP_LINKS "correlation")
    # Icon shown in Add/Remove Programs — relative to $INSTDIR (no leading backslash)
    set(CPACK_NSIS_INSTALLED_ICON_NAME "correlation.exe")
    string(REPLACE "/" "\\\\" CPACK_NSIS_MUI_ICON "${PROJECT_SOURCE_DIR}/Images/AppIcon.ico")
    string(REPLACE "/" "\\\\" CPACK_NSIS_MUI_UNIICON "${PROJECT_SOURCE_DIR}/Images/AppIcon.ico")
    # Branded installer graphics (24-bit BMP required by NSIS)
    string(REPLACE "/" "\\\\" CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP "${PROJECT_SOURCE_DIR}/packaging/nsis_welcome.bmp")
    string(REPLACE "/" "\\\\" CPACK_NSIS_MUI_HEADERIMAGE "${PROJECT_SOURCE_DIR}/packaging/nsis_header.bmp")
    set(CPACK_NSIS_MUI_HEADERIMAGE_RIGHT ON)
    # Use default zlib compression instead of lzma to avoid false-positive virus detections
    # set(CPACK_NSIS_COMPRESSOR "lzma")
    set(CPACK_NSIS_HELP_LINK "https://github.com/Isurwars/Correlation")
    set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/Isurwars/Correlation")
    set(CPACK_NSIS_CONTACT "isurwars@gmail.com")
    set(CPACK_NSIS_MODIFY_PATH ON)
elseif (APPLE)
    set(CPACK_GENERATOR "DragNDrop")
    set(CPACK_DMG_VOLUME_NAME "correlation")
    set(CPACK_SYSTEM_NAME "macOS")
else ()
    # Linux
    set(CPACK_GENERATOR "DEB;RPM;TGZ")
    set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Isaías Rodríguez <isurwars@gmail.com>")
    set(CPACK_DEBIAN_PACKAGE_DEPENDS "libtbb12 (>= 2021.5), libhdf5-103-1 | libhdf5-103, libfftw3-3")
    set(CPACK_DEBIAN_PACKAGE_SECTION "science")
    set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
    set(CPACK_RPM_PACKAGE_LICENSE "AGPL-3.0-only")
    set(CPACK_RPM_PACKAGE_RELEASE "1")
    set(CPACK_RPM_PACKAGE_AUTOREQ ON)
    set(CPACK_STRIP_FILES TRUE)
endif ()

set(CPACK_INSTALL_CMAKE_PROJECTS "${PROJECT_BINARY_DIR};Correlation;correlation;/")
# Unset the internal CPack included guard to force CPack to overwrite
# the CPackConfig with our top-level settings, overriding fetched dependencies.
unset(CPack_CMake_INCLUDED)
include(CPack)
