cmake_minimum_required(VERSION 3.27)
project(uv-co)

if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
    # This can cause trouble if compiling libraries for system-wide use.
    # Generally you must use the same compiler for application code as was used
    # to build the uvco code.
    if(NOT DEFINED ENABLE_LTO)
        set(ENABLE_LTO 1)
    endif()
    if(NOT DEFINED ENABLE_ASAN)
        set(ENABLE_ASAN 0)
    endif()
    if(NOT DEFINED ENABLE_COVERAGE)
        set(ENABLE_COVERAGE 0)
    endif()
elseif(CMAKE_BUILD_TYPE STREQUAL "Debug")
    if(NOT DEFINED ENABLE_LTO)
        set(ENABLE_LTO 0)
    endif()
    if(NOT DEFINED ENABLE_ASAN)
        set(ENABLE_ASAN 1)
    endif()
    if(NOT DEFINED ENABLE_COVERAGE)
        set(ENABLE_COVERAGE 1)
    endif()
else()
    if(NOT DEFINED ENABLE_LTO)
        set(ENABLE_LTO 0)
    endif()
    if(NOT DEFINED ENABLE_ASAN)
        set(ENABLE_ASAN 0)
    endif()
    if(NOT DEFINED ENABLE_COVERAGE)
        set(ENABLE_COVERAGE 0)
    endif()
endif()

MESSAGE(STATUS "LTO ${ENABLE_LTO} ASAN ${ENABLE_ASAN} COVERAGE ${ENABLE_COVERAGE}")

include(CTest)
include(CMakePackageConfigHelpers)

find_package(PkgConfig)
pkg_check_modules(FMT REQUIRED fmt)
pkg_check_modules(UV REQUIRED libuv)

# Allow using a custom libuv installation.
link_directories(BEFORE /usr/local/lib64 /usr/local/lib)

find_package(Boost 1.80
  CONFIG
  COMPONENTS
    log
    log_setup
    program_options
  REQUIRED)
find_package(benchmark)
find_package(CURL)
find_package(libpqxx)
find_package(GTest)

set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
set(CMAKE_CXX_STANDARD 23)
add_compile_options(
  -pipe
  -fno-omit-frame-pointer
  -Wall
  -Wextra
  -Wpedantic
)

if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    add_compile_options(-fcolor-diagnostics)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
    add_compile_options(-fdiagnostics-color=always)
endif()

if(ENABLE_ASAN)
    add_compile_options(-fsanitize=address,undefined)
    add_link_options(-fsanitize=address,undefined)
endif()

if(ENABLE_COVERAGE)
    add_compile_options(--coverage)
    add_link_options(--coverage -lgcov)
endif()

if (ENABLE_LTO)
    add_compile_options(-flto)
    add_link_options(-flto)
    if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        add_link_options(-fuse-ld=lld)
    elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
        # Works by default, usually.
    endif()
endif()

# Core utilities not relying on uv-co-promise or uv-co-lib.
add_library(uv-co-base STATIC
    uvco/exception.cc
    uvco/internal/internal_utils.cc
    uvco/loop/loop.cc
    uvco/loop/scheduler.cc
)
target_sources(uv-co-base INTERFACE
    FILE_SET HEADERS
    TYPE HEADERS
    FILES
    uvco/exception.h
    uvco/internal/internal_utils.h
    uvco/loop/loop.h
    uvco/loop/scheduler.h
)
target_include_directories(uv-co-base PRIVATE ${FMT_INCLUDEDIR} .)
install(TARGETS uv-co-base EXPORT uvco FILE_SET HEADERS DESTINATION include)

add_library(uv-co-promise STATIC
    uvco/promise/multipromise.cc
    uvco/promise/promise.cc
    uvco/promise/promise_core.cc
)
target_sources(uv-co-promise INTERFACE
    FILE_SET HEADERS
    TYPE HEADERS
    FILES
    uvco/promise/multipromise.h
    uvco/promise/promise.h
    uvco/promise/promise_core.h
    uvco/promise/promise_refcount.h
    uvco/promise/select.h
)
target_link_libraries(uv-co-promise PRIVATE uv-co-base)
target_include_directories(uv-co-promise PRIVATE .)
install(TARGETS uv-co-promise EXPORT uvco FILE_SET HEADERS DESTINATION include)

add_library(uv-co-lib STATIC
    uvco/async_work.cc
    uvco/channel.cc
    uvco/close.cc
    uvco/combinators.cc
    uvco/fs.cc
    uvco/name_resolution.cc
    uvco/pipe.cc
    uvco/run.cc
    uvco/stream.cc
    uvco/stream_server_base_impl.cc
    uvco/tcp.cc
    uvco/tcp_stream.cc
    uvco/timer.cc
    uvco/udp.cc
    uvco/uds.cc
    uvco/uds_stream.cc
)

target_sources(uv-co-lib INTERFACE
    FILE_SET HEADERS
    TYPE HEADERS
    FILES
    uvco/async_work.h
    uvco/bounded_queue.h
    uvco/cancellation_block.h
    uvco/channel.h
    uvco/close.h
    uvco/combinators.h
    uvco/fs.h
    uvco/name_resolution.h
    uvco/pipe.h
    uvco/run.h
    uvco/stream.h
    uvco/stream_server_base.h
    uvco/stream_server_base_impl.h
    uvco/tcp.h
    uvco/tcp_stream.h
    uvco/timer.h
    uvco/udp.h
    uvco/uds.h
    uvco/uds_stream.h
)

target_link_libraries(uv-co-lib
    PUBLIC
    uv-co-base
    uv-co-promise
    PRIVATE
    ${UV_LIBRARIES}
    ${FMT_LIBRARIES}
)

target_include_directories(uv-co-lib PRIVATE ${UV_INCLUDEDIR} ${FMT_INCLUDEDIR} .)
install(TARGETS uv-co-lib EXPORT uvco FILE_SET HEADERS DESTINATION include)

# Package configuration

install(EXPORT uvco FILE UvcoTargets.cmake DESTINATION lib/cmake/uvco NAMESPACE Uvco:: )
export(EXPORT uvco FILE UvcoTargets.cmake NAMESPACE Uvco::)
configure_package_config_file(Config.cmake.in UvcoConfig.cmake INSTALL_DESTINATION lib/cmake/uvco)
install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/UvcoConfig.cmake"
    DESTINATION lib/cmake/uvco
)

# Optional integrations

if(CURL_FOUND)
    add_library(uv-co-curl STATIC
        uvco/integrations/curl/curl.cc
    )
    target_sources(uv-co-curl INTERFACE
        FILE_SET HEADERS
        TYPE HEADERS
        BASE_DIRS uvco/
        FILES
        uvco/integrations/curl/curl.h
    )
    target_link_libraries(uv-co-curl PUBLIC uv-co-lib CURL::libcurl)
    target_include_directories(uv-co-curl PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
    install(TARGETS uv-co-curl)
    install(TARGETS uv-co-curl FILE_SET HEADERS DESTINATION include/uvco)
    set(GENPKGCONFIG_EXTRA_REQUIRES "${GENPKGCONFIG_EXTRA_REQUIRES}, libcurl")
    set(GENPKGCONFIG_EXTRA_LIBS "${GENPKGCONFIG_EXTRA_LIBS} -luv-co-curl")
endif()

if(libpqxx_FOUND)
    add_library(uv-co-pqxx STATIC
        uvco/integrations/pqxx/pqxx.cc
    )
    target_sources(uv-co-pqxx INTERFACE
        FILE_SET HEADERS
        TYPE HEADERS
        BASE_DIRS uvco/
        FILES
        uvco/integrations/pqxx/pqxx.h
    )
    target_link_libraries(uv-co-pqxx PUBLIC uv-co-lib libpqxx::pqxx)
    target_include_directories(uv-co-pqxx PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
    install(TARGETS uv-co-pqxx)
    install(TARGETS uv-co-pqxx FILE_SET HEADERS DESTINATION include/uvco)
    set(GENPKGCONFIG_EXTRA_REQUIRES "${GENPKGCONFIG_EXTRA_REQUIRES}, libpqxx")
    set(GENPKGCONFIG_EXTRA_LIBS "${GENPKGCONFIG_EXTRA_LIBS} -luv-co-pqxx")
endif()

# Unit tests

if(GTest_FOUND)
    enable_testing()

    add_library(test-util STATIC test/test_util.cc)
    target_include_directories(test-util PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
    target_link_libraries(test-util PRIVATE uv-co-lib)

    add_library(uv-co-example-http STATIC
      uvco/examples/http_server.cc
  )

    target_sources(uv-co-example-http INTERFACE
      FILE_SET HEADERS
      TYPE HEADERS
      FILES
      uvco/examples/http_server.h
  )

    target_link_libraries(uv-co-example-http
      PUBLIC
      uv-co-lib
  )

    target_include_directories(uv-co-example-http PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
    install(TARGETS uv-co-example-http EXPORT uvco FILE_SET HEADERS DESTINATION include)

    add_executable(http-server benchmark/http_server.cc)
    target_link_libraries(http-server
    PRIVATE
    uv-co-example-http
    Boost::program_options
  )

    foreach(TESTCASE IN ITEMS async_work channel combinators fs generator loop name_resolution promise select stream timer udp uds tcp misc)
        add_executable(${TESTCASE}-test test/${TESTCASE}_test.cc)
        target_link_libraries(${TESTCASE}-test PRIVATE uv-co-lib test-util GTest::gtest_main)
        add_test(NAME ${TESTCASE}-test COMMAND ${TESTCASE}-test)

        if(ENABLE_COVERAGE)
            target_compile_options(${TESTCASE}-test PRIVATE "--coverage")
            target_link_libraries(${TESTCASE}-test PRIVATE gcov)
        endif()
    endforeach()

    add_executable(http_server-test test/http_server_test.cc)
    target_link_libraries(http_server-test
    PRIVATE
    uv-co-example-http
    test-util
    GTest::gtest_main
  )
    add_test(NAME http_server-test COMMAND http_server-test)

    if(ENABLE_COVERAGE)
        target_compile_options(http_server-test PRIVATE "--coverage")
        target_link_libraries(http_server-test PRIVATE gcov)
    endif()

    foreach(TESTBINARY IN ITEMS tcp-broadcaster multicast http10 memcached-impl)
        add_executable(test-${TESTBINARY} test/${TESTBINARY}.exe.cc)
        target_link_libraries(test-${TESTBINARY} PRIVATE uv-co-lib Boost::program_options)
    endforeach()

    target_link_libraries(test-memcached-impl PRIVATE Boost::log Boost::log_setup)

    if(CURL_FOUND)
        add_executable(curl-test test/curl_test.cc)
        target_link_libraries(curl-test PRIVATE uv-co-curl test-util GTest::gtest_main)
        add_test(NAME curl-test COMMAND curl-test)
    endif()
    if(libpqxx_FOUND)
        add_executable(pqxx-test test/pqxx_test.cc)
        target_link_libraries(pqxx-test PRIVATE uv-co-pqxx test-util GTest::gtest_main)
        add_test(NAME pqxx-test COMMAND pqxx-test)
    endif()

else()
    message(WARNING "GTest not found, skipping tests")
endif()

# Benchmarks

if (benchmark_FOUND)
    foreach(BENCHMARKCASE IN ITEMS stream promise)
        add_executable(${BENCHMARKCASE}-benchmark benchmark/${BENCHMARKCASE}_bench.cc)
        target_link_libraries(${BENCHMARKCASE}-benchmark
      PRIVATE
        uv-co-lib
        benchmark::benchmark
        benchmark::benchmark_main
        GTest::gtest
    )
    endforeach()
else()
    message(WARNING "Google Benchmark not found, skipping benchmarks")
endif()

# Coverage

if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    find_program(LLVM_COV_BINARY NAMES llvm-cov llvm-cov-18)
    set(GCOV_BINARY "${LLVM_COV_BINARY} gcov")
else()
    set(GCOV_BINARY "gcov")
endif()

# Pkg-config

configure_file(uvco.pc.in
  uvco.pc @ONLY)

install(
 FILES ${CMAKE_CURRENT_BINARY_DIR}/uvco.pc
 DESTINATION lib/pkgconfig
)

add_custom_target(coverage
    COMMAND mkdir -p coverage
    COMMAND gcovr --html-nested -o coverage/uvco.html --gcov-executable ${GCOV_BINARY} -r .. --merge-mode-functions=separate
    COMMAND find . -name *.gcda -delete
)
add_custom_target(grcov
    COMMAND mkdir -p grcov
    COMMAND grcov . -s .. --binary-path . -t html --branch --ignore-not-existing -o grcov --llvm
    COMMAND find . -name *.gcda -delete
)
