cmake_minimum_required(VERSION 3.20)
project(psiv_recomp C CXX)

set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# The C runtime inside the exe: testers need no Visual C++ redistributable
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")

# The version shown in the menu, the log and crash reports: base + commit
set(PSIV_VERSION_BASE "0.2.0-alpha")
set(PSIV_VERSION "" CACHE STRING "version string (empty: ${PSIV_VERSION_BASE} + the git commit)")
if(PSIV_VERSION)
  set(PSIV_VERSION_STRING ${PSIV_VERSION})
else()
  find_package(Git QUIET)
  if(GIT_FOUND)
    execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
                    OUTPUT_VARIABLE PSIV_COMMIT OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
  endif()
  if(PSIV_COMMIT)
    set(PSIV_VERSION_STRING "${PSIV_VERSION_BASE}+${PSIV_COMMIT}")
  else()
    set(PSIV_VERSION_STRING ${PSIV_VERSION_BASE})
  endif()
endif()

# PSIV_PORTABLE: a Linux build to hand out -- libstdc++/libgcc inside, and a
# static SDL3 when one is found (see tools/package_linux.sh)
option(PSIV_PORTABLE "self-contained Linux build" OFF)
# the headless and trace front ends (not on Android)
if(ANDROID)
  set(PSIV_TOOLS_DEFAULT OFF)
else()
  set(PSIV_TOOLS_DEFAULT ON)
endif()
option(PSIV_TOOLS "build psiv_headless and psiv_headless_trace" ${PSIV_TOOLS_DEFAULT})

# Generated by tools/emit.py from your ROM; never committed.
set(GEN_DIR ${CMAKE_SOURCE_DIR}/build/gen)
file(GLOB GEN_SOURCES CONFIGURE_DEPENDS ${GEN_DIR}/psiv_*.c)
if(NOT GEN_SOURCES)
  message(FATAL_ERROR "No generated code in build/gen: run tools/emit.py first")
endif()

if(MSVC)
  # /bigobj: some generated files hold thousands of labels
  set(WARN /W3 /WX /bigobj /wd4102)
  # portable C (fopen, strtok) rather than the MSVC-only _s variants
  add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
else()
  set(WARN -Wall -Werror -Wno-unused-label -Wno-unused-but-set-variable)
  # snprintf into a fixed buffer cuts long paths and names on purpose
  include(CheckCCompilerFlag)
  check_c_compiler_flag(-Wformat-truncation HAVE_WFORMAT_TRUNCATION)
  if(HAVE_WFORMAT_TRUNCATION)
    list(APPEND WARN -Wno-format-truncation)
  endif()
endif()

# Recompiled game code + CPU semantics
add_library(psiv_code STATIC ${GEN_SOURCES} ${GEN_DIR}/psiv_table.c runtime/m68k.c runtime/diag.c)
target_include_directories(psiv_code PUBLIC runtime ${GEN_DIR})
target_compile_options(psiv_code PRIVATE ${WARN})

# The same with block tracing and return-address checks, for debugging
if(PSIV_TOOLS)
  add_library(psiv_code_trace STATIC ${GEN_SOURCES} ${GEN_DIR}/psiv_table.c runtime/m68k.c runtime/diag.c)
  target_include_directories(psiv_code_trace PUBLIC runtime ${GEN_DIR})
  target_compile_definitions(psiv_code_trace PUBLIC PSIV_TRACE PSIV_CHECK_RETURNS)
  target_compile_options(psiv_code_trace PRIVATE ${WARN})
endif()

# Sound chips: ymfm (YM2612, BSD-3) and floooh's z80.h (zlib), fetched into
# third_party/ (see README). Built without warnings-as-errors: their code.
set(YMFM_DIR ${CMAKE_SOURCE_DIR}/third_party/ymfm/src)
set(CHIPS_DIR ${CMAKE_SOURCE_DIR}/third_party/chips)
if(NOT EXISTS ${YMFM_DIR}/ymfm_opn.cpp OR NOT EXISTS ${CHIPS_DIR}/z80.h)
  message(FATAL_ERROR "third_party/ymfm and third_party/chips are missing (see README)")
endif()
# (ymfm_opn.cpp is compiled inside runtime/ym2612.cpp)
add_library(psiv_sound_chips STATIC
  ${YMFM_DIR}/ymfm_adpcm.cpp ${YMFM_DIR}/ymfm_ssg.cpp ${YMFM_DIR}/ymfm_misc.cpp
  runtime/ym2612.cpp runtime/z80_impl.c)
target_include_directories(psiv_sound_chips PUBLIC runtime ${YMFM_DIR} ${CHIPS_DIR})

# Modern text renders with stb_truetype (third_party/stb, public domain; see README)
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/third_party/stb/stb_truetype.h)
  message(FATAL_ERROR "third_party/stb/stb_truetype.h is missing (see README)")
endif()
set_source_files_properties(runtime/hdtext.c PROPERTIES INCLUDE_DIRECTORIES ${CMAKE_SOURCE_DIR}/third_party/stb)

# The game's own stack (runtime/coro.h, for rewind): one routine in assembly per platform
if(MSVC)
  enable_language(ASM_MASM)
  set(CORO_ASM runtime/coro_x64_win.asm)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
  enable_language(ASM)
  set(CORO_ASM runtime/coro_arm64.S)
else()
  enable_language(ASM)
  set(CORO_ASM runtime/coro_x64_sysv.S)
endif()

# Hardware: bus, VDP, sound, frame loop (object files, so the code<->hardware calls link either way)
set(HW_SOURCES runtime/bus.c runtime/vdp.c runtime/sound.c runtime/algol.c runtime/snesbank.c runtime/music.c runtime/fpview.c runtime/walkto.c runtime/frame.c runtime/depth.c runtime/sfx.c runtime/sfxsynth.c runtime/hdtext.c
               runtime/romart.c runtime/uikit.c runtime/panels.c runtime/battleui.c runtime/battledesc.c runtime/rumble.c runtime/widemap.c runtime/mixbus.c
               runtime/weather.c runtime/fieldfx.c runtime/battlevox.c runtime/scenevox.c runtime/cheats.c runtime/journal.c runtime/rewind.c runtime/coro.c ${CORO_ASM})
set(TEST_SOURCES runtime/headless.c runtime/sha256.c runtime/testbot.c)

# Headless front end: scripted input, PNG screenshots
if(PSIV_TOOLS)
  add_executable(psiv_headless ${TEST_SOURCES} ${HW_SOURCES})
  target_link_libraries(psiv_headless PRIVATE psiv_code psiv_sound_chips)
  target_compile_options(psiv_headless PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:${WARN}>)

  add_executable(psiv_headless_trace ${TEST_SOURCES} ${HW_SOURCES})
  target_link_libraries(psiv_headless_trace PRIVATE psiv_code_trace psiv_sound_chips)
  target_compile_options(psiv_headless_trace PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:${WARN}>)
endif()

# The game: SDL3 window, input, pacing, saves
if(WIN32 AND EXISTS ${CMAKE_SOURCE_DIR}/third_party/SDL3-3.4.20/cmake)
  set(SDL3_DIR ${CMAKE_SOURCE_DIR}/third_party/SDL3-3.4.20/cmake)
endif()
find_package(SDL3 CONFIG)
if(SDL3_FOUND)
  set(GAME_SOURCES runtime/main_sdl.c runtime/touch.c runtime/unzip.c runtime/sha256.c ${HW_SOURCES})
  if(ANDROID)
    # SDLActivity loads libSDL3.so, then libmain.so and calls its SDL_main
    add_library(psiv SHARED ${GAME_SOURCES})
    set_target_properties(psiv PROPERTIES OUTPUT_NAME main)
    target_link_options(psiv PRIVATE -Wl,-z,max-page-size=16384)   # 16 KB pages (Android 15+)
  else()
    add_executable(psiv WIN32 ${GAME_SOURCES})
  endif()
  if(PSIV_PORTABLE AND TARGET SDL3::SDL3-static)
    target_link_libraries(psiv PRIVATE psiv_code psiv_sound_chips SDL3::SDL3-static)
  else()
    target_link_libraries(psiv PRIVATE psiv_code psiv_sound_chips SDL3::SDL3)
  endif()
  if(PSIV_PORTABLE AND NOT WIN32 AND NOT APPLE AND NOT ANDROID)
    target_link_options(psiv PRIVATE -static-libgcc -static-libstdc++)
    # runs on glibc 2.35 though built on a newer one (see the file)
    target_sources(psiv PRIVATE runtime/glibc_compat.c)
    set_source_files_properties(runtime/glibc_compat.c PROPERTIES COMPILE_OPTIONS "-fno-builtin")
  endif()
  target_compile_definitions(psiv PRIVATE PSIV_VERSION="${PSIV_VERSION_STRING}")
  target_compile_options(psiv PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:${WARN}>)
  if(WIN32)
    target_sources(psiv PRIVATE res/psiv.rc)           # the icon (tools/make_icon.py)
    add_custom_command(TARGET psiv POST_BUILD
      COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:SDL3::SDL3> $<TARGET_FILE_DIR:psiv>)
  endif()
  message(STATUS "PSIV Recomp ${PSIV_VERSION_STRING}")
else()
  message(WARNING "SDL3 not found: building only the headless front end")
endif()
