cmake_minimum_required(VERSION 3.10)
project(leveldb_extension LANGUAGES CXX)

# Set C++ standard to match the main project
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# RUST_USIZE_SIZE is optionally provided by the build system
# It represents sizeof(usize) in Rust, which must match sizeof(uintptr_t) in C++
# If not provided, the type compatibility test will be skipped
if(DEFINED RUST_USIZE_SIZE)
    # Create a temporary source file for the check
    set(TYPE_COMPAT_CHECK_SRC "${CMAKE_CURRENT_BINARY_DIR}/type_compat_check.cpp")
    file(WRITE "${TYPE_COMPAT_CHECK_SRC}"
        "#include <cstdint>\n"
        "static_assert(sizeof(uintptr_t) == ${RUST_USIZE_SIZE},\"\");"
        "int main() { return 0; }"
    )
    try_compile(
        HAVE_TYPE_COMPATIBILITY
        ${CMAKE_CURRENT_BINARY_DIR}/compat_check
        SOURCES ${TYPE_COMPAT_CHECK_SRC}
    )
    if(NOT HAVE_TYPE_COMPATIBILITY)
        message(FATAL_ERROR
            "Type compatibility check failed!\n"
            "  sizeof(uintptr_t) != RUST_USIZE_SIZE (${RUST_USIZE_SIZE})\n"
            "  This is required for correct Rust/C++ FFI behavior"
        )
    else()
        message(STATUS "Type compatibility check passed (RUST_USIZE_SIZE=${RUST_USIZE_SIZE})")
        add_compile_definitions(RUST_USIZE_SIZE=${RUST_USIZE_SIZE})
    endif()
else()
    message(STATUS "RUST_USIZE_SIZE not provided, type compatibility test will be skipped")
endif()

# Disable RTTI to match LevelDB's compilation settings
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")

# Create the extension library
add_library(leveldb_extension STATIC
    src/logger.cpp
    src/cache_impl.cpp
)

# Add LevelDB install dir
get_filename_component(LEVELDB_INSTALL_DIR "${LEVELDB_INSTALL_DIR}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")

# Add t3lru install dir
get_filename_component(T3LRU_INSTALL_DIR "${T3LRU_INSTALL_DIR}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
message(STATUS "Using pre-built t3lru from: ${T3LRU_INSTALL_DIR}")

# Add Boost install dir
get_filename_component(BOOST_INSTALL_DIR "${BOOST_INSTALL_DIR}" ABSOLUTE BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
message(STATUS "Using pre-built Boost from: ${BOOST_INSTALL_DIR}")

find_library(LEVELDB_LIBRARY NAMES leveldb PATHS ${LEVELDB_INSTALL_DIR} NO_DEFAULT_PATH)
find_library(T3LRU_LIBRARY NAMES t3lru PATHS ${T3LRU_INSTALL_DIR}/lib NO_DEFAULT_PATH)
find_package(Threads REQUIRED)

set(EXTENSION_BASE_LIBRARIES
    ${LEVELDB_LIBRARY}
    ${T3LRU_LIBRARY}
    Threads::Threads
)

# Common include directories
set(COMMON_INCLUDE_DIRS
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/src
    ${LEVELDB_INSTALL_DIR}/include
    ${T3LRU_INSTALL_DIR}/include
    ${BOOST_INSTALL_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/../deps
)

include_directories(${COMMON_INCLUDE_DIRS})

# Link extension library with LevelDB and eviction_policy
target_link_libraries(leveldb_extension ${EXTENSION_BASE_LIBRARIES})

# Set target properties
set_target_properties(leveldb_extension
    PROPERTIES
    POSITION_INDEPENDENT_CODE ON
)

target_compile_options(leveldb_extension PRIVATE -Wno-interference-size)

# Platform-specific linking
if(WIN32)
    target_link_libraries(leveldb_extension c++)
else()
    target_link_libraries(leveldb_extension stdc++)
endif()

# Install the library
install(TARGETS leveldb_extension
    ARCHIVE DESTINATION lib
    LIBRARY DESTINATION lib
)

# Option to build tests
option(BUILD_TESTS "Build unit tests for extensions" OFF)

if(BUILD_TESTS)
    # Enable testing
    enable_testing()
    target_compile_definitions(leveldb_extension PUBLIC BUILD_TESTS=1)

    # Find Google Test
    find_package(GTest)
    if(NOT GTest_FOUND)
        message(FATAL_ERROR "Google Test is required for building tests but was not found. Consider to disable tests with -DBUILD_TESTS=OFF")
    endif()

    # Common include directories for tests
    set(TEST_INCLUDE_DIRS
        ${COMMON_INCLUDE_DIRS}
        # ${CMAKE_CURRENT_SOURCE_DIR}/include
        # ${CMAKE_CURRENT_SOURCE_DIR}/src
        # "${LEVELDB_INSTALL_DIR}/include"
    )

    # Common libraries for tests
    set(TEST_LIBRARIES
        GTest::gtest
        GTest::gtest_main
        GTest::gmock
        GTest::gmock_main
        ${EXTENSION_BASE_LIBRARIES}
        ${T3LRU_LIBRARY}
    )

    # Platform-specific test libraries
    if(WIN32)
        list(APPEND TEST_LIBRARIES c++)
    else()
        list(APPEND TEST_LIBRARIES stdc++)
    endif()

    # Helper function to create test executables
    function(add_extension_test TEST_NAME TEST_FILE)
        add_executable(${TEST_NAME}
            ${CMAKE_CURRENT_SOURCE_DIR}/tests/${TEST_FILE}
        )
        target_link_libraries(${TEST_NAME} PRIVATE leveldb_extension ${TEST_LIBRARIES})
        target_include_directories(${TEST_NAME} PRIVATE ${TEST_INCLUDE_DIRS})
        target_compile_options(${TEST_NAME} PRIVATE -Wno-interference-size)
        set_target_properties(${TEST_NAME}
            PROPERTIES
            POSITION_INDEPENDENT_CODE ON
        )
        add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
    endfunction()

    add_extension_test(logger_test logger_test.cpp)
    add_extension_test(cache_test cache/cache_test.cpp)
    add_extension_test(cache_concurrent_test cache/cache_concurrent_test.cpp)
    add_extension_test(cache_hot_cache_test cache/cache_hot_cache_test.cpp)

    # Create aggregate target for all tests
    add_custom_target(extension_tests)
    add_dependencies(extension_tests
        logger_test
        cache_test
        cache_concurrent_test
        cache_hot_cache_test
    )
endif()

# Option to build benchmarks
option(BUILD_BENCHMARKS "Build benchmarks for extensions" OFF)

if(BUILD_BENCHMARKS)
    # Find Google Benchmark
    find_package(benchmark)
    if(NOT benchmark_FOUND)
        message(FATAL_ERROR "Google Benchmark is required for building benchmarks but was not found. Consider to disable benchmarks with -DBUILD_BENCHMARKS=OFF")
    endif()

    if(WIN32)
        message(FATAL_ERROR "Windows platform isn't supported for building benchmarks. Consider to disable benchmarks with -DBUILD_BENCHMARKS=OFF")
    endif()

    # Common include directories for benchmarks
    set(BENCH_INCLUDE_DIRS
        ${COMMON_INCLUDE_DIRS}
        # ${CMAKE_CURRENT_SOURCE_DIR}/include
        # "${LEVELDB_INSTALL_DIR}/include"
    )

    # Common libraries for benchmarks
    set(BENCH_LIBRARIES
        benchmark::benchmark
        benchmark::benchmark_main
        ${EXTENSION_BASE_LIBRARIES}
    )

    # Helper function to create benchmark executables
    function(add_extension_bench BENCH_NAME SOURCE_FILE)
        add_executable(${BENCH_NAME}
            ${CMAKE_CURRENT_SOURCE_DIR}/bench/${SOURCE_FILE}
            ${CMAKE_CURRENT_SOURCE_DIR}/bench/cache_bench.cpp
        )
        target_link_libraries(${BENCH_NAME} PRIVATE leveldb_extension ${BENCH_LIBRARIES})
        target_include_directories(${BENCH_NAME} PRIVATE ${BENCH_INCLUDE_DIRS})
        set_target_properties(${BENCH_NAME}
            PROPERTIES
            POSITION_INDEPENDENT_CODE ON
        )
        # Enable optimization for benchmarks
        target_compile_options(${BENCH_NAME} PRIVATE -O2)
    endfunction()

    add_extension_bench(leveldb_lru_cache_bench leveldb_lru_cache_bench.cpp)
    add_extension_bench(flat_t3_cache_bench flat_t3_cache_bench.cpp)

    # Create aggregate target for all benchmarks
    add_custom_target(extension_benchmarks)
    add_dependencies(extension_benchmarks
        leveldb_lru_cache_bench
        flat_t3_cache_bench
    )
endif()
