cmake_minimum_required(VERSION 3.0.2)
project(mission_adapters VERSION 0.2.0 LANGUAGES CXX)

# ========================================================
# Build mode detection
# ========================================================
if(DEFINED CATKIN_DEVEL_PREFIX OR DEFINED CATKIN_TOPLEVEL)
  set(BUILDING_WITH_CATKIN TRUE)
  message(STATUS "Building mission_adapters with Catkin")
else()
  set(BUILDING_WITH_CATKIN FALSE)
  message(STATUS "Building mission_adapters with Standalone CMake")
endif()


# ========================================================
# C++ Standard
# ========================================================
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)


# ========================================================
# Common dependencies
#
# Chỉ những thứ code thật sự dùng: message contract (robot_protocol_msgs / robot_geometry_msgs),
# tiện ích ROS-like (robot_cpp / robot_time) và Boost header (boost::shared_ptr trong message).
# PCL / OpenMP / Eigen của bản trước là di sản copy từ template planner, gói này không dùng.
# ========================================================
# Boost.DLL cần system + filesystem để nạp plugin theo library_path.
find_package(Boost REQUIRED COMPONENTS system filesystem)
find_package(Threads REQUIRED)
find_package(yaml-cpp REQUIRED)


# ========================================================
# Standalone configuration
# ========================================================
if(NOT BUILDING_WITH_CATKIN)

  set(CMAKE_POSITION_INDEPENDENT_CODE ON)

  set(CMAKE_BUILD_RPATH_USE_ORIGIN TRUE)
  set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
  set(CMAKE_BUILD_RPATH "${CMAKE_BINARY_DIR}")

  # Test/<pkg> -> src/AMR_T800/pnkx_nav_core/src
  set(PNKX_NAV_CORE_SRC_DIR
    "${CMAKE_CURRENT_SOURCE_DIR}/../../pnkx_nav_core/src"
  )

  # Test/<pkg> -> T800_ws/devel/lib
  set(WORKSPACE_DEVEL_LIB_DIR
    "${CMAKE_CURRENT_SOURCE_DIR}/../../../../devel/lib"
  )

  file(GLOB STANDALONE_PACKAGE_INCLUDE_DIRS
    LIST_DIRECTORIES true

    ${PNKX_NAV_CORE_SRC_DIR}/*/include
    ${PNKX_NAV_CORE_SRC_DIR}/*/*/include
    ${PNKX_NAV_CORE_SRC_DIR}/*/*/*/include
    ${PNKX_NAV_CORE_SRC_DIR}/*/*/*/*/include
  )

  set(STANDALONE_INCLUDE_DIRS
    ${STANDALONE_PACKAGE_INCLUDE_DIRS}

    /usr/local/include
  )

  set(PACKAGES_DIR
    robot_cpp
    robot_time
    robot_xmlrpcpp
  )

  if(EXISTS ${WORKSPACE_DEVEL_LIB_DIR})
    link_directories(${WORKSPACE_DEVEL_LIB_DIR})
  endif()

  link_directories(/usr/local/lib)

# ========================================================
# Catkin configuration
# ========================================================
else()

  find_package(catkin REQUIRED COMPONENTS
    robot_cpp
    robot_time
    robot_geometry_msgs
    robot_protocol_msgs
    robot_std_msgs
  )

  catkin_package(
    INCLUDE_DIRS
      include

    LIBRARIES
      mission_adapters

    CATKIN_DEPENDS
      robot_cpp
      robot_time
      robot_geometry_msgs
      robot_protocol_msgs
      robot_std_msgs

    DEPENDS
      Boost
  )

  include_directories(include)

  # SYSTEM: header của dependency (robot_cpp/console.h khai hàng chục hằng màu không dùng) sẽ ngập
  # warning dưới -Wall -Wextra và che mất warning của chính gói này.
  include_directories(SYSTEM
    ${catkin_INCLUDE_DIRS}
    ${Boost_INCLUDE_DIRS}
  )

endif()


# ========================================================
# Core library
# ========================================================
add_library(mission_adapters SHARED
  src/types.cpp
  src/event.cpp
  src/mission_manager.cpp
  src/mission_executor.cpp
  src/event_processor.cpp
  src/mission_config.cpp
  src/plugin_registry.cpp
)

# Gói này KHÔNG kế thừa cờ -w của pnkx_nav_core: warning ở đây phải nhìn thấy được.
target_compile_options(mission_adapters PRIVATE -Wall -Wextra)

target_include_directories(mission_adapters
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)


# ========================================================
# Linking
# ========================================================
if(BUILDING_WITH_CATKIN)

  add_dependencies(mission_adapters
    ${${PROJECT_NAME}_EXPORTED_TARGETS}
    ${catkin_EXPORTED_TARGETS}
  )

  target_link_libraries(mission_adapters
    PUBLIC
      ${catkin_LIBRARIES}

    PRIVATE
      Boost::boost
      Boost::system
      Boost::filesystem
      Threads::Threads
      ${CMAKE_DL_LIBS}
      yaml-cpp
  )

else()

  target_include_directories(mission_adapters
    PRIVATE
      ${STANDALONE_INCLUDE_DIRS}
      ${Boost_INCLUDE_DIRS}
  )

  target_link_libraries(mission_adapters
    PUBLIC
      ${PACKAGES_DIR}

    PRIVATE
      Boost::boost
      Boost::system
      Boost::filesystem
      Threads::Threads
      ${CMAKE_DL_LIBS}
      yaml-cpp
  )

  set_target_properties(mission_adapters PROPERTIES
    LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
    BUILD_RPATH "${CMAKE_BINARY_DIR}"
    INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib"
  )

endif()


# ========================================================
# Plugins — mỗi nguồn mission là một .so riêng.
#
# Tên file .so phải khớp khoá `library_path` trong
# `pnkx_nav_core/config/mission_adapters_params.yaml`; tên symbol export phải khớp khoá `type`
# trong `mission_sources`. Lệch một trong hai thì build vẫn sạch còn runtime báo "không tìm thấy".
# ========================================================
set(MISSION_ADAPTERS_PLUGIN_TARGETS
  mission_adapters_goal_source
  mission_adapters_vda5050_source
)

add_library(mission_adapters_goal_source SHARED plugins/goal_source_adapter.cpp)
add_library(mission_adapters_vda5050_source SHARED plugins/vda5050_source_adapter.cpp)

foreach(plugin_target ${MISSION_ADAPTERS_PLUGIN_TARGETS})
  target_compile_options(${plugin_target} PRIVATE -Wall -Wextra)

  set_target_properties(${plugin_target} PROPERTIES POSITION_INDEPENDENT_CODE ON)

  target_include_directories(${plugin_target}
    PUBLIC
      $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/plugins>
  )

  target_link_libraries(${plugin_target}
    PUBLIC
      mission_adapters

    PRIVATE
      Boost::boost
      Threads::Threads
  )

  if(NOT BUILDING_WITH_CATKIN)
    target_include_directories(${plugin_target} PRIVATE ${STANDALONE_INCLUDE_DIRS})
    target_link_libraries(${plugin_target} PRIVATE ${PACKAGES_DIR})

    set_target_properties(${plugin_target} PROPERTIES
      LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
      BUILD_RPATH "${CMAKE_BINARY_DIR}"
      INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib"
    )
  endif()
endforeach()


# ========================================================
# Example
#
# Có build để tài liệu không trôi khỏi API: đổi contract mà quên ví dụ thì build gãy ngay.
# ========================================================
option(BUILD_MISSION_ADAPTERS_EXAMPLES "Build mission_adapters examples" ON)

if(BUILD_MISSION_ADAPTERS_EXAMPLES)

  add_executable(robot_control_example examples/robot_control_example.cpp)

  target_compile_options(robot_control_example PRIVATE -Wall -Wextra)

  target_link_libraries(robot_control_example
    PRIVATE
      mission_adapters
      Threads::Threads
  )

  if(NOT BUILDING_WITH_CATKIN)
    target_include_directories(robot_control_example
      PRIVATE
        ${STANDALONE_INCLUDE_DIRS}
    )

    target_link_libraries(robot_control_example PRIVATE ${PACKAGES_DIR})
  endif()

  # Demo: chạy một VDA5050 Order qua VDA5050SourceAdapter và in ra các chặng thu được.
  # Link thẳng plugin source adapter thay vì nạp qua boost::dll — demo chỉ cần xem kết quả cắt
  # chặng, không cần kiểm tra đường nạp plugin.
  add_executable(vda5050_order_demo examples/vda5050_order_demo.cpp)

  target_compile_options(vda5050_order_demo PRIVATE -Wall -Wextra)

  target_link_libraries(vda5050_order_demo
    PRIVATE
      mission_adapters
      mission_adapters_vda5050_source
      Threads::Threads
  )

  if(NOT BUILDING_WITH_CATKIN)
    target_include_directories(vda5050_order_demo PRIVATE ${STANDALONE_INCLUDE_DIRS})
    target_link_libraries(vda5050_order_demo PRIVATE ${PACKAGES_DIR})
  endif()

endif()


# ========================================================
# Install
# ========================================================
if(BUILDING_WITH_CATKIN)

  install(TARGETS mission_adapters ${MISSION_ADAPTERS_PLUGIN_TARGETS}
    ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
    LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
    RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
  )

  install(DIRECTORY include/${PROJECT_NAME}/
    DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
    FILES_MATCHING PATTERN "*.h"
  )

else()

  install(TARGETS mission_adapters ${MISSION_ADAPTERS_PLUGIN_TARGETS}
    EXPORT ${PROJECT_NAME}-targets
    ARCHIVE DESTINATION lib
    LIBRARY DESTINATION lib
    RUNTIME DESTINATION bin
  )

  install(EXPORT ${PROJECT_NAME}-targets
    FILE ${PROJECT_NAME}-targets.cmake
    NAMESPACE ${PROJECT_NAME}::
    DESTINATION lib/cmake/${PROJECT_NAME}
  )

  install(DIRECTORY include/${PROJECT_NAME}/
    DESTINATION include
    FILES_MATCHING PATTERN "*.h"
  )

endif()


# ========================================================
# Tests
#
# Gói này đăng ký test với catkin/ctest (chạy được qua `catkin_make run_tests`) VÀ mở
# EXCLUDE_FROM_ALL để binary có mặt sau `catkin_make` thường — chạy thẳng
# ./devel/lib/mission_adapters/<test> theo đúng quy trình verify của repo.
# ========================================================
if(CATKIN_ENABLE_TESTING AND BUILDING_WITH_CATKIN)

  if(NOT COMMAND catkin_add_gtest)
    message(FATAL_ERROR "catkin_add_gtest NOT FOUND")
  endif()

  set(MISSION_ADAPTERS_TESTS
    event_bus_test
    adapter_test
    mission_manager_test
    mission_lifecycle_test
    plugin_registry_test
  )

  foreach(test_name ${MISSION_ADAPTERS_TESTS})
    catkin_add_gtest(${test_name} test/${test_name}.cpp)

    if(TARGET ${test_name})
      set_target_properties(${test_name} PROPERTIES EXCLUDE_FROM_ALL FALSE)

      target_compile_options(${test_name} PRIVATE -Wall -Wextra)

      target_include_directories(${test_name}
        PRIVATE
          ${CMAKE_CURRENT_SOURCE_DIR}/include
          ${CMAKE_CURRENT_SOURCE_DIR}/plugins
          ${CMAKE_CURRENT_SOURCE_DIR}/test
      )

      target_include_directories(${test_name} SYSTEM
        PRIVATE
          ${catkin_INCLUDE_DIRS}
      )

      # catkin_add_gtest dùng target_link_libraries dạng plain, nên phần bổ sung cũng phải plain.
      #
      # Test link thẳng plugin .so để kiểm logic chuyển đổi ở mức unit; đường nạp qua Boost.DLL
      # được kiểm riêng trong plugin_registry_test.
      target_link_libraries(${test_name}
        mission_adapters
        ${MISSION_ADAPTERS_PLUGIN_TARGETS}
        ${catkin_LIBRARIES}
        pthread
      )

      # plugin_registry_test nạp .so từ devel/lib theo library_path, nên cần cả hai plugin đã build.
      add_dependencies(${test_name} ${MISSION_ADAPTERS_PLUGIN_TARGETS})

      # Đường tới cây config và thư mục .so của test, để binary tự trỏ đúng chỗ khi chạy qua ctest
      # (ctest không mang theo PNKX_NAV_CORE_CONFIG_DIR hay LD_LIBRARY_PATH của shell).
      target_compile_definitions(${test_name} PRIVATE
        MISSION_ADAPTERS_TEST_CONFIG_DIR="${CMAKE_CURRENT_SOURCE_DIR}/test/config"
        MISSION_ADAPTERS_TEST_LIBRARY_DIR="${CATKIN_DEVEL_PREFIX}/lib"
      )
    endif()
  endforeach()

endif()
