cmake_minimum_required(VERSION 3.25)
project(xd_port VERSION 0.4.2 LANGUAGES C CXX)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 20)

if (MSVC)
  set(XD_FLAG_UTF8 /utf-8)
  set(XD_FLAG_FORCE_INCLUDE /FI)
  set(XD_FLAG_NO_WARNINGS /w)
  set(XD_FLAG_NO_BUILTINS /Oi-)
  set(XD_FLAG_UNDEF_DEBUG /U_DEBUG)
  set(XD_FLAGS_OLD_CXX /permissive /Zc:strictStrings-)
  set(XD_FLAGS_NOOPT /Od /Ob0)
else ()
  set(XD_FLAG_UTF8 "")
  set(XD_FLAG_FORCE_INCLUDE -include)
  set(XD_FLAG_NO_WARNINGS -w)
  set(XD_FLAG_NO_BUILTINS -fno-builtin)
  set(XD_FLAG_UNDEF_DEBUG -U_DEBUG)
  set(XD_FLAGS_OLD_CXX -fpermissive)
  set(XD_FLAGS_NOOPT -O0)
  if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    set(XD_FLAGS_OLD_CXX -fms-extensions -Wno-c++11-narrowing -Wno-writable-strings -Wno-register
      -Wno-reserved-user-defined-literal -Wno-invalid-offsetof)
  endif ()
endif ()
set(XD_FLAGS_GAME_CODE "")
if (NOT MSVC)
  set(XD_FLAGS_GAME_CODE -mms-bitfields -fms-extensions -fno-strict-aliasing -fwrapv -fno-delete-null-pointer-checks
    -fsigned-char -fno-strict-enums)
endif ()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
  set(CMAKE_C_LINK_EXECUTABLE
    "<CMAKE_C_COMPILER> <FLAGS> <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> -Wl,--start-group <LINK_LIBRARIES> -Wl,--end-group")
  set(CMAKE_CXX_LINK_EXECUTABLE
    "<CMAKE_CXX_COMPILER> <FLAGS> <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> -Wl,--start-group <LINK_LIBRARIES> -Wl,--end-group")
endif ()
function(xd_link_windows_libs target scope)
  if (WIN32)
    target_link_libraries(${target} ${scope} ${ARGN})
  endif ()
endfunction()

if (CMAKE_SIZEOF_VOID_P EQUAL 8)
  message(STATUS "xd: 64-bit build")
elseif (CMAKE_SIZEOF_VOID_P EQUAL 4)
  message(STATUS "xd: 32-bit build")
else ()
  message(FATAL_ERROR "Unsupported pointer size ${CMAKE_SIZEOF_VOID_P}")
endif ()

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

set(SDL_SHARED ON CACHE BOOL "" FORCE)
set(SDL_STATIC OFF CACHE BOOL "" FORCE)
set(SDL_LIBC ON CACHE BOOL "" FORCE)
option(XD_LINUX_HEADLESS "Linux: build SDL without X11/Wayland (tools and tests only)" OFF)
if (XD_LINUX_HEADLESS)
  set(SDL_UNIX_CONSOLE_BUILD ON CACHE BOOL "" FORCE)
  foreach (_off X11 WAYLAND KMSDRM OPENGL OPENGLES VULKAN)
    set(SDL_${_off} OFF CACHE BOOL "" FORCE)
  endforeach ()
endif ()
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE)
set(SDL_TESTS OFF CACHE BOOL "" FORCE)
add_subdirectory(third_party/SDL EXCLUDE_FROM_ALL)

set(AURORA_SDL3_PROVIDER vendor CACHE STRING "" FORCE)
if (CMAKE_SIZEOF_VOID_P EQUAL 4)
  set(AURORA_DAWN_PROVIDER vendor CACHE STRING "" FORCE)
  set(AURORA_NOD_PROVIDER vendor CACHE STRING "" FORCE)
  set(DAWN_ENABLE_VULKAN OFF CACHE BOOL "" FORCE)
endif ()
set(AURORA_ENABLE_TESTS OFF CACHE BOOL "" FORCE)
if (APPLE)
  set(AURORA_NOD_PROVIDER package CACHE STRING "" FORCE)
endif ()
file(GLOB _aurora_patches CONFIGURE_DEPENDS "${PROJECT_SOURCE_DIR}/third_party/patches/aurora/*.patch")
list(SORT _aurora_patches)
set(_aurora_dir "${PROJECT_SOURCE_DIR}/third_party/aurora")
set(_aurora_stamp_file "${_aurora_dir}/.port_patches_applied")
set(_aurora_stamp "")
foreach (_patch ${_aurora_patches})
  file(SHA1 "${_patch}" _hash)
  get_filename_component(_name "${_patch}" NAME)
  string(APPEND _aurora_stamp "${_name} ${_hash}\n")
endforeach ()
set(_aurora_old_stamp "")
if (EXISTS "${_aurora_stamp_file}")
  file(READ "${_aurora_stamp_file}" _aurora_old_stamp)
endif ()
if (NOT _aurora_old_stamp STREQUAL _aurora_stamp)
  message(STATUS "Aurora patches changed: resetting the Aurora checkout and applying them all")
  execute_process(COMMAND git checkout -- .
    WORKING_DIRECTORY "${_aurora_dir}" RESULT_VARIABLE _result)
  if (NOT _result EQUAL 0)
    message(FATAL_ERROR "Couldn't reset the Aurora checkout (${_aurora_dir})")
  endif ()
  execute_process(COMMAND git clean -fdq -e ".*"
    WORKING_DIRECTORY "${_aurora_dir}" RESULT_VARIABLE _result)
  if (NOT _result EQUAL 0)
    message(FATAL_ERROR "Couldn't remove the patches' added files in ${_aurora_dir}")
  endif ()
  file(REMOVE "${_aurora_stamp_file}")
  foreach (_patch ${_aurora_patches})
    execute_process(COMMAND git apply "${_patch}"
      WORKING_DIRECTORY "${_aurora_dir}" RESULT_VARIABLE _result)
    if (NOT _result EQUAL 0)
      message(FATAL_ERROR "Aurora patch doesn't apply: ${_patch}")
    endif ()
    message(STATUS "Applied Aurora patch ${_patch}")
  endforeach ()
  file(WRITE "${_aurora_stamp_file}" "${_aurora_stamp}")
endif ()
if (NOT MSVC)
  include(CheckCXXSourceCompiles)
  set(_xd_jthread_test "#include <stop_token>\n#include <thread>\nint main() { std::jthread t([](std::stop_token) {}); return 0; }")
  set(_xd_saved_flags "${CMAKE_REQUIRED_FLAGS}")
  set(CMAKE_CXX_SCAN_FOR_MODULES OFF)
  list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES CMAKE_CXX_SCAN_FOR_MODULES)
  set(CMAKE_REQUIRED_FLAGS "${_xd_saved_flags} -std=c++20")
  check_cxx_source_compiles("${_xd_jthread_test}" XD_HAVE_JTHREAD)
  if (NOT XD_HAVE_JTHREAD)
    set(CMAKE_REQUIRED_FLAGS "${_xd_saved_flags} -std=c++20 -fexperimental-library")
    check_cxx_source_compiles("${_xd_jthread_test}" XD_HAVE_JTHREAD_EXPERIMENTAL)
    if (XD_HAVE_JTHREAD_EXPERIMENTAL)
      message(STATUS "xd: this C++ library has std::jthread only with -fexperimental-library: added")
      add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-fexperimental-library>)
      add_link_options(-fexperimental-library)
    else ()
      message(WARNING "xd: this C++ library has no std::jthread, with or without -fexperimental-library: "
        "Aurora will not compile (a newer compiler / Xcode is needed)")
    endif ()
  endif ()
  set(CMAKE_REQUIRED_FLAGS "${_xd_saved_flags}")
endif ()
add_subdirectory(third_party/aurora EXCLUDE_FROM_ALL)
include(third_party/aurora/cmake/AuroraCopyRuntimeDLLs.cmake)

foreach (_off CLI HTTP WEBSOCKET NET UPDATE CRASH CONFIG DISC UI SENTRY DISCORD)
  set(BOREALIS_ENABLE_${_off} OFF CACHE BOOL "" FORCE)
endforeach ()
foreach (_on DATA IO FILE_SELECT)
  set(BOREALIS_ENABLE_${_on} ON CACHE BOOL "" FORCE)
endforeach ()
set(BOREALIS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(BOREALIS_APP_SOURCE_DIR "${PROJECT_SOURCE_DIR}" CACHE PATH "" FORCE)
add_subdirectory(third_party/borealis EXCLUDE_FROM_ALL)

file(STRINGS "${PROJECT_SOURCE_DIR}/snapshot/CURRENT.txt" _current LIMIT_COUNT 1)
set(XD_SNAPSHOT_DIR "${PROJECT_SOURCE_DIR}/snapshot/${_current}" CACHE INTERNAL "")
set(XD_SNAPSHOT_OVERRIDE "" CACHE PATH "Build the game code from this folder instead of the snapshot")
if (XD_SNAPSHOT_OVERRIDE)
  set(XD_SNAPSHOT_DIR "${XD_SNAPSHOT_OVERRIDE}" CACHE INTERNAL "" FORCE)
  set(_current "override copy at ${XD_SNAPSHOT_OVERRIDE}")
endif ()
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
  "${PROJECT_SOURCE_DIR}/snapshot/CURRENT.txt" "${XD_SNAPSHOT_DIR}/objects.json")
message(STATUS "xd: building from decomp snapshot ${_current}")

option(XD_BUILD_GAME "Build the game program (xd) and the game code from the snapshot" ON)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
  add_compile_options(-ffp-contract=off)
endif ()
if (APPLE)
  if (NOT CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64")
    message(FATAL_ERROR "xd: the Mac build is x86_64 only (cmake --preset mac-release sets CMAKE_OSX_ARCHITECTURES); "
      "a native arm64 build cannot map the game's memory below 4 GB (docs/MAC.md)")
  endif ()
  add_compile_options(-ffp-contract=off) # as on Linux: nothing fused by the compiler itself
  list(APPEND XD_FLAGS_GAME_CODE -fsigned-char -fno-strict-enums)
  add_compile_definitions(_XOPEN_SOURCE=700 _DARWIN_C_SOURCE=1)
  include_directories(BEFORE "${PROJECT_SOURCE_DIR}/platform/compat/mac") # <malloc.h>, which macOS doesn't have
  set(XD_MAC_COMPAT_OPTION "-include${PROJECT_SOURCE_DIR}/platform/os/include/port_mac_compat.h")
  set(XD_MAC_LOW_LINK_OPTIONS -Wl,-pagezero_size,0x1000 -Wl,-no_pie -Wl,-image_base,0x40000000
    -Wl,-no_fixup_chains -Wl,-no_huge)
  add_link_options(${XD_MAC_LOW_LINK_OPTIONS})
endif ()

add_subdirectory(platform)
add_subdirectory(engine)
add_subdirectory(launcher)
add_subdirectory(games/xd)

if (MSVC AND DEFINED ENV{XD_TARGET_NOOPT} AND NOT "$ENV{XD_TARGET_NOOPT}" STREQUAL "")
  function(_xd_collect_targets _dir _out)
    get_property(_t DIRECTORY "${_dir}" PROPERTY BUILDSYSTEM_TARGETS)
    get_property(_subs DIRECTORY "${_dir}" PROPERTY SUBDIRECTORIES)
    foreach (_sub ${_subs})
      _xd_collect_targets("${_sub}" _more)
      list(APPEND _t ${_more})
    endforeach ()
    set(${_out} ${_t} PARENT_SCOPE)
  endfunction()
  _xd_collect_targets("${PROJECT_SOURCE_DIR}" _all_targets)
  set(_noopt_targets "")
  foreach (_t ${_all_targets})
    get_target_property(_type ${_t} TYPE)
    if (_type MATCHES "^(STATIC_LIBRARY|SHARED_LIBRARY|MODULE_LIBRARY|OBJECT_LIBRARY|EXECUTABLE)$")
      set(_skip FALSE)
      foreach (_re $ENV{XD_TARGET_NOOPT_EXCEPT}) # (e.g. xd_game: keep the game optimised)
        if (_t MATCHES "${_re}")
          set(_skip TRUE)
        endif ()
      endforeach ()
      if (_skip)
        continue()
      endif ()
      foreach (_re $ENV{XD_TARGET_NOOPT})
        if (_t MATCHES "${_re}")
          target_compile_options(${_t} PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:/Od /Ob0>)
          list(APPEND _noopt_targets ${_t})
          break()
        endif ()
      endforeach ()
    endif ()
  endforeach ()
  message(STATUS "xd: XD_TARGET_NOOPT: ${_noopt_targets}")
endif ()
