# Copyright (c) 2024 Lawrence Livermore National Security, LLC and other
# HYPRE Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: MIT

# CMake minimum version requirement
cmake_minimum_required(VERSION 3.23)

# Project name and version
project(hypredrive VERSION 0.2.0 LANGUAGES C)
set(PROJECT_URL "https://github.com/hypre-space/hypredrive")
set(PROJECT_BUGREPORT "https://github.com/hypre-space/hypredrive/issues")

# Include GNUInstallDirs
include(GNUInstallDirs)

# Specify the C standard
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED True)

# Disallow in-source builds
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
    message(FATAL_ERROR "In-source builds are not allowed. Please create a separate build directory and run CMake from there.")
endif()

# Option to build shared libraries
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)

# HYPREDRV build options
option(HYPREDRV_ENABLE_EIGSPEC      "Enable full eigenspectrum computation" OFF)
option(HYPREDRV_ENABLE_EXPERIMENTAL "Enable experimental features" OFF)
option(HYPREDRV_ENABLE_TESTING      "Enable testing support and check target" OFF)
option(HYPREDRV_ENABLE_ALL_TESTS    "Force-enable tests that are otherwise disabled in some configurations" OFF)
option(HYPREDRV_ENABLE_COVERAGE     "Enable code coverage instrumentation" OFF)
option(HYPREDRV_ENABLE_ANALYSIS     "Enable static code analysis (clang-tidy) and sanitizers (ASan/UBSan)" OFF)
option(HYPREDRV_ENABLE_DATA         "Enable 'data' target to fetch example datasets" OFF)
option(HYPREDRV_ENABLE_DOCS         "Enable documentation generation" OFF)
option(HYPREDRV_ENABLE_EXAMPLES     "Enable the HYPREDRV examples" OFF)
option(HYPREDRV_ENABLE_EXAMPLE_OMP  "Enable OpenMP parallel assembly in example drivers" OFF)
option(HYPREDRV_ENABLE_HWLOC        "Enable hwloc support for detailed system topology information" OFF)
option(HYPREDRV_ENABLE_CALIPER      "Enable Caliper instrumentation support" OFF)
option(HYPREDRV_ENABLE_COMPRESSION  "Enable optional lossless compression backends" OFF)
option(HYPREDRV_ENABLE_FUZZING      "Enable opt-in fuzzing harnesses and replay tests" OFF)
option(HYPREDRV_ENABLE_CUDA         "Enable CUDA support in HYPRE and bundled dependencies" OFF)
option(HYPREDRV_ENABLE_HIP          "Enable HIP support in HYPRE and bundled dependencies" OFF)
option(HYPREDRV_ENABLE_SYCL         "Enable SYCL support in HYPRE and bundled dependencies" OFF)
option(HYPREDRV_ENABLE_CPP          "Enable the hypredrive C++ interface" OFF)
option(HYPREDRV_ENABLE_FORTRAN      "Enable the hypredrive Fortran interface" OFF)
option(HYPREDRV_ENABLE_PYTHON       "Enable the hypredrive Python interface" OFF)
option(HYPREDRV_ENABLE_MATLAB       "Enable the hypredrive MATLAB/Octave MEX interface" OFF)
option(HYPREDRV_ENABLE_JULIA        "Enable the hypredrive Julia interface" OFF)
option(HYPREDRV_BUILD_DSUPERLU      "Fetch and build SuperLU_DIST for HYPRE" OFF)

include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Fuzzing.cmake)

if(HYPREDRV_ENABLE_ANALYSIS)
    set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE BOOL
        "Export compile commands for static analysis" FORCE)
endif()

# Optionally set the build type to Release by default
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
endif()

set(HYPREDRV_USING_DEBUG OFF)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(HYPREDRV_USING_DEBUG ON)
endif()

# Testing implies data
if(HYPREDRV_ENABLE_TESTING)
    set(HYPREDRV_ENABLE_DATA ON)
endif()

# Coverage implies testing, examples, and data
if(HYPREDRV_ENABLE_COVERAGE)
    set(HYPREDRV_ENABLE_DATA ON)
    set(HYPREDRV_ENABLE_TESTING ON)
    set(HYPREDRV_ENABLE_EXAMPLES ON)
endif()

# Configure instrumentation before bringing in fetched dependencies so options
# like sanitizers/coverage also apply to HYPRE when we build it in-tree.
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Instrumentation.cmake)

# Configure unified output directories for all libraries and executables
# This ensures all libraries (including dependencies) are in the same folder
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

# Some external HYPRE package configs pull in C++/SYCL-side imported targets
# during find_package(HYPRE). Make CXX available up front when a compiler
# exists, without forcing pure-C builds to fail on toolchains that lack C++.
enable_language(CXX OPTIONAL)

# Include directories
include_directories(${PROJECT_SOURCE_DIR}/include)

# Find and configure dependencies
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Deps.cmake)

function(hypredrv_add_hypre_build_dependency target_name)
    if(HYPREDRV_HYPRE_BUILD_DEPENDENCY)
        add_dependencies(${target_name} ${HYPREDRV_HYPRE_BUILD_DEPENDENCY})
    endif()
endfunction()

# Check for headers, functions, and libraries
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Checks.cmake)

# Download and extract datasets
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Data.cmake)

# Capture git version info for hypredrive
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Git.cmake)
# Export helpers are included here before interface subdirectories so they can
# share the same build-tree export decision used later by install packaging.
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_ExportHelpers.cmake)
hypredrv_setup_git_version_info(${CMAKE_CURRENT_SOURCE_DIR})
if(HYPREDRV_GIT_FOUND)
    message(STATUS "HYPREDRV develop string: ${HYPREDRV_DEVELOP_STRING}")
    message(STATUS "HYPREDRV branch: ${HYPREDRV_BRANCH_NAME}")
    if(HYPREDRV_GIT_SHA)
        message(STATUS "HYPREDRV git SHA: ${HYPREDRV_GIT_SHA}")
    endif()
else()
    message(STATUS "HYPREDRV git SHA: not available (using release ${PROJECT_VERSION})")
endif()

# Generate the config header
configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_config.h.in"
    "${CMAKE_CURRENT_BINARY_DIR}/HYPREDRV_config.h"
)

# Include the generated config header
include_directories(${CMAKE_CURRENT_BINARY_DIR})

# Explicit library source list (keep in sync with Makefile.am)
set(SOURCE_FILES
    src/internal/error.c
    src/internal/eigspec.c
    src/internal/info.c
    src/internal/stats.c
    src/internal/utils.c
    src/internal/containers.c
    src/internal/yaml.c
    src/internal/field.c
    src/internal/vector.c
    src/internal/matrix.c
    src/internal/linsys.c
    src/internal/linsys_print.c
    src/internal/cheby.c
    src/internal/amg.c
    src/internal/ams.c
    src/internal/ads.c
    src/internal/fsai.c
    src/internal/ilu.c
    src/internal/schwarz.c
    src/internal/mgr.c
    src/internal/precon.c
    src/internal/precon_reuse.c
    src/internal/presets.c
    src/internal/krylov.c
    src/internal/pcg.c
    src/internal/gmres.c
    src/internal/fgmres.c
    src/internal/bicgstab.c
    src/internal/solver.c
    src/internal/args.c
    src/internal/comp.c
    src/internal/lsseq.c
    src/internal/scaling.c
    src/internal/runtime.c
    src/internal/logging.c
    src/internal/help.c
    src/HYPREDRV.c
)

# Compile the implementation once so shared builds can also provide a private,
# non-installed static link path to bundled tools and white-box tests.
add_library(HYPREDRV_objects OBJECT ${SOURCE_FILES})
add_library(HYPREDRV $<TARGET_OBJECTS:HYPREDRV_objects>)
add_library(HYPREDRV::HYPREDRV ALIAS HYPREDRV)
hypredrv_add_hypre_build_dependency(HYPREDRV_objects)
hypredrv_add_hypre_build_dependency(HYPREDRV)

# Set library properties
set_target_properties(HYPREDRV PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION ${PROJECT_VERSION_MAJOR}
    POSITION_INDEPENDENT_CODE ON
    C_VISIBILITY_PRESET hidden
    DEFINE_SYMBOL HYPREDRV_EXPORTS
    PUBLIC_HEADER "${PROJECT_SOURCE_DIR}/include/HYPREDRV.h;${PROJECT_SOURCE_DIR}/include/HYPREDRV_utils.h;${CMAKE_CURRENT_BINARY_DIR}/HYPREDRV_config.h"
)
set_target_properties(HYPREDRV_objects PROPERTIES
    POSITION_INDEPENDENT_CODE ON
    C_VISIBILITY_PRESET hidden
)

# HYPREDRV_EXPORT_SYMBOL must be empty for static-library consumers on Windows.
# PUBLIC ensures the definition is also present on the installed CMake target.
get_target_property(_hypredrv_library_type HYPREDRV TYPE)
if(_hypredrv_library_type STREQUAL "STATIC_LIBRARY")
    target_compile_definitions(HYPREDRV PUBLIC HYPREDRV_STATIC_DEFINE)
endif()

# Include directories for HYPREDRV
target_include_directories(HYPREDRV
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
        $<INSTALL_INTERFACE:include>
    PRIVATE
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/internal>
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/internal>
)

# Link HYPRE library to HYPREDRV
target_link_libraries(HYPREDRV
    PUBLIC
        $<BUILD_INTERFACE:HYPRE::HYPRE>
    INTERFACE
        $<INSTALL_INTERFACE:HYPRE::HYPRE>)

if(HYPREDRV_INSTRUMENTATION_LINK_FLAGS)
    target_link_options(HYPREDRV PUBLIC ${HYPREDRV_INSTRUMENTATION_LINK_FLAGS})
endif()

if(HYPREDRV_COVERAGE_RUNTIME_LIBRARY)
    target_link_libraries(HYPREDRV PUBLIC ${HYPREDRV_COVERAGE_RUNTIME_LIBRARY})
endif()

# Link math library as PUBLIC so static consumers that also link static HYPRE
# receive libm after transitive dependencies (required in compatibility builds).
target_link_libraries(HYPREDRV PUBLIC m)

if(UNIX AND NOT APPLE)
    # Guard against regressions where static consumers lose libm and fail link.
    get_target_property(_hypredrv_iface_link_libs HYPREDRV INTERFACE_LINK_LIBRARIES)
    set(_hypredrv_has_libm OFF)
    foreach(_lib IN LISTS _hypredrv_iface_link_libs)
        if(_lib STREQUAL "m" OR _lib STREQUAL "$<LINK_ONLY:m>")
            set(_hypredrv_has_libm ON)
            break()
        endif()
    endforeach()
    if(NOT _hypredrv_has_libm)
        message(FATAL_ERROR "HYPREDRV must propagate libm in INTERFACE_LINK_LIBRARIES on Unix")
    endif()
endif()

if(HWLOC_FOUND)
    target_include_directories(HYPREDRV PRIVATE ${HWLOC_INCLUDE_DIR})
    target_link_libraries(HYPREDRV PRIVATE ${HWLOC_LIBRARY})
endif()

if(HYPREDRV_ENABLE_COMPRESSION)
    if(HYPREDRV_COMPRESSION_INCLUDE_DIRS)
        target_include_directories(HYPREDRV PRIVATE ${HYPREDRV_COMPRESSION_INCLUDE_DIRS})
    endif()
    if(HYPREDRV_COMPRESSION_LIBRARIES)
        target_link_libraries(HYPREDRV PRIVATE ${HYPREDRV_COMPRESSION_LIBRARIES})
    endif()
endif()

if(CALIPER_FOUND)
    if(TARGET caliper::caliper)
        target_link_libraries(HYPREDRV PRIVATE caliper::caliper)
        # Ensure Caliper headers are visible to dependents when HYPRE enables Caliper
        get_target_property(CALIPER_INCLUDE_DIRS caliper::caliper INTERFACE_INCLUDE_DIRECTORIES)
        if(CALIPER_INCLUDE_DIRS)
            target_include_directories(HYPREDRV PUBLIC $<BUILD_INTERFACE:${CALIPER_INCLUDE_DIRS}>)
        endif()
        # Caliper is a C++ library - ensure C++ standard library is available
        # Enable C++ language support for linking C++ libraries
        enable_language(CXX OPTIONAL)
        if(CMAKE_CXX_COMPILER)
        # Link C++ standard library PUBLIC so it propagates to dependents (examples, etc.)
        # For Clang, check if we're using libstdc++ (GNU's) or libc++ (Clang's)
        if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
            target_link_libraries(HYPREDRV PUBLIC stdc++)
        elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
            # Clang can use either libstdc++ or libc++
            # Try to detect which one, but default to stdc++ on Linux
            if(UNIX AND NOT APPLE)
                target_link_libraries(HYPREDRV PUBLIC stdc++)
            else()
                target_link_libraries(HYPREDRV PUBLIC c++)
            endif()
        endif()
        endif()
    elseif(TARGET caliper)
        # Caliper was built via FetchContent - add include directories
        get_property(CALIPER_INCLUDE_DIR_FOR_HYPRE CACHE CALIPER_INCLUDE_DIR_FOR_HYPRE PROPERTY VALUE)
        if(CALIPER_INCLUDE_DIR_FOR_HYPRE)
            target_include_directories(HYPREDRV PUBLIC $<BUILD_INTERFACE:${CALIPER_INCLUDE_DIR_FOR_HYPRE}>)
        endif()
        target_link_libraries(HYPREDRV PRIVATE caliper)
        # Enable C++ and link standard library PUBLIC
        enable_language(CXX OPTIONAL)
        if(CMAKE_CXX_COMPILER)
            if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
                target_link_libraries(HYPREDRV PUBLIC stdc++)
            elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
                if(UNIX AND NOT APPLE)
                    target_link_libraries(HYPREDRV PUBLIC stdc++)
                else()
                    target_link_libraries(HYPREDRV PUBLIC c++)
                endif()
            endif()
        endif()
    else()
        target_include_directories(HYPREDRV PUBLIC $<BUILD_INTERFACE:${CALIPER_INCLUDE_DIR}>)
        target_link_libraries(HYPREDRV PRIVATE ${CALIPER_LIBRARY})
        # Enable C++ and link standard library PUBLIC for manual linking case
        enable_language(CXX OPTIONAL)
        if(CMAKE_CXX_COMPILER)
            if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
                target_link_libraries(HYPREDRV PUBLIC stdc++)
            elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
                target_link_libraries(HYPREDRV PUBLIC c++)
            endif()
        endif()
    endif()
endif()

# Optional eigenspectrum feature
if(HYPREDRV_ENABLE_EIGSPEC)
    find_package(LAPACK REQUIRED)
    target_compile_definitions(HYPREDRV PUBLIC HYPREDRV_ENABLE_EIGSPEC=1)
    target_link_libraries(HYPREDRV PRIVATE LAPACK::LAPACK)
endif()

if(HYPREDRV_ENABLE_EXPERIMENTAL)
    target_compile_definitions(HYPREDRV PUBLIC HYPREDRV_ENABLE_EXPERIMENTAL=1)
endif()

# Transfer the public library's accumulated compile requirements to the object
# target. Link requirements are attached as usage requirements so dependency
# include paths and compile definitions also reach the implementation sources.
foreach(_property IN ITEMS
        INCLUDE_DIRECTORIES
        COMPILE_DEFINITIONS
        COMPILE_OPTIONS
        COMPILE_FEATURES)
    get_target_property(_value HYPREDRV ${_property})
    if(_value AND NOT _value MATCHES "-NOTFOUND$")
        set_property(TARGET HYPREDRV_objects PROPERTY ${_property} "${_value}")
    endif()
endforeach()
get_target_property(_hypredrv_object_link_libraries HYPREDRV LINK_LIBRARIES)
if(_hypredrv_object_link_libraries AND
   NOT _hypredrv_object_link_libraries MATCHES "-NOTFOUND$")
    target_link_libraries(HYPREDRV_objects
        PRIVATE ${_hypredrv_object_link_libraries})
endif()
if(_hypredrv_library_type STREQUAL "SHARED_LIBRARY")
    target_compile_definitions(HYPREDRV_objects PRIVATE HYPREDRV_EXPORTS)
else()
    target_compile_definitions(HYPREDRV_objects PRIVATE HYPREDRV_STATIC_DEFINE)
endif()

# The installed shared library intentionally hides implementation symbols.  The
# bundled command-line tools and the white-box test/fuzz targets use some of
# those internals, so shared builds get a non-installed static implementation
# library for build-tree consumers.  Keeping this target separate avoids
# widening the shared-library ABI solely for in-tree programs.
set(HYPREDRV_INTERNAL_LINK_TARGET HYPREDRV)
if(_hypredrv_library_type STREQUAL "SHARED_LIBRARY")
    add_library(HYPREDRV_internal STATIC EXCLUDE_FROM_ALL
        $<TARGET_OBJECTS:HYPREDRV_objects>)
    set_target_properties(HYPREDRV_internal PROPERTIES
        POSITION_INDEPENDENT_CODE ON
    )

    foreach(_property IN ITEMS
            LINK_DIRECTORIES
            LINK_OPTIONS
            INTERFACE_INCLUDE_DIRECTORIES
            INTERFACE_COMPILE_DEFINITIONS
            INTERFACE_COMPILE_OPTIONS
            INTERFACE_COMPILE_FEATURES
            INTERFACE_LINK_DIRECTORIES
            INTERFACE_LINK_LIBRARIES
            INTERFACE_LINK_OPTIONS)
        get_target_property(_value HYPREDRV ${_property})
        if(_value AND NOT _value MATCHES "-NOTFOUND$")
            set_property(TARGET HYPREDRV_internal PROPERTY ${_property} "${_value}")
        endif()
    endforeach()
    get_target_property(_hypredrv_private_link_libraries HYPREDRV LINK_LIBRARIES)
    if(_hypredrv_private_link_libraries AND
       NOT _hypredrv_private_link_libraries MATCHES "-NOTFOUND$")
        target_link_libraries(HYPREDRV_internal
            PRIVATE ${_hypredrv_private_link_libraries})
    endif()
    target_compile_definitions(HYPREDRV_internal PUBLIC HYPREDRV_STATIC_DEFINE)
    set(HYPREDRV_INTERNAL_LINK_TARGET HYPREDRV_internal)
endif()

# Define the main executable (hypredrive-cli)
add_executable(hypredrive-cli src/internal/main.c)
target_link_libraries(hypredrive-cli PRIVATE ${HYPREDRV_INTERNAL_LINK_TARGET})
hypredrv_add_hypre_build_dependency(hypredrive-cli)

if(HYPREDRV_ENABLE_COMPRESSION)
    add_executable(hypredrive-lsseq utils/lsseq_driver.c)
    target_link_libraries(hypredrive-lsseq PRIVATE ${HYPREDRV_INTERNAL_LINK_TARGET})
    hypredrv_add_hypre_build_dependency(hypredrive-lsseq)
endif()

# Ensure dataset-driven examples/tests have data available before running hypredrive-cli.
if(HYPREDRV_ENABLE_DATA AND TARGET data)
    add_dependencies(hypredrive-cli data)
endif()

# Add Caliper include directories if Caliper is enabled
if(HYPREDRV_ENABLE_CALIPER AND CALIPER_FOUND)
    if(TARGET caliper)
        # Caliper was built via FetchContent - get include dir from cache
        get_property(CALIPER_INCLUDE_DIR_FOR_HYPRE CACHE CALIPER_INCLUDE_DIR_FOR_HYPRE PROPERTY VALUE)
        if(CALIPER_INCLUDE_DIR_FOR_HYPRE)
            target_include_directories(hypredrive-cli PRIVATE $<BUILD_INTERFACE:${CALIPER_INCLUDE_DIR_FOR_HYPRE}>)
        endif()
    elseif(TARGET caliper::caliper)
        # Caliper was found via find_package
        target_include_directories(hypredrive-cli PRIVATE caliper::caliper)
    elseif(CALIPER_INCLUDE_DIR)
        # Caliper was found via find_path
        target_include_directories(hypredrive-cli PRIVATE ${CALIPER_INCLUDE_DIR})
    endif()
endif()

# If Caliper is enabled, we need to link C++ standard library (Caliper is C++)
if(HYPREDRV_ENABLE_CALIPER)
    enable_language(CXX OPTIONAL)
    if(CMAKE_CXX_COMPILER)
        if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
            target_link_options(hypredrive-cli PRIVATE "-lstdc++")
        elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
            # Clang can use either libstdc++ or libc++
            # Default to stdc++ on Linux
            if(UNIX AND NOT APPLE)
                target_link_options(hypredrive-cli PRIVATE "-lstdc++")
            else()
                target_link_options(hypredrive-cli PRIVATE "-lc++")
            endif()
        endif()
    endif()
endif()

# Ensure Caliper is built before hypredrive-cli if Caliper is enabled
# This is critical because HYPRE may create file dependencies on libcaliper.a
if(CALIPER_FOUND)
    # Find the actual caliper target (not the imported one)
    if(TARGET caliper)
        add_dependencies(hypredrive-cli caliper)
        # Also add to HYPREDRV to ensure it's available when linking
        add_dependencies(HYPREDRV caliper)
        if(TARGET HYPREDRV_internal)
            add_dependencies(HYPREDRV_internal caliper)
        endif()
    endif()
    # Ensure HYPRE is built (which transitively ensures Caliper is built)
    if(TARGET HYPRE::HYPRE)
        add_dependencies(hypredrive-cli HYPRE::HYPRE)
    endif()
endif()

# Formatting target
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Format.cmake)

# Platform-specific settings
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Platform.cmake)

# Keep symbols pulled from static dependencies out of language-module ABIs.
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Visibility.cmake)
hypredrv_hide_static_archive_symbols(HYPREDRV)

# Optional interfaces may register tests before the main testing module adds the
# C test suite, so enable CTest before descending into those subdirectories.
if(HYPREDRV_ENABLE_TESTING)
    enable_testing()
    hypredrv_add_dynamic_export_test(HYPREDRV CORE)
endif()

hypredrv_compute_build_tree_export_available(_hypredrv_build_tree_export_available)
set(HYPREDRV_BUILD_TREE_EXPORT_AVAILABLE
    ${_hypredrv_build_tree_export_available}
    CACHE INTERNAL "Whether HYPREDRV build-tree package export is safe" FORCE)

# Python interface (optional)
if(HYPREDRV_ENABLE_PYTHON)
    add_subdirectory(interfaces/python)
endif()

# Fortran interface (optional)
if(HYPREDRV_ENABLE_FORTRAN)
    enable_language(Fortran)
    find_package(MPI COMPONENTS C Fortran REQUIRED)
    add_subdirectory(interfaces/fortran)
endif()

# MATLAB/Octave interface (optional)
if(HYPREDRV_ENABLE_MATLAB)
    add_subdirectory(interfaces/matlab-octave)
endif()

# Julia interface (optional)
if(HYPREDRV_ENABLE_JULIA)
    add_subdirectory(interfaces/julia)
endif()

# C++ interface (optional)
if(HYPREDRV_ENABLE_CPP)
    enable_language(CXX)
    add_subdirectory(interfaces/cpp)
endif()

# Installation and package configuration
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Install.cmake)

# Coverage support (optional)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Coverage.cmake)

# Static analysis and sanitizers support (optional)
# Must be included before testing so sanitizer flags are available when test executables are created
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Analysis.cmake)

# Testing setup (optional)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Testing.cmake)

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

# Documentation support (optional)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_Docs.cmake)

# Add examples
if(HYPREDRV_ENABLE_EXAMPLES)
    add_subdirectory(examples/src/C_laplacian)
    add_subdirectory(examples/src/C_darcy)
    add_subdirectory(examples/src/C_elasticity)
    add_subdirectory(examples/src/C_heatflow)
    add_subdirectory(examples/src/C_convdif)
    add_subdirectory(examples/src/C_lidcavity)
    add_subdirectory(examples/src/C_maxwell)
    add_subdirectory(examples/src/C_graddiv)
endif()

if(HYPREDRV_ENABLE_JULIA AND HYPREDRV_ENABLE_EXAMPLES)
    set(_hypredrv_julia_example_targets
        laplacian
        darcy
        elasticity
        heatflow
        lidcavity
    )
    foreach(_hypredrv_julia_example_target IN LISTS _hypredrv_julia_example_targets)
        if(TARGET julia-test AND TARGET ${_hypredrv_julia_example_target})
            add_dependencies(julia-test ${_hypredrv_julia_example_target})
        endif()
        if(TARGET julia-install-consumer-test AND TARGET ${_hypredrv_julia_example_target})
            add_dependencies(julia-install-consumer-test ${_hypredrv_julia_example_target})
        endif()
    endforeach()
endif()

# Add a check target
set(_hypredrv_smoke_check_args
    -DLAUNCH_DIR=${CMAKE_SOURCE_DIR}
    -DTARGET_BIN=$<TARGET_FILE:hypredrive-cli>
)
if(HYPREDRV_TEST_RUNTIME_ENV_ASSIGNMENT)
    list(APPEND _hypredrv_smoke_check_args
         "-DTEST_RUNTIME_ENV_ASSIGNMENT=${HYPREDRV_TEST_RUNTIME_ENV_ASSIGNMENT}")
endif()
if(HYPREDRV_HYPRE_USES_SYCL)
    list(APPEND _hypredrv_smoke_check_args -DHYPREDRV_SMOKE_TEST_USES_SYCL=ON)
endif()

add_custom_target(check
  COMMAND ${CMAKE_COMMAND}
          ${_hypredrv_smoke_check_args}
          -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/HYPREDRV_SmokeCheck.cmake
  DEPENDS hypredrive-cli
  COMMENT "Running smoke test (ex1.yml)"
  VERBATIM
)
