cmake_minimum_required(VERSION 3.22)
project(dpm-core VERSION 0.1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# ---------------------------------------------------------------------
# libdpm-core.so — the library (C ABI)
# ---------------------------------------------------------------------
add_library(dpm-core SHARED
        src/context.cpp
        src/modules.cpp
        src/version.cpp
)

target_include_directories(dpm-core PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
)

target_compile_definitions(dpm-core PRIVATE
        DPM_CORE_VERSION_STR="${PROJECT_VERSION}"
)

# The public C API is the library's entire exported surface; internals
# stay hidden. The version script pins the export list and versions the
# symbols.
target_compile_options(dpm-core PRIVATE
        -fvisibility=hidden
        -fvisibility-inlines-hidden
)
target_link_options(dpm-core PRIVATE
        -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/libdpm-core.map
)
set_property(TARGET dpm-core APPEND PROPERTY
        LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/libdpm-core.map
)

target_link_libraries(dpm-core PRIVATE ${CMAKE_DL_LIBS})

set_target_properties(dpm-core PROPERTIES
        OUTPUT_NAME "dpm-core"
        VERSION ${PROJECT_VERSION}
        SOVERSION 1
        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)

# ---------------------------------------------------------------------
# dpm — the CLI (thin client of libdpm-core)
# ---------------------------------------------------------------------
add_executable(dpm src/cli/dpm.cpp)

target_link_libraries(dpm PRIVATE dpm-core)

set_target_properties(dpm PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
        BUILD_RPATH "$ORIGIN/../lib"
)

# ---------------------------------------------------------------------
# info — the bundled module (tests and reports libdpm-core functionality)
# ---------------------------------------------------------------------
add_library(info MODULE
        src/bundled-modules/info/info.cpp
        src/bundled-modules/info/src/commands.cpp
)

target_include_directories(info PRIVATE src/bundled-modules/info/include)

# The one permitted link dependency: libdpm-core.
target_link_libraries(info PRIVATE dpm-core)

set_target_properties(info PROPERTIES
        PREFIX ""
        SUFFIX ".so"
        LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/modules
)

# The module bundles with libdpm-core, so building the CLI builds it too: after a
# clean, running dpm from the build tree finds a module directory that is
# populated. This orders the build only — the CLI does not link the module.
add_dependencies(dpm info)

# ---------------------------------------------------------------------
# Test fixture modules: one known-good stub, five deliberately broken
# ---------------------------------------------------------------------
enable_testing()

# CTest's scratch area is named by its BuildDirectory configuration option.
# Writing it into DartConfiguration.tcl puts it where ctest reads it on every
# invocation — the test target, a bare ctest, and the IDE's test discovery
# alike — rather than only the ones the build drives.
file(WRITE ${CMAKE_BINARY_DIR}/DartConfiguration.tcl
        "BuildDirectory: ${CMAKE_BINARY_DIR}/test-results\n")

set(FIXTURE_MODULE_DIR ${CMAKE_BINARY_DIR}/tests/fixtures)

foreach(fixture good missing_symbols bad_magic lying_manifest core_too_new bad_version)
    add_library(fixture_${fixture} MODULE tests/fixtures/src/${fixture}.cpp)
    set_target_properties(fixture_${fixture} PROPERTIES
            PREFIX ""
            SUFFIX ".so"
            OUTPUT_NAME ${fixture}
            LIBRARY_OUTPUT_DIRECTORY ${FIXTURE_MODULE_DIR}
    )
endforeach()

# ---------------------------------------------------------------------
# API test binary — drives libdpm-core through its public C API
# ---------------------------------------------------------------------
add_executable(test_core tests/test_core.cpp)

target_link_libraries(test_core PRIVATE dpm-core)

target_compile_definitions(test_core PRIVATE
        TEST_FIXTURE_MODULES="${FIXTURE_MODULE_DIR}"
        TEST_FIXTURE_CONF="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/conf"
        DPM_CORE_VERSION_EXPECTED="${PROJECT_VERSION}"
)

set_target_properties(test_core PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests
        BUILD_RPATH "$ORIGIN/../lib"
)

foreach(fixture good missing_symbols bad_magic lying_manifest core_too_new bad_version)
    add_dependencies(test_core fixture_${fixture})
endforeach()

add_test(NAME core_api COMMAND test_core)

# ---------------------------------------------------------------------
# CLI end-to-end tests (the dpm binary is the cheapest full-stack client)
# ---------------------------------------------------------------------
add_test(NAME cli_help COMMAND dpm --help)
set_tests_properties(cli_help PROPERTIES PASS_REGULAR_EXPRESSION "Usage: dpm")

add_test(NAME cli_list
        COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/conf
                    -m ${CMAKE_BINARY_DIR}/modules --list-modules)
set_tests_properties(cli_list PROPERTIES PASS_REGULAR_EXPRESSION "info")

add_test(NAME cli_info_version
        COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/conf
                    -m ${CMAKE_BINARY_DIR}/modules -L INFO info version)
set_tests_properties(cli_info_version PROPERTIES
        PASS_REGULAR_EXPRESSION "libdpm-core Version: ${PROJECT_VERSION}")

add_test(NAME cli_module_not_found
        COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/conf
                    -m ${CMAKE_BINARY_DIR}/modules nonexistent)
set_tests_properties(cli_module_not_found PROPERTIES WILL_FAIL TRUE)

add_test(NAME cli_rejects_invalid_module
        COMMAND dpm -c ${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/conf
                    -m ${FIXTURE_MODULE_DIR} missing_symbols)
set_tests_properties(cli_rejects_invalid_module PROPERTIES WILL_FAIL TRUE)

# ---------------------------------------------------------------------
# Code reference (Doxygen) — optional 'docs' target
# ---------------------------------------------------------------------
option(DPM_DOCS_HTML "Generate the code reference in HTML" OFF)
option(DPM_DOCS_PDF  "Generate the code reference as PDF (requires LaTeX)" ON)

find_package(Doxygen)
if(DOXYGEN_FOUND)
    set(DOXYGEN_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/docs)
    set(DOXYGEN_EXTRACT_ALL YES)
    set(DOXYGEN_EXTRACT_STATIC YES)
    set(DOXYGEN_QUIET YES)
    set(DOXYGEN_WARN_IF_UNDOCUMENTED NO)
    set(DOXYGEN_JAVADOC_AUTOBRIEF YES)

    if(DPM_DOCS_HTML)
        set(DOXYGEN_GENERATE_HTML YES)
    else()
        set(DOXYGEN_GENERATE_HTML NO)
    endif()

    if(DPM_DOCS_PDF)
        set(DOXYGEN_GENERATE_LATEX YES)
        set(DOXYGEN_USE_PDFLATEX YES)
        set(DOXYGEN_PDF_HYPERLINKS YES)
    else()
        set(DOXYGEN_GENERATE_LATEX NO)
    endif()

    doxygen_add_docs(docs
            ${CMAKE_CURRENT_SOURCE_DIR}/include
            ${CMAKE_CURRENT_SOURCE_DIR}/src
            COMMENT "Generating code reference with Doxygen"
    )

    if(DPM_DOCS_PDF)
        find_program(PDFLATEX_EXECUTABLE pdflatex)
        if(PDFLATEX_EXECUTABLE)
            add_custom_target(docs-pdf
                    COMMAND make -C ${CMAKE_BINARY_DIR}/docs/latex
                    COMMENT "Building PDF code reference"
            )
            add_dependencies(docs-pdf docs)
        else()
            message(WARNING "DPM_DOCS_PDF is ON but pdflatex was not found; the docs-pdf target is unavailable")
        endif()
    endif()
endif()

# ---------------------------------------------------------------------
# Clean
# ---------------------------------------------------------------------
# clean removes what the build produced: the artifact directories, the
# generated documentation, and the test scratch areas.
#
# The generated build system stays. CMakeCache.txt, the makefiles,
# CMakeFiles/, and the CMake file API directory under .cmake/ are what an
# IDE reads to know the project's targets exist; deleting them leaves the
# directory unconfigured, so every target in the IDE stops resolving until
# the project is reloaded. Removing the build system is what configuring a
# fresh directory is for.
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_CLEAN_FILES
        ${CMAKE_BINARY_DIR}/test-results
        ${CMAKE_BINARY_DIR}/Testing
        ${CMAKE_BINARY_DIR}/bin
        ${CMAKE_BINARY_DIR}/lib
        ${CMAKE_BINARY_DIR}/modules
        ${CMAKE_BINARY_DIR}/tests
        ${CMAKE_BINARY_DIR}/docs
)

# ---------------------------------------------------------------------
# Installation
# ---------------------------------------------------------------------
install(TARGETS dpm RUNTIME DESTINATION bin)
install(TARGETS dpm-core LIBRARY DESTINATION lib)
install(TARGETS info LIBRARY DESTINATION lib/dpm/modules)
install(DIRECTORY include/dpm DESTINATION include)
install(FILES data/core.conf DESTINATION /etc/dpm/conf.d)
