cmake_minimum_required(VERSION 3.16)

# RM 是独立睿尔曼 RM75 机械臂子工程，和仓库根目录的 LinkerHand O6 灵巧手工程分开构建。
# 当前支持边界只覆盖“睿尔曼机械臂 + 灵心巧手 O6”目标程序，不做多机械臂品牌泛化。
project(RMJsonControl
    VERSION 0.1.0
    LANGUAGES CXX
    DESCRIPTION "RM75 control, drag-teach capture, validation, and raw trajectory storage"
)

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

option(RM_BUILD_TESTING "Build RM unit and no-hardware integration tests" ON)

if(MSVC)
    add_compile_options(/W4 /permissive-)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(OpenSSL REQUIRED COMPONENTS Crypto)
find_package(Threads REQUIRED)

# core 库只包含 JSON 解析和命令执行流程，不链接任何真实硬件 SDK。
# 这样 parser/runner 的单元测试可以在没有机械臂的机器上运行。
add_library(rm_json_core
    src/config/simple_json.cpp
    src/config/json_command.cpp
    src/core/json_command_runner.cpp
)

target_include_directories(rm_json_core
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)

target_compile_features(rm_json_core PUBLIC cxx_std_17)

add_library(rm_trajectory_core
    src/modules/trajectory/trajectory_file_store.cpp
    src/modules/trajectory/sha256_file_checksum.cpp
    src/modules/trajectory/raw_trajectory_store.cpp
    src/modules/trajectory/drag_trajectory_validator.cpp
    src/modules/trajectory/vendor_drag_trajectory_parser.cpp
    src/modules/trajectory/reversed_vendor_trajectory_store.cpp
    src/modules/trajectory/trajectory_playback_report_store.cpp
    src/core/drag_teach_recorder.cpp
)

target_include_directories(rm_trajectory_core
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)

target_compile_features(rm_trajectory_core PUBLIC cxx_std_17)
target_link_libraries(rm_trajectory_core
    PUBLIC
        OpenSSL::Crypto
)
target_compile_definitions(rm_trajectory_core
    PRIVATE
        RM_RUNTIME_DATA_ROOT="${CMAKE_CURRENT_BINARY_DIR}/runtime_data"
)

add_library(rm_drag_teach_core
    src/core/hardware_execution_gate.cpp
    src/core/drag_teach_service.cpp
)

target_include_directories(rm_drag_teach_core
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)

target_compile_features(rm_drag_teach_core PUBLIC cxx_std_17)
target_link_libraries(rm_drag_teach_core
    PUBLIC
        rm_trajectory_core
        Threads::Threads
)

add_library(rm_linker_hand_adapter
    ${CMAKE_CURRENT_LIST_DIR}/../src/linker_hand/linker_hand_adapter.cpp
)
target_include_directories(rm_linker_hand_adapter
    PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}/../include
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_compile_features(rm_linker_hand_adapter PUBLIC cxx_std_17)

# RM_API2 的 Modbus byte 数组编解码保持为无硬件纯转换库，使边界值测试无需连接 SDK 或设备。
add_library(rm_realman_modbus_codec
    ${CMAKE_CURRENT_LIST_DIR}/../src/realman/modbus_register_codec.cpp
)
target_include_directories(rm_realman_modbus_codec
    PUBLIC
        ${CMAKE_CURRENT_LIST_DIR}/../include
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_compile_features(rm_realman_modbus_codec PUBLIC cxx_std_17)

add_library(rm_o6_bus_scanner
    src/core/o6_bus_scanner.cpp
)
target_include_directories(rm_o6_bus_scanner
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_compile_features(rm_o6_bus_scanner PUBLIC cxx_std_17)

add_library(rm_arm_o6_task_core
    src/core/arm_o6_trajectory_task.cpp
)
target_include_directories(rm_arm_o6_task_core
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)
target_compile_features(rm_arm_o6_task_core PUBLIC cxx_std_17)
target_link_libraries(rm_arm_o6_task_core
    PUBLIC
        rm_drag_teach_core
        rm_linker_hand_adapter
        Threads::Threads
)

add_library(rm_cli_config
    src/config/json_runner_cli_config.cpp
    src/config/drag_teach_cli_config.cpp
    src/config/drag_teach_safety_config.cpp
    src/config/trajectory_convert_cli_config.cpp
    src/config/trajectory_o6_task_cli_config.cpp
)

target_include_directories(rm_cli_config
    PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)

target_compile_features(rm_cli_config PUBLIC cxx_std_17)
target_compile_definitions(rm_cli_config
    PRIVATE
        RM_PROJECT_ROOT="${CMAKE_CURRENT_SOURCE_DIR}"
)
target_link_libraries(rm_cli_config
    PUBLIC
        rm_json_core
        rm_trajectory_core
)

add_executable(rm_trajectory_convert
    src/trajectory_convert_main.cpp
)
target_link_libraries(rm_trajectory_convert
    PRIVATE
        rm_cli_config
        rm_trajectory_core
)

set(REALMAN_RM_API2_ROOT "" CACHE PATH "Path to the RealMan RM_API2 C++ SDK root")
set(REALMAN_ARM_ROOT "")
set(REALMAN_ARM_INCLUDE_DIR "")
set(REALMAN_ARM_LIB_DIR "")
set(REALMAN_ARM_LIB "")
set(REALMAN_ARM_DLL "")

set(REALMAN_ARM_CANDIDATE_ROOTS)
if(REALMAN_RM_API2_ROOT)
    list(APPEND REALMAN_ARM_CANDIDATE_ROOTS ${REALMAN_RM_API2_ROOT})
endif()
list(APPEND REALMAN_ARM_CANDIDATE_ROOTS
    ${CMAKE_CURRENT_LIST_DIR}/../third_party/Robotic_Arm
)
list(REMOVE_DUPLICATES REALMAN_ARM_CANDIDATE_ROOTS)

foreach(CANDIDATE_ROOT ${REALMAN_ARM_CANDIDATE_ROOTS})
    # 兼容本机 SDK 路径和仓库 third_party 备份路径。
    if(REALMAN_ARM_LIB)
        break()
    endif()

    set(CANDIDATE_INCLUDE_DIR ${CANDIDATE_ROOT}/include)
    if(NOT EXISTS ${CANDIDATE_INCLUDE_DIR}/rm_interface.h)
        continue()
    endif()

    set(CANDIDATE_LIB_DIRS)
    if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 8)
        file(GLOB CANDIDATE_RELEASE_LIB_DIRS CONFIGURE_DEPENDS
            ${CANDIDATE_ROOT}/windows/win_x64_c++_vv*
        )
        file(GLOB CANDIDATE_DEBUG_LIB_DIRS CONFIGURE_DEPENDS
            ${CANDIDATE_ROOT}/windows/win_x64_c++_debug*
        )
        list(APPEND CANDIDATE_LIB_DIRS
            ${CANDIDATE_RELEASE_LIB_DIRS}
            ${CANDIDATE_DEBUG_LIB_DIRS}
        )
    elseif(WIN32)
        file(GLOB CANDIDATE_RELEASE_LIB_DIRS CONFIGURE_DEPENDS
            ${CANDIDATE_ROOT}/windows/win_x86_c++_vv*
        )
        file(GLOB CANDIDATE_DEBUG_LIB_DIRS CONFIGURE_DEPENDS
            ${CANDIDATE_ROOT}/windows/win_x86_c++_debug*
        )
        list(APPEND CANDIDATE_LIB_DIRS
            ${CANDIDATE_RELEASE_LIB_DIRS}
            ${CANDIDATE_DEBUG_LIB_DIRS}
        )
    elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
        file(GLOB CANDIDATE_RELEASE_LIB_DIRS CONFIGURE_DEPENDS ${CANDIDATE_ROOT}/linux/linux_x86_c++_vv*)
        file(GLOB CANDIDATE_DEBUG_LIB_DIRS CONFIGURE_DEPENDS ${CANDIDATE_ROOT}/linux/linux_x86_c++_debug*)
        list(APPEND CANDIDATE_LIB_DIRS
            ${CANDIDATE_RELEASE_LIB_DIRS}
            ${CANDIDATE_DEBUG_LIB_DIRS}
            ${CANDIDATE_ROOT}/linux/lib
        )
    elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
        file(GLOB CANDIDATE_RELEASE_LIB_DIRS CONFIGURE_DEPENDS ${CANDIDATE_ROOT}/linux/linux_arm64_c++_vv*)
        file(GLOB CANDIDATE_DEBUG_LIB_DIRS CONFIGURE_DEPENDS ${CANDIDATE_ROOT}/linux/linux_arm64_c++_debug*)
        list(APPEND CANDIDATE_LIB_DIRS
            ${CANDIDATE_RELEASE_LIB_DIRS}
            ${CANDIDATE_DEBUG_LIB_DIRS}
            ${CANDIDATE_ROOT}/linux/lib
        )
    else()
        file(GLOB CANDIDATE_LIB_DIRS CONFIGURE_DEPENDS
            ${CANDIDATE_ROOT}/linux/*c++*
            ${CANDIDATE_ROOT}/linux/lib
        )
    endif()

    unset(REALMAN_ARM_LIB_CANDIDATE CACHE)
    find_library(REALMAN_ARM_LIB_CANDIDATE
        NAMES api_cpp libapi_cpp
        PATHS ${CANDIDATE_LIB_DIRS}
        NO_DEFAULT_PATH
    )

    if(REALMAN_ARM_LIB_CANDIDATE)
        set(REALMAN_ARM_ROOT ${CANDIDATE_ROOT})
        set(REALMAN_ARM_INCLUDE_DIR ${CANDIDATE_INCLUDE_DIR})
        get_filename_component(REALMAN_ARM_LIB_DIR ${REALMAN_ARM_LIB_CANDIDATE} DIRECTORY)
        set(REALMAN_ARM_LIB ${REALMAN_ARM_LIB_CANDIDATE})
        if(WIN32)
            unset(REALMAN_ARM_DLL_CANDIDATE CACHE)
            find_file(REALMAN_ARM_DLL_CANDIDATE
                NAMES api_cpp.dll
                PATHS ${REALMAN_ARM_LIB_DIR}
                NO_DEFAULT_PATH
            )
            set(REALMAN_ARM_DLL ${REALMAN_ARM_DLL_CANDIDATE})
        endif()
    endif()
endforeach()

if(REALMAN_ARM_LIB AND EXISTS ${REALMAN_ARM_INCLUDE_DIR}/rm_interface.h AND
   (NOT WIN32 OR REALMAN_ARM_DLL))
    # 不同版本 RM_API2 的销毁函数可能拼写不同：
    # 有的版本是 rm_destroy，有的版本是 rm_destory。
    # 这里在配置阶段读取头文件，给 C++ 代码注入对应宏。
    file(READ ${REALMAN_ARM_INCLUDE_DIR}/rm_interface.h REALMAN_INTERFACE_HEADER)
    if(REALMAN_INTERFACE_HEADER MATCHES "rm_destroy")
        set(REALMAN_DESTROY_COMPILE_DEFINITION REALMAN_RM_API_HAS_RM_DESTROY=1)
    else()
        set(REALMAN_DESTROY_COMPILE_DEFINITION REALMAN_RM_API_HAS_RM_DESTORY=1)
    endif()

    add_library(realman_api_cpp SHARED IMPORTED GLOBAL)
    if(WIN32)
        set_target_properties(realman_api_cpp PROPERTIES
            IMPORTED_IMPLIB ${REALMAN_ARM_LIB}
            IMPORTED_LOCATION ${REALMAN_ARM_DLL}
            INTERFACE_INCLUDE_DIRECTORIES ${REALMAN_ARM_INCLUDE_DIR}
            INTERFACE_COMPILE_DEFINITIONS ${REALMAN_DESTROY_COMPILE_DEFINITION}
        )
    else()
        set_target_properties(realman_api_cpp PROPERTIES
            IMPORTED_LOCATION ${REALMAN_ARM_LIB}
            INTERFACE_INCLUDE_DIRECTORIES ${REALMAN_ARM_INCLUDE_DIR}
            INTERFACE_COMPILE_DEFINITIONS ${REALMAN_DESTROY_COMPILE_DEFINITION}
        )
    endif()

    add_library(rm_realman_driver
        ${CMAKE_CURRENT_LIST_DIR}/../src/realman/realman_arm_adapter.cpp
    )
    target_include_directories(rm_realman_driver
        PUBLIC
            ${CMAKE_CURRENT_LIST_DIR}/../include
            ${CMAKE_CURRENT_SOURCE_DIR}/include
    )
    target_link_libraries(rm_realman_driver
        PUBLIC
            realman_api_cpp
        PRIVATE
            rm_realman_modbus_codec
            Threads::Threads
    )

    add_executable(rm_json_runner
        src/main.cpp
    )
    target_link_libraries(rm_json_runner
        PRIVATE
            rm_cli_config
            rm_json_core
            rm_realman_driver
    )
    set_target_properties(rm_json_runner PROPERTIES
        BUILD_RPATH ${REALMAN_ARM_LIB_DIR}
        INSTALL_RPATH ${REALMAN_ARM_LIB_DIR}
    )

    add_executable(rm_drag_teach_record
        src/drag_teach_record_main.cpp
    )
    target_link_libraries(rm_drag_teach_record
        PRIVATE
            rm_cli_config
            rm_drag_teach_core
            rm_realman_driver
    )
    set_target_properties(rm_drag_teach_record PROPERTIES
        BUILD_RPATH ${REALMAN_ARM_LIB_DIR}
        INSTALL_RPATH ${REALMAN_ARM_LIB_DIR}
    )
    if(WIN32)
        add_custom_command(TARGET rm_drag_teach_record POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy_if_different
                ${REALMAN_ARM_DLL}
                $<TARGET_FILE_DIR:rm_drag_teach_record>
        )
    endif()

    add_executable(rm_trajectory_o6_grasp
        src/trajectory_o6_grasp_main.cpp
    )
    target_link_libraries(rm_trajectory_o6_grasp
        PRIVATE
            rm_cli_config
            rm_arm_o6_task_core
            rm_realman_driver
            rm_linker_hand_adapter
    )
    set_target_properties(rm_trajectory_o6_grasp PROPERTIES
        BUILD_RPATH ${REALMAN_ARM_LIB_DIR}
        INSTALL_RPATH ${REALMAN_ARM_LIB_DIR}
    )
    if(WIN32)
        add_custom_command(TARGET rm_trajectory_o6_grasp POST_BUILD
            COMMAND ${CMAKE_COMMAND} -E copy_if_different
                ${REALMAN_ARM_DLL}
                $<TARGET_FILE_DIR:rm_trajectory_o6_grasp>
        )
    endif()

    add_executable(rm_o6_bus_probe
        src/o6_bus_probe_main.cpp
    )
    target_link_libraries(rm_o6_bus_probe
        PRIVATE
            rm_o6_bus_scanner
            rm_realman_driver
            rm_linker_hand_adapter
    )
    set_target_properties(rm_o6_bus_probe PROPERTIES
        BUILD_RPATH ${REALMAN_ARM_LIB_DIR}
        INSTALL_RPATH ${REALMAN_ARM_LIB_DIR}
    )

    add_executable(rm_o6_grip_demo
        src/o6_grip_demo_main.cpp
    )
    target_link_libraries(rm_o6_grip_demo
        PRIVATE
            rm_realman_driver
            rm_linker_hand_adapter
    )
    set_target_properties(rm_o6_grip_demo PROPERTIES
        BUILD_RPATH ${REALMAN_ARM_LIB_DIR}
        INSTALL_RPATH ${REALMAN_ARM_LIB_DIR}
    )

    message(STATUS "Found RealMan RM_API2 root: ${REALMAN_ARM_ROOT}")
    message(STATUS "Found RealMan RM_API2 library: ${REALMAN_ARM_LIB}")
    message(STATUS "Found RealMan RM_API2 headers: ${REALMAN_ARM_INCLUDE_DIR}")
else()
    message(WARNING "RealMan RM_API2 library or headers not found; hardware executables will not be built.")
endif()

if(RM_BUILD_TESTING)
    # 当前测试覆盖解析、轨迹、存储、安全门控和服务状态机，全部禁止访问真实机械臂。
    enable_testing()
    add_executable(rm_json_command_tests
        tests/test_json_command_parser.cpp
    )
    target_link_libraries(rm_json_command_tests
        PRIVATE
            rm_json_core
    )
    add_test(NAME rm_json_command_tests COMMAND rm_json_command_tests)

    add_executable(rm_trajectory_file_store_tests
        tests/test_trajectory_file_store.cpp
    )
    target_link_libraries(rm_trajectory_file_store_tests
        PRIVATE
            rm_trajectory_core
    )
    target_compile_definitions(rm_trajectory_file_store_tests
        PRIVATE
            RM_TEST_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}/test_outputs"
    )
    add_test(NAME rm_trajectory_file_store_tests COMMAND rm_trajectory_file_store_tests)

    add_executable(rm_cli_config_tests
        tests/test_cli_config.cpp
    )
    target_link_libraries(rm_cli_config_tests
        PRIVATE
            rm_cli_config
    )
    add_test(NAME rm_cli_config_tests COMMAND rm_cli_config_tests)

    add_executable(rm_drag_teach_safety_config_tests
        tests/test_drag_teach_safety_config.cpp
    )
    target_link_libraries(rm_drag_teach_safety_config_tests
        PRIVATE
            rm_cli_config
    )
    target_compile_definitions(rm_drag_teach_safety_config_tests
        PRIVATE
            RM_TEST_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}/test_outputs"
    )
    add_test(NAME rm_drag_teach_safety_config_tests
        COMMAND rm_drag_teach_safety_config_tests)

    add_executable(rm_hardware_execution_gate_tests
        tests/test_hardware_execution_gate.cpp
    )
    target_link_libraries(rm_hardware_execution_gate_tests
        PRIVATE
            rm_drag_teach_core
    )
    add_test(NAME rm_hardware_execution_gate_tests
        COMMAND rm_hardware_execution_gate_tests)

    add_executable(rm_drag_trajectory_validator_tests
        tests/test_drag_trajectory_validator.cpp
    )
    target_link_libraries(rm_drag_trajectory_validator_tests
        PRIVATE
            rm_trajectory_core
    )
    add_test(NAME rm_drag_trajectory_validator_tests
        COMMAND rm_drag_trajectory_validator_tests)

    add_executable(rm_raw_trajectory_store_tests
        tests/test_raw_trajectory_store.cpp
    )
    target_link_libraries(rm_raw_trajectory_store_tests
        PRIVATE
            rm_json_core
            rm_trajectory_core
    )
    target_compile_definitions(rm_raw_trajectory_store_tests
        PRIVATE
            RM_TEST_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}/test_outputs"
    )
    add_test(NAME rm_raw_trajectory_store_tests
        COMMAND rm_raw_trajectory_store_tests)

    add_executable(rm_drag_teach_service_tests
        tests/test_drag_teach_service.cpp
    )
    target_link_libraries(rm_drag_teach_service_tests
        PRIVATE
            rm_drag_teach_core
    )
    target_compile_definitions(rm_drag_teach_service_tests
        PRIVATE
            RM_TEST_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}/test_outputs"
    )
    add_test(NAME rm_drag_teach_service_tests
        COMMAND rm_drag_teach_service_tests)

    add_executable(rm_vendor_drag_trajectory_parser_tests
        tests/test_vendor_drag_trajectory_parser.cpp
    )
    target_link_libraries(rm_vendor_drag_trajectory_parser_tests
        PRIVATE
            rm_trajectory_core
    )
    target_compile_definitions(rm_vendor_drag_trajectory_parser_tests
        PRIVATE
            RM_TEST_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}/test_outputs"
    )
    add_test(NAME rm_vendor_drag_trajectory_parser_tests
        COMMAND rm_vendor_drag_trajectory_parser_tests)

    add_executable(rm_reversed_vendor_trajectory_store_tests
        tests/test_reversed_vendor_trajectory_store.cpp
    )
    target_link_libraries(rm_reversed_vendor_trajectory_store_tests
        PRIVATE
            rm_trajectory_core
    )
    target_compile_definitions(rm_reversed_vendor_trajectory_store_tests
        PRIVATE
            RM_TEST_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}/test_outputs"
    )
    add_test(NAME rm_reversed_vendor_trajectory_store_tests
        COMMAND rm_reversed_vendor_trajectory_store_tests)

    add_executable(rm_trajectory_o6_task_cli_config_tests
        tests/test_trajectory_o6_task_cli_config.cpp
    )
    target_link_libraries(rm_trajectory_o6_task_cli_config_tests
        PRIVATE
            rm_cli_config
    )
    add_test(NAME rm_trajectory_o6_task_cli_config_tests
        COMMAND rm_trajectory_o6_task_cli_config_tests)

    add_executable(rm_linker_hand_adapter_tests
        tests/test_linker_hand_adapter.cpp
    )
    target_link_libraries(rm_linker_hand_adapter_tests
        PRIVATE
            rm_linker_hand_adapter
    )
    add_test(NAME rm_linker_hand_adapter_tests
        COMMAND rm_linker_hand_adapter_tests)

    add_executable(rm_realman_modbus_codec_tests
        tests/test_modbus_register_codec.cpp
    )
    target_link_libraries(rm_realman_modbus_codec_tests
        PRIVATE
            rm_realman_modbus_codec
    )
    add_test(NAME rm_realman_modbus_codec_tests
        COMMAND rm_realman_modbus_codec_tests)

    add_executable(rm_trajectory_playback_report_store_tests
        tests/test_trajectory_playback_report_store.cpp
    )
    target_link_libraries(rm_trajectory_playback_report_store_tests
        PRIVATE
            rm_trajectory_core
    )
    target_compile_definitions(rm_trajectory_playback_report_store_tests
        PRIVATE
            RM_TEST_OUTPUT_DIR="${CMAKE_CURRENT_BINARY_DIR}/test_outputs"
    )
    add_test(NAME rm_trajectory_playback_report_store_tests
        COMMAND rm_trajectory_playback_report_store_tests)

    add_executable(rm_arm_o6_trajectory_task_tests
        tests/test_arm_o6_trajectory_task.cpp
    )
    target_link_libraries(rm_arm_o6_trajectory_task_tests
        PRIVATE
            rm_arm_o6_task_core
    )
    add_test(NAME rm_arm_o6_trajectory_task_tests
        COMMAND rm_arm_o6_trajectory_task_tests)

    add_executable(rm_o6_bus_scanner_tests
        tests/test_o6_bus_scanner.cpp
    )
    target_link_libraries(rm_o6_bus_scanner_tests
        PRIVATE
            rm_o6_bus_scanner
    )
    add_test(NAME rm_o6_bus_scanner_tests
        COMMAND rm_o6_bus_scanner_tests)
endif()
