cmake_minimum_required(VERSION 3.16)

# Project configuration with version details
project(QyberSafe
    VERSION 0.1.0
    DESCRIPTION "Post-quantum cryptography library"
    HOMEPAGE_URL "https://github.com/qyber/qybersafe"
    LANGUAGES CXX
)

# Extract version components
math(EXPR QYBERSAFE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
math(EXPR QYBERSAFE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
math(EXPR QYBERSAFE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")

# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Strict warnings apply only to QyberSafe's own targets, never to vendored
# dependencies (liboqs, GoogleTest). Link this interface library into our
# targets to opt in. Applying these globally would break third-party C code
# (for example -Wextra-semi is rejected for C sources under -Werror).
add_library(qybersafe_warnings INTERFACE)
if(MSVC)
    target_compile_options(qybersafe_warnings INTERFACE /W4 /WX)
    add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
else()
    # -Wnull-dereference is intentionally omitted: under -O3 it produces false
    # positives inside libstdc++ (e.g. std::vector copies). Real null
    # dereferences are caught by the AddressSanitizer/UBSan CI job.
    target_compile_options(qybersafe_warnings INTERFACE
        -Wall -Wextra -Wpedantic -Werror
        -Wformat=2 -Wformat-security
        $<$<COMPILE_LANGUAGE:CXX>:-Wextra-semi>)
endif()

# Build type configuration
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()

# Build type specific flags
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    # Enable sanitizers for debug builds
    if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
        add_compile_options(-fsanitize=address -fsanitize=undefined)
        add_link_options(-fsanitize=address -fsanitize=undefined)
    endif()
    add_compile_definitions(DEBUG_BUILD)
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
    # Optimizations for release builds. -march=native is opt-in and off by
    # default: it bakes the building machine's instruction set into the binary,
    # which is wrong for distributable artifacts such as Python wheels (they
    # would crash on other CPUs).
    option(QYBERSAFE_NATIVE_ARCH "Optimize for the building machine's CPU (-march=native)" OFF)
    if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
        add_compile_options(-O3)
        if(QYBERSAFE_NATIVE_ARCH)
            add_compile_options(-march=native)
        endif()
    endif()
    add_compile_definitions(NDEBUG)
endif()

# Find dependencies
find_package(OpenSSL REQUIRED)

# The Python module links the static library and liboqs into a shared object,
# so everything must be position-independent. Set this before liboqs is fetched.
option(QYBERSAFE_BUILD_PYTHON "Build the pybind11 Python module" OFF)
if(QYBERSAFE_BUILD_PYTHON)
    set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()

# Installing the C++ library/headers/package files. Turned off when building a
# Python wheel, which should contain only the extension module.
option(QYBERSAFE_INSTALL_CXX "Install the C++ library, headers, and package files" ON)

# Fetch and build liboqs (provides the post-quantum primitives)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(liboqs)

# Include directories - using target_include_directories for better scope
include_directories(src/include)

# Create interface library for headers only
add_library(qybersafe_headers INTERFACE)
add_library(qybersafe::headers ALIAS qybersafe_headers)

# Set include directories for the interface target
target_include_directories(qybersafe_headers INTERFACE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/include>
    $<INSTALL_INTERFACE:include>
)

# Add subdirectories
add_subdirectory(src/src)

# Testing configuration
option(BUILD_TESTS "Build tests" ON)
option(BUILD_BENCHMARKS "Build benchmarks" OFF)
option(BUILD_EXAMPLES "Build examples" ON)

# Only add tests and benchmarks if enabled and they exist
if(BUILD_TESTS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/tests")
    enable_testing()
    add_subdirectory(src/tests)
endif()

if(BUILD_BENCHMARKS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/benchmarks")
    add_subdirectory(src/benchmarks)
endif()

# Examples are optional - only add if CMakeLists.txt exists
if(BUILD_EXAMPLES)
    if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/examples/CMakeLists.txt")
        add_subdirectory(src/examples)
    endif()
endif()

# Installation configuration
include(GNUInstallDirs)

# Static analysis configuration
find_program(CLANG_TIDY_PATH clang-tidy)
if(CLANG_TIDY_PATH)
    option(ENABLE_CLANG_TIDY "Enable clang-tidy static analysis" ON)
    if(ENABLE_CLANG_TIDY)
        set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_PATH}")
    endif()
endif()

# Documentation configuration
find_package(Doxygen QUIET)
if(DOXYGEN_FOUND)
    option(BUILD_DOCUMENTATION "Create and install the HTML based API documentation (requires Doxygen)" ON)
    if(BUILD_DOCUMENTATION)
        # Set input and output files
        set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile)
        set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)

        # Request to configure the file
        configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)

        # Note: The variable "DOXYGEN_OUTPUT_DIR" is a custom variable we use
        # to get the configured Doxyfile in a directory that is cleaned upon
        # "make clean"
        file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs)

        # Add the documentation target
        add_custom_target(docs
            ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
            COMMENT "Generating API documentation with Doxygen"
            VERBATIM
        )

        # Install the documentation
        install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/docs/html
                DESTINATION ${CMAKE_INSTALL_DOCDIR}
                OPTIONAL)
    endif()
endif()

if(QYBERSAFE_INSTALL_CXX)
    # Install the library
    install(TARGETS qybersafe
        EXPORT QyberSafeTargets
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    )

    # Install the headers
    install(DIRECTORY src/include/qybersafe
            DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
            FILES_MATCHING PATTERN "*.h"
    )

    # Export configuration
    install(EXPORT QyberSafeTargets
        FILE QyberSafeTargets.cmake
        NAMESPACE qybersafe::
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QyberSafe
    )

    # Create and install config files (only if template exists)
    include(CMakePackageConfigHelpers)

    if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/QyberSafeConfig.cmake.in")
        configure_package_config_file(
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/QyberSafeConfig.cmake.in"
            "${CMAKE_CURRENT_BINARY_DIR}/QyberSafeConfig.cmake"
            INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QyberSafe
        )

        write_basic_package_version_file(
            "${CMAKE_CURRENT_BINARY_DIR}/QyberSafeConfigVersion.cmake"
            VERSION ${PROJECT_VERSION}
            COMPATIBILITY SameMajorVersion
        )

        install(FILES
            "${CMAKE_CURRENT_BINARY_DIR}/QyberSafeConfig.cmake"
            "${CMAKE_CURRENT_BINARY_DIR}/QyberSafeConfigVersion.cmake"
            DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/QyberSafe
        )
    endif()

    # Create pkg-config file
    configure_file(
        "${CMAKE_CURRENT_SOURCE_DIR}/qybersafe.pc.in"
        "${CMAKE_CURRENT_BINARY_DIR}/qybersafe.pc"
        @ONLY
    )
    install(FILES "${CMAKE_CURRENT_BINARY_DIR}/qybersafe.pc"
            DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
endif()

# Print configuration summary
message(STATUS "")
message(STATUS "QyberSafe Configuration Summary:")
message(STATUS "  Version: ${PROJECT_VERSION}")
message(STATUS "  Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "  C++ standard: ${CMAKE_CXX_STANDARD}")
message(STATUS "  Install prefix: ${CMAKE_INSTALL_PREFIX}")
message(STATUS "  Build tests: ${BUILD_TESTS}")
message(STATUS "  Build benchmarks: ${BUILD_BENCHMARKS}")
message(STATUS "  Build examples: ${BUILD_EXAMPLES}")
message(STATUS "  Build documentation: ${BUILD_DOCUMENTATION}")
message(STATUS "  Enable clang-tidy: ${ENABLE_CLANG_TIDY}")
message(STATUS "")