cmake_minimum_required(VERSION 3.15)
project(LinkerHand-CPP-SDK
    VERSION 1.1.7
    LANGUAGES CXX
    DESCRIPTION "LinkerHand C++ SDK for robotic hand control"
)

if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
    set(LIB_SUBDIR "x86_64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
    set(LIB_SUBDIR "aarch64")
else()
    message(WARNING "Unknown architecture, defaulting to x86_64")
    set(LIB_SUBDIR "x86_64")
endif()

#-----------------------------------------------------------------------------
# LINKER_HAND_CPP_SDK
#-----------------------------------------------------------------------------
find_library(LINKER_HAND_LIB
    NAMES linkerhand_cpp_sdk linkerhand_cpp
    PATHS ${CMAKE_CURRENT_SOURCE_DIR}/lib/${LIB_SUBDIR}
        /usr/local/lib/linkerhand-cpp-sdk/${LIB_SUBDIR}
        /usr/lib/linkerhand-cpp-sdk/${LIB_SUBDIR}
        ${CMAKE_INSTALL_PREFIX}/lib/linkerhand-cpp-sdk/${LIB_SUBDIR}
    NO_DEFAULT_PATH
)

set(LINKER_HAND_INCLUDE_DIR
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    /usr/local/include/linkerhand-cpp-sdk
    /usr/include/linkerhand-cpp-sdk
    ${CMAKE_INSTALL_PREFIX}/include/linkerhand-cpp-sdk
)

if(NOT LINKER_HAND_LIB)
    message(FATAL_ERROR "linkerhand_cpp_sdk library not found!")
endif()

if(NOT LINKER_HAND_INCLUDE_DIR)
    message(FATAL_ERROR "LinkerHand headers not found!")
endif()

message(STATUS "Found linkerhand_cpp_sdk library: ${LINKER_HAND_LIB}")
message(STATUS "Found LinkerHand headers: ${LINKER_HAND_INCLUDE_DIR}")

#-----------------------------------------------------------------------------
# REALMAN_RM_API2
#-----------------------------------------------------------------------------
unset(REALMAN_ARM_LIB CACHE)
unset(REALMAN_ARM_LIB_CANDIDATE CACHE)

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_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_SOURCE_DIR}/third_party/Robotic_Arm
)
list(REMOVE_DUPLICATES REALMAN_ARM_CANDIDATE_ROOTS)

foreach(CANDIDATE_ROOT ${REALMAN_ARM_CANDIDATE_ROOTS})
    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(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})
    endif()
endforeach()

if(REALMAN_ARM_LIB AND EXISTS ${REALMAN_ARM_INCLUDE_DIR}/rm_interface.h)
    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)
    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}
    )
    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; RealMan demos will not be built.")
endif()

#-----------------------------------------------------------------------------
# INCLUDE_DIRECTORIES
#-----------------------------------------------------------------------------
include_directories(
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${LINKER_HAND_INCLUDE_DIR}
)

if(REALMAN_ARM_INCLUDE_DIR)
    include_directories(${REALMAN_ARM_INCLUDE_DIR})
endif()

option(REALMAN_BUILD_HARDWARE_TESTS "Build independent RealMan hardware module test executables" ON)
option(REALMAN_REGISTER_HARDWARE_TESTS "Register manual RealMan hardware programs with CTest" OFF)

if(REALMAN_REGISTER_HARDWARE_TESTS)
    enable_testing()
endif()

#-----------------------------------------------------------------------------
# EXECUTABLE
#-----------------------------------------------------------------------------
add_executable(my_project src/main.cpp)
target_link_libraries(my_project ${LINKER_HAND_LIB} pthread)

add_executable(toolset_example examples/toolset_example.cpp)
target_link_libraries(toolset_example ${LINKER_HAND_LIB} pthread)

add_executable(action_group_show_l10 examples/action_group_show_l10.cpp)
target_link_libraries(action_group_show_l10 ${LINKER_HAND_LIB} pthread)

add_executable(o6_async_reader examples/o6_async_reader.cpp)
target_link_libraries(o6_async_reader ${LINKER_HAND_LIB} pthread)

if(TARGET realman_api_cpp)
    function(add_realman_demo TARGET_NAME SOURCE_FILE)
        add_executable(${TARGET_NAME} ${SOURCE_FILE})
        target_compile_features(${TARGET_NAME} PRIVATE cxx_std_17)
        target_include_directories(${TARGET_NAME} PRIVATE ${REALMAN_ARM_INCLUDE_DIR})
        target_compile_definitions(${TARGET_NAME} PRIVATE LINKERHAND_PROJECT_ROOT="${CMAKE_CURRENT_SOURCE_DIR}")
        target_link_libraries(${TARGET_NAME} PRIVATE realman_api_cpp pthread)
        set_target_properties(${TARGET_NAME} PROPERTIES
            BUILD_RPATH ${REALMAN_ARM_LIB_DIR}
            INSTALL_RPATH ${REALMAN_ARM_LIB_DIR}
        )
    endfunction()

    add_realman_demo(realman_minimal_demo examples/realman_minimal_demo.cpp)
    add_realman_demo(realman_slow_motion_demo examples/realman_slow_motion_demo.cpp)
    add_realman_demo(realman_joint_pose_config_demo examples/realman_joint_pose_config_demo.cpp)
    add_realman_demo(realman_metrics_test examples/realman_metrics_test.cpp)
    add_realman_demo(realman_trajectory_record examples/realman_trajectory_record.cpp)
    add_realman_demo(realman_trajectory_replay examples/realman_trajectory_replay.cpp)

    if(REALMAN_BUILD_HARDWARE_TESTS)
        function(add_realman_hardware_test TARGET_NAME SOURCE_FILE TEST_LABEL TEST_TIMEOUT)
            add_realman_demo(${TARGET_NAME} ${SOURCE_FILE})
            target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/realman_hardware)

            if(REALMAN_REGISTER_HARDWARE_TESTS)
                add_test(NAME ${TARGET_NAME} COMMAND $<TARGET_FILE:${TARGET_NAME}>)
                set_tests_properties(${TARGET_NAME} PROPERTIES
                    LABELS "realman;hardware;${TEST_LABEL}"
                    RESOURCE_LOCK realman_robot
                    TIMEOUT ${TEST_TIMEOUT}
                )
            endif()
        endfunction()

        add_realman_hardware_test(realman_hw_test_connection
            tests/realman_hardware/test_realman_connection.cpp readonly 20)
        add_realman_hardware_test(realman_hw_test_joint_telemetry
            tests/realman_hardware/test_realman_joint_telemetry.cpp readonly 20)
        add_realman_hardware_test(realman_hw_test_force_sensor
            tests/realman_hardware/test_realman_force_sensor.cpp readonly 30)
        add_realman_hardware_test(realman_hw_test_controller_state
            tests/realman_hardware/test_realman_controller_state.cpp readonly 20)
        add_realman_hardware_test(realman_hw_test_motion_tracking
            tests/realman_hardware/test_realman_motion_tracking.cpp motion 60)
        add_realman_hardware_test(rm75_acceptance_preflight
            tests/rm75_acceptance/test_rm75_acceptance_preflight.cpp "rm75;acceptance;readonly" 30)
        add_realman_hardware_test(rm75_acceptance_communication
            tests/rm75_acceptance/test_rm75_acceptance_communication.cpp "rm75;acceptance;readonly" 60)

        add_realman_demo(realman_hw_test_force_retreat
            tests/realman_hardware/test_realman_force_retreat.cpp)
        target_include_directories(realman_hw_test_force_retreat PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/realman_hardware)

        add_realman_demo(realman_hw_test_force_retreat_hold
            tests/realman_hardware/test_realman_force_retreat_hold.cpp)
        target_include_directories(realman_hw_test_force_retreat_hold PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/realman_hardware)

        if(REALMAN_REGISTER_HARDWARE_TESTS)
            add_test(NAME realman_hw_test_force_retreat COMMAND $<TARGET_FILE:realman_hw_test_force_retreat>)
            set_tests_properties(realman_hw_test_force_retreat PROPERTIES
                LABELS "hardware;manual;force_retreat"
                RESOURCE_LOCK realman_robot
                TIMEOUT 90
                SKIP_RETURN_CODE 77
            )
        endif()

        add_custom_target(realman_hardware_readonly_tests
            DEPENDS
                realman_hw_test_connection
                realman_hw_test_joint_telemetry
                realman_hw_test_force_sensor
                realman_hw_test_controller_state
        )

        add_custom_target(rm75_acceptance_readonly_tests
            DEPENDS
                rm75_acceptance_preflight
                rm75_acceptance_communication
        )

        add_custom_target(realman_hardware_tests
            DEPENDS
                realman_hw_test_connection
                realman_hw_test_joint_telemetry
                realman_hw_test_force_sensor
                realman_hw_test_controller_state
                realman_hw_test_motion_tracking
        )

        add_custom_target(realman_hardware_manual_tests
            DEPENDS
                realman_hw_test_force_retreat
                realman_hw_test_force_retreat_hold
        )

        if(REALMAN_REGISTER_HARDWARE_TESTS)
            add_custom_target(run_realman_hardware_readonly_tests
                COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --label-regex "readonly"
                DEPENDS realman_hardware_readonly_tests
                WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
            )

            add_custom_target(run_rm75_acceptance_readonly_tests
                COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --label-regex "rm75"
                DEPENDS rm75_acceptance_readonly_tests
                WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
            )

            add_custom_target(run_realman_hardware_tests
                COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --label-regex "realman"
                DEPENDS realman_hardware_tests
                WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
            )

            add_custom_target(run_realman_hardware_manual_tests
                COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -R realman_hw_test_force_retreat
                DEPENDS realman_hardware_manual_tests
                WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
            )
        endif()
    endif()
endif()

#-----------------------------------------------------------------------------
# INSTALL TARGETS
#-----------------------------------------------------------------------------
# 安装头文件
install(DIRECTORY include/
    DESTINATION include/linkerhand-cpp-sdk
    FILES_MATCHING PATTERN "*.h"
)

# 安装库文件
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/lib/${LIB_SUBDIR})
    install(DIRECTORY lib/${LIB_SUBDIR}/
        DESTINATION lib/linkerhand-cpp-sdk/${LIB_SUBDIR}
        FILES_MATCHING PATTERN "*.so*"
    )
endif()

#-----------------------------------------------------------------------------
# PACKAGE CONFIGURATION
#-----------------------------------------------------------------------------
include(CMakePackageConfigHelpers)

# 创建包配置文件
write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/LinkerHandCPPSDKConfigVersion.cmake"
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY AnyNewerVersion
)

# 设置包配置变量（用于 configure_file）
set(PACKAGE_INCLUDE_INSTALL_DIR "include/linkerhand-cpp-sdk")
set(PACKAGE_LIB_INSTALL_DIR "lib/linkerhand-cpp-sdk/${LIB_SUBDIR}")

# 创建包配置文件模板
configure_file(
    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/LinkerHandCPPSDKConfig.cmake.in"
    "${CMAKE_CURRENT_BINARY_DIR}/LinkerHandCPPSDKConfig.cmake"
    @ONLY
)

# 安装包配置文件
install(FILES
    "${CMAKE_CURRENT_BINARY_DIR}/LinkerHandCPPSDKConfig.cmake"
    "${CMAKE_CURRENT_BINARY_DIR}/LinkerHandCPPSDKConfigVersion.cmake"
    DESTINATION lib/cmake/LinkerHandCPPSDK
)

#-----------------------------------------------------------------------------
# TESTING
#-----------------------------------------------------------------------------
# 添加选项以启用/禁用测试
option(BUILD_TESTING "Build the testing tree" ON)

# 如果启用测试，包含测试子目录
if(BUILD_TESTING)
    add_subdirectory(tests)
endif()
