cmake_minimum_required(VERSION 3.22)
get_filename_component(VIDF_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE)
set(VIDF_PORT "${CMAKE_CURRENT_LIST_DIR}")
include("${CMAKE_CURRENT_LIST_DIR}/sources.cmake")

if(ESP_PLATFORM)
    # IDF dependency discovery evaluates this file before Kconfig is available.
    idf_component_register(SRCS "${VIDF_PORT}/src/access.cpp"
        INCLUDE_DIRS "${VIDF_PORT}/include" "${VIDF_ROOT}" "${VIDF_ROOT}/vanetza/asn1/support"
        REQUIRES esp-boost mbedtls
        PRIV_REQUIRES esp_wifi esp_phy esp_event nvs_flash)
    if(CMAKE_BUILD_EARLY_EXPANSION)
        return()
    endif()
    if(NOT CONFIG_COMPILER_CXX_EXCEPTIONS OR NOT CONFIG_COMPILER_CXX_RTTI)
        message(FATAL_ERROR "vanetza-idf requires CONFIG_COMPILER_CXX_EXCEPTIONS=y and CONFIG_COMPILER_CXX_RTTI=y")
    endif()
    set(VIDF_TARGET ${COMPONENT_LIB})
    set(VIDF_NETWORK ${CONFIG_VANETZA_IDF_NETWORK})
    set(VIDF_CAM ${CONFIG_VANETZA_IDF_CAM})
    set(VIDF_DENM ${CONFIG_VANETZA_IDF_DENM})
    set(VIDF_VAM ${CONFIG_VANETZA_IDF_VAM})
    set(VIDF_HIL ${CONFIG_VANETZA_IDF_HIL})
    set(VIDF_SECURITY ${CONFIG_VANETZA_IDF_SECURITY})
    set(VIDF_SECURITY_VERIFY ${CONFIG_VANETZA_IDF_SECURITY_VERIFY})
    set(VIDF_PKI ${CONFIG_VANETZA_IDF_PKI})
    set(VIDF_NVS_CREDENTIALS ${CONFIG_VANETZA_IDF_NVS_CREDENTIALS})
else()
    project(vanetza_idf LANGUAGES C CXX)
    option(VIDF_NETWORK "GeoNetworking router, BTP and DCC" ON)
    option(VIDF_CAM "Release 2 CAM codec/endpoint" ON)
    option(VIDF_DENM "Release 2 DENM codec/endpoint" ON)
    option(VIDF_VAM "Release 2 VAM codec/endpoint" ON)
    option(VIDF_HIL "Hardware independent upper/lower tester boundary" ON)
    option(VIDF_TESTS "Build port regression tests" ON)
    option(VIDF_SECURITY "TS 103 097 signing security entity and TS 102 723-8 identifier-change service" ON)
    option(VIDF_SECURITY_VERIFY "SN-DECAP verification of received secured packets (IEEE 1609.2 clause 5.2, TS 103 097 clause 7.1)" ON)
    option(VIDF_PKI "TS 102 941 enrolment/authorization request-response core" OFF)
    set(VIDF_MBEDTLS_ROOT "" CACHE PATH
        "Host only: mbedTLS (>= 3.6 or 4.x/TF-PSA-Crypto) source tree; builds the PSA backend for testing against OpenSSL")
    add_library(vanetza_idf STATIC src/access.cpp)
    add_library(Vanetza::idf ALIAS vanetza_idf)
    set(VIDF_TARGET vanetza_idf)
    target_include_directories(${VIDF_TARGET} PUBLIC include "${VIDF_ROOT}" "${VIDF_ROOT}/vanetza/asn1/support")
    if(VIDF_BOOST_ROOT)
        target_include_directories(${VIDF_TARGET} SYSTEM PUBLIC "${VIDF_BOOST_ROOT}")
        if(WIN32)
            target_compile_definitions(${VIDF_TARGET} PUBLIC BOOST_NO_USER_CONFIG
                BOOST_PLATFORM_CONFIG="boost/config/platform/win32.hpp")
        endif()
    else()
        find_package(Boost 1.70 REQUIRED)
        target_include_directories(${VIDF_TARGET} SYSTEM PUBLIC ${Boost_INCLUDE_DIRS})
    endif()
endif()

target_compile_features(${VIDF_TARGET} PUBLIC cxx_std_17)
set_target_properties(${VIDF_TARGET} PROPERTIES C_STANDARD 11 CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES)
if(VIDF_SECURITY AND NOT VIDF_NETWORK)
    message(FATAL_ERROR "The security entity serves the GeoNetworking layer: VIDF_SECURITY requires VIDF_NETWORK")
endif()
if(VIDF_SECURITY_VERIFY AND NOT VIDF_SECURITY)
    message(FATAL_ERROR "VIDF_SECURITY_VERIFY requires VIDF_SECURITY")
endif()
if(VIDF_PKI AND NOT VIDF_SECURITY)
    message(FATAL_ERROR "VIDF_PKI requires VIDF_SECURITY")
endif()
target_compile_definitions(${VIDF_TARGET} PUBLIC
    VIDF_NETWORK=$<BOOL:${VIDF_NETWORK}> VIDF_CAM=$<BOOL:${VIDF_CAM}>
    VIDF_DENM=$<BOOL:${VIDF_DENM}> VIDF_VAM=$<BOOL:${VIDF_VAM}> VIDF_HIL=$<BOOL:${VIDF_HIL}>
    VIDF_SECURITY=$<BOOL:${VIDF_SECURITY}> VIDF_SECURITY_VERIFY=$<BOOL:${VIDF_SECURITY_VERIFY}>
    VIDF_PKI=$<BOOL:${VIDF_PKI}>)
set(_sources ${VIDF_BASE_SOURCES} "${VIDF_PORT}/src/its_g5_frame.cpp")
if(VIDF_NETWORK)
    list(APPEND _sources ${VIDF_NETWORK_SOURCES} ${VIDF_SECURITY_ASN_SOURCES} "${VIDF_PORT}/src/stack.cpp"
        "${VIDF_PORT}/src/management.cpp" "${VIDF_PORT}/src/sha.cpp")
    if(NOT ESP_PLATFORM)
        find_package(OpenSSL REQUIRED COMPONENTS Crypto)
        target_link_libraries(${VIDF_TARGET} PRIVATE OpenSSL::Crypto)
    endif()
endif()
if(VIDF_NETWORK OR VIDF_CAM OR VIDF_DENM OR VIDF_VAM)
    list(APPEND _sources ${VIDF_ASN_SUPPORT_SOURCES})
endif()
foreach(_service CAM DENM VAM)
    if(VIDF_${_service})
        list(APPEND _sources ${VIDF_${_service}_SOURCES})
    endif()
endforeach()
if(VIDF_CAM OR VIDF_DENM OR VIDF_VAM)
    list(APPEND _sources "${VIDF_PORT}/src/facilities.cpp")
endif()
if(VIDF_HIL)
    list(APPEND _sources "${VIDF_PORT}/src/hil.cpp")
endif()
if(VIDF_SECURITY)
    list(APPEND _sources ${VIDF_SECURITY_PROVIDER_SOURCES} "${VIDF_PORT}/src/ecc.cpp"
        "${VIDF_PORT}/src/security.cpp" "${VIDF_PORT}/src/identity_manager.cpp"
        "${VIDF_PORT}/src/sign_header_policy.cpp" "${VIDF_PORT}/src/credentials.cpp")
    if(ESP_PLATFORM)
        list(APPEND _sources "${VIDF_PORT}/src/backend_mbedtls.cpp")
        target_compile_definitions(${VIDF_TARGET} PUBLIC VIDF_BACKEND_MBEDTLS=1)
        if(VIDF_NVS_CREDENTIALS)
            # CredentialStore on nvs_flash (credentials.hpp); the application initialises NVS
            list(APPEND _sources "${VIDF_PORT}/src/nvs_credential_store.cpp")
            target_compile_definitions(${VIDF_TARGET} PUBLIC VIDF_NVS_CREDENTIALS=1)
        endif()
    else()
        # persistence.hpp declares the OpenSSL loaders under this define: keep it PUBLIC.
        list(APPEND _sources ${VIDF_SECURITY_HOST_SOURCES})
        target_compile_definitions(${VIDF_TARGET} PUBLIC VANETZA_WITH_OPENSSL VIDF_BACKEND_OPENSSL=1)
        target_compile_definitions(${VIDF_TARGET} PRIVATE OPENSSL_API_COMPAT=0x10101000L)
        if(VIDF_MBEDTLS_ROOT)
            set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
            set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
            set(MBEDTLS_FATAL_WARNINGS OFF CACHE BOOL "" FORCE)
            set(GEN_FILES OFF CACHE BOOL "" FORCE)
            # ESP-IDF's mbedTLS fork includes "mbedtls/bignum.h"/"mbedtls/ecp.h" wrappers that only
            # its own port directory provides (they wrap the TF-PSA-Crypto private headers and add
            # hardware-acceleration hooks). Recreate the plain wrappers so the same tree builds here.
            set(_vidf_mbedtls_shim "${CMAKE_BINARY_DIR}/mbedtls-shim/mbedtls")
            foreach(_hdr bignum ecp)
                if(EXISTS "${VIDF_MBEDTLS_ROOT}/tf-psa-crypto/drivers/builtin/include/mbedtls/private/${_hdr}.h"
                   AND NOT EXISTS "${VIDF_MBEDTLS_ROOT}/include/mbedtls/${_hdr}.h")
                    file(WRITE "${_vidf_mbedtls_shim}/${_hdr}.h"
                        "#pragma once
#define MBEDTLS_DECLARE_PRIVATE_IDENTIFIERS
#include \"mbedtls/private/${_hdr}.h\"
")
                endif()
            endforeach()
            if(EXISTS "${_vidf_mbedtls_shim}")
                file(WRITE "${CMAKE_BINARY_DIR}/mbedtls-shim/sdkconfig.h" "#pragma once
")
                set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_BINARY_DIR}/mbedtls-shim")
            endif()
            add_subdirectory("${VIDF_MBEDTLS_ROOT}" "${CMAKE_BINARY_DIR}/mbedtls" EXCLUDE_FROM_ALL)
            if(TARGET tfpsacrypto)
                set(_vidf_psa tfpsacrypto)  # mbedTLS 4.x
            else()
                set(_vidf_psa mbedcrypto)   # mbedTLS 3.x
            endif()
            list(APPEND _sources "${VIDF_PORT}/src/backend_mbedtls.cpp")
            target_link_libraries(${VIDF_TARGET} PUBLIC ${_vidf_psa})
            target_compile_definitions(${VIDF_TARGET} PUBLIC VIDF_BACKEND_MBEDTLS=1)
        endif()
    endif()
    if(VIDF_PKI)
        list(APPEND _sources ${VIDF_PKI_ASN_SOURCES} "${VIDF_PORT}/src/pki.cpp")
        if(ESP_PLATFORM OR VIDF_MBEDTLS_ROOT)
            list(APPEND _sources "${VIDF_PORT}/src/ecies_mbedtls.cpp")
        endif()
        if(NOT ESP_PLATFORM)
            list(APPEND _sources "${VIDF_PORT}/src/ecies_openssl.cpp")
        endif()
    endif()
endif()
list(REMOVE_DUPLICATES _sources)
target_sources(${VIDF_TARGET} PRIVATE ${_sources})
if(ESP_PLATFORM AND CONFIG_VANETZA_IDF_RADIO_C5)
    include("${VIDF_PORT}/radio_c5.cmake")
endif()
if(ESP_PLATFORM AND CONFIG_IDF_TARGET_ESP32C5)
    # ROM UART0 clock repair for warm resets (see the source); kept by an undefined-symbol reference.
    target_sources(${VIDF_TARGET} PRIVATE "${VIDF_PORT}/src/esp32c5_rom_uart_clock.c")
    target_link_libraries(${VIDF_TARGET} INTERFACE "-u vidf_esp32c5_rom_uart_clock_link")
endif()
if(VIDF_NETWORK)
    foreach(_module circular_buffer geometry heap rational multiprecision)
        if(NOT EXISTS "${VIDF_PORT}/third_party/${_module}/include/boost")
            message(FATAL_ERROR "Missing Boost.${_module}; run git submodule update --init --recursive")
        endif()
        target_include_directories(${VIDF_TARGET} SYSTEM PUBLIC "${VIDF_PORT}/third_party/${_module}/include")
    endforeach()
    # Last-resort fallback headers for older system Boost releases (see
    # docs/idf/test-campaigns.md); searched after every real Boost include root.
    target_include_directories(${VIDF_TARGET} SYSTEM PUBLIC "${VIDF_PORT}/compat/include")
endif()
# Iostreams stream devices are header-only here; esp-boost omits these headers.
if(VIDF_IOSTREAMS_ROOT)
    target_include_directories(${VIDF_TARGET} SYSTEM PUBLIC "${VIDF_IOSTREAMS_ROOT}")
elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/third_party/iostreams/include/boost/iostreams/stream.hpp")
    target_include_directories(${VIDF_TARGET} SYSTEM PUBLIC "${CMAKE_CURRENT_LIST_DIR}/third_party/iostreams/include")
elseif(ESP_PLATFORM)
    message(FATAL_ERROR "Missing Boost.Iostreams. Clone with --recurse-submodules or set VIDF_IOSTREAMS_ROOT.")
endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    target_compile_options(${VIDF_TARGET} PRIVATE -ffunction-sections -fdata-sections
        -Wno-error=deprecated-declarations)
    if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 15)
        target_compile_options(${VIDF_TARGET} PRIVATE -Wno-error=deprecated-variadic-comma-omission)
    endif()
endif()
if(ESP_PLATFORM)
    target_compile_definitions(${VIDF_TARGET} PUBLIC HAVE_NETINET_IN_H)
    # The generated IOC table intentionally zero-initializes trailing fields.
    set_source_files_properties("${VIDF_ROOT}/vanetza/asn1/its/r2/WrappedExtensionContainer.c"
        PROPERTIES COMPILE_OPTIONS -Wno-error=missing-field-initializers)
    # ESP-IDF 6 uses picolibc's native timegm, without a tm_gmtoff member.
    # Do not confuse the missing struct member with timezone-mutating emulation.
    if(IDF_VERSION_MAJOR GREATER_EQUAL 6)
        set_source_files_properties("${VIDF_ROOT}/vanetza/asn1/support/GeneralizedTime.c"
            PROPERTIES COMPILE_DEFINITIONS HAVE_TIMEGM)
    endif()
    target_compile_options(${VIDF_TARGET} PRIVATE
        $<$<COMPILE_LANGUAGE:C>:-Wno-error=unused-function>
        $<$<COMPILE_LANGUAGE:C>:-Wno-error=maybe-uninitialized>)
elseif(VIDF_TESTS)
    enable_testing()
    add_executable(vidf_tests tests/test_main.cpp tests/test_its_time.cpp)
    if(VIDF_NETWORK)
        target_sources(vidf_tests PRIVATE tests/test_management.cpp tests/test_dcc.cpp tests/test_dcc_net.cpp)
    endif()
    if(VIDF_SECURITY)
        target_sources(vidf_tests PRIVATE tests/test_security.cpp tests/test_backend_kat.cpp
            tests/test_security_entity.cpp tests/test_trust_domain.cpp tests/test_credentials.cpp)
        if(VIDF_PKI)
            target_sources(vidf_tests PRIVATE tests/test_pki.cpp tests/pki_authority.cpp)
        endif()
        target_link_libraries(vidf_tests PRIVATE OpenSSL::Crypto)
    endif()
    target_link_libraries(vidf_tests PRIVATE ${VIDF_TARGET})
    if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
        target_link_options(vidf_tests PRIVATE -Wl,--gc-sections)
    endif()
    add_test(NAME vidf_regression COMMAND vidf_tests)
    if(VIDF_NETWORK)
        add_executable(vidf_sut tests/hil_sut.cpp tests/hil_stdio.cpp)
        target_link_libraries(vidf_sut PRIVATE ${VIDF_TARGET})
    endif()
    if(VIDF_SECURITY)
        # Isolated test trust domain in the ETSI framework's certificate pool layout (not a PKI).
        add_executable(vidf_test_pool tests/trust_domain_pool.cpp tests/test_trust_domain.cpp)
        target_link_libraries(vidf_test_pool PRIVATE ${VIDF_TARGET})
        # Lab issuing under an existing (project) root: reads PEM keys through OpenSSL, host only.
        add_executable(vidf_issue tests/issue_tool.cpp tests/test_trust_domain.cpp tests/pki_authority.cpp)
        target_link_libraries(vidf_issue PRIVATE ${VIDF_TARGET} OpenSSL::Crypto)
        target_compile_definitions(vidf_issue PRIVATE OPENSSL_API_COMPAT=0x10101000L)
    endif()
endif()
