cmake_minimum_required(VERSION 3.24)
project(ReforgeShaper VERSION 2.0.9 LANGUAGES CXX)

include(CMakePackageConfigHelpers)
include(CTest)
include(GNUInstallDirs)

option(
    REFORGE_SHAPER_QUALIFY_CLARABEL
    "Build the private Phase 9A Clarabel qualification adapter and corpus"
    OFF
)
option(
    REFORGE_SHAPER_ENABLE_SANITIZERS
    "Instrument native targets with address and undefined-behavior sanitizers"
    OFF
)
option(
    REFORGE_SHAPER_CARGO_OFFLINE
    "Require the locked Clarabel Rust build to use only the Cargo cache"
    OFF
)
set(
    REFORGE_SHAPER_SANITIZERS
    "address,undefined"
    CACHE STRING
    "Comma-separated Clang/GCC sanitizers enabled for native targets"
)
if(REFORGE_SHAPER_ENABLE_SANITIZERS)
    if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU")
        message(FATAL_ERROR
            "REFORGE_SHAPER_ENABLE_SANITIZERS requires Clang or GCC."
        )
    endif()
    add_compile_options(
        "-fsanitize=${REFORGE_SHAPER_SANITIZERS}"
        -fno-omit-frame-pointer
    )
    add_link_options("-fsanitize=${REFORGE_SHAPER_SANITIZERS}")
    # The qualified Pinocchio binary is compiled against Eigen's glibc-aligned
    # allocator mode. ASan otherwise changes this inline Eigen ABI only in the
    # instrumented consumer, pairing Pinocchio allocations with the handmade
    # aligned deallocator.
    add_compile_definitions(EIGEN_MALLOC_ALREADY_ALIGNED=1)
endif()
set(
    REFORGE_SHAPER_CLARABEL_ROOT
    ""
    CACHE PATH
    "Materialized checksum-verified Clarabel.cpp source root"
)
set(
    REFORGE_SHAPER_NLOHMANN_INCLUDE_DIR
    ""
    CACHE PATH
    "Explicit nlohmann/json 3.12.0 include root for Python-free builds"
)

if(SKBUILD AND NOT REFORGE_SHAPER_CLARABEL_ROOT)
    set(
        _reforge_shaper_wheel_deps_root
        "${CMAKE_CURRENT_BINARY_DIR}/wheel-dependencies"
    )
    execute_process(
        COMMAND
            "${CMAKE_COMMAND}"
            "-DREFORGE_SHAPER_DEPS_ROOT=${_reforge_shaper_wheel_deps_root}"
            -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/MaterializeClarabel.cmake"
        RESULT_VARIABLE _reforge_shaper_materialize_result
        OUTPUT_VARIABLE _reforge_shaper_materialize_output
        ERROR_VARIABLE _reforge_shaper_materialize_error
    )
    if(NOT _reforge_shaper_materialize_result EQUAL 0)
        message(FATAL_ERROR
            "Failed to materialize the locked wheel solver sources:\n"
            "${_reforge_shaper_materialize_output}"
            "${_reforge_shaper_materialize_error}"
        )
    endif()

    find_program(_reforge_shaper_rustc rustc REQUIRED)
    find_program(_reforge_shaper_cargo cargo REQUIRED)
    execute_process(
        COMMAND "${_reforge_shaper_rustc}" --version
        RESULT_VARIABLE _reforge_shaper_rustc_result
        OUTPUT_VARIABLE _reforge_shaper_rustc_version
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
    if(
        NOT _reforge_shaper_rustc_result EQUAL 0
        OR NOT _reforge_shaper_rustc_version MATCHES "^rustc 1\\.84\\.1 "
    )
        message(FATAL_ERROR
            "Native wheels require rustc 1.84.1 exactly; found "
            "'${_reforge_shaper_rustc_version}'."
        )
    endif()

    set(
        _reforge_shaper_wheel_clarabel_root
        "${_reforge_shaper_wheel_deps_root}/Clarabel.cpp"
    )
    set(
        _reforge_shaper_cargo_command
        "${_reforge_shaper_cargo}" build --release --locked
        --no-default-features
    )
    if(REFORGE_SHAPER_CARGO_OFFLINE)
        list(APPEND _reforge_shaper_cargo_command --offline)
    endif()
    execute_process(
        COMMAND ${_reforge_shaper_cargo_command}
        WORKING_DIRECTORY
            "${_reforge_shaper_wheel_clarabel_root}/rust_wrapper"
        RESULT_VARIABLE _reforge_shaper_cargo_result
        OUTPUT_VARIABLE _reforge_shaper_cargo_output
        ERROR_VARIABLE _reforge_shaper_cargo_error
    )
    if(NOT _reforge_shaper_cargo_result EQUAL 0)
        message(FATAL_ERROR
            "Failed to build the locked wheel solver with "
            "rustc 1.84.1:\n${_reforge_shaper_cargo_output}"
            "${_reforge_shaper_cargo_error}"
        )
    endif()
    set(
        REFORGE_SHAPER_CLARABEL_ROOT
        "${_reforge_shaper_wheel_clarabel_root}"
        CACHE PATH
        "Materialized checksum-verified Clarabel.cpp source root"
        FORCE
    )
endif()

find_package(Eigen3 REQUIRED NO_MODULE)
find_package(nlohmann_json 3.12.0 EXACT CONFIG QUIET)
find_package(pinocchio 4 CONFIG REQUIRED)
set(PYBIND11_FINDPYTHON ON)
find_package(pybind11 CONFIG QUIET)

if(NOT TARGET nlohmann_json::nlohmann_json)
    if(REFORGE_SHAPER_NLOHMANN_INCLUDE_DIR)
        set(
            _reforge_nlohmann_include_dir
            "${REFORGE_SHAPER_NLOHMANN_INCLUDE_DIR}"
        )
        set(_reforge_nlohmann_version "3.12.0")
    else()
        find_package(Python3 COMPONENTS Interpreter QUIET)
    endif()
    if(
        NOT _reforge_nlohmann_version
        AND Python3_Interpreter_FOUND
    )
        execute_process(
            COMMAND
                "${Python3_EXECUTABLE}" -c
                "from importlib.metadata import version; import nlohmann_json; print(version('nlohmann-json')); print(nlohmann_json.get_include())"
            RESULT_VARIABLE _reforge_nlohmann_result
            OUTPUT_VARIABLE _reforge_nlohmann_output
            ERROR_QUIET
            OUTPUT_STRIP_TRAILING_WHITESPACE
        )
        if(_reforge_nlohmann_result EQUAL 0)
            string(REPLACE "\n" ";" _reforge_nlohmann_values
                "${_reforge_nlohmann_output}")
            list(LENGTH _reforge_nlohmann_values
                _reforge_nlohmann_value_count)
            if(_reforge_nlohmann_value_count EQUAL 2)
                list(GET _reforge_nlohmann_values 0
                    _reforge_nlohmann_version)
                list(GET _reforge_nlohmann_values 1
                    _reforge_nlohmann_include_dir)
            endif()
        endif()
    endif()

    if(
        NOT _reforge_nlohmann_version STREQUAL "3.12.0"
        OR NOT EXISTS "${_reforge_nlohmann_include_dir}/nlohmann/json.hpp"
    )
        message(FATAL_ERROR
            "nlohmann_json 3.12.0 is required. Install a discoverable CMake "
            "package or the pinned nlohmann-json Python build dependency."
        )
    endif()

    add_library(nlohmann_json::nlohmann_json INTERFACE IMPORTED)
    set_target_properties(nlohmann_json::nlohmann_json PROPERTIES
        INTERFACE_INCLUDE_DIRECTORIES "${_reforge_nlohmann_include_dir}"
    )
endif()

set(_reforge_shaper_generated_include_dir
    "${CMAKE_CURRENT_BINARY_DIR}/generated/include")
configure_file(
    include/reforge_core/native/common/export.hpp.in
    "${_reforge_shaper_generated_include_dir}/reforge_core/native/common/export.hpp"
    @ONLY
)

add_library(reforge_shaper_common
    src/control/shaper/modal_inference_strategy.cpp
    src/control/shaper/residual_switch_config.cpp
)
add_library(ReforgeShaper::common ALIAS reforge_shaper_common)

set_target_properties(reforge_shaper_common PROPERTIES
    EXPORT_NAME common
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_common PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_common
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_common PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_common
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
target_link_libraries(reforge_shaper_common PUBLIC Eigen3::Eigen)

add_library(reforge_shaper_trajectory
    src/control/shaper/shaper_window_helpers.cpp
    src/util/interpolation.cpp
    src/util/timing/joint_limits.cpp
    src/util/timing/joint_trajectory.cpp
    src/util/timing/resampling.cpp
)
add_library(ReforgeShaper::trajectory ALIAS reforge_shaper_trajectory)

set_target_properties(reforge_shaper_trajectory PROPERTIES
    EXPORT_NAME trajectory
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_trajectory PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_trajectory
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_trajectory PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_trajectory
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
target_link_libraries(
    reforge_shaper_trajectory PUBLIC ReforgeShaper::common
)

add_library(reforge_shaper_model
    src/control/model/map_loader.cpp
    src/control/model/model_types.cpp
)
add_library(ReforgeShaper::model ALIAS reforge_shaper_model)

set_target_properties(reforge_shaper_model PROPERTIES
    EXPORT_NAME model
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_model PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_model
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_model PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_model
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
target_link_libraries(reforge_shaper_model PUBLIC ReforgeShaper::common)
target_link_libraries(
    reforge_shaper_model
    PRIVATE "$<BUILD_INTERFACE:nlohmann_json::nlohmann_json>"
)

add_library(reforge_shaper_runtime
    src/control/runtime/base_shaper.cpp
    src/control/runtime/modal_inference.cpp
    src/control/runtime/task_space.cpp
    src/control/runtime/weighting.cpp
)
add_library(ReforgeShaper::runtime ALIAS reforge_shaper_runtime)

set_target_properties(reforge_shaper_runtime PROPERTIES
    EXPORT_NAME runtime
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_runtime PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_runtime
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_runtime PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_runtime
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
target_link_libraries(
    reforge_shaper_runtime PUBLIC ReforgeShaper::common
)

add_library(reforge_shaper_window
    src/control/window/windowed_buffer.cpp
    src/control/window/shaper_window_handle.cpp
)
add_library(ReforgeShaper::window ALIAS reforge_shaper_window)

set_target_properties(reforge_shaper_window PROPERTIES
    EXPORT_NAME window
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_window PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_window
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_window PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_window
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
target_link_libraries(
    reforge_shaper_window
    PUBLIC ReforgeShaper::common ReforgeShaper::trajectory
)

add_library(reforge_shaper_dynamics
    src/util/robot_dynamics.cpp
)
add_library(ReforgeShaper::dynamics ALIAS reforge_shaper_dynamics)

set_target_properties(reforge_shaper_dynamics PROPERTIES
    EXPORT_NAME dynamics
    POSITION_INDEPENDENT_CODE ON
)
target_compile_features(reforge_shaper_dynamics PUBLIC cxx_std_17)
target_compile_definitions(reforge_shaper_dynamics
    PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
)
if(NOT BUILD_SHARED_LIBS)
    target_compile_definitions(
        reforge_shaper_dynamics PUBLIC REFORGE_SHAPER_STATIC_DEFINE
    )
endif()
target_include_directories(reforge_shaper_dynamics
    PUBLIC
        "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
        "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
        "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
target_link_libraries(
    reforge_shaper_dynamics
    PUBLIC ReforgeShaper::common pinocchio::pinocchio
)
target_link_libraries(
    reforge_shaper_runtime
    PUBLIC
        ReforgeShaper::dynamics
        ReforgeShaper::model
        ReforgeShaper::trajectory
)

if(REFORGE_SHAPER_QUALIFY_CLARABEL AND NOT REFORGE_SHAPER_CLARABEL_ROOT)
    message(FATAL_ERROR
        "REFORGE_SHAPER_QUALIFY_CLARABEL requires "
        "REFORGE_SHAPER_CLARABEL_ROOT."
    )
endif()

if(REFORGE_SHAPER_CLARABEL_ROOT)
    if(NOT IS_DIRECTORY "${REFORGE_SHAPER_CLARABEL_ROOT}/include")
        message(FATAL_ERROR
            "REFORGE_SHAPER_CLARABEL_ROOT must name the locked "
            "Clarabel.cpp source root."
        )
    endif()
    set(
        _reforge_clarabel_static_library
        "${REFORGE_SHAPER_CLARABEL_ROOT}/rust_wrapper/target/release/libclarabel_c.a"
    )
    if(NOT EXISTS "${_reforge_clarabel_static_library}")
        message(FATAL_ERROR
            "The locked Clarabel static library is missing: "
            "${_reforge_clarabel_static_library}"
        )
    endif()

    add_library(reforge_clarabel_c_static STATIC IMPORTED GLOBAL)
    set_target_properties(reforge_clarabel_c_static PROPERTIES
        IMPORTED_LOCATION "${_reforge_clarabel_static_library}"
        INTERFACE_INCLUDE_DIRECTORIES
            "${REFORGE_SHAPER_CLARABEL_ROOT}/include"
    )

    add_library(reforge_shaper_solver
        src/control/shaper/modal_switch_optimizer.cpp
        src/solver/clarabel_solver.cpp
        src/util/trajopt/traj_qp.cpp
    )
    add_library(ReforgeShaper::solver ALIAS reforge_shaper_solver)
    set_target_properties(reforge_shaper_solver PROPERTIES
        EXPORT_NAME solver
        POSITION_INDEPENDENT_CODE ON
    )
    target_compile_features(reforge_shaper_solver PUBLIC cxx_std_17)
    target_compile_definitions(
        reforge_shaper_solver PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
    )
    if(NOT BUILD_SHARED_LIBS)
        target_compile_definitions(
            reforge_shaper_solver PUBLIC REFORGE_SHAPER_STATIC_DEFINE
        )
    endif()
    target_include_directories(reforge_shaper_solver
        PUBLIC
            "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
            "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
            "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
        PRIVATE
            "${CMAKE_CURRENT_SOURCE_DIR}/src"
            "${REFORGE_SHAPER_CLARABEL_ROOT}/include"
    )
    target_link_libraries(
        reforge_shaper_solver
        PUBLIC ReforgeShaper::trajectory
        PRIVATE reforge_clarabel_c_static
    )
    target_sources(
        reforge_shaper_runtime PRIVATE
            src/control/shaper/residual_alignment.cpp
            src/control/shaper/residual_switch.cpp
            src/control/shaper/windowing.cpp
    )
    target_link_libraries(
        reforge_shaper_runtime PUBLIC
            ReforgeShaper::solver
            ReforgeShaper::trajectory
            ReforgeShaper::window
    )

    add_library(reforge_shaper_backend
        src/control/shaper/backend/native_shaper.cpp
    )
    add_library(ReforgeShaper::backend ALIAS reforge_shaper_backend)
    set_target_properties(reforge_shaper_backend PROPERTIES
        EXPORT_NAME backend
        POSITION_INDEPENDENT_CODE ON
    )
    target_compile_features(reforge_shaper_backend PUBLIC cxx_std_17)
    target_compile_definitions(
        reforge_shaper_backend PRIVATE REFORGE_SHAPER_COMMON_EXPORTS
    )
    if(NOT BUILD_SHARED_LIBS)
        target_compile_definitions(
            reforge_shaper_backend PUBLIC REFORGE_SHAPER_STATIC_DEFINE
        )
    endif()
    target_include_directories(reforge_shaper_backend
        PUBLIC
            "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
            "$<BUILD_INTERFACE:${_reforge_shaper_generated_include_dir}>"
            "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
    )
    target_link_libraries(
        reforge_shaper_backend
        PUBLIC
            ReforgeShaper::common
            ReforgeShaper::dynamics
            ReforgeShaper::model
            ReforgeShaper::runtime
            ReforgeShaper::solver
            ReforgeShaper::trajectory
            ReforgeShaper::window
    )
endif()

if(REFORGE_SHAPER_QUALIFY_CLARABEL)
    add_library(reforge_shaper_clarabel_qualification STATIC
        src/solver/clarabel_qualification_adapter.cpp
    )
    set_target_properties(
        reforge_shaper_clarabel_qualification
        PROPERTIES POSITION_INDEPENDENT_CODE ON
    )
    target_compile_features(
        reforge_shaper_clarabel_qualification PRIVATE cxx_std_17
    )
    target_include_directories(
        reforge_shaper_clarabel_qualification
        PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
    )
    target_link_libraries(
        reforge_shaper_clarabel_qualification
        PUBLIC Eigen3::Eigen
        PRIVATE ReforgeShaper::solver
    )
endif()

if(pybind11_FOUND)
    pybind11_add_module(_native_shaper NO_EXTRAS
        bindings/python/reforge_core/control/native_shaper_module.cpp
        bindings/python/reforge_core/control/bind_base_shaper.cpp
        bindings/python/reforge_core/control/bind_runtime_services.cpp
        bindings/python/reforge_core/control/bind_map_loader.cpp
        bindings/python/reforge_core/control/bind_model_types.cpp
        bindings/python/reforge_core/control/bind_shaper_foundation.cpp
        bindings/python/reforge_core/control/bind_windowed_buffer.cpp
        bindings/python/reforge_core/util/bind_interpolation.cpp
        bindings/python/reforge_core/util/bind_robot_dynamics.cpp
        bindings/python/reforge_core/util/timing/bind_timing.cpp
    )
    target_compile_features(_native_shaper PRIVATE cxx_std_17)
    set_target_properties(
        _native_shaper PROPERTIES INSTALL_RPATH_USE_LINK_PATH ON
    )
    target_include_directories(
        _native_shaper PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/bindings/python"
    )
    target_link_libraries(
        _native_shaper
        PRIVATE
            ReforgeShaper::dynamics
            ReforgeShaper::model
            ReforgeShaper::runtime
            ReforgeShaper::trajectory
            ReforgeShaper::window
    )
    if(TARGET reforge_shaper_solver)
        target_sources(
            _native_shaper PRIVATE
                bindings/python/reforge_core/control/bind_residual.cpp
                bindings/python/reforge_core/control/bind_solver.cpp
        )
        target_compile_definitions(
            _native_shaper PRIVATE REFORGE_SHAPER_SOLVER_AVAILABLE
        )
        target_link_libraries(_native_shaper PRIVATE ReforgeShaper::solver)
    endif()
    if(TARGET reforge_shaper_backend)
        target_sources(
            _native_shaper PRIVATE
                bindings/python/reforge_core/control/bind_native_backend.cpp
        )
        target_compile_definitions(
            _native_shaper PRIVATE REFORGE_SHAPER_BACKEND_AVAILABLE
        )
        target_link_libraries(
            _native_shaper PRIVATE ReforgeShaper::backend
        )
    endif()
    if(REFORGE_SHAPER_QUALIFY_CLARABEL)
        target_compile_definitions(
            _native_shaper PRIVATE REFORGE_SHAPER_CLARABEL_QUALIFICATION
        )
        target_include_directories(
            _native_shaper PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
        )
        target_link_libraries(
            _native_shaper PRIVATE reforge_shaper_clarabel_qualification
        )
    endif()
    install(TARGETS _native_shaper
        LIBRARY DESTINATION reforge_core/control
        RUNTIME DESTINATION reforge_core/control
    )
    set(_reforge_shaper_complete_backend_available "false")
    if(TARGET reforge_shaper_backend)
        set(_reforge_shaper_complete_backend_available "true")
    endif()
    configure_file(
        qualification/native_build_manifest.json.in
        "${CMAKE_CURRENT_BINARY_DIR}/native_build_manifest.json"
        @ONLY
    )
    install(
        FILES
            "${CMAKE_CURRENT_BINARY_DIR}/native_build_manifest.json"
            qualification/THIRD_PARTY_NOTICES.txt
        DESTINATION reforge_core/control/native
    )
endif()

if(BUILD_TESTING)
    find_package(Threads REQUIRED)

    add_executable(
        reforge_shaper_common_tests
        tests/control/shaper/test_foundation.cpp
    )
    target_compile_features(reforge_shaper_common_tests PRIVATE cxx_std_17)
    target_link_libraries(
        reforge_shaper_common_tests PRIVATE ReforgeShaper::common
    )
    add_test(
        NAME reforge_shaper_common_tests
        COMMAND reforge_shaper_common_tests
    )

    add_executable(
        reforge_shaper_common_header_independence
        tests/native/header_independence.cpp
    )
    target_compile_features(
        reforge_shaper_common_header_independence PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_common_header_independence
        PRIVATE
            ReforgeShaper::dynamics
            ReforgeShaper::model
            ReforgeShaper::runtime
            ReforgeShaper::trajectory
            ReforgeShaper::window
    )
    if(TARGET reforge_shaper_backend)
        target_link_libraries(
            reforge_shaper_common_header_independence
            PRIVATE ReforgeShaper::backend
        )
    endif()
    add_test(
        NAME reforge_shaper_common_header_independence
        COMMAND reforge_shaper_common_header_independence
    )

    add_executable(
        reforge_shaper_trajectory_tests
        tests/util/test_trajectory_foundation.cpp
    )
    target_compile_features(
        reforge_shaper_trajectory_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_trajectory_tests
        PRIVATE ReforgeShaper::trajectory
    )
    add_test(
        NAME reforge_shaper_trajectory_tests
        COMMAND reforge_shaper_trajectory_tests
    )

    add_executable(
        reforge_shaper_model_tests
        tests/control/model/test_model_types.cpp
    )
    target_compile_features(
        reforge_shaper_model_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_model_tests PRIVATE ReforgeShaper::model
    )
    add_test(
        NAME reforge_shaper_model_tests
        COMMAND reforge_shaper_model_tests
    )

    add_executable(
        reforge_shaper_map_loader_tests
        tests/control/model/test_map_loader.cpp
    )
    target_compile_features(
        reforge_shaper_map_loader_tests PRIVATE cxx_std_17
    )
    target_compile_definitions(
        reforge_shaper_map_loader_tests
        PRIVATE
            REFORGE_MODEL_FIXTURE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/models"
    )
    target_link_libraries(
        reforge_shaper_map_loader_tests PRIVATE ReforgeShaper::model
    )
    add_test(
        NAME reforge_shaper_map_loader_tests
        COMMAND reforge_shaper_map_loader_tests
    )

    add_executable(
        reforge_shaper_runtime_tests
        tests/control/runtime/test_base_shaper.cpp
    )
    target_compile_features(
        reforge_shaper_runtime_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_runtime_tests PRIVATE ReforgeShaper::runtime
    )
    add_test(
        NAME reforge_shaper_runtime_tests
        COMMAND reforge_shaper_runtime_tests
    )

    add_executable(
        reforge_shaper_runtime_service_tests
        tests/control/runtime/test_runtime_services.cpp
    )
    target_compile_features(
        reforge_shaper_runtime_service_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_runtime_service_tests PRIVATE ReforgeShaper::runtime
    )
    add_test(
        NAME reforge_shaper_runtime_service_tests
        COMMAND reforge_shaper_runtime_service_tests
    )

    add_executable(
        reforge_shaper_window_tests
        tests/control/window/test_windowed_buffer.cpp
    )
    target_compile_features(
        reforge_shaper_window_tests PRIVATE cxx_std_17
    )
    target_link_libraries(
        reforge_shaper_window_tests
        PRIVATE ReforgeShaper::window Threads::Threads
    )
    add_test(
        NAME reforge_shaper_window_tests
        COMMAND reforge_shaper_window_tests
    )

    add_executable(
        reforge_shaper_dynamics_tests
        tests/util/test_robot_dynamics.cpp
    )
    target_compile_features(
        reforge_shaper_dynamics_tests PRIVATE cxx_std_17
    )
    target_compile_definitions(
        reforge_shaper_dynamics_tests
        PRIVATE
            REFORGE_DYNAMICS_FIXTURE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/urdf"
    )
    target_link_libraries(
        reforge_shaper_dynamics_tests
        PRIVATE ReforgeShaper::dynamics Threads::Threads
    )
    add_test(
        NAME reforge_shaper_dynamics_tests
        COMMAND reforge_shaper_dynamics_tests
    )

    if(REFORGE_SHAPER_QUALIFY_CLARABEL)
        add_executable(
            reforge_shaper_clarabel_qualification_tests
            tests/solver/test_clarabel_qualification.cpp
        )
        target_compile_features(
            reforge_shaper_clarabel_qualification_tests PRIVATE cxx_std_17
        )
        target_include_directories(
            reforge_shaper_clarabel_qualification_tests
            PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
        )
        target_link_libraries(
            reforge_shaper_clarabel_qualification_tests
            PRIVATE reforge_shaper_clarabel_qualification
        )
        add_test(
            NAME reforge_shaper_clarabel_qualification_tests
            COMMAND reforge_shaper_clarabel_qualification_tests
        )
    endif()
    if(TARGET reforge_shaper_solver)
        add_executable(
            reforge_shaper_backend_tests
            tests/control/shaper/backend/test_native_shaper.cpp
        )
        target_compile_features(
            reforge_shaper_backend_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_backend_tests PRIVATE ReforgeShaper::backend
        )
        add_test(
            NAME reforge_shaper_backend_tests
            COMMAND reforge_shaper_backend_tests
        )
        add_executable(
            reforge_shaper_backend_phase10d_tests
            tests/control/shaper/backend/test_native_shaper_phase10d.cpp
        )
        target_compile_features(
            reforge_shaper_backend_phase10d_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_backend_phase10d_tests
            PRIVATE ReforgeShaper::backend
        )
        add_test(
            NAME reforge_shaper_backend_phase10d_tests
            COMMAND reforge_shaper_backend_phase10d_tests
        )

        add_executable(
            reforge_shaper_solver_tests
            tests/util/trajopt/test_traj_qp.cpp
        )
        target_compile_features(
            reforge_shaper_solver_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_solver_tests PRIVATE ReforgeShaper::solver
        )
        add_test(
            NAME reforge_shaper_solver_tests
            COMMAND reforge_shaper_solver_tests
        )
        add_executable(
            reforge_shaper_residual_tests
            tests/control/runtime/test_residual.cpp
        )
        target_compile_features(
            reforge_shaper_residual_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_residual_tests
            PRIVATE ReforgeShaper::runtime
        )
        add_test(
            NAME reforge_shaper_residual_tests
            COMMAND reforge_shaper_residual_tests
        )
        add_executable(
            reforge_shaper_windowing_tests
            tests/control/runtime/test_windowing.cpp
        )
        target_compile_features(
            reforge_shaper_windowing_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_windowing_tests PRIVATE ReforgeShaper::runtime
        )
        add_test(
            NAME reforge_shaper_windowing_tests
            COMMAND reforge_shaper_windowing_tests
        )
        add_executable(
            reforge_shaper_task_space_tests
            tests/control/runtime/test_task_space.cpp
        )
        target_compile_features(
            reforge_shaper_task_space_tests PRIVATE cxx_std_17
        )
        target_link_libraries(
            reforge_shaper_task_space_tests PRIVATE ReforgeShaper::runtime
        )
        add_test(
            NAME reforge_shaper_task_space_tests
            COMMAND reforge_shaper_task_space_tests
        )
    endif()
endif()

if(NOT SKBUILD)
    install(
        TARGETS reforge_shaper_common
        EXPORT ReforgeShaperTargets
        ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    )
    install(
        TARGETS reforge_shaper_trajectory
        EXPORT ReforgeShaperTargets
        ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    )
    install(
        TARGETS reforge_shaper_model
        EXPORT ReforgeShaperTargets
        ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    )
    install(
        TARGETS reforge_shaper_dynamics
        EXPORT ReforgeShaperTargets
        ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    )
    install(
        TARGETS reforge_shaper_runtime
        EXPORT ReforgeShaperTargets
        ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    )
    install(
        TARGETS reforge_shaper_window
        EXPORT ReforgeShaperTargets
        ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
        INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
    )
    if(TARGET reforge_shaper_solver)
        install(
            TARGETS reforge_shaper_solver
            EXPORT ReforgeShaperTargets
            ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
            LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
            RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
            INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
        )
        install(
            FILES "${_reforge_clarabel_static_library}"
            DESTINATION "${CMAKE_INSTALL_LIBDIR}"
        )
    endif()
    if(TARGET reforge_shaper_backend)
        install(
            TARGETS reforge_shaper_backend
            EXPORT ReforgeShaperTargets
            ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
            LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
            RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
            INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
        )
    endif()
    install(
        DIRECTORY include/reforge_core
        DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
        FILES_MATCHING PATTERN "*.hpp"
    )
    install(
        FILES
            "${_reforge_shaper_generated_include_dir}/reforge_core/native/common/export.hpp"
        DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/reforge_core/native/common"
    )

    configure_package_config_file(
        cmake/ReforgeShaperConfig.cmake.in
        "${CMAKE_CURRENT_BINARY_DIR}/ReforgeShaperConfig.cmake"
        INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ReforgeShaper"
    )
    write_basic_package_version_file(
        "${CMAKE_CURRENT_BINARY_DIR}/ReforgeShaperConfigVersion.cmake"
        VERSION "${PROJECT_VERSION}"
        COMPATIBILITY SameMajorVersion
    )
    install(
        EXPORT ReforgeShaperTargets
        FILE ReforgeShaperTargets.cmake
        NAMESPACE ReforgeShaper::
        DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ReforgeShaper"
    )
    install(
        FILES
            "${CMAKE_CURRENT_BINARY_DIR}/ReforgeShaperConfig.cmake"
            "${CMAKE_CURRENT_BINARY_DIR}/ReforgeShaperConfigVersion.cmake"
        DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/ReforgeShaper"
    )
endif()
