cmake_minimum_required(VERSION 3.10)
project(acoustic_analyzer)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# ── Windows / MinGW fixes ────────────────────────────────────────
if(WIN32)
    add_compile_definitions(_USE_MATH_DEFINES)
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
    add_compile_options("-Wa,-mbig-obj")
endif()

option(BUILD_TESTS "Build unit tests" ON)
option(BUILD_ROS_WRAPPER "Build ROS wrapper node" OFF)
option(BUILD_ONNX_TESTS "Build ONNX classifier tests" ON)

# ── Eigen3 (bundled) ─────────────────────────────────────────────
set(EIGEN3_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/eigen-3.4.0")
if(NOT EXISTS "${EIGEN3_INCLUDE_DIR}/Eigen/Core")
    message(FATAL_ERROR "Eigen3 not found at ${EIGEN3_INCLUDE_DIR}")
endif()

# ── yaml-cpp (optional) ──────────────────────────────────────────
set(YAML_CPP_ROOT "")
if(WIN32)
    set(_conda_yaml "C:/Users/$ENV{USERNAME}/miniconda3/Library")
    if(EXISTS "${_conda_yaml}/include/yaml-cpp/yaml.h")
        set(YAML_CPP_ROOT "${_conda_yaml}")
        message(STATUS "Found yaml-cpp in conda: ${YAML_CPP_ROOT}")
    endif()
endif()

set(YAML_CPP_FOUND FALSE)
find_package(yaml-cpp QUIET)
if(yaml-cpp_FOUND AND TARGET yaml-cpp)
    set(YAML_CPP_TARGET yaml-cpp)
    set(YAML_CPP_FOUND TRUE)
    if(NOT yaml-cpp_INCLUDE_DIRS AND DEFINED YAML_CPP_INCLUDE_DIR)
        set(yaml-cpp_INCLUDE_DIRS "${YAML_CPP_INCLUDE_DIR}")
    endif()
elseif(yaml-cpp_FOUND AND DEFINED YAML_CPP_INCLUDE_DIR)
    set(YAML_CPP_TARGET yaml-cpp)
    set(YAML_CPP_FOUND TRUE)
    add_library(yaml-cpp UNKNOWN IMPORTED)
    set_target_properties(yaml-cpp PROPERTIES
        IMPORTED_LOCATION "${YAML_CPP_LIBRARY_DIR}/yaml-cpp.lib"
        INTERFACE_INCLUDE_DIRECTORIES "${YAML_CPP_INCLUDE_DIR}"
    )
    if(NOT yaml-cpp_INCLUDE_DIRS)
        set(yaml-cpp_INCLUDE_DIRS "${YAML_CPP_INCLUDE_DIR}")
    endif()
elseif(YAML_CPP_ROOT)
    set(yaml-cpp_INCLUDE_DIRS "${YAML_CPP_ROOT}/include")
    set(yaml-cpp_LIBRARIES "${YAML_CPP_ROOT}/lib/yaml-cpp.lib")
    if(EXISTS "${yaml-cpp_LIBRARIES}")
        add_library(yaml-cpp UNKNOWN IMPORTED)
        set_target_properties(yaml-cpp PROPERTIES
            IMPORTED_LOCATION "${yaml-cpp_LIBRARIES}"
            INTERFACE_INCLUDE_DIRECTORIES "${yaml-cpp_INCLUDE_DIRS}"
        )
        set(YAML_CPP_TARGET yaml-cpp)
        set(YAML_CPP_FOUND TRUE)
    endif()
endif()

if(YAML_CPP_FOUND)
    message(STATUS "yaml-cpp found. Pipeline will be built.")
else()
    message(WARNING "yaml-cpp not found. Pipeline and ROS wrapper will be disabled.")
endif()

# ── ONNX Runtime ─────────────────────────────────────────────────
set(ONNXRuntime_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/onnxruntime" CACHE PATH "ONNX Runtime root")
set(ONNXRuntime_INCLUDE_DIRS "${ONNXRuntime_DIR}/include")

if(WIN32)
    if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
        set(ONNXRuntime_LIB_CANDIDATE "${ONNXRuntime_DIR}/lib/libonnxruntime.a")
    else()
        set(ONNXRuntime_LIB_CANDIDATE "${ONNXRuntime_DIR}/lib/onnxruntime.lib")
    endif()
else()
    set(ONNXRuntime_LIB_CANDIDATE "${ONNXRuntime_DIR}/lib/libonnxruntime.so")
endif()

if(EXISTS "${ONNXRuntime_LIB_CANDIDATE}")
    set(ONNXRuntime_LIBS "${ONNXRuntime_LIB_CANDIDATE}")
    set(ONNXRuntime_FOUND TRUE)
    message(STATUS "ONNX Runtime found: ${ONNXRuntime_LIBS}")
else()
    set(ONNXRuntime_FOUND FALSE)
    message(WARNING "ONNX Runtime NOT found. ONNX tests disabled.")
endif()



# ── Core library sources ─────────────────────────────────────────
set(CORE_BASE_SOURCES
    src/core/fft_utils.cpp
    src/core/audio_buffer.cpp
    src/core/feature_extractor.cpp
    src/core/gcc_phat_localizer.cpp
    src/core/distance_estimator.cpp
    src/core/threat_tracker.cpp
)

set(CORE_ONNX_SOURCES
    src/core/gunshot_classifier.cpp
)
if(YAML_CPP_FOUND)
    list(APPEND CORE_ONNX_SOURCES src/core/pipeline.cpp)
endif()

set(IO_SOURCES
    src/io/wav_file_source.cpp
)
if(NOT WIN32)
    list(APPEND IO_SOURCES src/io/mobile_phone_source.cpp)
endif()

# Build base core library
add_library(${PROJECT_NAME}_core_base STATIC ${CORE_BASE_SOURCES})
target_include_directories(${PROJECT_NAME}_core_base PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
    ${EIGEN3_INCLUDE_DIR}

)

# Full core library
if(ONNXRuntime_FOUND)
    add_library(${PROJECT_NAME}_core STATIC ${CORE_BASE_SOURCES} ${CORE_ONNX_SOURCES} ${IO_SOURCES})
    target_include_directories(${PROJECT_NAME}_core PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
        ${ONNXRuntime_INCLUDE_DIRS}
        ${EIGEN3_INCLUDE_DIR}
    
    )
    if(YAML_CPP_FOUND)
        target_include_directories(${PROJECT_NAME}_core PUBLIC ${yaml-cpp_INCLUDE_DIRS})
        target_link_libraries(${PROJECT_NAME}_core PUBLIC ${YAML_CPP_TARGET})
    endif()
    target_link_libraries(${PROJECT_NAME}_core PUBLIC ${ONNXRuntime_LIBS})
    if(NOT WIN32)
        target_link_libraries(${PROJECT_NAME}_core PUBLIC m)
    endif()
else()
    add_library(${PROJECT_NAME}_core ALIAS ${PROJECT_NAME}_core_base)
endif()

# ── ROS Wrapper ──────────────────────────────────────────────────
if(BUILD_ROS_WRAPPER)
    find_package(catkin REQUIRED COMPONENTS
        roscpp
        std_msgs
        sensor_msgs
        geometry_msgs
        message_generation
    )

    add_message_files(FILES AcousticThreat.msg AcousticThreatArray.msg)
    generate_messages(DEPENDENCIES std_msgs)

    catkin_package(CATKIN_DEPENDS roscpp std_msgs sensor_msgs message_runtime)

    add_executable(${PROJECT_NAME}_node
        src/ros/acoustic_node.cpp
        src/ros/threat_publisher.cpp
        src/main.cpp
    )
    target_include_directories(${PROJECT_NAME}_node PRIVATE ${catkin_INCLUDE_DIRS})
    target_link_libraries(${PROJECT_NAME}_node ${PROJECT_NAME}_core ${catkin_LIBRARIES})
    add_dependencies(${PROJECT_NAME}_node ${PROJECT_NAME}_generate_messages_cpp)
endif()

# ── Unit tests ───────────────────────────────────────────────────
if(BUILD_TESTS)
    enable_testing()

    add_executable(test_core_lib tests/test_core_lib.cpp ${CORE_BASE_SOURCES})
    target_include_directories(test_core_lib PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include ${EIGEN3_INCLUDE_DIR} ${KISSFFT_DIR})
    if(NOT WIN32)
        target_link_libraries(test_core_lib PRIVATE m)
    endif()
    add_test(NAME core_lib COMMAND test_core_lib)

    add_executable(extract_mel_cpp tests/extract_mel_cpp.cpp
        src/core/fft_utils.cpp src/core/feature_extractor.cpp)
    target_include_directories(extract_mel_cpp PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include ${EIGEN3_INCLUDE_DIR})
    if(NOT WIN32)
        target_link_libraries(extract_mel_cpp PRIVATE m)
    endif()

    if(ONNXRuntime_FOUND AND BUILD_ONNX_TESTS)
        add_executable(test_classifier_cpp tests/test_classifier_cpp.cpp
            src/core/fft_utils.cpp src/core/feature_extractor.cpp src/core/gunshot_classifier.cpp)
        target_include_directories(test_classifier_cpp PRIVATE
            ${CMAKE_CURRENT_SOURCE_DIR}/include ${EIGEN3_INCLUDE_DIR} ${ONNXRuntime_INCLUDE_DIRS})
        target_compile_definitions(test_classifier_cpp PRIVATE _stdcall=)
        target_link_libraries(test_classifier_cpp PRIVATE ${ONNXRuntime_LIBS})
        if(NOT WIN32)
            target_link_libraries(test_classifier_cpp PRIVATE m)
        endif()
        add_test(NAME classifier_cpp COMMAND test_classifier_cpp
                 --model ${CMAKE_CURRENT_SOURCE_DIR}/models/gunshot_classifier.onnx
                 --wav ${CMAKE_CURRENT_SOURCE_DIR}/dataset/binary/train/ambient/drone_noise_simulated.wav)
    endif()
endif()

# ── Install ──────────────────────────────────────────────────────
if(NOT WIN32 AND TARGET ${PROJECT_NAME}_core)
    install(TARGETS ${PROJECT_NAME}_core
        ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
        LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
        RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
    )
    install(DIRECTORY include/${PROJECT_NAME}/ DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION})
    install(DIRECTORY config/ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/config)
    install(DIRECTORY models/ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/models)
endif()
