cmake_minimum_required(VERSION 3.22)

# Single source of truth for the plugin version: version.txt. This feeds
# project()/juce_add_plugin (so JucePlugin_VersionString compiles to the same
# value shown in the plugin's About section) and the release/installer tooling.
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/version.txt" SYNCDROP_PLUGIN_VERSION LIMIT_COUNT 1)
string(STRIP "${SYNCDROP_PLUGIN_VERSION}" SYNCDROP_PLUGIN_VERSION)
if(NOT SYNCDROP_PLUGIN_VERSION MATCHES "^[0-9]+\\.[0-9]+\\.[0-9]+")
    message(FATAL_ERROR "version.txt must contain a semver version (got '${SYNCDROP_PLUGIN_VERSION}')")
endif()

project(SyncDropBridge VERSION ${SYNCDROP_PLUGIN_VERSION} LANGUAGES C CXX)

# ---------------------------------------------------------------------------
# Options
# ---------------------------------------------------------------------------
option(SYNCDROP_BUILD_SPIKE  "Build the headless sine-wave interop spike CLI" ON)
option(SYNCDROP_BUILD_PLUGIN "Build the JUCE audio plugin (VST3/AU/Standalone)" ON)
option(SYNCDROP_BUILD_TESTS  "Build unit tests" ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()

# MSVC defaults to the system code page for both the source and the runtime
# narrow-string encoding. Source files here are UTF-8 and contain non-ASCII
# literals (em dashes, middle dots); without this flag MSVC silently
# reinterprets those bytes, producing mojibake (e.g. "\xC2\xB7" -> "Â·") in
# strings shown in the plugin UI and sent over the wire to the web app.
if(MSVC)
    add_compile_options(/utf-8)
endif()

# CMake 4 removed compatibility with cmake_minimum_required(<3.5); some
# fetched dependencies (libdatachannel's bundled plog, opus) still declare
# older minimums. Pre-seed the official escape hatch so they configure on
# new CMake; older CMake ignores this variable.
if(NOT DEFINED CMAKE_POLICY_VERSION_MINIMUM)
    set(CMAKE_POLICY_VERSION_MINIMUM 3.5)
endif()

include(FetchContent)

# ---------------------------------------------------------------------------
# Dependencies
# ---------------------------------------------------------------------------

# libdatachannel: WebRTC (ICE/DTLS/SRTP), media tracks, RTP packetizers, and
# the WebSocket client used to talk to the PeerJS signaling server.
set(NO_EXAMPLES ON CACHE INTERNAL "")
set(NO_TESTS ON CACHE INTERNAL "")
FetchContent_Declare(
    libdatachannel
    GIT_REPOSITORY https://github.com/paullouisageneau/libdatachannel.git
    GIT_TAG        v0.21.2
    GIT_SHALLOW    ON
)

# Opus encoder
set(OPUS_BUILD_SHARED_LIBRARY OFF CACHE INTERNAL "")
set(OPUS_BUILD_TESTING OFF CACHE INTERNAL "")
set(OPUS_BUILD_PROGRAMS OFF CACHE INTERNAL "")
FetchContent_Declare(
    opus
    GIT_REPOSITORY https://github.com/xiph/opus.git
    GIT_TAG        v1.5.2
    GIT_SHALLOW    ON
)

# JSON (PeerJS wire protocol + SyncDrop room protocol are both JSON)
FetchContent_Declare(
    nlohmann_json
    GIT_REPOSITORY https://github.com/nlohmann/json.git
    GIT_TAG        v3.11.3
    GIT_SHALLOW    ON
)

FetchContent_MakeAvailable(libdatachannel opus nlohmann_json)

# ---------------------------------------------------------------------------
# Core library: PeerJS signaling + WebRTC audio fanout + Opus pipeline.
# Shared by the spike CLI and the JUCE plugin.
# ---------------------------------------------------------------------------
add_library(syncdrop_bridge_core STATIC
    src/net/Log.cpp
    src/net/BinaryPack.cpp
    src/net/PeerJsWire.cpp
    src/net/PeerJsSignaling.cpp
    src/net/PeerJsDataConnection.cpp
    src/net/FileTransfer.cpp
    src/net/MediaSender.cpp
    src/net/MediaReceiver.cpp
    src/net/RtpUtil.cpp
    src/net/RoomClient.cpp
    src/net/RoomHost.cpp
    src/audio/OpusStream.cpp
)
target_include_directories(syncdrop_bridge_core PUBLIC src)
# libdatachannel is linked statically: a DLL next to a .vst3 bundle is not
# deployable, and on MSVC the DLL's exported std::vector<std::byte>
# instantiations collide with JUCE's at link time (LNK2005).
target_link_libraries(syncdrop_bridge_core
    PUBLIC
        LibDataChannel::LibDataChannelStatic
        nlohmann_json::nlohmann_json
        opus
)
if(WIN32)
    # ws2_32 for sockets; crypt32/bcrypt for a statically linked OpenSSL.
    target_link_libraries(syncdrop_bridge_core PUBLIC ws2_32 crypt32 bcrypt)
endif()

# ---------------------------------------------------------------------------
# M0 spike: headless CLI that joins a room and streams a sine wave.
# Kept building forever as an interop canary / test harness.
# ---------------------------------------------------------------------------
if(SYNCDROP_BUILD_SPIKE)
    add_executable(sine_bridge spike/sine_bridge_main.cpp)
    target_link_libraries(sine_bridge PRIVATE syncdrop_bridge_core)
endif()

# ---------------------------------------------------------------------------
# JUCE plugin (VST3 + AU + Standalone)
# ---------------------------------------------------------------------------
if(SYNCDROP_BUILD_PLUGIN)
    FetchContent_Declare(
        juce
        GIT_REPOSITORY https://github.com/juce-framework/JUCE.git
        GIT_TAG        7.0.12
        GIT_SHALLOW    ON
    )
    FetchContent_MakeAvailable(juce)

    set(SYNCDROP_PLUGIN_FORMATS VST3 Standalone)
    if(APPLE)
        list(APPEND SYNCDROP_PLUGIN_FORMATS AU)
    endif()

    juce_add_plugin(SyncDropBridge
        VERSION ${SYNCDROP_PLUGIN_VERSION}
        PLUGIN_MANUFACTURER_CODE Sydr
        PLUGIN_CODE Sdbr
        COMPANY_NAME "SyncDrop"
        PRODUCT_NAME "SyncDrop Bridge"
        FORMATS ${SYNCDROP_PLUGIN_FORMATS}
        IS_SYNTH FALSE
        NEEDS_MIDI_INPUT FALSE
        NEEDS_MIDI_OUTPUT FALSE
        IS_MIDI_EFFECT FALSE
        EDITOR_WANTS_KEYBOARD_FOCUS TRUE
        COPY_PLUGIN_AFTER_BUILD FALSE
        VST3_CATEGORIES "Fx" "Network"
        AU_MAIN_TYPE "kAudioUnitType_Effect"
    )

    target_sources(SyncDropBridge PRIVATE
        src/PluginProcessor.cpp
        src/PluginEditor.cpp
        src/BridgeEngine.cpp
    )
    target_compile_definitions(SyncDropBridge PUBLIC
        JUCE_WEB_BROWSER=0
        JUCE_USE_CURL=0
        JUCE_VST3_CAN_REPLACE_VST2=0
        JUCE_DISPLAY_SPLASH_SCREEN=1  # required by the JUCE GPL/free license
        # Version string for the room-protocol client identifier (BridgeEngine),
        # kept out of the JUCE-free net/ headers. Same source as
        # JucePlugin_VersionString (version.txt) so the wire and About agree.
        SYNCDROP_PLUGIN_VERSION="${SYNCDROP_PLUGIN_VERSION}"
    )
    target_link_libraries(SyncDropBridge
        PRIVATE
            syncdrop_bridge_core
            juce::juce_audio_utils
            juce::juce_dsp
        PUBLIC
            juce::juce_recommended_config_flags
            juce::juce_recommended_lto_flags
            juce::juce_recommended_warning_flags
    )
    if(WIN32)
        # winmm: timeBeginPeriod/timeEndPeriod raise the timer resolution for
        # the 10/5 ms low-latency send cadence (BridgeEngine::bridgeThreadRun).
        target_link_libraries(SyncDropBridge PRIVATE winmm)
    endif()
endif()

# ---------------------------------------------------------------------------
# Unit tests (Catch2): ring buffer, wire building/parsing, peer-list diff.
# ---------------------------------------------------------------------------
if(SYNCDROP_BUILD_TESTS)
    FetchContent_Declare(
        catch2
        GIT_REPOSITORY https://github.com/catchorg/Catch2.git
        GIT_TAG        v3.5.4
        GIT_SHALLOW    ON
    )
    FetchContent_MakeAvailable(catch2)

    enable_testing()
    add_executable(bridge_tests
        tests/test_binarypack.cpp
        tests/test_capture_buffer.cpp
        tests/test_filetransfer.cpp
        tests/test_jitter.cpp
        tests/test_log.cpp
        tests/test_opus_lowlatency.cpp
        tests/test_ring_buffer.cpp
        tests/test_rtp.cpp
        tests/test_wire.cpp
    )
    target_link_libraries(bridge_tests PRIVATE syncdrop_bridge_core Catch2::Catch2WithMain)
    add_test(NAME bridge_tests COMMAND bridge_tests)
endif()
