set(GAME_DIR ${PROJECT_SOURCE_DIR}/game)

# Small standalone decoder, also usable by tests without SDL or game data.
add_library(opencmr2_bink STATIC video/bink_decoder.cpp)
target_include_directories(opencmr2_bink PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

# The decompiled game. zlib is C, as in the original (ZlibZutil.cpp is C++).
file(GLOB GAME_SOURCES CONFIGURE_DEPENDS ${GAME_DIR}/*.cpp)
file(GLOB ZLIB_C_SOURCES CONFIGURE_DEPENDS ${GAME_DIR}/Zlib*.cpp)
list(REMOVE_ITEM ZLIB_C_SOURCES ${GAME_DIR}/ZlibZutil.cpp)
set_source_files_properties(${ZLIB_C_SOURCES} PROPERTIES LANGUAGE C)

# Built as objects: the game and the platform layer call each other, so both
# go into one archive (opencmr2_platform) and link on every toolchain.
add_library(cmr2_game OBJECT ${GAME_SOURCES})
target_include_directories(cmr2_game PUBLIC ${GAME_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/port/compat)
target_compile_options(cmr2_game PRIVATE
    $<$<COMPILE_LANGUAGE:C>:-x c>
    "SHELL:-include \"${CMAKE_CURRENT_SOURCE_DIR}/port/types.h\""
    "SHELL:-include \"${CMAKE_CURRENT_SOURCE_DIR}/port/msvcrt.h\""
    -fms-extensions
    -Wno-everything
    -ferror-limit=0)
target_link_libraries(cmr2_game PUBLIC SDL3::SDL3)

# The original builds these with /QIfist: their float-to-integer conversions
# (FTOI, game/FloatToInt.h) round to nearest like x87 FISTP instead of
# truncating. Same list as CMR2Decomp's scripts/build.py.
set(GAME_QIFIST_UNITS
    Race StageUI TimingUtils Frontend FrontendScreens Game GameInfo Graphics Sprite Car Sound
    CarPhysics HudDash CarEffects TrackCollision RallyData Mesh Sector StageTiming StageObjects
    SceneNode FixedPoint RallyTiming NetRace)
list(TRANSFORM GAME_QIFIST_UNITS PREPEND ${GAME_DIR}/)
list(TRANSFORM GAME_QIFIST_UNITS APPEND .cpp)
set_source_files_properties(${GAME_QIFIST_UNITS} TARGET_DIRECTORY cmr2_game
    PROPERTIES COMPILE_DEFINITIONS CMR2_QIFIST)

# Platform layer plus the game objects; tests link this too.
add_library(opencmr2_platform STATIC
    platform/sys.cpp
    platform/keys.cpp
    platform/msvcrt.cpp
    port/disk.cpp
    port/replay_disk.cpp
    $<TARGET_OBJECTS:cmr2_game>)
target_sources(opencmr2_platform PRIVATE diagnostics/observer.cpp diagnostics/timing.cpp)
target_include_directories(opencmr2_platform PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${GAME_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/port/compat)
target_link_libraries(opencmr2_platform PUBLIC SDL3::SDL3)
target_sources(opencmr2_platform PRIVATE vr/vr_input.cpp vr/diagnostic.cpp)
if(OPENCMR2_VR)
    target_compile_definitions(opencmr2_platform PUBLIC OPENCMR2_VR=1)
    target_sources(opencmr2_platform PRIVATE vr/runtime.cpp)
    target_link_libraries(opencmr2_platform PUBLIC OpenXR::openxr_loader)
endif()


if(ANDROID)
    # SDLActivity loads libmain.so and calls its SDL_main.
    add_library(opencmr2 SHARED platform/main.cpp)
    set_target_properties(opencmr2 PROPERTIES OUTPUT_NAME main)
else()
    add_executable(opencmr2 platform/main.cpp)
endif()
target_link_libraries(opencmr2 PRIVATE opencmr2_platform)
if(WIN32)
    target_sources(opencmr2 PRIVATE ${PROJECT_SOURCE_DIR}/res/opencmr2.rc)
    # SDL3.dll (and any other runtime DLL) next to the executable.
    add_custom_target(opencmr2_runtime_files ALL DEPENDS opencmr2
        COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_RUNTIME_DLLS:opencmr2> $<TARGET_FILE_DIR:opencmr2>
        COMMAND_EXPAND_LISTS)
elseif(OPENCMR2_BUILT_SDL AND NOT ANDROID)
    # The SDL3 built here goes next to the executable, which looks for it
    # there, so the release archives run without SDL3 installed.
    if(APPLE)
        set_property(TARGET opencmr2 APPEND PROPERTY BUILD_RPATH "@executable_path")
    else()
        set_property(TARGET opencmr2 APPEND PROPERTY BUILD_RPATH "$ORIGIN")
    endif()
    add_custom_command(TARGET opencmr2 POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_SONAME_FILE:SDL3::SDL3> $<TARGET_FILE_DIR:opencmr2>)
endif()

# ---- shaders (GLSL -> SPIR-V, plus MSL for Metal; embedded) ----------------------
# SDL_GPU takes SPIR-V on Vulkan (Linux, Windows) and MSL on Metal (macOS).
# The MSL comes from the same SPIR-V through SPIRV-Cross; the SPIR-V bindings
# already follow SDL_GPU's resource order, so they become the Metal indices.
option(OPENCMR2_SHADERS_MSL "Also build Metal (MSL) shaders" ${APPLE})
# Recent glslang releases use glslang.exe; older packages keep glslangValidator.
find_program(GLSLANG_VALIDATOR NAMES glslangValidator glslang REQUIRED)
if(OPENCMR2_SHADERS_MSL)
    find_program(SPIRV_CROSS_EXECUTABLE spirv-cross)
    if(SPIRV_CROSS_EXECUTABLE)
        set(SPIRV_CROSS ${SPIRV_CROSS_EXECUTABLE})
    else()
        message(STATUS "OpenCMR2: building spirv-cross from source")
        include(FetchContent)
        # The command-line tool needs the GLSL, HLSL, MSL, C++ and reflection back ends.
        foreach(option SPIRV_CROSS_ENABLE_TESTS SPIRV_CROSS_ENABLE_C_API SPIRV_CROSS_SHARED)
            set(${option} OFF CACHE BOOL "" FORCE)
        endforeach()
        set(SPIRV_CROSS_CLI ON CACHE BOOL "" FORCE)
        set(SPIRV_CROSS_STATIC ON CACHE BOOL "" FORCE)
        set(SPIRV_CROSS_SKIP_INSTALL ON CACHE BOOL "" FORCE)
        FetchContent_Declare(SPIRV-Cross
            GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Cross.git
            GIT_TAG vulkan-sdk-1.4.363.0
            GIT_SHALLOW TRUE)
        FetchContent_MakeAvailable(SPIRV-Cross)
        set(SPIRV_CROSS $<TARGET_FILE:spirv-cross>)
    endif()
endif()

set(SHADER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/render/shaders)
set(SHADER_SOURCES fixed3d.vert fixedtl.vert fixed.frag fixed_opaque.frag fixed_simple.frag fixed_simple_opaque.frag clear.vert clear.frag)
set(SHADER_FILES)
foreach(shader IN LISTS SHADER_SOURCES)
    set(shader_source ${SHADER_DIR}/${shader})
    set(shader_defines)
    if(shader MATCHES "^fixed_.*\\.frag$")
        set(shader_source ${SHADER_DIR}/fixed.frag)
        if(shader MATCHES "simple")
            list(APPEND shader_defines -DFIXED_SIMPLE=1)
        endif()
        if(shader MATCHES "opaque")
            list(APPEND shader_defines -DFIXED_OPAQUE=1)
        endif()
    endif()
    set(output ${CMAKE_CURRENT_BINARY_DIR}/shaders/${shader}.spv)
    add_custom_command(
        OUTPUT ${output}
        COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/shaders
        COMMAND ${GLSLANG_VALIDATOR} -V --target-env vulkan1.0 ${shader_defines} -o ${output} ${shader_source}
        DEPENDS ${shader_source} ${SHADER_DIR}/common.glsl
        COMMENT "Compiling shader ${shader}")
    list(APPEND SHADER_FILES ${output})
    if(OPENCMR2_SHADERS_MSL)
        set(msl ${CMAKE_CURRENT_BINARY_DIR}/shaders/${shader}.msl)
        add_custom_command(
            OUTPUT ${msl}
            COMMAND ${SPIRV_CROSS} --msl --msl-version 20100 --msl-decoration-binding --output ${msl} ${output}
            DEPENDS ${output}
            COMMENT "Translating shader ${shader} to MSL")
        list(APPEND SHADER_FILES ${msl})
    endif()
endforeach()
set(SHADER_HEADER ${CMAKE_CURRENT_BINARY_DIR}/generated/shaders_spirv.h)
string(REPLACE ";" "|" SHADER_LIST "${SHADER_FILES}")
add_custom_command(
    OUTPUT ${SHADER_HEADER}
    COMMAND ${CMAKE_COMMAND} -DOUTPUT=${SHADER_HEADER} -DINPUTS=${SHADER_LIST} -P ${PROJECT_SOURCE_DIR}/cmake/embed_spirv.cmake
    VERBATIM
    DEPENDS ${SHADER_FILES} ${PROJECT_SOURCE_DIR}/cmake/embed_spirv.cmake
    COMMENT "Embedding shaders")

# ---- window icon (embedded) ------------------------------------------------------
# res/opencmr2.png is the source; the derived icons are regenerated with:
#   magick res/opencmr2.png -define icon:auto-resize=256,128,96,64,48,40,32,24,20,16 res/opencmr2.ico
#   magick res/opencmr2.png -resize 128x128 -type TrueColor BMP3:res/opencmr2_icon.bmp
#   magick res/opencmr2.png -resize 256x256 -strip PNG24:res/opencmr2_256.png
# The BMP is the window icon; the PNG goes to the Linux icon theme with the
# applications menu entry.
set(ICON_BMP ${PROJECT_SOURCE_DIR}/res/opencmr2_icon.bmp)
set(ICON_PNG ${PROJECT_SOURCE_DIR}/res/opencmr2_256.png)
set(ICON_HEADER ${CMAKE_CURRENT_BINARY_DIR}/generated/opencmr2_icon.h)
add_custom_command(
    OUTPUT ${ICON_HEADER}
    COMMAND ${CMAKE_COMMAND} -DOUTPUT=${ICON_HEADER} "-DINPUTS=${ICON_BMP}|${ICON_PNG}" -P ${PROJECT_SOURCE_DIR}/cmake/embed_spirv.cmake
    VERBATIM
    DEPENDS ${ICON_BMP} ${ICON_PNG} ${PROJECT_SOURCE_DIR}/cmake/embed_spirv.cmake
    COMMENT "Embedding the window icon")

# Applications menu entry: on its own, as it includes the real <windows.h>
# that port/compat replaces for the game.
add_library(opencmr2_shortcut STATIC platform/menu_shortcut.cpp ${ICON_HEADER})
target_include_directories(opencmr2_shortcut PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/generated)
target_link_libraries(opencmr2_shortcut PUBLIC SDL3::SDL3)
if(WIN32)
    target_link_libraries(opencmr2_shortcut PRIVATE ole32 uuid shell32)
endif()
target_link_libraries(opencmr2_platform PUBLIC opencmr2_shortcut)

# Native registry headers must also stay outside the game compatibility target.
add_library(opencmr2_install_discovery STATIC platform/install_discovery.cpp)
target_include_directories(opencmr2_install_discovery PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
if(WIN32)
    target_link_libraries(opencmr2_install_discovery PRIVATE advapi32)
endif()
target_link_libraries(opencmr2_platform PUBLIC opencmr2_install_discovery)

# ---- network (UDP with ENet, third_party/enet) -------------------------------------
set(ENET_DIR ${PROJECT_SOURCE_DIR}/third_party/enet)
add_library(enet STATIC
    ${ENET_DIR}/callbacks.c ${ENET_DIR}/compress.c ${ENET_DIR}/host.c ${ENET_DIR}/list.c
    ${ENET_DIR}/packet.c ${ENET_DIR}/peer.c ${ENET_DIR}/protocol.c ${ENET_DIR}/unix.c ${ENET_DIR}/win32.c)
target_include_directories(enet PUBLIC ${ENET_DIR}/include)
if(WIN32)
    target_link_libraries(enet PUBLIC ws2_32 winmm)
else()
    target_compile_definitions(enet PRIVATE HAS_FCNTL=1 HAS_POLL=1 HAS_GETADDRINFO=1 HAS_GETNAMEINFO=1
        HAS_INET_PTON=1 HAS_INET_NTOP=1 HAS_MSGHDR_FLAGS=1 HAS_SOCKLEN_T=1)
endif()

# Sessions over ENet, without SDL: tests run hosts and clients in one process.
add_library(opencmr2_net STATIC net/net_session.cpp)
target_include_directories(opencmr2_net PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
find_package(Threads REQUIRED)
target_link_libraries(opencmr2_net PUBLIC enet Threads::Threads)

# ---- platform subsystems ---------------------------------------------------------
target_sources(opencmr2_platform PRIVATE
    render/gfx_core.cpp
    render/gfx_gpu.cpp
    ${SHADER_HEADER}
    ${ICON_HEADER}
    audio/audio_sdl.cpp
    input/input_sdl.cpp
    video/movie.cpp
    net/net_api.cpp)
target_include_directories(opencmr2_platform PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/generated)
target_link_libraries(opencmr2_platform PRIVATE opencmr2_bink opencmr2_net)
