cmake_minimum_required(VERSION 3.20)
project(ShadowsOfTheEmpireRecompiled LANGUAGES C CXX)

set(CMAKE_C_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(RT64_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/rt64")
if(NOT EXISTS "${RT64_DIR}/CMakeLists.txt")
    message(FATAL_ERROR
        "RT64 submodule is missing. Run git submodule update --init --recursive.")
endif()
set(RT64_STATIC TRUE)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(RT64_SDL_WINDOW_VULKAN ON CACHE BOOL "Use SDL Vulkan windows" FORCE)
    find_package(SDL2 REQUIRED CONFIG)
endif()
add_subdirectory("${RT64_DIR}" "${CMAKE_BINARY_DIR}/rt64")
target_include_directories(rt64 PRIVATE "${CMAKE_BINARY_DIR}/rt64/src")

set(N64_MODERN_RUNTIME "${CMAKE_CURRENT_SOURCE_DIR}/third_party/N64ModernRuntime")
add_subdirectory("${N64_MODERN_RUNTIME}")

if(MSVC)
    # The upstream runtime currently adds a GCC warning switch
    # unconditionally. Replace it with the equivalent MSVC diagnostic setting.
    set_property(TARGET ultramodern PROPERTY COMPILE_OPTIONS /Zi /wd4100)
    set_property(TARGET librecomp PROPERTY COMPILE_OPTIONS /Zi /wd4100)
endif()

file(GLOB RECOMPILED_C CONFIGURE_DEPENDS
    "${CMAKE_CURRENT_SOURCE_DIR}/generated/RecompiledFuncs/*.c")
file(GLOB RECOMPILED_CXX CONFIGURE_DEPENDS
    "${CMAKE_CURRENT_SOURCE_DIR}/generated/RecompiledFuncs/*.cpp")

if(NOT RECOMPILED_C AND NOT RECOMPILED_CXX)
    message(FATAL_ERROR
        "No recompiled sources found. Run N64Recomp with sote.toml first.")
endif()

add_library(sote_recompiled STATIC ${RECOMPILED_C} ${RECOMPILED_CXX})
target_include_directories(sote_recompiled PRIVATE
    "${N64_MODERN_RUNTIME}/ultramodern/include"
    "${N64_MODERN_RUNTIME}/librecomp/include"
    "${N64_MODERN_RUNTIME}/N64Recomp/include"
)

if(MSVC)
    target_compile_options(sote_recompiled PRIVATE
        "/FI${CMAKE_CURRENT_SOURCE_DIR}/src/recomp_hooks.h"
        /Zi
        /wd4101
        /wd4189
        /wd4244
        /wd4701
    )
else()
    target_compile_options(sote_recompiled PRIVATE
        -include "${CMAKE_CURRENT_SOURCE_DIR}/src/recomp_hooks.h"
        -fno-strict-aliasing
        -Wno-unused-variable
        $<$<COMPILE_LANGUAGE:C>:-Wno-implicit-function-declaration>
    )
endif()

set(SOTE_RUNTIME_SOURCES
    src/controls_menu.cpp
    src/control_bindings.cpp
    src/modern_controls.cpp
    src/modern_aim.cpp
    src/frontend.cpp
    src/graphics_menu.cpp
    src/menu_model.cpp
    src/menu_canvas.cpp
    src/hud_labels.cpp
    src/menu_skin.cpp
    src/menu_renderer.cpp
    src/hd_audio.cpp
    src/pc_voices.cpp
    src/hd_music.cpp
    src/san_movies.cpp
    src/music_replacement.cpp
    src/libultra_stubs.c
    src/main.cpp
    src/register_overlays.cpp
    src/rt64_renderer.cpp
)

if(WIN32)
    list(APPEND SOTE_RUNTIME_SOURCES src/sote_recomp.rc)
endif()

# Generated from the ROM's audio microcode by RSPRecomp. Keeping it under
# generated/ makes the checked-in source tree independent of copyrighted ROM
# bytes while still enabling real synthesis after the generation step.
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/generated/aspMain.cpp")
    list(APPEND SOTE_RUNTIME_SOURCES
        generated/aspMain.cpp)
else()
    message(FATAL_ERROR
        "Missing generated audio RSP code. Run RSPRecomp with "
        "config/aspMain.toml.")
endif()

add_executable(sote_recomp ${SOTE_RUNTIME_SOURCES})
if(MSVC)
    # Explorer launches a GUI process, without a transient console. Keep main's
    # argument parsing for process-local tests and redirected diagnostics.
    target_link_options(sote_recomp PRIVATE /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup)
endif()
# Menu presentation reuses RT64's generated fullscreen/texture-copy shaders.
# RT64's build_shader() writes to CMAKE_BINARY_DIR/src/shaders, NOT to the
# rt64 subdirectory's binary tree. Its include path is PRIVATE to rt64, so the
# executable must also name that directory. Keep the dependency to ensure the
# generated headers exist before menu_renderer.cpp compiles on a clean build.
add_dependencies(sote_recomp rt64)
target_include_directories(sote_recomp PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/generated/RecompiledFuncs"
    "${CMAKE_BINARY_DIR}/src"
    "${N64_MODERN_RUNTIME}/ultramodern/include"
    "${N64_MODERN_RUNTIME}/librecomp/include"
    "${N64_MODERN_RUNTIME}/N64Recomp/include"
    "${RT64_DIR}/include"
    "${RT64_DIR}/src"
    "${RT64_DIR}/src/rhi"
    "${RT64_DIR}/src/render"
    "${RT64_DIR}/src/contrib"
    "${RT64_DIR}/src/contrib/plume"
    "${RT64_DIR}/src/contrib/imgui"
    "${RT64_DIR}/src/contrib/hlslpp/include"
    "${RT64_DIR}/src/contrib/nativefiledialog-extended/src/include"
)
target_link_libraries(sote_recomp PRIVATE
    sote_recompiled
    librecomp
    ultramodern
    rt64
)
target_compile_definitions(sote_recomp PRIVATE NOMINMAX)
set(RT64_SDL_BIN
    "${RT64_DIR}/src/contrib/mupen64plus-win32-deps/SDL2-2.26.3")
if(WIN32)
    target_include_directories(sote_recomp PRIVATE "${RT64_SDL_BIN}/include")
    target_link_directories(sote_recomp PRIVATE "${RT64_SDL_BIN}/lib/x64")
else()
    target_link_libraries(sote_recomp PRIVATE SDL2::SDL2)
endif()

# Host-side harness for the control-scheme module. It links only
# controls_menu.cpp, so it needs neither the ROM nor a graphics device, and
# stubs the one guest hook that module calls.
add_executable(controls_harness
    tools/controls_harness.cpp
    src/controls_menu.cpp
)
target_include_directories(controls_harness PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/src")
if(MSVC)
    target_compile_options(controls_harness PRIVATE /W4)
endif()
enable_testing()
add_test(NAME controls_harness COMMAND controls_harness)

add_executable(modern_controls_harness tools/modern_controls_harness.cpp
    src/modern_controls.cpp src/controls_menu.cpp)
target_include_directories(modern_controls_harness PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
add_test(NAME modern_controls_harness COMMAND modern_controls_harness)

add_executable(modern_aim_harness tools/modern_aim_harness.cpp
    src/modern_aim.cpp src/modern_controls.cpp src/controls_menu.cpp)
target_include_directories(modern_aim_harness PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src"
    "${N64_MODERN_RUNTIME}/N64Recomp/include")
add_test(NAME modern_aim_harness COMMAND modern_aim_harness "${CMAKE_BINARY_DIR}/aim-test-settings")

add_executable(pc_voices_harness tools/pc_voices_harness.cpp src/pc_voices.cpp)
target_include_directories(pc_voices_harness PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
add_test(NAME pc_voices_harness COMMAND pc_voices_harness)

add_executable(leebo_voices_harness tools/leebo_voices_harness.cpp src/hd_audio.cpp)
target_include_directories(leebo_voices_harness PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
add_test(NAME leebo_voices_harness COMMAND leebo_voices_harness
    "${CMAKE_CURRENT_SOURCE_DIR}/SotE_Recompiled"
    "${CMAKE_CURRENT_SOURCE_DIR}/tools/leebo_voice_cases.tsv")

add_executable(texture_slots_harness tools/texture_slots_harness.cpp)
target_include_directories(texture_slots_harness PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src" "${RT64_DIR}/src/contrib")
add_test(NAME texture_slots_harness COMMAND texture_slots_harness "${CMAKE_BINARY_DIR}/texture_slots_test")

add_library(sandec SHARED third_party/sandec/sandec.c)
set_target_properties(sandec PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
target_include_directories(sandec PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/third_party/sandec")
if(MSVC)
    target_compile_definitions(sandec PRIVATE __BYTE_ORDER__=1234 __ORDER_LITTLE_ENDIAN__=1234)
    target_compile_options(sandec PRIVATE /wd4996)
endif()
target_link_libraries(sote_recomp PRIVATE sandec)

add_executable(san_movies_harness tools/san_movies_harness.cpp src/san_movies.cpp)
target_link_libraries(san_movies_harness PRIVATE sandec)
target_compile_definitions(san_movies_harness PRIVATE SOTE_SAN_TESTING)
target_include_directories(san_movies_harness PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
add_test(NAME san_movies_harness COMMAND san_movies_harness
    "${CMAKE_BINARY_DIR}/san_movies_test"
    "${CMAKE_CURRENT_SOURCE_DIR}/SotE_Recompiled/Sdata")

add_executable(sandec_audio_cache
    tools/sandec_audio_cache.c
    third_party/sandec/sandec.c)
target_include_directories(sandec_audio_cache PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/third_party/sandec")
if(MSVC)
    target_compile_definitions(sandec_audio_cache PRIVATE
        __BYTE_ORDER__=1234 __ORDER_LITTLE_ENDIAN__=1234)
    target_compile_options(sandec_audio_cache PRIVATE /wd4996)
endif()

add_executable(menu_revamp_harness
    tools/menu_revamp_harness.cpp
    src/control_bindings.cpp
    src/menu_model.cpp
    src/menu_canvas.cpp
    src/menu_skin.cpp
    src/graphics_menu.cpp
    src/controls_menu.cpp
)
target_include_directories(menu_revamp_harness PRIVATE
    "${CMAKE_CURRENT_SOURCE_DIR}/src"
    "${N64_MODERN_RUNTIME}/N64Recomp/include"
    "${N64_MODERN_RUNTIME}/librecomp/include"
    "${N64_MODERN_RUNTIME}/ultramodern/include"
    "${RT64_DIR}/src/contrib"
)
add_test(NAME menu_revamp_harness COMMAND menu_revamp_harness
    "${CMAKE_BINARY_DIR}/menu_revamp_test")

if(MSVC)
    target_compile_options(sote_recomp PRIVATE /W4 /Zi)
    target_link_options(sote_recomp PRIVATE
        /DEBUG
        /OPT:NOICF
        "/MAP:${CMAKE_BINARY_DIR}/sote_recomp.map"
    )
    target_link_libraries(sote_recomp PRIVATE Dbghelp)
    set(RT64_DXC_BIN "${RT64_DIR}/src/contrib/dxc/bin/x64")
    add_custom_command(TARGET sote_recomp POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different
            "${RT64_SDL_BIN}/lib/x64/SDL2.dll"
            "${RT64_DXC_BIN}/dxcompiler.dll"
            "${RT64_DXC_BIN}/dxil.dll"
            "$<TARGET_FILE_DIR:sote_recomp>")
else()
    target_compile_options(sote_recomp PRIVATE
        -fno-strict-aliasing
        -fms-extensions
    )
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$")
        # RSPRecomp's vector implementation uses SSE4.1 intrinsics.
        target_compile_options(sote_recomp PRIVATE -msse4.1)
    endif()
endif()
