# Module layout mirrors docs/ARCHITECTURE.md. Dependencies point strictly one
# way -- app -> {health, socks5, egress, selector} -> {netstack, ovpn, vpngate}
# -> common -- and CMake is where that is enforced: if a link edge is missing
# here, the include is wrong.

# Settings every module shares.
add_library(ovg_flags INTERFACE)
target_include_directories(ovg_flags INTERFACE
  ${CMAKE_CURRENT_SOURCE_DIR}
  ${ASIO_INCLUDE_DIR})
target_compile_definitions(ovg_flags INTERFACE ASIO_STANDALONE)

# Compiled in, not #ifdef'd out at the include level: every module can test it
# with a plain `if constexpr`/`#if` and the value is never accidentally absent.
if(OVG_WITH_TUNNEL)
  target_compile_definitions(ovg_flags INTERFACE OVG_WITH_TUNNEL=1)
else()
  target_compile_definitions(ovg_flags INTERFACE OVG_WITH_TUNNEL=0)
endif()

target_compile_options(ovg_flags INTERFACE
  -Wall -Wextra -Wpedantic
  -Wno-unused-parameter)
target_link_libraries(ovg_flags INTERFACE
  fmt::fmt
  Threads::Threads
  PkgConfig::OPENSSL)

# ---- common: logging, config, metrics, errors, HTTP ------------------------
add_library(ovg_common STATIC
  common/logging.cpp
  common/endpoint.cpp
  common/error.cpp
  common/metrics.cpp
  common/config.cpp
  common/http_get.cpp)
target_link_libraries(ovg_common PUBLIC ovg_flags)

# ---- vpngate: API client, CSV parsing, node list ---------------------------
add_library(ovg_vpngate STATIC
  vpngate/csv_parser.cpp
  vpngate/node_store.cpp)
target_link_libraries(ovg_vpngate PUBLIC ovg_common)

# ---- ovpn: one OpenVPN session, and the socketpair standing in for a tun ---
# This is the only target that sees openvpn3 headers. Everything above it talks
# to TunnelClient, whose implementation is pimpl'd away, so a change in the core
# cannot ripple past this line.
add_library(ovg_ovpn STATIC
  ovpn/packet_pipe.cpp
  ovpn/profile_sanitizer.cpp
  ovpn/tunnel_client.cpp)
target_link_libraries(ovg_ovpn PUBLIC ovg_vpngate)
if(OVG_WITH_TUNNEL)
  # PRIVATE: ovg_openvpn3's include directories must not leak to our callers.
  # They arrive as -isystem (see cmake/Dependencies.cmake), so the core's own
  # warnings stay quiet while our code in tunnel_client.cpp keeps -Wall -Wextra.
  target_link_libraries(ovg_ovpn PRIVATE ovg_openvpn3)
endif()

# ---- netstack: the userspace TCP/IP stack ----------------------------------
# Only built with the tunnel: without lwIP there is nothing here to compile, and
# the direct egress uses host sockets through the same interfaces (stream.h).
if(OVG_WITH_TUNNEL)
  # The port layer: sys_now() and the three hooks arch/cc.h expands to. lwIP
  # itself will not link without them, so it has to be its own target rather
  # than part of ovg_netstack -- a static archive only resolves symbols that are
  # already undefined when the linker reaches it, and ovg_netstack sits *before*
  # ovg_lwip on the link line. As a separate target it lands after, where lwIP's
  # references to it are pending.
  #
  # It gets lwIP's headers by path rather than by linking ovg_lwip, which would
  # make the dependency circular for no benefit: it needs the declarations, not
  # the objects.
  add_library(ovg_lwip_port STATIC netstack/lwip_port/lwip_shim.cpp)
  target_link_libraries(ovg_lwip_port PUBLIC ovg_common)
  target_include_directories(ovg_lwip_port SYSTEM PRIVATE ${LWIP_INCLUDE_DIRS})
  target_link_libraries(ovg_lwip INTERFACE ovg_lwip_port)

  add_library(ovg_netstack STATIC
    netstack/lwip_stack.cpp
    netstack/lwip_tcp.cpp
    netstack/lwip_udp.cpp
    netstack/dns_resolver.cpp)
  target_link_libraries(ovg_netstack PUBLIC ovg_common)
  # PRIVATE: lwIP's headers stop here. Callers see stream.h and packet_link.h,
  # which is what makes replacing the stack a contained change.
  target_link_libraries(ovg_netstack PRIVATE ovg_lwip)
endif()

# ---- selector: scoring, probing, node choice -------------------------------
add_library(ovg_selector STATIC
  selector/history.cpp
  selector/scorer.cpp
  selector/prober.cpp
  selector/selector.cpp)
target_link_libraries(ovg_selector PUBLIC ovg_vpngate)

# ---- egress: the way out, and the make-before-break switch -----------------
# The direct egress is always built: it is what the SOCKS5 tests run against,
# and what `egress_mode = direct` selects. The tunnel one only exists when there
# is a netstack for it to sit on, which is why it is a conditional source rather
# than a file full of #ifdefs.
set(OVG_EGRESS_SOURCES
  egress/egress.cpp
  egress/direct_egress.cpp
  egress/egress_manager.cpp)
if(OVG_WITH_TUNNEL)
  list(APPEND OVG_EGRESS_SOURCES egress/tunnel_egress.cpp)
endif()
add_library(ovg_egress STATIC ${OVG_EGRESS_SOURCES})
target_link_libraries(ovg_egress PUBLIC ovg_selector ovg_ovpn)
if(OVG_WITH_TUNNEL)
  target_link_libraries(ovg_egress PUBLIC ovg_netstack)
endif()

# ---- socks5: the front door ------------------------------------------------
# Depends on ovg_egress only for the Egress interface; it never names a tunnel,
# a node, or lwIP. That is what lets the whole proxy be tested over loopback
# against a DirectEgress with no VPN in sight.
add_library(ovg_socks5 STATIC
  socks5/protocol.cpp
  socks5/auth.cpp
  socks5/session.cpp
  socks5/udp_relay.cpp
  socks5/server.cpp)
target_link_libraries(ovg_socks5 PUBLIC ovg_egress)

# ---- health: scoring and the decision to switch ----------------------------
# Links the manager (it drives switches) but not socks5: the health picture is
# built from the egress alone, so nothing here can start depending on what the
# proxy happens to be doing.
add_library(ovg_health STATIC
  health/health_monitor.cpp
  health/switch_controller.cpp)
target_link_libraries(ovg_health PUBLIC ovg_egress)

# ---- app: assembly, admin endpoint, entry point ----------------------------
# The only target that links everything. Split into a library plus a two-line
# main so the assembled service can be constructed by a test without spawning a
# process.
add_library(ovg_app STATIC
  app/app.cpp
  app/admin_server.cpp)
target_link_libraries(ovg_app PUBLIC ovg_socks5 ovg_health)
if(OVG_WITH_TUNNEL)
  target_link_libraries(ovg_app PUBLIC ovg_netstack)
endif()

add_executable(openvpngate app/main.cpp)
target_link_libraries(openvpngate PRIVATE ovg_app)

# ---- diagnostic tools ------------------------------------------------------
# Not part of the service. See each file's header comment for what it answers.
add_executable(ovg_tunnel_smoke ${CMAKE_SOURCE_DIR}/tools/tunnel_smoke.cpp)
target_link_libraries(ovg_tunnel_smoke PRIVATE ovg_ovpn)
