# SPDX-FileCopyrightText: 2026 The dynG Authors
# SPDX-License-Identifier: Apache-2.0
#
# The C++ library: libdyng (dyng::dyng), aggregated from private OBJECT libraries dyng_<module>
# (PLAN Section 7.2).

dyng_generate_version_headers("${DYNG_GENERATED_INCLUDE_DIR}")

# docs/references.bib is compiled into the library for dyng::citation().
set(_dyng_bib "${PROJECT_SOURCE_DIR}/docs/references.bib")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_dyng_bib}")
file(READ "${_dyng_bib}" _dyng_bib_text)
file(
  WRITE "${PROJECT_BINARY_DIR}/cpp/generated/references_bib.cpp.tmp"
  "// Generated by CMake from docs/references.bib; do not edit.\n"
  "namespace dyng::detail {\n"
  "extern const char* const references_bib;\n"
  "const char* const references_bib = R\"dyngbib(${_dyng_bib_text})dyngbib\";\n"
  "}  // namespace dyng::detail\n"
)
configure_file(
  "${PROJECT_BINARY_DIR}/cpp/generated/references_bib.cpp.tmp"
  "${PROJECT_BINARY_DIR}/cpp/generated/references_bib.cpp"
  COPYONLY
)

# The embedded BibTeX may exceed the 4095-character literal length that -Wpedantic warns about.
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
  set_source_files_properties(
    "${PROJECT_BINARY_DIR}/cpp/generated/references_bib.cpp"
    PROPERTIES COMPILE_OPTIONS "-Wno-overlength-strings"
  )
endif()

add_library(dyng)
add_library(dyng::dyng ALIAS dyng)
set_target_properties(
  dyng
  PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    EXPORT_NAME dyng
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(dyng PUBLIC cxx_std_17)
target_include_directories(
  dyng
  PUBLIC
    $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/cpp/include>
    $<BUILD_INTERFACE:${DYNG_GENERATED_INCLUDE_DIR}>
    $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
target_link_libraries(dyng PRIVATE Threads::Threads)
if(DYNG_ENABLE_OPENMP)
  target_link_libraries(dyng PRIVATE OpenMP::OpenMP_CXX)
endif()
if(DYNG_ENABLE_CUDA)
  target_link_libraries(dyng PRIVATE CUDA::cudart)
endif()
dyng_set_warnings(dyng)
dyng_enable_sanitizers(dyng)

# dyng_set_cuda_options(<target>)
#   The CUDA compile options of a library or test target (PLAN Section 7.3): C++17, extended
#   lambdas and relaxed constexpr as PRIVATE flags, -lineinfo in RelWithDebInfo (and with
#   DYNG_CUDA_LINEINFO), no separable compilation (cooperative launch does not need -rdc), the
#   CUDA_DEBUG_SYNC define of DYNG_CHECK_KERNEL, and the CUDA runtime for .cpp files that call it.
function(dyng_set_cuda_options target)
  if(NOT DYNG_ENABLE_CUDA)
    return()
  endif()
  target_compile_features(${target} PRIVATE cuda_std_17)
  target_compile_options(
    ${target}
    PRIVATE "$<$<COMPILE_LANGUAGE:CUDA>:--extended-lambda;--expt-relaxed-constexpr>"
            "$<$<AND:$<COMPILE_LANGUAGE:CUDA>,$<CONFIG:RelWithDebInfo>>:-lineinfo>"
  )
  if(DYNG_CUDA_LINEINFO)
    target_compile_options(${target} PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-lineinfo>)
  endif()
  if(DYNG_CUDA_DEBUG_SYNC)
    target_compile_definitions(${target} PRIVATE DYNG_CUDA_DEBUG_SYNC=1)
  endif()
  set_target_properties(${target} PROPERTIES CUDA_SEPARABLE_COMPILATION OFF)
  target_link_libraries(${target} PRIVATE CUDA::cudart)
endfunction()

# dyng_add_module(NAME <module> SOURCES <files...> [CUDA_SOURCES <files...>])
#   Creates the private OBJECT library dyng_<module> with the library's include directories,
#   warnings, OpenMP and CUDA flags, and adds its objects to libdyng. CUDA_SOURCES (.cu files) are
#   compiled only when DYNG_ENABLE_CUDA is ON. Host files that call the CUDA runtime are ordinary
#   SOURCES whose body is guarded by `#if DYNG_HAS_CUDA`, so every build (and clang-tidy) sees them.
function(dyng_add_module)
  cmake_parse_arguments(PARSE_ARGV 0 arg "" "NAME" "SOURCES;CUDA_SOURCES")
  set(target dyng_${arg_NAME})
  set(_sources ${arg_SOURCES})
  if(DYNG_ENABLE_CUDA)
    list(APPEND _sources ${arg_CUDA_SOURCES})
  endif()
  add_library(${target} OBJECT ${_sources})
  set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE ON)
  target_compile_features(${target} PRIVATE cxx_std_17)
  target_include_directories(
    ${target}
    PRIVATE "${PROJECT_SOURCE_DIR}/cpp/include" "${DYNG_GENERATED_INCLUDE_DIR}"
            "${PROJECT_SOURCE_DIR}/cpp/src"
  )
  if(DYNG_ENABLE_OPENMP)
    target_link_libraries(${target} PRIVATE OpenMP::OpenMP_CXX)
  endif()
  dyng_set_warnings(${target})
  dyng_enable_sanitizers(${target})
  dyng_set_cuda_options(${target})
  target_sources(dyng PRIVATE $<TARGET_OBJECTS:${target}>)
  # The module list and sources, for the mutation tests (cpp/tests: a copy of libdyng with one
  # module rebuilt from mutated sources).
  set_property(GLOBAL APPEND PROPERTY DYNG_MODULE_TARGETS ${target})
  set_property(GLOBAL PROPERTY DYNG_MODULE_SOURCES_${target} ${_sources})
endfunction()

dyng_add_module(
  NAME core
  SOURCES
    src/core/backend.cpp
    src/core/budget_counters.cpp
    src/core/citation.cpp
    src/core/cuda_runtime.cpp
    src/core/error.cpp
    src/core/logging.cpp
    src/core/memory.cpp
    src/core/memory_cuda.cpp
    src/core/names.cpp
    src/core/profiler.cpp
    src/core/registry.cpp
    src/core/resources.cpp
    src/core/staging.cpp
    src/core/version.cpp
    "${PROJECT_BINARY_DIR}/cpp/generated/references_bib.cpp"
)
if(DYNG_DEBUG_BUDGETS)
  # Invariant I9: the allocation and host-synchronization counters count (core/budget_counters.cpp;
  # every other file calls them unconditionally, so only this module needs the define).
  target_compile_definitions(dyng_core PRIVATE DYNG_DEBUG_BUDGETS=1)
endif()
if(DYNG_WITH_NVTX)
  # Header-only (NVTX 3 loads its tool at run time); nothing appears in the export set.
  target_link_libraries(dyng_core PRIVATE $<BUILD_INTERFACE:CUDA::nvtx3>)
  target_compile_definitions(dyng_core PRIVATE DYNG_HAS_NVTX=1)
  target_link_libraries(dyng PRIVATE ${CMAKE_DL_LIBS})
endif()

dyng_add_module(
  NAME util
  SOURCES
    src/util/device_error_flags.cpp
    src/util/kernel_registry.cpp
    src/util/parser.cpp
    src/util/text_writer.cpp
  CUDA_SOURCES src/util/device_fill.cu
)

dyng_add_module(
  NAME graph
  SOURCES src/graph/apply_host.cpp src/graph/apply_set_host.cpp src/graph/graph.cpp src/graph/names.cpp
          src/graph/structural_change.cpp
  CUDA_SOURCES src/graph/device_graph.cu src/graph/apply_set_device.cu
)

dyng_add_module(
  NAME io
  SOURCES
    src/io/batch_io.cpp
    src/io/batch_text.cpp
    src/io/csr_triplet.cpp
    src/io/edge_list_io.cpp
    src/io/matrix_market.cpp
    src/io/result_io.cpp
)

dyng_add_module(
  NAME framework SOURCES src/framework/budgets.cpp src/framework/composition.cpp
                         src/framework/workspace.cpp
)

dyng_add_module(
  NAME generators SOURCES src/generators/legacy.cpp src/generators/legacy_cycle_enum.cpp
)

# Algorithms: one folder per algorithm under src/algorithms (PLAN Section 4.8).
include("${PROJECT_SOURCE_DIR}/cmake/dyng_add_algorithm.cmake")
add_subdirectory(src/algorithms)
# The registry (core/registry.cpp) lists the algorithms built here: DYNG_ALGORITHM_<NAME> selects
# their entries of the generated table (scripts/regen.py writes src/core/registry_table.inc).
get_property(DYNG_BUILT_ALGORITHMS GLOBAL PROPERTY DYNG_BUILT_ALGORITHMS)
foreach(_dyng_algorithm IN LISTS DYNG_BUILT_ALGORITHMS)
  string(TOUPPER "${_dyng_algorithm}" _dyng_upper)
  target_compile_definitions(dyng_core PRIVATE DYNG_ALGORITHM_${_dyng_upper}=1)
endforeach()

# dyng::testing: host-only oracles for users and the test suites (PLAN Section 5.8). It shares no
# code with the algorithms it checks.
add_library(
  dyng_testing src/testing/dijkstra.cpp src/testing/check_sssp.cpp src/testing/cycle_oracle.cpp
)
add_library(dyng::testing ALIAS dyng_testing)
set_target_properties(
  dyng_testing
  PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    OUTPUT_NAME dyng_testing
    EXPORT_NAME testing
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(dyng_testing PUBLIC cxx_std_17)
target_link_libraries(dyng_testing PUBLIC dyng)
target_include_directories(dyng_testing PRIVATE "${PROJECT_SOURCE_DIR}/cpp/src")
dyng_set_warnings(dyng_testing)
dyng_enable_sanitizers(dyng_testing)
