cmake_minimum_required(VERSION 3.20)

# Function to get version from git tags
function(get_version_from_git)
    # Find git executable
    find_package(Git QUIET)
    
    if(GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
        # Get the latest annotated tag
        execute_process(
            COMMAND ${GIT_EXECUTABLE} tag -l --sort=-version:refname
            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
            OUTPUT_VARIABLE GIT_TAGS
            OUTPUT_STRIP_TRAILING_WHITESPACE
            ERROR_QUIET
        )
        
        if(GIT_TAGS)
            # Get the first (latest) tag from the list
            string(REPLACE "\n" ";" TAG_LIST ${GIT_TAGS})
            list(GET TAG_LIST 0 LATEST_TAG)
            
            # Check if it's an annotated tag
            execute_process(
                COMMAND ${GIT_EXECUTABLE} cat-file -t ${LATEST_TAG}
                WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
                OUTPUT_VARIABLE TAG_TYPE
                OUTPUT_STRIP_TRAILING_WHITESPACE
                ERROR_QUIET
            )
            
            if(TAG_TYPE STREQUAL "tag")
                # Remove 'v' prefix if present (e.g., v1.2.3 -> 1.2.3)
                string(REGEX REPLACE "^v" "" VERSION_FROM_GIT ${LATEST_TAG})
                set(PROJECT_VERSION_FROM_GIT ${VERSION_FROM_GIT} PARENT_SCOPE)
                set(HAS_GIT_VERSION TRUE PARENT_SCOPE)
                message(STATUS "Found git version: ${VERSION_FROM_GIT}")
                return()
            endif()
        endif()
    endif()
    
    # Fallback to default version
    set(PROJECT_VERSION_FROM_GIT "1.0.0" PARENT_SCOPE)
    set(HAS_GIT_VERSION FALSE PARENT_SCOPE)
    message(STATUS "Using fallback version: 1.0.0")
endfunction()

# Get version from git
get_version_from_git()

project(rpi-systemd-gpio 
    VERSION ${PROJECT_VERSION_FROM_GIT}
    DESCRIPTION "Use systemd to configure commands to run when a GPIO button is pressed on a Raspberry Pi"
    LANGUAGES CXX
)

# Add cmake modules directory
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

# Set version source information
if(HAS_GIT_VERSION)
    set(VERSION_SOURCE "git tag")
else()
    set(VERSION_SOURCE "fallback")
endif()

# Configure version header
configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/src/version.hpp.in"
    "${CMAKE_CURRENT_BINARY_DIR}/version.hpp"
    @ONLY
)

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

# OpenSSF Security Hardening Configuration
# Reference: https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html

# Option to enable/disable security hardening (default: ON)
option(ENABLE_SECURITY_HARDENING "Enable OpenSSF security hardening flags" ON)

# Find required packages
find_package(Threads REQUIRED)

# Option to make libgpiod optional (for development/testing on non-Linux systems)
option(REQUIRE_GPIOD "Require libgpiod to be available" ON)

# Find libgpiod
find_package(PkgConfig REQUIRED)
pkg_check_modules(GPIOD libgpiodcxx)

if(GPIOD_FOUND)
    message(STATUS "Found libgpiod: ${GPIOD_VERSION}")
    set(HAVE_GPIOD TRUE)
else()
    if(REQUIRE_GPIOD)
        message(FATAL_ERROR 
            "libgpiod C++ bindings not found. Install them with:\n"
            "  Debian/Ubuntu/Raspberry Pi OS: sudo apt-get install libgpiod-dev\n"
            "  Fedora/RHEL: sudo dnf install libgpiod-devel\n"
            "  Arch: sudo pacman -S libgpiod\n"
            "Or disable this requirement with: cmake -DREQUIRE_GPIOD=OFF ..")
    else()
        message(WARNING "libgpiod not found - GPIO functionality will be limited to mock/testing")
        set(HAVE_GPIOD FALSE)
    endif()
endif()

# Testing configuration
option(BUILD_TESTING "Build tests" OFF)
if(BUILD_TESTING)
    enable_testing()
    
    # Find or fetch Catch2
    find_package(Catch2 3 QUIET)
    if(NOT Catch2_FOUND)
        message(STATUS "Catch2 not found, fetching from GitHub...")
        include(FetchContent)
        FetchContent_Declare(
            Catch2
            GIT_REPOSITORY https://github.com/catchorg/Catch2.git
            GIT_TAG        v3.12.0
        )
        FetchContent_MakeAvailable(Catch2)
        
        # Add Catch2's CMake modules to the path
        list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras)
    endif()
    
    include(CTest)
    if(Catch2_FOUND OR TARGET Catch2::Catch2)
        include(Catch)
    endif()
endif()

# Include security hardening module
include(SecurityHardening)

# Create GPIO interface library (needed by main executable)
add_library(gpio_interface STATIC
    src/gpio_interface.cpp
    src/gpio_interface.hpp
)
target_link_libraries(gpio_interface PRIVATE Threads::Threads)

# Add libgpiod to gpio_interface if available
if(HAVE_GPIOD)
    target_link_libraries(gpio_interface PRIVATE ${GPIOD_LIBRARIES})
    target_include_directories(gpio_interface PRIVATE ${GPIOD_INCLUDE_DIRS})
    target_compile_options(gpio_interface PRIVATE ${GPIOD_CFLAGS_OTHER})
    target_compile_definitions(gpio_interface PRIVATE HAVE_GPIOD=1)
else()
    target_compile_definitions(gpio_interface PRIVATE HAVE_GPIOD=0)
endif()

# Apply security hardening to GPIO library
if(ENABLE_SECURITY_HARDENING)
    apply_security_hardening(gpio_interface)
endif()

# Create the main executable
add_executable(systemd-gpio src/systemd_gpio.cpp)

# Add include directories
target_include_directories(systemd-gpio PRIVATE 
    "${CMAKE_CURRENT_SOURCE_DIR}/src"
    "${CMAKE_CURRENT_BINARY_DIR}"
)

# Link libraries
target_link_libraries(systemd-gpio PRIVATE Threads::Threads gpio_interface)


# Add libgpiod if available
if(HAVE_GPIOD)
    target_link_libraries(systemd-gpio PRIVATE ${GPIOD_LIBRARIES})
    target_include_directories(systemd-gpio PRIVATE ${GPIOD_INCLUDE_DIRS})
    target_compile_options(systemd-gpio PRIVATE ${GPIOD_CFLAGS_OTHER})
    target_compile_definitions(systemd-gpio PRIVATE HAVE_GPIOD=1)
else()
    target_compile_definitions(systemd-gpio PRIVATE HAVE_GPIOD=0)
endif()

# Apply security hardening
if(ENABLE_SECURITY_HARDENING)
    apply_security_hardening(systemd-gpio ENABLE_SANITIZERS)
    check_security_support()
    print_security_summary()
else()
    # Basic compiler warnings when hardening is disabled
    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
        target_compile_options(systemd-gpio PRIVATE -Wall -Wextra -Wpedantic)
    endif()
endif()

# Set installation paths
include(GNUInstallDirs)

# Install the binary
install(TARGETS systemd-gpio
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

# Install the systemd service file
# Use the canonical systemd unit path (not multiarch libdir)
install(FILES gpio@.service
    DESTINATION lib/systemd/system
)

# Install man page if pandoc is available
find_program(PANDOC_EXECUTABLE pandoc)
if(PANDOC_EXECUTABLE)
    # Generate man page from README.rst
    add_custom_command(
        OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/systemd-gpio.1
        COMMAND ${PANDOC_EXECUTABLE} -s -t man ${CMAKE_CURRENT_SOURCE_DIR}/README.rst -o ${CMAKE_CURRENT_BINARY_DIR}/systemd-gpio.1
        DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/README.rst
        COMMENT "Generating man page"
    )
    
    add_custom_target(man ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/systemd-gpio.1)
    
    install(FILES ${CMAKE_CURRENT_BINARY_DIR}/systemd-gpio.1
        DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
    )
endif()

# Test configuration
if(BUILD_TESTING AND (Catch2_FOUND OR TARGET Catch2::Catch2))
    # Create test executable
    add_executable(systemd_gpio_tests
        tests/test_gpio_button.cpp
        tests/test_argument_parser.cpp
        tests/gpio_mock.hpp
    )
    
    # Link test dependencies
    target_link_libraries(systemd_gpio_tests PRIVATE 
        gpio_interface
        Catch2::Catch2WithMain
        Threads::Threads
    )
    
    # Apply security hardening to tests (but disable sanitizers for performance)
    if(ENABLE_SECURITY_HARDENING)
        apply_basic_security_hardening(systemd_gpio_tests)
    endif()
    
    # Include directories for tests
    target_include_directories(systemd_gpio_tests PRIVATE 
        ${CMAKE_CURRENT_SOURCE_DIR}/src
        ${CMAKE_CURRENT_SOURCE_DIR}/tests
    )
    
    # Create test results directory
    file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/test_results)
    
    # Discover and register tests
    catch_discover_tests(systemd_gpio_tests
        TEST_PREFIX "systemd_gpio."
    )
    
    # Add custom test targets
    add_custom_target(test_verbose
        COMMAND ${CMAKE_CTEST_COMMAND} --verbose
        DEPENDS systemd_gpio_tests
        COMMENT "Running tests with verbose output"
    )
    
    add_custom_target(test_coverage
        COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
        DEPENDS systemd_gpio_tests
        COMMENT "Running tests for coverage analysis"
    )
    
    message(STATUS "Tests enabled - run 'make test' or 'ctest' to execute")
else()
    message(STATUS "Tests disabled - install Catch2 or set BUILD_TESTING=OFF")
endif()
