cmake_minimum_required(VERSION 3.16)
project(SFS3_CppClient LANGUAGES C CXX)   # C enabled so the vendored miniz.c compiles as C

# C++17 is the base requirement for the SDK.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Emit compile_commands.json so clangd (Zed/VS Code) resolves include paths.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

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

# Warning flags are compiler-specific: MSVC's cl doesn't understand -Wextra (and its
# -Wall is a different, far noisier beast), so use /W4 there instead.
if(MSVC)
  add_compile_options(/W4)
else()
  add_compile_options(-Wall -Wextra)
endif()

# Disable floating-point expression contraction (fusing a*b+c into a single FMA).
# clang and gcc enable it by default, which silently changes results by an ULP versus the
# per-operation rounding the Java/Swift/.NET clients perform - vector math (SFSVector2/3)
# then disagrees across clients on values that must compare and hash identically.
# MSVC's default /fp:precise already forbids the fusion, so it needs no flag.
if(NOT MSVC)
  add_compile_options(-ffp-contract=off)
endif()

# Opt into Asio's modern API: ec-taking sync ops (close/connect/shutdown/...) return
# void instead of the deprecated error_code. Matches the Asio docs and drops reliance
# on transitional behavior. Applies to every target so clangd sees it too.
add_compile_definitions(ASIO_NO_DEPRECATED)

# --- Windows: Asio prerequisites -------------------------------------------
# _WIN32_WINNT pins the target OS API level (0x0A00 = Windows 10/11) so Asio enables
# IOCP and stops warning about an undefined target version. WIN32_LEAN_AND_MEAN trims
# <windows.h>; NOMINMAX stops it defining min()/max() macros that clash with std::.
# Socket libs (ws2_32/mswsock): MSVC auto-links them via #pragma comment in Asio's
# headers; MinGW does not, so they're linked per-target below.
if(WIN32)
  add_compile_definitions(_WIN32_WINNT=0x0A00 WIN32_LEAN_AND_MEAN NOMINMAX)
  if(NOT MSVC)
    set(SFS_WIN_SOCKET_LIBS ws2_32 mswsock)
  endif()
endif()

# Threads (pthread) — needed by anything using std::thread (TcpClient, Asio).
find_package(Threads REQUIRED)

# macOS: with TLS enabled, cpp-httplib loads root certificates from the system Keychain via
# Security.framework (+ CoreFoundation). These must be linked or the TLS-enabled `test`
# fails with undefined _Sec*/_CF* symbols. Linux/Windows have their own cert paths and don't
# need this.
if(APPLE)
  find_library(SFS_COREFOUNDATION_LIB CoreFoundation REQUIRED)
  find_library(SFS_SECURITY_LIB Security REQUIRED)
  set(SFS_APPLE_TLS_LIBS ${SFS_COREFOUNDATION_LIB} ${SFS_SECURITY_LIB})
endif()

# --- Compression: vendored miniz (zlib-format) behind a thin SDK facade ------
# util/Compression.cpp exposes our deflate/inflate facade (util/Compression.h); the
# vendored amalgamated miniz (third_party/miniz/miniz.c) is the implementation. Only
# Compression.cpp includes miniz.h, but that's now just a convention — miniz has a real
# impl TU, so there's no single-TU restriction.
add_library(sfs_compression STATIC
  "${CMAKE_CURRENT_SOURCE_DIR}/util/Compression.cpp"
  "${CMAKE_CURRENT_SOURCE_DIR}/third_party/miniz/miniz.c")
target_include_directories(sfs_compression PUBLIC
  "${CMAKE_CURRENT_SOURCE_DIR}"
  "${CMAKE_CURRENT_SOURCE_DIR}/third_party")

# MINIZ_NO_ZLIB_COMPATIBLE_NAMES: stop miniz's `#define deflate mz_deflate` (etc.) from
# rewriting our own facade function names. The other two trim ZIP + file I/O we never use
# (leaner binary, smaller surface for the eventual UE target).
target_compile_definitions(sfs_compression PRIVATE
  MINIZ_NO_ZLIB_COMPATIBLE_NAMES
  MINIZ_NO_ARCHIVE_APIS
  MINIZ_NO_STDIO)

# Silence warnings for the third-party miniz.c ONLY; our Compression.cpp keeps full
# -Wall/-Wextra (//W4) coverage.
if(MSVC)
  set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/third_party/miniz/miniz.c"
    PROPERTIES COMPILE_OPTIONS "/w")
else()
  set_source_files_properties("${CMAKE_CURRENT_SOURCE_DIR}/third_party/miniz/miniz.c"
    PROPERTIES COMPILE_OPTIONS "-w")
endif()

# --- TLS: vendored mbedTLS 3.6.6 (HTTPS for the cpp-httplib backend) ---------
# Compiled from source as a static lib (like sfs_compression) against mbedTLS's bundled
# default config — no MBEDTLS_CONFIG_FILE override yet. Self-contained: no system
# OpenSSL/mbedTLS dependency. The PUBLIC include dir propagates to anything that links it,
# so the httplib facade TU finds <mbedtls/...>.
file(GLOB MBEDTLS_SOURCES CONFIGURE_DEPENDS
  "${CMAKE_CURRENT_SOURCE_DIR}/third_party/mbedtls/library/*.c")
add_library(sfs_mbedtls STATIC ${MBEDTLS_SOURCES})
target_include_directories(sfs_mbedtls PUBLIC
  "${CMAKE_CURRENT_SOURCE_DIR}/third_party/mbedtls/include")

# Silence warnings for third-party mbedTLS ONLY; our code keeps full -Wall/-Wextra (/W4).
if(MSVC)
  target_compile_options(sfs_mbedtls PRIVATE /w)
else()
  target_compile_options(sfs_mbedtls PRIVATE -w)
endif()

# --- Primary test executable: single main() + entity classes + logging ------
# Entity translation units (namespace `entities`, formerly proto2).
file(GLOB ENTITY_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/entities/*.cpp")

# Matching expressions (sfs3::entities::match). Separate variable — the entities/*.cpp
# glob above is NON-recursive, so it does not pick up this subdir.
file(GLOB MATCH_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/entities/match/*.cpp")

# Logging facade (sfs3::log).
file(GLOB LOG_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/log/*.cpp")

# Core protocol/IO TUs (sfs3::core).
file(GLOB CORE_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/core/*.cpp")

# Sys SystemController response handlers (sfs3::core::sys). Separate variable —
# the core/*.cpp glob above is NON-recursive, so it does not pick up this subdir.
file(GLOB RES_HANDLER_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/core/res_handlers/*.cpp")

# Buddy List TUs (sfs3::core::buddy). Another core/ subdir missed by the non-recursive
# core/*.cpp glob above.
file(GLOB BUDDY_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/core/buddy/*.cpp")

# RDP TUs (sfs3::core::rdp). Also a core/ subdir, so it needs its own glob (the
# core/*.cpp glob above is non-recursive). rdp/data/ (sfs3::core::rdp::data — the RDP
# packet/segment types) is a further subdir, so it needs its own glob too.
file(GLOB RDP_SOURCES CONFIGURE_DEPENDS
  "${CMAKE_CURRENT_SOURCE_DIR}/core/rdp/*.cpp"
  "${CMAKE_CURRENT_SOURCE_DIR}/core/rdp/data/*.cpp")

file(GLOB REQUESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/requests/*.cpp")

# Game API requests (sfs3::requests, requests/game/). Separate variable — the requests/*.cpp
# glob above is NON-recursive, so it does not pick up this subdir.
file(GLOB GAME_REQUESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/requests/game/*.cpp")

# Invitation requests (sfs3::requests::inv, requests/invitation/), likewise missed by the
# non-recursive glob above.
file(GLOB INVITATION_REQUESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/requests/invitation/*.cpp")

# Buddy List requests (sfs3::requests::buddy, requests/buddylist/), likewise missed by the
# non-recursive glob above. Empty for now: the ported ones are header-only.
file(GLOB BUDDY_REQUESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/requests/buddylist/*.cpp")

# MMO requests (sfs3::requests, requests/mmo/), likewise missed by the non-recursive glob above.
file(GLOB MMO_REQUESTS_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/requests/mmo/*.cpp")

# Networking TUs.
file(GLOB NET_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/net/*.cpp")

# util/ TUs (e.g. Strings.cpp). Compression.cpp is excluded: it is already compiled
# into the sfs_compression static library below, so globbing it here too would produce
# duplicate-symbol link errors.
file(GLOB UTIL_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/util/*.cpp")
list(FILTER UTIL_SOURCES EXCLUDE REGEX "/Compression\\.cpp$")

# Root-level TUs: main.cpp plus the top-level API (e.g. SmartFox.cpp). Globbed like the
# subdirs so a new root source isn't silently left out of the build.
file(GLOB ROOT_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")

# Header files for IDE visibility only. CMake does not compile these — listing them in
# add_executable just makes them appear in the Xcode/VS project navigator (so you can
# browse and set breakpoints in header-resident inline/template code). event/ is
# header-only as far as `test` is concerned, so without this it'd be invisible.
# third_party/ is deliberately excluded to keep Asio's ~hundreds of headers out.
file(GLOB_RECURSE PROJECT_HEADERS CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
list(FILTER PROJECT_HEADERS EXCLUDE REGEX "/third_party/")

# `src/` is the single include root: headers are included project-root-relative
# (e.g. #include "entities/SFSObject.h", "event/EventDispatcher.h"), so includes
# don't depend on the including file's location and survive moves. third_party/
# is added too so net code can #include "asio.hpp".
# --- The shippable API, as a static library --------------------------------
# This is the distributable product. main.cpp (the test entrypoint) is NOT here:
# it lives under tests/ and is compiled into the `test` harness executable. The
# root glob (ROOT_SOURCES) is therefore pure API now (e.g. SmartFox.cpp).
add_library(sfs_client STATIC
  ${ROOT_SOURCES} ${ENTITY_SOURCES} ${MATCH_SOURCES} ${LOG_SOURCES} ${NET_SOURCES}
  ${CORE_SOURCES} ${RES_HANDLER_SOURCES} ${BUDDY_SOURCES} ${RDP_SOURCES} ${REQUESTS_SOURCES}
  ${GAME_REQUESTS_SOURCES} ${INVITATION_REQUESTS_SOURCES} ${BUDDY_REQUESTS_SOURCES}
  ${MMO_REQUESTS_SOURCES} ${UTIL_SOURCES}
  ${PROJECT_HEADERS})

# PUBLIC so anything that links sfs_client (the test exe, and any future
# UE/app target) inherits the include roots, transitive libs, and the httplib
# TLS switch — no consumer target has to restate them.
target_include_directories(sfs_client PUBLIC
  "${CMAKE_CURRENT_SOURCE_DIR}"
  "${CMAKE_CURRENT_SOURCE_DIR}/third_party")
target_link_libraries(sfs_client PUBLIC
  Threads::Threads sfs_compression sfs_mbedtls
  ${SFS_WIN_SOCKET_LIBS} ${SFS_APPLE_TLS_LIBS})

# Turn on cpp-httplib's TLS layer (mbedTLS backend). PRIVATE: net/HttpRequester_httplib.cpp
# is the only TU in the project that includes httplib.h, so there is no second TU to agree
# with on the SSL-enabled class layouts, and no reason to push the define onto consumers.
target_compile_definitions(sfs_client PRIVATE CPPHTTPLIB_MBEDTLS_SUPPORT)

# --- Test / example harness (developer-only, never distributed) ------------
# Confined entirely to tests/: a distribution build (or a build with the option
# OFF) produces only sfs_client and never references the tests/ folder.
#
# The EXISTS test is what lets this same file ship inside a release package
# unchanged (build-release.sh strips tests/): with the folder gone the harness is
# skipped silently, so there is no separate packaged copy of this file to drift.
option(SFS_BUILD_TESTS "Build the test/example harness executable" ON)
if(SFS_BUILD_TESTS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/CMakeLists.txt")
  add_subdirectory(tests)
endif()

# Note: bench/Benchmarks.cc also defines main() and is intentionally left out of
# the build (it was excluded before too). Add a target here if you want to run it.

# --- Convention checks ------------------------------------------------------
# `make check` (or: cmake --build build --target check) enforces project rules,
# starting with "every header has #pragma once".
#
# Developer-only: tools/ sits beside src/ in the dev tree and is not shipped, so
# the target is defined only when the script is actually there.
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../tools/check_headers.sh")
  add_custom_target(check
    COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/../tools/check_headers.sh" "${CMAKE_CURRENT_SOURCE_DIR}"
    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
    COMMENT "Running convention checks")
endif()
