cmake_minimum_required(VERSION 3.24)
project(DW3Recomp VERSION 0.1.0 LANGUAGES C CXX)
if(WIN32)
    enable_language(RC)  # the executable icon
endif()

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 11)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

if(MSVC)
    # /EHsc would let the compiler assume that extern "C" functions never throw, but the runtime's fatal-error and
    # state-restore paths throw through recompiled (C) frames, so use /EHs.
    string(REPLACE "/EHsc" "/EHs" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
endif()

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(Platform)
include(Dependencies)

option(DW3_BUILD_TESTS "Build unit tests" ON)
option(DW3_BUILD_APP "Build the game executable (requires recompiled sources)" ON)

# Location of the user-supplied disc image. Never committed.
set(DW3_GAME_DIR "${CMAKE_CURRENT_SOURCE_DIR}/game" CACHE PATH "Directory containing the Digimon World 3 (USA) bin/cue")

# Recompiled game code, produced by `psxrecomp config/dw3.toml emit --out generated` (never committed).
set(DW3_GENERATED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated" CACHE PATH "psxrecomp output directory")

add_subdirectory(tools/psxdisc)
add_subdirectory(tools/dw3extract)
add_subdirectory(tools/psxrecomp)
add_subdirectory(runtime)
add_subdirectory(render)
add_subdirectory(tools/texpack)

if(EXISTS "${DW3_GENERATED_DIR}/sources.txt")
    set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${DW3_GENERATED_DIR}/sources.txt")
    file(STRINGS "${DW3_GENERATED_DIR}/sources.txt" DW3_GENERATED_NAMES)
    list(TRANSFORM DW3_GENERATED_NAMES PREPEND "${DW3_GENERATED_DIR}/")
    add_library(dw3_generated STATIC ${DW3_GENERATED_NAMES})
    target_link_libraries(dw3_generated PUBLIC dw3runtime)
    # Generated code is large and label-heavy; always optimise it and keep it quiet.
    # -fexceptions: the HLE kernel's ReturnFromException unwinds through recompiled frames.
    target_compile_options(dw3_generated PRIVATE $<$<C_COMPILER_ID:Clang,GNU>:-O1> $<$<C_COMPILER_ID:Clang,GNU>:-w>
        $<$<C_COMPILER_ID:Clang,GNU>:-fexceptions> $<$<C_COMPILER_ID:MSVC>:/O1> $<$<C_COMPILER_ID:MSVC>:/w>
        $<$<C_COMPILER_ID:MSVC>:/EHsc>)
    set(DW3_HAVE_GENERATED ON)
    message(STATUS "DW3: using recompiled code from ${DW3_GENERATED_DIR}")
else()
    set(DW3_HAVE_GENERATED OFF)
    message(STATUS "DW3: no recompiled code found; run psxrecomp emit to generate it")
endif()

add_subdirectory(host)
add_subdirectory(tools/scenario)  # after the generated-code check: the runner needs dw3host

# The recompiled code as a game module (a shared library the executable loads at run time), for testing the module path
# from a checkout. Releases build the module on the player's machine instead.
option(DW3_BUILD_GAME_MODULE "Build the recompiled code as a loadable game module" OFF)
if(DW3_BUILD_GAME_MODULE AND DW3_HAVE_GENERATED)
    file(WRITE "${CMAKE_BINARY_DIR}/dw3game_build_id.c" "const char dw3_module_build_id[] = \"dev\";\n")
    add_library(dw3game MODULE ${DW3_GENERATED_NAMES} runtime/module/module_entry.c "${CMAKE_BINARY_DIR}/dw3game_build_id.c")
    target_include_directories(dw3game PRIVATE runtime/include)
    target_compile_definitions(dw3game PRIVATE DW3_MODULE=1)
    set_target_properties(dw3game PROPERTIES PREFIX "" C_VISIBILITY_PRESET hidden)
    target_compile_options(dw3game PRIVATE $<$<C_COMPILER_ID:Clang,GNU>:-O1> $<$<C_COMPILER_ID:Clang,GNU>:-w>
        $<$<C_COMPILER_ID:Clang,GNU>:-fexceptions> $<$<C_COMPILER_ID:Clang,GNU>:-ffp-contract=off>)
endif()

# Regenerate recompiled code: cmake --build <dir> --target recompile
add_custom_target(recompile
    COMMAND psxrecomp "${CMAKE_CURRENT_SOURCE_DIR}/config/dw3.toml" emit --out "${DW3_GENERATED_DIR}"
    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
    DEPENDS psxrecomp
    COMMENT "Recompiling game code with psxrecomp")

add_subdirectory(app/icon)
add_subdirectory(app/input)
add_subdirectory(app/settings)
add_subdirectory(app/ingame)
add_subdirectory(app/wizard)

if(DW3_BUILD_APP)
    add_subdirectory(app)
endif()

if(DW3_BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

# Packaging: `cmake --install <build dir> --prefix <dir>` or `cpack --config <build dir>/CPackConfig.cmake`.
# The Windows package is a ZIP with the executable (static CRT and SDL, no DLLs), the license and a note on
# supplying the disc. It contains no game data and no SDK files.
if(DW3_BUILD_APP)
    if(WIN32)
        set(CPACK_GENERATOR ZIP)
        set(CPACK_PACKAGE_FILE_NAME "DW3Recomp-${PROJECT_VERSION}-windows-x64")
    else()
        set(CPACK_GENERATOR TGZ)
        set(CPACK_PACKAGE_FILE_NAME "DW3Recomp-${PROJECT_VERSION}-${DW3_OS}-${DW3_ARCH}")
    endif()
    if(APPLE)
        # The .app bundle (see packaging/macos/README.md): cmake --build <dir> --target macos_app
        add_custom_target(macos_app
            COMMAND bash "${CMAKE_CURRENT_SOURCE_DIR}/packaging/macos/make_app.sh" "${CMAKE_BINARY_DIR}"
            DEPENDS dw3recomp
            USES_TERMINAL)
    endif()
    set(CPACK_PACKAGE_NAME "DW3Recomp")
    set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
    set(CPACK_PACKAGE_VENDOR "DW3Recomp")
    set(CPACK_COMPONENTS_ALL app)  # dependencies install their own files; the package has only ours
    set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY ON)
    set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
    include(CPack)
endif()
