cmake_minimum_required(VERSION 3.14.4)

project(mapviz)

if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17)
endif()
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

function(mapviz_ensure_interface_alias alias_target dependency_target)
  if(TARGET ${alias_target})
    return()
  endif()

  add_library(${alias_target} INTERFACE IMPORTED)
  set_target_properties(${alias_target} PROPERTIES
    INTERFACE_LINK_LIBRARIES ${dependency_target})
endfunction()

# Ensures that the OpenCV component target exists by finding it from various sources
# (direct target, OpenCV namespace target, or pkg-config) and creating an alias if needed
function(mapviz_ensure_opencv_component component)
  if(TARGET opencv_${component})
    return()
  endif()

  if(TARGET OpenCV::opencv_${component})
    mapviz_ensure_interface_alias(opencv_${component} OpenCV::opencv_${component})
    return()
  endif()

  if(TARGET PkgConfig::OpenCV)
    mapviz_ensure_interface_alias(opencv_${component} PkgConfig::OpenCV)
    return()
  endif()

  message(FATAL_ERROR "Unable to resolve OpenCV component target opencv_${component}")
endfunction()

set(MAPVIZ_AUTOGEN_INCLUDE_DIR
  ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_core_autogen/include)
set(MAPVIZ_AUTOGEN_UI_INCLUDE_DIR
  ${MAPVIZ_AUTOGEN_INCLUDE_DIR}/ui)

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)

find_package(qt_gui_cpp REQUIRED)

set(QT_VERSION_MAJOR 5)
find_package(Qt5 QUIET
  COMPONENTS
    Core
    Gui
    OpenGL
    Widgets
)
if(NOT Qt5_FOUND)
  set(QT_VERSION_MAJOR 6)
  find_package(Qt6 REQUIRED
    COMPONENTS
      Core
      Gui
      OpenGL
      OpenGLWidgets
      Widgets
  )
endif()

find_package(geometry_msgs REQUIRED)
find_package(image_transport REQUIRED)

# image_transport (part of image_common) changed the ImageTransport
# constructor from taking an rclcpp::Node::SharedPtr to taking an
# rclcpp::Node& starting with image_common 6.4.0. Detect which API is
# available so mapviz.cpp can call the correct one.
if(image_transport_VERSION VERSION_LESS "6.4.0")
  set(MAPVIZ_IMAGE_TRANSPORT_TAKES_NODE_REF OFF)
else()
  set(MAPVIZ_IMAGE_TRANSPORT_TAKES_NODE_REF ON)
endif()

find_package(mapviz_interfaces REQUIRED)
find_package(pluginlib REQUIRED)
find_package(rqt_gui_cpp REQUIRED)
find_package(std_srvs REQUIRED)
find_package(swri_math_util REQUIRED)
find_package(swri_transform_util REQUIRED)
find_package(tf2 REQUIRED)
find_package(tf2_geometry_msgs REQUIRED)
find_package(tf2_ros REQUIRED)

# tf2_ros 0.46.1 removed the deprecated TransformListener constructor overload
# that accepted a Node::SharedPtr, requiring a bare node reference instead.
# Older tf2_ros releases (e.g. on Kilted) don't provide tf2_ros/version.h at
# all, so this has to be a CMake-time check rather than a runtime
# TF2_ROS_VERSION_GTE macro.
if("${tf2_ros_VERSION}" VERSION_GREATER_EQUAL "0.46.1")
  set(USE_NEW_TF2_ROS_CTORS 1)
else()
  set(USE_NEW_TF2_ROS_CTORS 0)
endif()

find_package(OpenCV QUIET CONFIG COMPONENTS core imgcodecs imgproc videoio)
if(NOT OpenCV_FOUND)
  find_package(PkgConfig REQUIRED)
  pkg_check_modules(OpenCV REQUIRED IMPORTED_TARGET opencv4)
endif()
foreach(component IN ITEMS core imgcodecs imgproc videoio)
  mapviz_ensure_opencv_component(${component})
endforeach()

find_package(yaml-cpp QUIET CONFIG)
if(NOT TARGET yaml-cpp)
  find_package(PkgConfig REQUIRED)
  pkg_check_modules(YamlCpp REQUIRED IMPORTED_TARGET yaml-cpp)
  mapviz_ensure_interface_alias(yaml-cpp PkgConfig::YamlCpp)
endif()

# Need to include the current dir to include the results of building Qt UI files
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/ui)

# Qt UI files
set(UI_FILES
  ui/configitem.ui
  ui/mapviz.ui
  ui/pluginselect.ui
)
# Headers containing QObject definitions (core library)
set(HEADER_FILES
  include/${PROJECT_NAME}/color_button.hpp
  include/${PROJECT_NAME}/config_item.hpp
  include/${PROJECT_NAME}/map_canvas.hpp
  include/${PROJECT_NAME}/${PROJECT_NAME}.hpp
  include/${PROJECT_NAME}/${PROJECT_NAME}_plugin.hpp
  include/${PROJECT_NAME}/select_frame_dialog.hpp
  include/${PROJECT_NAME}/select_service_dialog.hpp
  include/${PROJECT_NAME}/video_writer.hpp
  include/${PROJECT_NAME}/widgets.hpp
)
# Source files for mapviz core library
set(SRC_FILES
  src/${PROJECT_NAME}.cpp
  src/color_button.cpp
  src/config_item.cpp
  src/display_loader.cpp
  src/${PROJECT_NAME}_application.cpp
  src/map_canvas.cpp
  src/select_frame_dialog.cpp
  src/select_service_dialog.cpp
  src/video_writer.cpp
  src/resources/icons.qrc
)

set(MAPVIZ_QT_LIBRARIES
  Qt${QT_VERSION_MAJOR}::Core
  Qt${QT_VERSION_MAJOR}::Gui
  Qt${QT_VERSION_MAJOR}::OpenGL
  Qt${QT_VERSION_MAJOR}::Widgets
)

if(QT_VERSION_MAJOR EQUAL 6)
  list(APPEND MAPVIZ_QT_LIBRARIES Qt${QT_VERSION_MAJOR}::OpenGLWidgets)
endif()

set(MAPVIZ_QT_EXPORT_DEPENDENCIES
  Qt${QT_VERSION_MAJOR}Core
  Qt${QT_VERSION_MAJOR}Gui
  Qt${QT_VERSION_MAJOR}OpenGL
  Qt${QT_VERSION_MAJOR}Widgets)

if(QT_VERSION_MAJOR EQUAL 6)
  list(APPEND MAPVIZ_QT_EXPORT_DEPENDENCIES Qt${QT_VERSION_MAJOR}OpenGLWidgets)
endif()

### Build mapviz core library ###
add_library(${PROJECT_NAME}_core SHARED
  ${HEADER_FILES}
  ${SRC_FILES}
  ${UI_FILES}
)
target_include_directories(${PROJECT_NAME}_core
  PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<BUILD_INTERFACE:${MAPVIZ_AUTOGEN_INCLUDE_DIR}>
    $<BUILD_INTERFACE:${MAPVIZ_AUTOGEN_UI_INCLUDE_DIR}>
    $<INSTALL_INTERFACE:include/${PROJECT_NAME}>
)

target_link_libraries(${PROJECT_NAME}_core PUBLIC
  opencv_core
  ${mapviz_interfaces_TARGETS}
  pluginlib::pluginlib
  ${MAPVIZ_QT_LIBRARIES}
  rclcpp::rclcpp
  ${std_srvs_TARGETS}
  ${swri_transform_util_TARGETS}
  tf2::tf2
  tf2_geometry_msgs::tf2_geometry_msgs
  tf2_ros::tf2_ros
  yaml-cpp
)
target_link_libraries(${PROJECT_NAME}_core PRIVATE
  ${geometry_msgs_TARGETS}
  image_transport::image_transport
  opencv_imgcodecs
  opencv_imgproc
  opencv_videoio
  ${swri_math_util_TARGETS}
)
target_compile_definitions(${PROJECT_NAME}_core PRIVATE
  WFlags=WindowFlags
  __STDC_FORMAT_MACROS
)
if(MAPVIZ_IMAGE_TRANSPORT_TAKES_NODE_REF)
  target_compile_definitions(${PROJECT_NAME}_core PRIVATE
    MAPVIZ_IMAGE_TRANSPORT_TAKES_NODE_REF
  )
endif()
target_compile_definitions(${PROJECT_NAME}_core PRIVATE
  USE_NEW_TF2_ROS_CTORS=${USE_NEW_TF2_ROS_CTORS}
)

set_target_properties(${PROJECT_NAME}_core PROPERTIES
  AUTOMOC ON
  AUTOUIC ON
  AUTORCC ON
  AUTOUIC_SEARCH_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/ui"
)

### Build rqt plugin (thin wrapper, loaded dynamically by rqt) ###
add_library(rqt_${PROJECT_NAME} SHARED
  include/${PROJECT_NAME}/rqt_${PROJECT_NAME}.hpp
  src/rqt_${PROJECT_NAME}.cpp
)
target_link_libraries(rqt_${PROJECT_NAME} PUBLIC
  ${PROJECT_NAME}_core
  qt_gui_cpp::qt_gui_cpp
  rqt_gui_cpp::rqt_gui_cpp
)
set_target_properties(rqt_${PROJECT_NAME} PROPERTIES
  AUTOMOC ON
)
# rqt_gui_cpp 1.7.2 added plugin.hpp and 1.10.3 removed plugin.h.
if(rqt_gui_cpp_VERSION VERSION_LESS "1.7.2")
  target_compile_definitions(rqt_${PROJECT_NAME} PUBLIC MAPVIZ_RQT_GUI_CPP_PLUGIN_H)
endif()

### Build mapviz as a standalone executable ###
add_executable(${PROJECT_NAME}
  src/${PROJECT_NAME}_main.cpp)

target_link_libraries(${PROJECT_NAME} PRIVATE
  ${PROJECT_NAME}_core
)
target_compile_definitions(${PROJECT_NAME} PRIVATE
  __STDC_FORMAT_MACROS
)

### Install mapviz ###
install(DIRECTORY include/
  DESTINATION include/${PROJECT_NAME}
  FILES_MATCHING PATTERN "*.hpp"
)

install(FILES
  ${MAPVIZ_AUTOGEN_UI_INCLUDE_DIR}/ui_configitem.h
  ${MAPVIZ_AUTOGEN_UI_INCLUDE_DIR}/ui_mapviz.h
  ${MAPVIZ_AUTOGEN_UI_INCLUDE_DIR}/ui_pluginselect.h
  DESTINATION include/${PROJECT_NAME}
)

install(TARGETS ${PROJECT_NAME}
  DESTINATION lib/${PROJECT_NAME}
)

install(TARGETS ${PROJECT_NAME}_core
  EXPORT export_${PROJECT_NAME}
  RUNTIME DESTINATION bin
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib
)

install(TARGETS rqt_${PROJECT_NAME}
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib
)

install(DIRECTORY launch
  DESTINATION share/${PROJECT_NAME}
)

install(FILES plugin.xml
  DESTINATION share/${PROJECT_NAME}
)

pluginlib_export_plugin_description_file(rqt_gui "plugin.xml")

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()

  find_package(ament_cmake_gtest REQUIRED)
  find_package(std_msgs REQUIRED)

  ament_add_gtest(test_${PROJECT_NAME}_plugin test/test_${PROJECT_NAME}_plugin.cpp)
  if(TARGET test_${PROJECT_NAME}_plugin)
    # ament_add_gtest() already used the plain signature on this target.
    target_link_libraries(test_${PROJECT_NAME}_plugin
      ${PROJECT_NAME}_core
      ${std_msgs_TARGETS})
  endif()

  ament_add_gtest(test_display_loader test/test_display_loader.cpp)
  if(TARGET test_display_loader)
    target_link_libraries(test_display_loader ${PROJECT_NAME}_core)
  endif()

  ament_add_gtest(test_qt_font_compat test/test_qt_font_compat.cpp
    ENV QT_QPA_PLATFORM=offscreen)
  if(TARGET test_qt_font_compat)
    target_link_libraries(test_qt_font_compat ${PROJECT_NAME}_core)
  endif()

  ament_add_gtest(test_video_writer test/test_video_writer.cpp)
  if(TARGET test_video_writer)
    target_link_libraries(test_video_writer ${PROJECT_NAME}_core opencv_core opencv_videoio)
  endif()
endif()

ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)
ament_export_dependencies(
  ${MAPVIZ_QT_EXPORT_DEPENDENCIES}
  mapviz_interfaces
  pluginlib
  rclcpp
  std_srvs
  swri_transform_util
  tf2
  tf2_geometry_msgs
  tf2_ros
)
ament_package(CONFIG_EXTRAS cmake/mapviz-extras.cmake)
