cmake_minimum_required(VERSION 3.25)

project(UniversalContainer VERSION 0.1.0 LANGUAGES CXX)

option(UC_BUILD_TESTS "Build the correctness test executable" ON)
option(UC_BUILD_BENCHMARKS "Build the benchmark executable" ON)
set(UC_VECTOR_PROFILE "baseline" CACHE STRING "Code generation profile: scalar, baseline, or avx2")
set_property(CACHE UC_VECTOR_PROFILE PROPERTY STRINGS scalar baseline avx2)

if(NOT UC_VECTOR_PROFILE MATCHES "^(scalar|baseline|avx2)$")
    message(FATAL_ERROR "UC_VECTOR_PROFILE must be scalar, baseline, or avx2")
endif()

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION FALSE)

if(MSVC)
    # Self-contained executables: /MT in Release and /MTd in Debug.
    set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()

set(UC_OUTPUT_ROOT "${CMAKE_SOURCE_DIR}/out")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${UC_OUTPUT_ROOT}/bin/${UC_VECTOR_PROFILE}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${UC_OUTPUT_ROOT}/lib/${UC_VECTOR_PROFILE}")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${UC_OUTPUT_ROOT}/lib/${UC_VECTOR_PROFILE}")
set(CMAKE_PDB_OUTPUT_DIRECTORY "${UC_OUTPUT_ROOT}/symbols/${UC_VECTOR_PROFILE}")
set(CMAKE_COMPILE_PDB_OUTPUT_DIRECTORY "${UC_OUTPUT_ROOT}/symbols/${UC_VECTOR_PROFILE}")
foreach(config Debug Release RelWithDebInfo MinSizeRel)
    string(TOUPPER "${config}" config_upper)
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${config_upper} "${UC_OUTPUT_ROOT}/bin/${UC_VECTOR_PROFILE}/${config}")
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${config_upper} "${UC_OUTPUT_ROOT}/lib/${UC_VECTOR_PROFILE}/${config}")
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${config_upper} "${UC_OUTPUT_ROOT}/lib/${UC_VECTOR_PROFILE}/${config}")
    set(CMAKE_PDB_OUTPUT_DIRECTORY_${config_upper} "${UC_OUTPUT_ROOT}/symbols/${UC_VECTOR_PROFILE}/${config}")
    set(CMAKE_COMPILE_PDB_OUTPUT_DIRECTORY_${config_upper} "${UC_OUTPUT_ROOT}/symbols/${UC_VECTOR_PROFILE}/${config}")
endforeach()

add_library(universal_container INTERFACE)
target_include_directories(universal_container INTERFACE "${CMAKE_SOURCE_DIR}/include")
target_compile_features(universal_container INTERFACE cxx_std_20)
target_compile_definitions(universal_container INTERFACE UC_BUILD_PROFILE="${UC_VECTOR_PROFILE}")

if(MSVC)
    target_compile_options(universal_container INTERFACE /W4 /permissive- /EHsc /Zc:__cplusplus)
    if(UC_VECTOR_PROFILE STREQUAL "scalar")
        # MSVC has no documented TU-wide switch.  /d2Qvec- is accepted by the
        # installed compiler and its presence is command-line audited together
        # with /Qvec-report:1; the report text itself is not parsed;
        # _USE_STD_VECTOR_ALGORITHMS=0 also disables explicit SIMD in MSVC STL
        # algorithms.  CRT memmove may still dispatch to SIMD internally.
        target_compile_options(universal_container INTERFACE
            $<$<CONFIG:Release>:/O2> /d2Qvec- /Qvec-report:1 /Oi-)
        target_compile_definitions(universal_container INTERFACE
            UC_SIMD_PROFILE_SCALAR=1 _USE_STD_VECTOR_ALGORITHMS=0)
    elseif(UC_VECTOR_PROFILE STREQUAL "avx2")
        target_compile_options(universal_container INTERFACE $<$<CONFIG:Release>:/O2> /arch:AVX2)
        target_compile_definitions(universal_container INTERFACE UC_SIMD_PROFILE_AVX2=1)
    else()
        # Baseline x64 code generation: auto-vectorization is enabled, but no AVX ISA is requested.
        target_compile_options(universal_container INTERFACE $<$<CONFIG:Release>:/O2>)
        target_compile_definitions(universal_container INTERFACE UC_SIMD_PROFILE_BASELINE=1)
    endif()
else()
    target_compile_options(universal_container INTERFACE -Wall -Wextra -Wpedantic)
    if(UC_VECTOR_PROFILE STREQUAL "scalar")
        target_compile_options(universal_container INTERFACE
            $<$<CONFIG:Release>:-O2;-fno-tree-vectorize;-fno-tree-slp-vectorize;-mno-avx>)
    elseif(UC_VECTOR_PROFILE STREQUAL "avx2")
        target_compile_options(universal_container INTERFACE $<$<CONFIG:Release>:-O2;-mavx2>)
    else()
        target_compile_options(universal_container INTERFACE $<$<CONFIG:Release>:-O2;-mno-avx>)
    endif()
endif()

add_executable(uc_demo src/demo.cpp)
target_link_libraries(uc_demo PRIVATE universal_container)
set_property(TARGET uc_demo PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)

if(UC_BUILD_TESTS)
    enable_testing()
    add_executable(uc_tests tests/adaptive_sequence_tests.cpp)
    target_link_libraries(uc_tests PRIVATE universal_container)
    set_property(TARGET uc_tests PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)
    add_test(NAME adaptive_sequence COMMAND uc_tests)
endif()

if(UC_BUILD_BENCHMARKS)
    add_executable(uc_bench src/benchmark.cpp)
    target_link_libraries(uc_bench PRIVATE universal_container)
    set_property(TARGET uc_bench PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)

    add_executable(uc_focused_bench src/focused_benchmark.cpp)
    target_link_libraries(uc_focused_bench PRIVATE universal_container)
    set_property(TARGET uc_focused_bench PROPERTY INTERPROCEDURAL_OPTIMIZATION FALSE)
endif()
