cmake_minimum_required(VERSION 3.20)
# macOS (Apple Silicon only; CI builds it on GitHub's runners, see .github/workflows): before project(), as CMake reads them there.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
    set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "")
    set(CMAKE_OSX_DEPLOYMENT_TARGET "15.0" CACHE STRING "")
endif()
project(AidynReChronicled 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(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})

if (APPLE)
    if (NOT CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
        message(FATAL_ERROR "macOS: Apple Silicon (arm64) only.")
    endif()
    enable_language(OBJC OBJCXX)
elseif (NOT CMAKE_SYSTEM_NAME MATCHES "Linux|Windows" OR NOT CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
    message(FATAL_ERROR "Linux x86_64 only (C7), Windows x86_64 cross-built from Linux (tools/windows), or macOS arm64 (CI).")
endif()

# RT64 and the runtime, as submodules pinned by new-game.sh (same arrangement as Zelda64Recomp).
# Changes to the pinned RT64 (tools/patches/rt64-*.patch, see each file) and to its plume submodule
# (tools/patches/plume-*.patch) are applied here once. Without git (the portable build's container) the tree is taken
# as it is: the source it's given has them applied already.
find_package(Git QUIET)
if (NOT GIT_FOUND)
    message(STATUS "git not found: tools/patches not checked (lib/rt64 is used as it is)")
endif()
foreach(target rt64 plume)
    if (NOT GIT_FOUND)
        break()
    endif()
    if (target STREQUAL "rt64")
        set(patch_dir ${CMAKE_SOURCE_DIR}/lib/rt64)
    else()
        set(patch_dir ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/plume)
    endif()
    file(GLOB target_patches ${CMAKE_SOURCE_DIR}/tools/patches/${target}-*.patch)
    foreach(patch ${target_patches})
        execute_process(COMMAND ${GIT_EXECUTABLE} -C ${patch_dir} apply --reverse --check ${patch}
                        RESULT_VARIABLE already_applied OUTPUT_QUIET ERROR_QUIET)
        if (NOT already_applied EQUAL 0)
            execute_process(COMMAND ${GIT_EXECUTABLE} -C ${patch_dir} apply ${patch} RESULT_VARIABLE applied)
            if (NOT applied EQUAL 0)
                message(FATAL_ERROR "Could not apply ${patch} to ${patch_dir}")
            endif()
            message(STATUS "Applied ${patch}")
        endif()
    endforeach()
endforeach()
set(RT64_STATIC TRUE)
add_compile_definitions(HLSL_CPU)
if (WIN32)
    # As RT64 does for itself: no min/max macros from windows.h, and the SDL 2.26 headers' _m_prefetch left out
    # (it clashes with clang-cl's builtin); no warnings for the C library's portable functions.
    add_compile_definitions(NOMINMAX __PRFCHWINTRIN_H _CRT_SECURE_NO_WARNINGS)
endif()
if (NOT WIN32 AND NOT APPLE)  # on Windows RT64 takes the window's HWND, as in Zelda64Recomp; on macOS the window and its Metal view
    set(RT64_SDL_WINDOW_VULKAN TRUE)
    add_compile_definitions(PLUME_SDL_VULKAN_ENABLED RT64_SDL_WINDOW_VULKAN)
endif()
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/rt64 ${CMAKE_BINARY_DIR}/rt64)
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime)
target_include_directories(rt64 PRIVATE ${CMAKE_BINARY_DIR}/rt64/src)

# The recompiled game code: generated by tools/recomp.sh, never committed (C1).
add_library(RecompiledFuncs STATIC)
# Hidden visibility: the game defines functions named like libc ones (strncpy, ...). Exported from the
# executable they would hijack the system libraries' own calls (libdbus crashed inside our strncpy).
target_compile_options(RecompiledFuncs PRIVATE -fvisibility=hidden -fno-strict-aliasing -Wno-unused-variable -Wno-implicit-function-declaration)
target_include_directories(RecompiledFuncs PRIVATE
    ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/ultramodern/include
    ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/librecomp/include
    ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/N64Recomp/include
)
file(GLOB FUNC_C_SOURCES ${CMAKE_SOURCE_DIR}/RecompiledFuncs/*.c)
file(GLOB FUNC_CXX_SOURCES ${CMAKE_SOURCE_DIR}/RecompiledFuncs/*.cpp)
if (NOT FUNC_C_SOURCES)
    message(FATAL_ERROR "RecompiledFuncs/ is empty: run tools/split.sh, tools/build_elf.sh and tools/recomp.sh first.")
endif()
target_sources(RecompiledFuncs PRIVATE ${FUNC_C_SOURCES} ${FUNC_CXX_SOURCES})

add_executable(AidynReChronicled
    src/main/main.cpp
    src/main/audio.cpp
    src/main/input.cpp
    src/main/controls.cpp
    src/main/rt64_context.cpp
    src/main/settings.cpp
    src/main/register_overlays.cpp
    src/main/report.cpp
    src/main/watch.cpp
    src/main/platform.cpp
    src/main/packs.cpp
    src/main/updater.cpp
    lib/monocypher/monocypher.c
    lib/monocypher/monocypher-ed25519.c
    src/game/pak.cpp
    src/game/page_words.cpp
    src/game/music_log.cpp
    src/game/patches.cpp
    src/game/save_pictures.cpp
    src/game/stubs.cpp
    rsp/aspMain.cpp
)
target_compile_options(AidynReChronicled PRIVATE -fno-strict-aliasing -fms-extensions)
if (CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
    target_compile_options(AidynReChronicled PRIVATE -march=nehalem)
endif()
# The version the title screen shows (generated/aidyn_version.h, tools/version.cmake): AIDYN_VERSION when given
# (make_release.sh: the release's), else from git on every build ("v0.4.0" on a release tag, "v0.4.0 dev" after it).
set(AIDYN_VERSION "" CACHE STRING "The release version shown on the title screen; empty: from git")
add_custom_target(aidyn_version
    COMMAND ${CMAKE_COMMAND} -DOUT=${CMAKE_BINARY_DIR}/generated/aidyn_version.h -DSRC=${CMAKE_SOURCE_DIR}
            -DFIXED=${AIDYN_VERSION} -P ${CMAKE_SOURCE_DIR}/tools/version.cmake
    BYPRODUCTS ${CMAKE_BINARY_DIR}/generated/aidyn_version.h ${CMAKE_BINARY_DIR}/generated/aidyn_build.h)
add_dependencies(AidynReChronicled aidyn_version)
target_include_directories(AidynReChronicled PRIVATE
    ${CMAKE_BINARY_DIR}/generated
    ${CMAKE_SOURCE_DIR}/src/main
    ${CMAKE_SOURCE_DIR}/lib/rt64/include
    ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/N64Recomp/include
    ${CMAKE_SOURCE_DIR}/lib/N64ModernRuntime/thirdparty/sse2neon
    ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib
    ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/hlslpp/include
    ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/dxc/inc
    ${CMAKE_SOURCE_DIR}/lib/rt64/src
    ${CMAKE_SOURCE_DIR}/lib/rt64/src/rhi
    ${CMAKE_SOURCE_DIR}/lib/rt64/src/render
    ${CMAKE_BINARY_DIR}/rt64/src
    ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended/src/include
    ${CMAKE_SOURCE_DIR}/lib/monocypher
)

find_package(SDL2 REQUIRED)
target_include_directories(AidynReChronicled PRIVATE ${SDL2_INCLUDE_DIRS})
if (WIN32)  # RT64's Windows SDL2 keeps its headers in include/, where "SDL2/SDL.h" expects include/SDL2/
    file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/sdl2-include)
    file(CREATE_LINK ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/mupen64plus-win32-deps/SDL2-2.26.3/include
         ${CMAKE_BINARY_DIR}/sdl2-include/SDL2 SYMBOLIC)
    target_include_directories(AidynReChronicled PRIVATE ${CMAKE_BINARY_DIR}/sdl2-include)
    # SDL2.lib and SDL2main.lib (RT64 adds their folder for its own targets only), and the SDK libraries RT64 names
    # with other capitals than the SDK's (tools/windows/toolchain.cmake links them in build/windows-case).
    target_link_directories(AidynReChronicled PRIVATE
        ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/mupen64plus-win32-deps/SDL2-2.26.3/lib/x64
        ${CMAKE_SOURCE_DIR}/build/windows-case)
    # A windowed program (no console): SDL2main gives it WinMain, which calls main().
    set_target_properties(AidynReChronicled PROPERTIES WIN32_EXECUTABLE TRUE)
    target_link_libraries(AidynReChronicled PRIVATE SDL2::SDL2main)
    # The function names in a .pdb beside the .exe (public symbols only: small), for reading crash reports'
    # addresses (llvm-symbolizer --obj=AidynReChronicled.exe 0x...). Never shipped.
    target_link_options(AidynReChronicled PRIVATE /DEBUG:FULL /OPT:REF /OPT:ICF)
    # The icon (Explorer, the taskbar, shortcuts): the swordsman, art/steam/icon.png from the art repository, as a
    # multi-size .ico in a resource (left out without the art).
    if (EXISTS ${CMAKE_SOURCE_DIR}/art/steam/icon.png)
        add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/aidyn.ico
            COMMAND magick ${CMAKE_SOURCE_DIR}/art/steam/icon.png -background none
                    -define icon:auto-resize=256,128,64,48,32,16 ${CMAKE_BINARY_DIR}/aidyn.ico
            DEPENDS ${CMAKE_SOURCE_DIR}/art/steam/icon.png VERBATIM)
        file(WRITE ${CMAKE_BINARY_DIR}/aidyn.rc "1 ICON \"aidyn.ico\"\n")
        set_source_files_properties(${CMAKE_BINARY_DIR}/aidyn.rc PROPERTIES OBJECT_DEPENDS ${CMAKE_BINARY_DIR}/aidyn.ico)
        target_sources(AidynReChronicled PRIVATE ${CMAKE_BINARY_DIR}/aidyn.rc)
    endif()
endif()
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED)
target_link_libraries(AidynReChronicled PRIVATE
    RecompiledFuncs librecomp ultramodern rt64 nfd SDL2::SDL2 ${CMAKE_DL_LIBS} Threads::Threads
)
if (APPLE)
    target_link_libraries(AidynReChronicled PRIVATE "-framework Cocoa -framework Metal -framework QuartzCore -framework IOKit")
elseif (NOT WIN32)
    target_link_libraries(AidynReChronicled PRIVATE "-latomic -static-libstdc++")
endif()

# tools/hdui: RT64's texture hash for textures the game hasn't drawn yet (portraits read from the ROM). A tool for the
# build machine: not made in the Windows cross-build (tools/windows).
if (NOT WIN32 AND NOT APPLE)
add_executable(tmemhash tools/hdui/tmemhash.cpp lib/rt64/src/common/rt64_load_types.cpp)
target_include_directories(tmemhash PRIVATE ${CMAKE_SOURCE_DIR}/lib/rt64/src ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib)
endif()
