cmake_minimum_required(VERSION 3.28)  # 3.28+: FILE_SET CXX_MODULES
if(POLICY CMP0058)
	cmake_policy(SET CMP0058 NEW)
endif()
if(POLICY CMP0074)
	cmake_policy(SET CMP0074 NEW)
endif()
if(POLICY CMP0069)
	cmake_policy(SET CMP0069 NEW)
endif()

include(CheckCXXSymbolExists)

set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMake_modules/")

set(DEPS_DIR ${DEPS_DIR} "${CMAKE_CURRENT_SOURCE_DIR}/ref")
project("eu07")

# Clang is the compiler this project is built and tested with: clang on Linux,
# clang-cl on Windows, both pinned to 22 in CI. MSVC still configures and is not
# rejected, but nothing verifies it. GCC is not supported: its lazy loading of
# module interfaces is broken here, and working around it costs roughly twice
# the memory per compiler process.
if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang|MSVC")
	message(FATAL_ERROR
		"Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}. "
		"Build with Clang (CI uses 22) or MSVC.")
endif()

# Only Platform/Windows-MSVC.cmake passes /DWIN32; the GNU-driver platform files
# do not. Plenty of third-party code tests the unprefixed WIN32 rather than
# _WIN32 - asio and AMD's FSR2 among it, the latter swapping in POSIX shims for
# wcscpy_s and _countof that then collide with the real CRT. Define it here,
# before any add_subdirectory, so every target sees what an MSVC build saw.
if (WIN32 AND NOT MSVC)
	add_compile_definitions(WIN32)
endif()

#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize=undefined")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fsanitize=undefined -Wformat")
#set(CMAKE_LD_FLAGS "${CMAKE_LD_FLAGS} -fsanitize=address -fsanitize=undefined")

string(TIMESTAMP VERSION_YEAR "%y")
string(TIMESTAMP VERSION_MONTH "%m")
string(TIMESTAMP VERSION_DAY "%d")

# Use these as version numbers
set(VERSION_MAJOR ${VERSION_YEAR})
set(VERSION_MINOR ${VERSION_MONTH})
set(VERSION_PATCH ${VERSION_DAY})

set(VERSION_STRING "${VERSION_YEAR}.${VERSION_MONTH}.${VERSION_DAY}")


set(CMAKE_CXX_STANDARD 20)

file(GLOB HEADERS "*.h"
"Console/*.h"
"McZapkie/*.h"
"gl/*.h"
"network/*.h"
"vr/*.h"
"network/backend/*.h"
"widgets/*.h"
"launcher/*.h"
"extras/*.h"
"global_include/*.h"
"simulation/*.h"
"scene/*.h"
"rendering/*.h"
"audio/*.h"
"environment/*.h"
"application/*.h"
"input/*.h"
"scripting/*.h"
"model/*.h"
"utilities/*.h"
"world/*.h"
"vehicle/*.h")

set(GLOBAL_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(GLOBAL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/global_include")
set(IMGUI_DIR "${CMAKE_CURRENT_SOURCE_DIR}/imgui")

if (APPLE)
	set (CMAKE_FIND_FRAMEWORK LAST)
endif()

option(WITH_OPENGL_MODERN "Compile with OpenGL modern renderer" ON)
option(WITH_OPENGL_LEGACY "Compile with OpenGL legacy renderer" ON)
option(WITH_PYTHON "Python 3.14 embedded scripting" ON)
option(WITH_UART "Compile with libserialport" ON)
option(WITH_LUA "Compile with lua scripting support" ON)
option(WITH_OPENVR "Compile with OpenVR" OFF)
option(WITH_ZMQ "Compile with cppzmq" OFF)
option(WITH_CRASHPAD "Compile with crashpad" OFF)
option(WITH_DISCORD_RPC "Compile with DISCORD RICH PRESENCE" ON)
option(USE_LTO "Use link-time optimization" OFF)
option(WITH_BETTER_RENDERER "Experimental multi-backend renderer based on NVRHI" ON)
option(GENERATE_PDB "Generate executable with program debugging symbols" ON)
option(ENABLE_MCC "Enable multicore compilation" ON)
option(WITHDUMPGEN "Enable generating DMP files on crash" ON)
option(ENABLE_AVX2 "Enable AVX2 instruction set (requires AVX2-capable CPU, Haswell/2013 or newer)" ON)
option(ADD_VERSION_SUFFIX "Add date and commit hash to the executable name" ON)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)

# vcpkg init. The triplet has to follow the host, or a non-Windows build looks
# for its packages (DXC, used by the betterRenderer shader compiler) in a
# directory that only ever exists on Windows.
set(VCPKG_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/ref/vcpkg")
if (WIN32)
	set(VCPKG_TRIPLET "x64-windows")
elseif (APPLE)
	set(VCPKG_TRIPLET "x64-osx")
else()
	set(VCPKG_TRIPLET "x64-linux")
endif()
set(VCPKG_INSTALLED_PATH "${VCPKG_ROOT}/installed/${VCPKG_TRIPLET}")
list(APPEND CMAKE_PREFIX_PATH "${VCPKG_INSTALLED_PATH}")
list(APPEND CMAKE_MODULE_PATH "${VCPKG_INSTALLED_PATH}/share")

set(SOURCES
"model/Texture.cpp"
"utilities/Timer.cpp"
"world/Track.cpp"
"world/Traction.cpp"
"world/TractionPower.cpp"
"vehicle/Train.cpp"
"world/TrkFoll.cpp"
"vehicle/AirCoupler.cpp"
"model/AnimModel.cpp"
"vehicle/Button.cpp"
"vehicle/Camera.cpp"
"vehicle/Driver.cpp"
"application/driverhints.cpp"
"vehicle/DynObj.cpp"
"EU07.cpp"
"export_e3d_standalone.cpp"
"world/Event.cpp"
"world/EvLaunch.cpp"
"utilities/Float3d.cpp"
"vehicle/Gauge.cpp"
"utilities/Globals.cpp"
"utilities/Logs.cpp"
"McZapkie/friction.cpp"
"McZapkie/hamulce.cpp"
"McZapkie/Mover.cpp"
"McZapkie/Oerlikon_ESt.cpp"
"model/MdlMngr.cpp"
"world/MemCell.cpp"
"model/Model3d.cpp"
"world/mtable.cpp"
"utilities/parser.cpp"
"rendering/nullrenderer.cpp"
"rendering/renderer.cpp"
"model/ResourceManager.cpp"
"scene/sn_utils.cpp"
"world/Segment.cpp"
"environment/sky.cpp"
"environment/sun.cpp"
"environment/stars.cpp"
"rendering/lightarray.cpp"
"environment/skydome.cpp"
"audio/sound.cpp"
"world/Spring.cpp"
"rendering/frustum.cpp"
"application/uilayer.cpp"
"rendering/openglmatrixstack.cpp"
"environment/moon.cpp"
"input/command.cpp"
"input/keyboardinput.cpp"
"input/gamepadinput.cpp"
"input/drivermouseinput.cpp"
"utilities/translation.cpp"
"model/material.cpp"
"input/messaging.cpp"
"scene/scene.cpp"
"scene/logiccontainer.cpp"
"scene/prefab.cpp"
"scene/scenenode.cpp"
"simulation/simulation.cpp"
"model/vertex.cpp"
"audio/audio.cpp"
"audio/audiorenderer.cpp"
"utilities/motiontelemetry.cpp"
"utilities/utilities.cpp"
"application/uitranscripts.cpp"
"world/station.cpp"
"application/application.cpp"
"application/shotrunner.cpp"
"rendering/terrainclipmap.cpp"
"rendering/terraintileloader.cpp"
"rendering/terraintilepool.cpp"
"rendering/terrainmesharena.cpp"
"rendering/terrainground.cpp"
"simulation/loadprofile.cpp"
"simulation/cookdeps.cpp"
"simulation/logiccook.cpp"
"simulation/terrainbake.cpp"
"scene/quantizedmeshcooker.cpp"
"simulation/simulationtime.cpp"
"scene/sceneeditor.cpp"
"rendering/screenshot.cpp"
"input/driverkeyboardinput.cpp"
"application/drivermode.cpp"
"application/driveruilayer.cpp"
"application/driveruipanels.cpp"
"input/editorkeyboardinput.cpp"
"editor/editorSettings.cpp"
"editor/puwg1992.cpp"
"editor/polandmap.cpp"
"editor/turnouts.cpp"
"editor/orthophoto.cpp"
"editor/plan_chain.cpp"
"editor/plan_document_io.cpp"
"editor/plan_join.cpp"
"editor/plan_layout.cpp"
"editor/plan_model.cpp"
"editor/plan_rail_renderer.cpp"
"editor/plan_render.cpp"
"editor/plan_scn_export.cpp"
"editor/plan_sketch.cpp"
"editor/plan_solution.cpp"
"editor/plan_tilecache.cpp"
"editor/plan_turnout_geometry.cpp"
"editor/editorTerrain.cpp"
"editor/editorTerrainStreamer.cpp"
"application/editormode.cpp"
"input/editormouseinput.cpp"
"application/editoruilayer.cpp"
"application/editoruipanels.cpp"
"application/planpanel.cpp"
"application/scenarioloadermode.cpp"
"application/scenarioloaderuilayer.cpp"
"scene/scenenodegroups.cpp"
"simulation/simulationenvironment.cpp"
"simulation/simulationstateserializer.cpp"
"rendering/precipitation.cpp"
"scripting/pythonscreenviewer.cpp"
"utilities/dictionary.cpp"
"rendering/particles.cpp"
"utilities/headtrack.cpp"
"scripting/ladderlogic.cpp"
"rendering/geometrybank.cpp"
"rendering/openglcolor.cpp"
"simulation/simulationsounds.cpp"
"rendering/openglcamera.cpp"

"network/network.cpp"
"network/message.cpp"
"network/manager.cpp"

"widgets/vehiclelist.cpp"
"widgets/map.cpp"
"widgets/map_objects.cpp"
"widgets/popup.cpp"
"widgets/time.cpp"
"widgets/vehicleparams.cpp"
"widgets/trainingcard.cpp"
"widgets/perfgraphs.cpp"

"${DEPS_DIR}/glad/src/glad.c"
"${DEPS_DIR}/dds-ktx/src/dds-ktx.c"

"gl/shader.cpp"
"gl/vao.cpp"
"gl/ubo.cpp"
"gl/framebuffer.cpp"
"gl/renderbuffer.cpp"
"gl/postfx.cpp"
"gl/cubemap.cpp"
"gl/glsl_common.cpp"
"gl/buffer.cpp"
"gl/fence.cpp"
"gl/pbo.cpp"
"gl/query.cpp"

"imgui/imgui.cpp"
"imgui/imgui_demo.cpp"
"imgui/imgui_draw.cpp"
"imgui/imgui_widgets.cpp"
"imgui/imgui_impl_glfw.cpp"
"imgui/ImGuizmo.cpp"

"stb/stb_image.c"

"launcher/launchermode.cpp"
"launcher/scenery_list.cpp"
"launcher/launcheruilayer.cpp"
"launcher/vehicle_picker.cpp"
"launcher/keymapper.cpp"
"launcher/textures_scanner.cpp"
"launcher/scenery_scanner.cpp"

"vr/vr_interface.cpp"
)

set(DEFINITIONS)

# Definitions that used to live at the top of stdafx.h. They must be identical in
# every translation unit (glm is header-only and its GLM_FORCE_* switches change
# type layout), so they are set on the target rather than in a header that a file
# could forget to include first.
set(DEFINITIONS ${DEFINITIONS}
	"_USE_MATH_DEFINES"
	"NOMINMAX"
	"GLFW_INCLUDE_NONE"
	"GLM_FORCE_SWIZZLE"
	"GLM_ENABLE_EXPERIMENTAL"
	"GLM_FORCE_CTOR_INIT"
	# empty value, not 1: glm/simd/platform.h defines this itself as a bare
	# #define, and a differing definition would warn on every translation unit
	"GLM_FORCE_INTRINSICS=")

if (WIN32)
	# _WIN32_WINNT came from targetver.h, which only ever existed to be pulled in
	# by stdafx.h; keep the same Windows 7 API level now that both are gone
	set(DEFINITIONS ${DEFINITIONS} "WIN32_LEAN_AND_MEAN" "_WIN32_WINNT=0x0601")
endif()

if (APPLE)
	# parts of the codebase gate posix-only code on __unix__, which Apple does not define
	set(DEFINITIONS ${DEFINITIONS} "__unix__=1")
endif()

if (MSVC)
	set(DEFINITIONS ${DEFINITIONS} "GLM_FORCE_INLINE" "_CRTDBG_MAP_ALLOC")
endif()

if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
	set(DEFINITIONS ${DEFINITIONS} "GLM_FORCE_NEON")
elseif (ENABLE_AVX2)
	set(DEFINITIONS ${DEFINITIONS} "GLM_FORCE_AVX2")
endif()
set(SOURCES ${SOURCES} "imgui/imgui_impl_opengl3.cpp")
set(SOURCES ${SOURCES} "imgui/imgui_impl_opengl2.cpp")

if (WITH_CRASHPAD)
	set(DEFINITIONS ${DEFINITIONS} "WITH_CRASHPAD")
	set(SOURCES ${SOURCES} "utilities/crashreporter.cpp")
	if (WIN32)
		include_directories("${DEPS_DIR}/crashpad/include" "${DEPS_DIR}/crashpad/include/mini_chromium")
	else()
		include_directories("${DEPS_DIR}/crashpad/crashpad" "${DEPS_DIR}/crashpad/crashpad/third_party/mini_chromium/mini_chromium")
	endif()
endif()

if(WITH_PYTHON)
	set(DEFINITIONS ${DEFINITIONS} "WITH_PYTHON")
	set(SOURCES ${SOURCES} "scripting/PyInt.cpp")
else()
	set(SOURCES ${SOURCES} "scripting/PyIntStub.cpp")
endif()

if (WITH_UART)
	set(DEFINITIONS ${DEFINITIONS} "WITH_UART")
	set(SOURCES ${SOURCES} "utilities/uart.cpp")
endif()

if (WITH_ZMQ)
	set(DEFINITIONS ${DEFINITIONS} "WITH_ZMQ")
	set(SOURCES ${SOURCES} "input/zmq_input.cpp")
endif()

if (WITH_OPENVR)
	set(DEFINITIONS ${DEFINITIONS} "WITH_OPENVR")
	set(SOURCES ${SOURCES} "vr/openvr_imp.cpp")
endif()

if (WITH_LUA)
	set(DEFINITIONS ${DEFINITIONS} "WITH_LUA")
	set(SOURCES ${SOURCES} "scripting/lua.cpp")
endif()

if (WITHDUMPGEN)
	set(DEFINITIONS ${DEFINITIONS} "WITHDUMPGEN")
endif()

if (WITH_OPENGL_MODERN)
        set(SOURCES ${SOURCES}
            "rendering/opengl33geometrybank.cpp"
            "rendering/opengl33light.cpp"
            "rendering/opengl33skydome.cpp"
            "rendering/opengl33precipitation.cpp"
            "rendering/opengl33particles.cpp"
            "rendering/opengl33renderer.cpp")
		set(DEFINITIONS ${DEFINITIONS} "WITH_OPENGL_MODERN")
endif()

if (WITH_OPENGL_LEGACY)
        set(SOURCES ${SOURCES}
            "rendering/openglgeometrybank.cpp"
            "rendering/opengllight.cpp"
            "rendering/openglskydome.cpp"
            "rendering/openglprecipitation.cpp"
            "rendering/openglparticles.cpp"
            "rendering/openglrenderer.cpp")
		set(DEFINITIONS ${DEFINITIONS} "WITH_OPENGL_LEGACY")
endif()

add_subdirectory("${DEPS_DIR}/glfw/" EXCLUDE_FROM_ALL)

set(SOURCES ${SOURCES} "extras/piped_proc.cpp" "widgets/cameraview_extcam.cpp")

find_package(Git)

if (GIT_FOUND AND EXISTS "${CMAKE_SOURCE_DIR}/.git")
	execute_process(
	  COMMAND ${GIT_EXECUTABLE} describe --match=DUMMY --always --dirty
	  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
	  OUTPUT_VARIABLE GIT_HASH
	  OUTPUT_STRIP_TRAILING_WHITESPACE
	)

    execute_process(
	  COMMAND ${GIT_EXECUTABLE} show -s --format=%ci
	  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
	  OUTPUT_VARIABLE SRC_DATE
	  OUTPUT_STRIP_TRAILING_WHITESPACE
	)

    execute_process(
	  COMMAND ${GIT_EXECUTABLE} show -s --format=%cs
	  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
	  OUTPUT_VARIABLE SRC_SHORT_DATE
	  OUTPUT_STRIP_TRAILING_WHITESPACE
	)
else()
	string(TIMESTAMP SRC_SHORT_DATE %Y-%m-%d UTC)
	string(TIMESTAMP SRC_DATE UTC)
	set(GIT_HASH "(unknown)")
endif()

configure_file("${CMAKE_SOURCE_DIR}/version_info.h.in" "${CMAKE_BINARY_DIR}/geninc/version_info.h")

set (PREFIX "")
set (SUFFIX "")

if (ADD_VERSION_SUFFIX)
	set (SUFFIX "_${SRC_SHORT_DATE}_${GIT_HASH}")
endif()

set(OPENVR_LIBRARY "openvr_api")
set(OPENVR_INCLUDE_DIR "")

if (WIN32)
	set(DEFINITIONS ${DEFINITIONS} "HAVE_ROUND") # to make pymath to not redefine round
	# set(DEFINITIONS ${DEFINITIONS} "GLFW_DLL")

	set(SOURCES ${SOURCES} "windows.cpp" "Console.cpp" "Console/LPT.cpp" "Console/PoKeys55.cpp")
	#set(GLFW3_INCLUDE_DIR ${GLFW3_INCLUDE_DIR} "${DEPS_DIR}/glfw/include/")
	set(PNG_INCLUDE_DIRS ${PNG_INCLUDE_DIRS} "${DEPS_DIR}/libpng/include/")
	#set(ZLIB_INCLUDE_DIR ${ZLIB_INCLUDE_DIR} "${DEPS_DIR}/zlib/")
	set(OPENAL_INCLUDE_DIR ${OPENAL_INCLUDE_DIR} "${DEPS_DIR}/openal/include")
	set(LIBSNDFILE_INCLUDE_DIR ${LIBSNDFILE_INCLUDE_DIR} "${DEPS_DIR}/libsndfile/include")
	set(LUAJIT_INCLUDE_DIR ${LUAJIT_INCLUDE_DIR} "${DEPS_DIR}/luajit/include")
	set(libserialport_INCLUDE_DIR ${LIBSERIALPORT_INCLUDE_DIR} "${DEPS_DIR}/libserialport/include")
	set(OPENVR_INCLUDE_DIR "${DEPS_DIR}/openvr/include")

	#if(${CMAKE_EXE_LINKER_FLAGS} STREQUAL "/machine:x64")
	#	set (ARCH "x64")
	#else()
	#	set (ARCH "x86")
	#endif()

	set(ARCH "x64")
	set (PREFIX "-${ARCH}")

	#set(GLFW3_LIBRARIES ${GLFW3_LIBRARIES} "${DEPS_DIR}/glfw/lib/${ARCH}/glfw3dll.lib")
	set(PNG_LIBRARIES ${PNG_LIBRARIES} "${DEPS_DIR}/libpng/lib/${ARCH}/libpng16.lib")
	set(OPENAL_LIBRARY ${OPENAL_LIBRARY} "${DEPS_DIR}/openal/lib/${ARCH}/OpenAL32.lib")
	set(LIBSNDFILE_LIBRARY ${LIBSNDFILE_LIBRARY} "${DEPS_DIR}/libsndfile/lib/${ARCH}/libsndfile-1.lib")
	set(LUAJIT_LIBRARIES ${LUAJIT_LIBRARIES} "${DEPS_DIR}/luajit/lib/${ARCH}/lua51.lib")
	set(libserialport_LIBRARY ${LIBSERIALPORT_LIBRARY} "${DEPS_DIR}/libserialport/lib/${ARCH}/libserialport-0.lib")
	set(cppzmq_LIBRARY ${cppzmq_LIBRARY} "${DEPS_DIR}/cppzmq/lib/${ARCH}/libzmq.lib")
	set(OPENVR_LIBRARY "${DEPS_DIR}/openvr/lib/${ARCH}/openvr_api.lib")
endif()

set(cppzmq_INCLUDE_DIR ${cppzmq_INCLUDE_DIR} "${DEPS_DIR}/cppzmq/include")
set(GLM_INCLUDE_DIR ${GLM_INCLUDE_DIR} "${DEPS_DIR}/glm/")

# Process version.rc.in into version.rc
configure_file(${CMAKE_SOURCE_DIR}/eu07.rc.in
               ${CMAKE_BINARY_DIR}/eu07.rc @ONLY)

# Gated on WIN32, not MSVC: CMake only sets MSVC for the MSVC-style driver, and
# the Windows build uses clang's GNU driver, which needs the version resource
# and the icon just the same.
if (WIN32)
    # the Visual Studio generators enable RC on their own; Ninja does not
    enable_language(RC)
    set(SOURCES ${SOURCES} ${CMAKE_BINARY_DIR}/eu07.rc)
    set(SOURCES ${SOURCES} eu07.ico)
    if (MSVC)
        add_compile_options("/utf-8")   # clang's GNU driver already reads UTF-8
    endif()
endif()

add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS})

# C++20 module interface units. They must be declared through a CXX_MODULES file
# set so CMake runs the dependency scanner and orders the build; the matching
# .cpp implementation units stay ordinary sources in ${SOURCES}.
file(GLOB MODULE_INTERFACES CONFIGURE_DEPENDS "*.cppm"
"Console/*.cppm"
"McZapkie/*.cppm"
"gl/*.cppm"
"network/*.cppm"
"vr/*.cppm"
"network/backend/*.cppm"
"widgets/*.cppm"
"launcher/*.cppm"
"extras/*.cppm"
"global_include/*.cppm"
"global_include/interfaces/*.cppm"
"simulation/*.cppm"
"scene/*.cppm"
"rendering/*.cppm"
"audio/*.cppm"
"environment/*.cppm"
"application/*.cppm"
"input/*.cppm"
"scripting/*.cppm"
"model/*.cppm"
"utilities/*.cppm"
"world/*.cppm"
"editor/*.cppm"
"vehicle/*.cppm"
"simcore/*.cppm")

# Unlike ordinary sources, every interface unit in the file set is scanned even
# when the feature that owns it is switched off, so the optional ones have to be
# dropped explicitly or the scan fails on their missing SDK headers.
list(REMOVE_ITEM MODULE_INTERFACES "${CMAKE_CURRENT_SOURCE_DIR}/world/Curve.cppm")
if (NOT WITH_ZMQ)
	list(REMOVE_ITEM MODULE_INTERFACES "${CMAKE_CURRENT_SOURCE_DIR}/input/zmq_input.cppm")
endif()
if (NOT WITH_OPENVR)
	list(REMOVE_ITEM MODULE_INTERFACES "${CMAKE_CURRENT_SOURCE_DIR}/vr/openvr_imp.cppm")
endif()

set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)


target_compile_definitions(${PROJECT_NAME} PRIVATE ${DEFINITIONS})

target_include_directories(${PROJECT_NAME} PRIVATE
	"."
	"Console"
	"McZapkie"
	"gl"
	"${DEPS_DIR}/glad/include"
	"${DEPS_DIR}/dds-ktx/include"
	"${CMAKE_BINARY_DIR}/geninc"
	"${GLOBAL_INCLUDE_DIR}")

if (MSVC)
	# override cmake defaults
	set(CMAKE_CXX_FLAGS_RELWITHDEBINFO " /MD /Zi /O2 /DNDEBUG")
	set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO " /DEBUG /INCREMENTAL:NO /OPT:REF /OPT:ICF")

	# /wd4996: disable "deprecation" warnings
	# /wd4244: disable warnings for conversion with possible loss of data
	# /wd5033: disable because it is all over Python headers
	set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "/wd4996 /wd4244 /wd5033")
	set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/LARGEADDRESSAWARE")

	# /MAP is a link.exe option; lld-link, which clang-cl usually drives, spells
	# it differently. The map only feeds the debug-symbols artifact, whose upload
	# tolerates it being absent.
	if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
		target_link_options(${PROJECT_NAME} PRIVATE "/MAP")
	endif()
endif()

set_target_properties( ${PROJECT_NAME}
    PROPERTIES
    ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
    LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
    PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/pdb"
	OUTPUT_NAME "${PROJECT_NAME}${PREFIX}${SUFFIX}"
	DEBUG_POSTFIX "_d"
)

check_cxx_symbol_exists(__GNUC__ "" COMPILER_HAVE_GNUC)
if (COMPILER_HAVE_GNUC)
	set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fvisibility=hidden")
endif()

if (USE_LTO)
	set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()

if (WITH_OPENVR)
	target_include_directories(${PROJECT_NAME} PRIVATE ${OPENVR_INCLUDE_DIR})
	target_link_libraries(${PROJECT_NAME} ${OPENVR_LIBRARY})
endif()

if (WITH_CRASHPAD)
	if (WIN32)
		target_link_libraries(${PROJECT_NAME} "${DEPS_DIR}/crashpad/lib_md/base.lib")
		target_link_libraries(${PROJECT_NAME} "${DEPS_DIR}/crashpad/lib_md/client.lib")
		target_link_libraries(${PROJECT_NAME} "${DEPS_DIR}/crashpad/lib_md/util.lib")
	else()
		target_link_libraries(${PROJECT_NAME} "${DEPS_DIR}/crashpad/crashpad/out/Default/obj/client/libclient.a")
		target_link_libraries(${PROJECT_NAME} "${DEPS_DIR}/crashpad/crashpad/out/Default/obj/util/libutil.a")
		target_link_libraries(${PROJECT_NAME} "${DEPS_DIR}/crashpad/crashpad/out/Default/obj/third_party/mini_chromium/mini_chromium/base/libbase.a")
	endif()
endif()

target_link_libraries(${PROJECT_NAME} glfw)
# the cooked terrain heightfield stores its tiles compressed
target_link_libraries(${PROJECT_NAME} zstd::libzstd)

if(WITH_PYTHON)
	find_package(Python3 3.14 REQUIRED COMPONENTS Development)
	target_include_directories(${PROJECT_NAME} PRIVATE ${Python3_INCLUDE_DIRS})
	target_link_libraries(${PROJECT_NAME} ${Python3_LIBRARIES})

	if(WIN32)
		# Python3_ROOT_DIR is only an input hint and is not populated by
		# find_package(... COMPONENTS Development). Derive the install root from
		# the include dir (parent of <root>/include) when it was not provided.
		if(NOT Python3_ROOT_DIR)
			get_filename_component(Python3_ROOT_DIR "${Python3_INCLUDE_DIRS}" DIRECTORY)
		endif()
		set(EU07_PY_DLL "${Python3_ROOT_DIR}/python314.dll")
		set(EU07_PY_ZLIB "${Python3_ROOT_DIR}/DLLs/zlib1.dll")
		set(EU07_PY_LIBDIR "${Python3_ROOT_DIR}/Lib")
		set(EU07_PY_DLLSDIR "${Python3_ROOT_DIR}/DLLs")
	endif()
endif()

if (NOT WIN32)
	find_package(PNG 1.6 REQUIRED)
	target_link_libraries(${PROJECT_NAME} PNG::PNG)
else()
	include_directories(${PNG_INCLUDE_DIRS})
	target_link_libraries(${PROJECT_NAME} ${PNG_LIBRARIES})
endif()

find_package(zstd CONFIG QUIET)
if(NOT zstd_FOUND)
	find_package(PkgConfig REQUIRED)
	pkg_check_modules(ZSTD REQUIRED IMPORTED_TARGET libzstd)
	add_library(zstd::libzstd ALIAS PkgConfig::ZSTD)
endif()

find_package(Threads REQUIRED)
target_link_libraries(${PROJECT_NAME} Threads::Threads)

# the editor's map imagery talks to a WMS over https. windows has WinHttp in the platform sdk;
# everywhere else libcurl is the transport (see editor/orthophoto.cpp). it is not required: a tree
# without it builds an editor that draws and exports as usual, only with no imagery behind the plan
if (NOT WIN32)
	find_package(CURL)
	if (CURL_FOUND)
		target_link_libraries(${PROJECT_NAME} CURL::libcurl)
		target_compile_definitions(${PROJECT_NAME} PRIVATE WITH_CURL)
	else()
		message(WARNING
			"libcurl not found: the editor will have no map imagery. "
			"Install libcurl4-openssl-dev (Debian/Ubuntu) or libcurl-devel (Fedora) to get it back.")
	endif()
endif()

find_package(GLM REQUIRED)
target_include_directories(${PROJECT_NAME} PRIVATE ${GLM_INCLUDE_DIR})

# JSON lib
add_subdirectory(ref/json)
target_link_libraries(${PROJECT_NAME} nlohmann_json::nlohmann_json)
target_include_directories(${PROJECT_NAME} PRIVATE ${NLOHMANN_JSON_INCLUDE_INSTALL_DIR})

# track layout domain model used by the editor; plain C++20, no dependencies of its own



if (WITH_DISCORD_RPC)
	add_subdirectory(ref/discord-rpc)
	set_target_properties(discord-rpc PROPERTIES FOLDER "libraries")
	target_link_libraries(${PROJECT_NAME} discord-rpc)

	# Add the include directory so the compiler can find discord_rpc.h
	target_include_directories(${PROJECT_NAME} PUBLIC
		"${CMAKE_CURRENT_SOURCE_DIR}/ref/discord-rpc/include"
	)

	target_compile_definitions(${PROJECT_NAME} PUBLIC WITH_DISCORD_RPC=1)
else()
	target_compile_definitions(${PROJECT_NAME} PUBLIC WITH_DISCORD_RPC=0)
endif()

find_package(OpenAL REQUIRED)
if (TARGET OpenAL::OpenAL)
	target_link_libraries(${PROJECT_NAME} OpenAL::OpenAL)
else()
	target_include_directories(${PROJECT_NAME} PRIVATE ${OPENAL_INCLUDE_DIR})
	target_link_libraries(${PROJECT_NAME} ${OPENAL_LIBRARY})
endif()

find_package(SndFile REQUIRED)
target_link_libraries(${PROJECT_NAME} SndFile::sndfile)

if (WITH_LUA)
	find_package(LuaJIT REQUIRED)
	target_include_directories(${PROJECT_NAME} PRIVATE ${LUAJIT_INCLUDE_DIR})
	target_link_libraries(${PROJECT_NAME} ${LUAJIT_LIBRARIES})
endif()

if (WITH_UART)
	find_package(libserialport REQUIRED)
	target_include_directories(${PROJECT_NAME} PRIVATE ${libserialport_INCLUDE_DIR})
	target_link_libraries(${PROJECT_NAME} ${libserialport_LIBRARY})
endif()

find_package(ASIO REQUIRED)
target_link_libraries(${PROJECT_NAME} asio::asio)

if (WIN32)
	target_link_libraries(${PROJECT_NAME} ws2_32)
endif()

if (WITH_ZMQ)
	find_package(cppzmq REQUIRED)
	target_include_directories(${PROJECT_NAME} PRIVATE ${cppzmq_INCLUDE_DIR})
	target_link_libraries(${PROJECT_NAME} ${cppzmq_LIBRARY})
endif()

if (WIN32)
	add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
			COMMAND ${CMAKE_COMMAND} -E copy -t $<TARGET_FILE_DIR:${PROJECT_NAME}> $<TARGET_RUNTIME_DLLS:${PROJECT_NAME}>
			COMMAND_EXPAND_LISTS
	)
endif ()

if(WIN32 AND WITH_PYTHON)
	add_custom_command(
		TARGET ${PROJECT_NAME} POST_BUILD
		COMMAND ${CMAKE_COMMAND} -E copy_if_different "${EU07_PY_DLL}" "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
		COMMAND ${CMAKE_COMMAND} -E copy_if_different "${EU07_PY_ZLIB}" "$<TARGET_FILE_DIR:${PROJECT_NAME}>"
		COMMAND ${CMAKE_COMMAND} -E copy_directory "${EU07_PY_LIBDIR}" "$<TARGET_FILE_DIR:${PROJECT_NAME}>/python64/Lib"
		COMMAND ${CMAKE_COMMAND} -E copy_directory "${EU07_PY_DLLSDIR}" "$<TARGET_FILE_DIR:${PROJECT_NAME}>/python64/DLLs"
	)
endif()

# The module interface units live in their own library so that other targets --
# betterRenderer, which is a separate add_library -- can import them. A target
# can only consume C++20 modules by linking the target that owns the file set,
# and the executable's own modules are not reachable that way.
#
# The interfaces need exactly the flags the executable was given, so they are
# copied off it rather than restated; this has to happen after all the
# third-party wiring above and before betterRenderer is added, or the mirrored
# link list would point back at betterRenderer and make the dependency circular.
add_subdirectory(editor/tests)

add_library(eu07_modules STATIC)

get_target_property(EU07_INCLUDES ${PROJECT_NAME} INCLUDE_DIRECTORIES)
get_target_property(EU07_DEFINITIONS ${PROJECT_NAME} COMPILE_DEFINITIONS)
get_target_property(EU07_LINKS ${PROJECT_NAME} LINK_LIBRARIES)

target_sources(eu07_modules
	PUBLIC FILE_SET CXX_MODULES
	BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}
	FILES ${MODULE_INTERFACES})

target_include_directories(eu07_modules PUBLIC ${EU07_INCLUDES})
target_compile_definitions(eu07_modules PUBLIC ${EU07_DEFINITIONS})
target_link_libraries(eu07_modules PUBLIC ${EU07_LINKS})
set_target_properties(eu07_modules PROPERTIES
	CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF
	POSITION_INDEPENDENT_CODE ON)

target_link_libraries(${PROJECT_NAME} eu07_modules)

if (WITH_BETTER_RENDERER)
    add_subdirectory("betterRenderer")
	set_target_properties(betterRenderer betterRenderer_fsr2 betterRenderer_shaders PROPERTIES FOLDER "renderers")
	target_link_libraries(${PROJECT_NAME} "betterRenderer")
endif ()


# Enable compiling with debug symbols
if(GENERATE_PDB)
    if (MSVC)
        # Ensure Debug and Release configurations use appropriate runtime libraries
        set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zi /MDd")
        set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi /MD")

        # Add compile options for debugging
        add_compile_options(/Zi)   # Always enable debug symbols
        add_link_options(/DEBUG /MAP)

        # Ensure that the PCH is built with the correct runtime library for each configuration
        set_target_properties(${PROJECT_NAME} PROPERTIES
            COMPILE_OPTIONS "$<$<CONFIG:Debug>:/MDd>$<$<CONFIG:Release>:/MD>")
    else()
        # GCC/Clang (ensure -g is set for all configurations)
        add_compile_options(-g)
    endif()
endif()

# /MP is an MSVC-only option; clang-cl rejects it, and under Ninja the generator
# already runs one compiler process per core.
if(ENABLE_MCC AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "Ninja")
    message(STATUS "Enabling multi-processor compilation for MSVC.")
	add_compile_options(/MP)
endif()

if(ENABLE_AVX2)
    message(STATUS "Enabling AVX2 instruction set.")
    if (MSVC)
        target_compile_options(${PROJECT_NAME} PRIVATE /arch:AVX2)
    else()
        target_compile_options(${PROJECT_NAME} PRIVATE -mavx2)
    endif()
endif()