include(FindPkgConfig)
find_package(PkgConfig)

if (TARGET sodium)
    set(sodium_libs sodium)
else ()
    # prefer package config (libsodium provided)
    # over unofficial VCPKG CMake Config
    pkg_check_modules(sodium IMPORTED_TARGET libsodium)
    if (sodium_FOUND)
        set(sodium_libs PkgConfig::sodium)
        message("sodium[${sodium_VERSION}] is ${sodium_LINK_LIBRARIES}")
    else ()
        find_package(unofficial-sodium REQUIRED)
        set(sodium_libs unofficial-sodium::sodium)
        get_target_property(sodium_loc unofficial-sodium::sodium LOCATION)
        message("sodium is ${sodium_loc}")
    endif ()
endif ()
if (NOT sodium_libs)
    message(FATAL_ERROR "could not find required library[sodium]")
endif ()

pkg_check_modules(PBUFC REQUIRED IMPORTED_TARGET libprotobuf-c)
message("libprotobuf-c[${PBUFC_VERSION}] is ${PBUFC_LINK_LIBRARIES}")

if (ziti_DEVELOPER_MODE)
    find_program(PROTO_GEN protoc)
    find_program(PROTO_GEN_C protoc-gen-c)
    if (PROTO_GEN AND PROTO_GEN_C)
        message(NOTICE "protobuf generator: ${PROTO_GEN}")
        FetchContent_Declare(sdk_golang
                GIT_REPOSITORY https://github.com/openziti/sdk-golang.git
                GIT_TAG main
                GIT_SHALLOW 1
        )
        FetchContent_MakeAvailable(sdk_golang)

        set(PROTO_FILES
                edge_client.proto
                google/protobuf/timestamp.proto
        )
        add_custom_target(generate-protobuf
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/proto
                COMMAND protoc --c_out=. -I ${PBUFC_INCLUDE_DIRS} -I ${sdk_golang_SOURCE_DIR}/pb/edge_client_pb ${PROTO_FILES}
        )
        message(NOTICE "dev mode: use generate-protobuf target to update protobuf objects")
    endif ()
endif ()

pkg_check_modules(JSONC json-c)
if (TARGET PkgConfig::JSONC)
    set(jsonc_libs PkgConfig::JSONC)
    message(STATUS "Using PkgConfig::JSONC target")
else()
    find_package(json-c REQUIRED)
    if (TARGET json-c::json-c)
        set(jsonc_libs json-c::json-c)
        message(STATUS "Using json-c::json-c target")
    else()
        message(FATAL_ERROR "Could not find required library [json-c]")
    endif()
endif()

pkg_check_modules(STC REQUIRED IMPORTED_TARGET stc)

set(ZITI_HEADER_FILES
        ${PROJECT_SOURCE_DIR}/includes/ziti/errors.h
        ${PROJECT_SOURCE_DIR}/includes/ziti/ziti.h
        ${PROJECT_SOURCE_DIR}/includes/ziti/enums.h
        ${PROJECT_SOURCE_DIR}/includes/ziti/ziti_src.h
        ${PROJECT_SOURCE_DIR}/includes/ziti/ziti_events.h
        ${PROJECT_SOURCE_DIR}/includes/ziti/ziti_buffer.h
        ${PROJECT_SOURCE_DIR}/includes/ziti/zitilib.h
        ${PROJECT_SOURCE_DIR}/includes/ziti/model_collections.h
        ${PROJECT_SOURCE_DIR}/includes/ziti/types.h
        )

SET(ZITI_SRC_FILES
        sdk_info.c
        utils.c
        credentials.c
        ziti.c
        config.c
        errors.c
        ziti_enroll.c
        ziti_ctrl.c
        model_support.c
        internal_model.c
        connect.c
        channel.c
        message.c
        buffer.c
        ziti_src.c
        metrics.c
        posture.c
        auth_queries.c
        conn_bridge.c
        pool.c
        model_collections.c
        authenticators.c
        crypto.c
        bind.c
        oidc.c
        proto/edge_client.pb-c.c
        proto/google/protobuf/timestamp.pb-c.c
        legacy_auth.c
        util/future.c
        external_auth.c
        edge_protocol.c
        ext_oidc.c
        session_certs.c
        e2ee/e2ee_libsodium.c
        e2ee/e2ee_none.c
)

if (WIN32)
    list(APPEND ZITI_SRC_FILES e2ee/e2ee_aes_gcm_wincrypto.c)
elseif (APPLE)
    list(APPEND ZITI_SRC_FILES e2ee/e2ee_aes_gcm_apple.c)
else ()
    list(APPEND ZITI_SRC_FILES e2ee/e2ee_aes_gcm_ossl.c)
endif ()

FILE(GLOB zitilib_FILES ${CMAKE_CURRENT_LIST_DIR}/zitilib/*.c)
LIST(APPEND ZITI_SRC_FILES ${zitilib_FILES})

# AES-GCM for the Apple e2ee backend lives in CryptoKit, which is Swift-only, so
# e2ee_aes_gcm_cryptokit.swift is compiled here and its object is archived into libziti
# like any other. Deliberately NOT enable_language(Swift): that makes CMake pick swiftc as
# the linker driver for every target that links ziti -- including downstream consumers --
# and swiftc rejects flags like -pthread. Handing CMake a finished object keeps the build,
# and every consumer's link line, plain C. The Swift runtime is part of the OS and is
# autolinked from the object, so `clang prog.c libziti.a` needs no extra flags.
if (APPLE)
    find_program(SWIFTC swiftc REQUIRED)

    if (CMAKE_OSX_ARCHITECTURES)
        set(cryptokit_arch ${CMAKE_OSX_ARCHITECTURES})
    else ()
        set(cryptokit_arch ${CMAKE_SYSTEM_PROCESSOR})
    endif ()
    list(LENGTH cryptokit_arch cryptokit_arch_count)
    if (cryptokit_arch_count GREATER 1)
        message(FATAL_ERROR "e2ee CryptoKit shim: one architecture at a time, got '${cryptokit_arch}'")
    endif ()

    # CryptoKit floors: macOS 10.15, iOS 13. arm64 macOS itself does not exist before 11.0.
    if (CMAKE_SYSTEM_NAME STREQUAL "iOS")
        set(cryptokit_target ${cryptokit_arch}-apple-ios13.0)
        set(cryptokit_sdk iphoneos)
    elseif (cryptokit_arch STREQUAL "arm64")
        set(cryptokit_target ${cryptokit_arch}-apple-macosx11.0)
        set(cryptokit_sdk macosx)
    else ()
        set(cryptokit_target ${cryptokit_arch}-apple-macosx10.15)
        set(cryptokit_sdk macosx)
    endif ()

    # CMAKE_OSX_SYSROOT is empty unless a toolchain set it
    if (CMAKE_OSX_SYSROOT)
        set(cryptokit_sysroot ${CMAKE_OSX_SYSROOT})
    else ()
        execute_process(COMMAND xcrun --sdk ${cryptokit_sdk} --show-sdk-path
                OUTPUT_VARIABLE cryptokit_sysroot OUTPUT_STRIP_TRAILING_WHITESPACE)
    endif ()

    if (CMAKE_BUILD_TYPE STREQUAL "Debug")
        set(cryptokit_opt -Onone)
    else ()
        set(cryptokit_opt -O)
    endif ()

    set(ZITI_CRYPTOKIT_OBJECT ${CMAKE_CURRENT_BINARY_DIR}/e2ee_aes_gcm_cryptokit.o)
    add_custom_command(
            OUTPUT ${ZITI_CRYPTOKIT_OBJECT}
            COMMAND ${SWIFTC} -c ${CMAKE_CURRENT_LIST_DIR}/e2ee/e2ee_aes_gcm_cryptokit.swift
                    -o ${ZITI_CRYPTOKIT_OBJECT}
                    -module-name ziti_cryptokit
                    -parse-as-library
                    -target ${cryptokit_target}
                    # back-deploying below macOS 12.3 makes swiftc emit force-load references
                    # to libswiftCompatibility*.a, which only exist in the Xcode toolchain and
                    # would have to be added to every consumer's link line. This shim uses no
                    # concurrency and no generics over Obj-C, so it needs none of those fixups.
                    -runtime-compatibility-version none
                    -sdk ${cryptokit_sysroot}
                    ${cryptokit_opt}
            DEPENDS ${CMAKE_CURRENT_LIST_DIR}/e2ee/e2ee_aes_gcm_cryptokit.swift
            COMMENT "Building Swift object e2ee_aes_gcm_cryptokit.o"
            VERBATIM)
    set_source_files_properties(${ZITI_CRYPTOKIT_OBJECT}
            PROPERTIES EXTERNAL_OBJECT TRUE GENERATED TRUE)
endif ()

SET(ZITI_INCLUDE_DIRS
        PUBLIC ../includes
        PRIVATE ../inc_internal
        PRIVATE ${CMAKE_CURRENT_LIST_DIR}
        PRIVATE ${CMAKE_CURRENT_LIST_DIR}/proto
        PRIVATE ${PROJECT_BINARY_DIR}/include
        PRIVATE ${tlsuv_SOURCE_DIR}/src
)

set(ziti_compile_defs
        ZITI_VERSION=${PROJECT_VERSION}
        ZITI_BRANCH=${GIT_BRANCH}
        ZITI_COMMIT=${GIT_COMMIT_HASH}
        PRIVATE ZITI_LOG_MODULE="${PROJECT_NAME}"
)

if (NOT WIN32)
    find_package(OpenSSL REQUIRED)
endif ()

function(define_file_basename_for_sources targetname)
    get_target_property(source_files "${targetname}" SOURCES)
    foreach(sourcefile ${source_files})
        # Add the FILE_BASENAME=filename compile definition to the list.
        get_filename_component(basename "${sourcefile}" NAME)
        # Set the updated compile definitions on the source file.
        set_property(
                SOURCE "${sourcefile}" APPEND
                PROPERTY COMPILE_DEFINITIONS "FILE_BASENAME=\"${basename}\"")
    endforeach()
endfunction()

function(config_ziti_library target)
    target_sources(${target} PRIVATE
            ${ZITI_SRC_FILES}
            ${ZITI_HEADER_FILES})

    set_target_properties(${target} PROPERTIES
            C_STANDARD 11
            POSITION_INDEPENDENT_CODE ON
            C_VISIBILITY_PRESET hidden
            CXX_VISIBILITY_PRESET hidden
    )

    target_include_directories(${target} ${ZITI_INCLUDE_DIRS})
    target_compile_definitions(${target} PUBLIC
            ${ziti_compile_defs}
    )

    target_link_libraries(${target} PUBLIC
            tlsuv
            ${sodium_libs}
            ${jsonc_libs}
            PkgConfig::PBUFC
            PkgConfig::STC
    )

    if (CMAKE_SYSTEM_NAME MATCHES "Linux")
        target_link_libraries(${target} PUBLIC atomic)
    endif ()

    if (APPLE)
        # e2ee_aes_gcm_apple.c: P-256/ECDH via Security.framework, HKDF via CommonCrypto (libSystem)
        # e2ee_aes_gcm_cryptokit.swift: AES-GCM via CryptoKit
        target_link_libraries(${target}
                PUBLIC "-framework Security"
                PUBLIC "-framework CoreFoundation"
                PUBLIC "-framework CryptoKit"
                PUBLIC "-framework Foundation"
        )
        target_sources(${target} PRIVATE ${ZITI_CRYPTOKIT_OBJECT})
    endif ()

    if (NOT WIN32)
        target_link_libraries(${target} PUBLIC m)
        target_link_libraries(${target} PUBLIC OpenSSL::Crypto)
    endif ()


    if (WIN32)
        #without this libsodium was complaining:
        set_target_properties(${target} PROPERTIES PREFIX "")
        # on windows GDI defines ERROR which conflicts with the SDK declaration of DEBUG_LEVELS in utils.h
        target_compile_definitions(${target}
                PUBLIC NOGDI _CRT_NONSTDC_NO_DEPRECATE _CRT_SECURE_NO_WARNINGS
        )

        target_link_libraries(${target}
                PUBLIC bcrypt
                PUBLIC crypt32
                PUBLIC netapi32
                PUBLIC ws2_32
        )
    endif ()

    if (MSVC)
        target_compile_options(${target}
                PUBLIC /experimental:c11atomics
                PRIVATE /wd4100 /wd4101 /wd4018
        )
    endif (MSVC)

    install(TARGETS ${target}
            COMPONENT ziti-sdk
            DESTINATION ${CMAKE_INSTALL_LIBDIR}
    )
    define_file_basename_for_sources(${target})
endfunction(config_ziti_library)

add_library(ziti STATIC)
config_ziti_library(ziti)

if (BUILD_SHARED_LIBS)
    add_library(ziti_dll SHARED)
    config_ziti_library(ziti_dll)
    set_target_properties(ziti_dll PROPERTIES OUTPUT_NAME "ziti")

    # when building on windows an "import library" is generated which is overwriting the
    # static library (ziti.lib) (google/see /IMPLIB (Name Import Library))
    # work around discovered at stack overflow:
    #     https://stackoverflow.com/questions/34575066/how-to-prevent-cmake-from-issuing-implib
    #
    # I chose to set the suffix and not the prefix
    # set_target_properties(ziti_dll PROPERTIES IMPORT_PREFIX "import-lib-")
    set_target_properties(ziti_dll PROPERTIES
            IMPORT_SUFFIX ".imp.lib"
            OUTPUT_NAME "ziti")

    target_compile_definitions(ziti_dll PUBLIC
            INTERFACE USING_ZITI_SHARED=1
            PRIVATE BUILDING_ZITI_SHARED=1
    )
endif ()

set(includedir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR})
set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR})
set(prefix ${CMAKE_INSTALL_PREFIX})

configure_file(${PROJECT_SOURCE_DIR}/ziti.pc.in ${CMAKE_CURRENT_BINARY_DIR}/ziti.pc @ONLY)

set(CMAKE_INSTALL_DOCDIR share/doc)

install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../includes/
        COMPONENT ziti-sdk
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ziti.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)

