cmake_minimum_required(VERSION 3.20)

project(lymphosat_composite_solver LANGUAGES CXX)

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

set(CORE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/core")
set(ORDER_JSON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/order.json")

if(NOT EXISTS "${ORDER_JSON_PATH}")
    message(FATAL_ERROR "order.json not found")
endif()

file(READ "${ORDER_JSON_PATH}" ORDER_JSON)
string(JSON SOLVER_COUNT LENGTH "${ORDER_JSON}" solver_order)

set(MATCHER_OBJECTS "")
set(REGISTRY_DECLS "")
set(REGISTRY_ENTRIES "")
set(USED_SYMBOLS "")

function(_append_matcher SOLVER_INDEX SOLVER_NAME)
    if(NOT "${SOLVER_NAME}" MATCHES "^[A-Za-z0-9_.-]+$")
        message(FATAL_ERROR "Invalid solver name in order.json: ${SOLVER_NAME}")
    endif()

    set(MATCH_CPP "${CMAKE_CURRENT_SOURCE_DIR}/solvers/${SOLVER_NAME}/matcher/match.cpp")
    if(NOT EXISTS "${MATCH_CPP}")
        message(FATAL_ERROR "Missing matcher source for '${SOLVER_NAME}': ${MATCH_CPP}")
    endif()

    string(REGEX REPLACE "[^A-Za-z0-9_]" "_" SYMBOL_SAFE "${SOLVER_NAME}")
    if("${SYMBOL_SAFE}" MATCHES "^[0-9]")
        set(SYMBOL_SAFE "_${SYMBOL_SAFE}")
    endif()
    set(SYMBOL "matcher_${SOLVER_INDEX}_${SYMBOL_SAFE}")

    set(TARGET_NAME "matcher_obj_${SOLVER_INDEX}_${SYMBOL_SAFE}")
    add_library("${TARGET_NAME}" OBJECT "${MATCH_CPP}")
    target_include_directories("${TARGET_NAME}" PRIVATE "${CORE_DIR}")
    target_compile_definitions("${TARGET_NAME}" PRIVATE "LYMPHOSAT_MATCHER_SYMBOL=${SYMBOL}")
    target_compile_features("${TARGET_NAME}" PRIVATE cxx_std_20)

    set(SOLVER_PATH "solvers/${SOLVER_NAME}/solver/solver")
    set(REGISTRY_DECLS_LOCAL
        "extern \"C\" bool lymphosat_match_${SYMBOL}(const lymphosat::FormulaProfile& profile);\n")
    set(REGISTRY_ENTRIES_LOCAL
        "        {\"${SOLVER_NAME}\", \"${SOLVER_PATH}\", &lymphosat_match_${SYMBOL}},\n")

    set(MATCHER_OBJECTS "${MATCHER_OBJECTS};$<TARGET_OBJECTS:${TARGET_NAME}>" PARENT_SCOPE)
    set(REGISTRY_DECLS "${REGISTRY_DECLS}${REGISTRY_DECLS_LOCAL}" PARENT_SCOPE)
    set(REGISTRY_ENTRIES "${REGISTRY_ENTRIES}${REGISTRY_ENTRIES_LOCAL}" PARENT_SCOPE)
endfunction()

if(SOLVER_COUNT GREATER 0)
    math(EXPR LAST_INDEX "${SOLVER_COUNT} - 1")
    foreach(INDEX RANGE 0 ${LAST_INDEX})
        string(JSON SOLVER_NAME GET "${ORDER_JSON}" solver_order ${INDEX})
        _append_matcher("${INDEX}" "${SOLVER_NAME}")
    endforeach()
endif()

set(FALLBACK_SOLVER "")
string(JSON FALLBACK_TYPE ERROR_VARIABLE FALLBACK_ERROR TYPE "${ORDER_JSON}" fallback_solver)
if(NOT FALLBACK_ERROR)
    string(JSON FALLBACK_SOLVER GET "${ORDER_JSON}" fallback_solver)
    if(NOT "${FALLBACK_SOLVER}" MATCHES "^[A-Za-z0-9_.-]+$")
        message(FATAL_ERROR "Invalid fallback_solver in order.json: ${FALLBACK_SOLVER}")
    endif()
endif()

set(BUNDLE_VERIFY false)
string(JSON VERIFY_TYPE ERROR_VARIABLE VERIFY_ERROR TYPE "${ORDER_JSON}" verify)
if(NOT VERIFY_ERROR)
    string(JSON BUNDLE_VERIFY GET "${ORDER_JSON}" verify)
endif()

set(GENERATED_REGISTRY "${CMAKE_CURRENT_BINARY_DIR}/matcher_registry.cpp")
file(WRITE "${GENERATED_REGISTRY}"
"#include \"formula_profile.h\"\n"
"#include <vector>\n"
"\n"
"${REGISTRY_DECLS}"
"\n"
"namespace lymphosat {\n"
"const std::vector<MatcherEntry>& matcher_registry() {\n"
"    static const std::vector<MatcherEntry> entries = {\n"
"${REGISTRY_ENTRIES}"
"    };\n"
"    return entries;\n"
"}\n"
"\n"
"const char* fallback_solver_name() {\n"
"    return \"${FALLBACK_SOLVER}\";\n"
"}\n"
"}  // namespace lymphosat\n")

add_executable(
    lymphosat_composite
    "${CORE_DIR}/main.cpp"
    "${CORE_DIR}/profiler.cpp"
    "${GENERATED_REGISTRY}"
    ${MATCHER_OBJECTS}
)

target_include_directories(lymphosat_composite PRIVATE "${CORE_DIR}")
target_compile_features(lymphosat_composite PRIVATE cxx_std_20)

if(BUNDLE_VERIFY)
    target_compile_definitions(lymphosat_composite PRIVATE LYMPHOSAT_BUNDLE_VERIFY=1)
endif()

if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
    target_compile_options(lymphosat_composite PRIVATE -Wall -Wextra -Wpedantic)
endif()

set_target_properties(
    lymphosat_composite
    PROPERTIES
    OUTPUT_NAME "solver"
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
)
