cmake_minimum_required(VERSION 3.24)
project(kartpad_android_fixture LANGUAGES C CXX ASM)

if(NOT ANDROID OR NOT CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a")
  message(FATAL_ERROR "KartPad A0 supports Android arm64-v8a only")
endif()
if(NOT DEFINED DAWN_ANDROID_ROOT OR DAWN_ANDROID_ROOT STREQUAL "")
  message(FATAL_ERROR "DAWN_ANDROID_ROOT must name the prepared pinned Dawn package")
endif()
if(NOT DEFINED MINIZIP_ANDROID_ROOT OR MINIZIP_ANDROID_ROOT STREQUAL "" OR
   NOT EXISTS "${MINIZIP_ANDROID_ROOT}/CMakeLists.txt")
  message(FATAL_ERROR "MINIZIP_ANDROID_ROOT must name the prepared pinned minizip-ng source")
endif()
if(NOT DEFINED MBEDTLS_ANDROID_ROOT OR MBEDTLS_ANDROID_ROOT STREQUAL "" OR
   NOT EXISTS "${MBEDTLS_ANDROID_ROOT}/CMakeLists.txt")
  message(FATAL_ERROR "MBEDTLS_ANDROID_ROOT must name the prepared pinned Mbed TLS source")
endif()

find_package(Threads REQUIRED)
find_package(SDL3 REQUIRED CONFIG)
# The Android toolchain intentionally roots package discovery inside the NDK
# and Prefab sysroots. Dawn is a separately hash-verified host path, so include
# its relocatable config directly rather than weakening global root modes.
include("${DAWN_ANDROID_ROOT}/lib/cmake/Dawn/DawnConfig.cmake")
if(NOT TARGET dawn::webgpu_dawn)
  message(FATAL_ERROR "Pinned Dawn config did not define dawn::webgpu_dawn")
endif()

get_filename_component(KARTPAD_REPO_ROOT
  "${CMAKE_CURRENT_LIST_DIR}/../../../../.." ABSOLUTE)

set(MZ_COMPAT OFF CACHE BOOL "" FORCE)
set(MZ_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(MZ_BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
set(MZ_BUILD_FUZZ_TESTS OFF CACHE BOOL "" FORCE)
set(MZ_CODE_COVERAGE OFF CACHE BOOL "" FORCE)
set(MZ_FETCH_LIBS OFF CACHE BOOL "" FORCE)
set(MZ_BZIP2 OFF CACHE BOOL "" FORCE)
set(MZ_LZMA OFF CACHE BOOL "" FORCE)
set(MZ_ZSTD OFF CACHE BOOL "" FORCE)
set(MZ_PKCRYPT OFF CACHE BOOL "" FORCE)
set(MZ_WZAES OFF CACHE BOOL "" FORCE)
set(MZ_OPENSSL OFF CACHE BOOL "" FORCE)
set(MZ_LIBBSD OFF CACHE BOOL "" FORCE)
set(MZ_ICONV OFF CACHE BOOL "" FORCE)
set(MZ_DECOMPRESS_ONLY ON CACHE BOOL "" FORCE)
set(SKIP_INSTALL_ALL ON CACHE BOOL "" FORCE)
add_subdirectory("${MINIZIP_ANDROID_ROOT}" minizip-ng EXCLUDE_FROM_ALL)
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(USE_SHARED_MBEDTLS_LIBRARY OFF CACHE BOOL "" FORCE)
set(USE_STATIC_MBEDTLS_LIBRARY ON CACHE BOOL "" FORCE)
set(MBEDTLS_FATAL_WARNINGS OFF CACHE BOOL "" FORCE)
add_compile_options(
  "-ffile-prefix-map=${MBEDTLS_ANDROID_ROOT}=mbedtls-4.1.1"
  "-fmacro-prefix-map=${MBEDTLS_ANDROID_ROOT}=mbedtls-4.1.1")
add_subdirectory("${MBEDTLS_ANDROID_ROOT}" mbedtls EXCLUDE_FROM_ALL)

set(KARTPAD_GAME_RUNTIME_SOURCE "" CACHE PATH
  "Prepared private WiiCompiled source; empty builds the source-only fixture")
set(KARTPAD_TRANSLATED_SHARD_MANIFEST "" CACHE FILEPATH
  "Ignored private translated shard manifest used by game-runtime builds")

if(KARTPAD_GAME_RUNTIME_SOURCE)
  if(NOT EXISTS "${KARTPAD_GAME_RUNTIME_SOURCE}/CMakeLists.txt")
    message(FATAL_ERROR "Prepared game runtime is missing: ${KARTPAD_GAME_RUNTIME_SOURCE}")
  endif()
  if(NOT MKW_GAME_PACK STREQUAL "APP" AND NOT EXISTS "${KARTPAD_TRANSLATED_SHARD_MANIFEST}")
    message(FATAL_ERROR
      "Private translated shard manifest is missing: ${KARTPAD_TRANSLATED_SHARD_MANIFEST}")
  endif()
  add_compile_options(
    "-ffile-prefix-map=${KARTPAD_REPO_ROOT}=."
    "-fmacro-prefix-map=${KARTPAD_REPO_ROOT}=."
    # Prepared runtimes may live outside this checkout, e.g. a release worktree.
    "-ffile-prefix-map=${KARTPAD_GAME_RUNTIME_SOURCE}=wiicompiled-runtime"
    "-fmacro-prefix-map=${KARTPAD_GAME_RUNTIME_SOURCE}=wiicompiled-runtime")
  list(PREPEND CMAKE_PREFIX_PATH "${DAWN_ANDROID_ROOT}")
  set(Dawn_DIR "${DAWN_ANDROID_ROOT}/lib/cmake/Dawn" CACHE PATH "" FORCE)
  # aurora-main is the classic graphics layer; aurora-wiicompiled is WiiCompiled 0.9's.
  set(KARTPAD_AURORA_DIR "aurora-main" CACHE STRING "Graphics layer folder in the runtime source")
  set(MKW_AURORA_DIR "${KARTPAD_GAME_RUNTIME_SOURCE}/${KARTPAD_AURORA_DIR}" CACHE PATH "" FORCE)
  if(KARTPAD_TRANSLATED_SHARD_MANIFEST)
    set(MKW_TRANSLATED_SHARD_MANIFEST
      "${KARTPAD_TRANSLATED_SHARD_MANIFEST}" CACHE FILEPATH "" FORCE)
  endif()
  set(MKW_KARTPAD_RUNTIME_INCLUDE
    "${KARTPAD_REPO_ROOT}/runtime/include" CACHE PATH "" FORCE)
  set(MKW_KARTPAD_REPO_ROOT "${KARTPAD_REPO_ROOT}" CACHE PATH "" FORCE)
  set(MKW_TRANSLATED_COMPILE_JOBS 2 CACHE STRING "" FORCE)
  # Definitions that shape runtime code inlined into translated functions. One
  # list, shared with scripts/build-game-pack.sh so a game pack matches its app:
  # KARTPAD_ANDROID_COMBINED_FENV preserves arithmetic/rounding semantics while
  # merging the scalar exception capture + clear at an opaque ARM64 call
  # boundary; KARTPAD_ANDROID_UNOBSERVED_FP_STATUS skips host overflow/underflow/
  # inexact capture while the guest leaves the matching FPSCR enables clear
  # (see docs/artifacts/2026-09-23/android-copy-stream-loop.md).
  file(STRINGS "${CMAKE_CURRENT_LIST_DIR}/translated-definitions.txt" KARTPAD_TRANSLATED_DEFINITIONS)
  add_compile_definitions(${KARTPAD_TRANSLATED_DEFINITIONS})
  add_subdirectory("${KARTPAD_GAME_RUNTIME_SOURCE}" game-runtime)
  # Local debug game builds hash pipeline configurations on the measured draw path.
  # Keep xxHash's debug assertions; optimize only this measured integer hot path.
  # Release/RelWithDebInfo already provide optimization through their toolchain.
  target_compile_options(xxhash PRIVATE "$<$<CONFIG:Debug>:-O2>")
  if(TARGET KartPadDual)
    set(KARTPAD_GAME_RUNTIME_TARGET KartPadDual)
  elseif(TARGET WiiCompiled)
    set(KARTPAD_GAME_RUNTIME_TARGET WiiCompiled)
  else()
    message(FATAL_ERROR "Prepared game runtime has no Android product target")
  endif()
  # Keep JNI exports, but bind calls to functions defined in this same product
  # locally. Function-only binding preserves external data-symbol semantics.
  target_link_options(${KARTPAD_GAME_RUNTIME_TARGET} PRIVATE "-Wl,-Bsymbolic-functions")
  # Android 9's libandroid.so also exports __emutls_get_address. Listing
  # libc++_shared.so first makes this runtime and the game pack bind the same
  # emulated-TLS copy; otherwise the pack never sees the CPU context.
  target_link_options(${KARTPAD_GAME_RUNTIME_TARGET} BEFORE PRIVATE "-lc++_shared")
  target_sources(${KARTPAD_GAME_RUNTIME_TARGET} PRIVATE
    kartpad_touch_input_jni.cpp
    kartpad_runtime_settings_jni.cpp
    kartpad_controller_slots_jni.cpp
    kartpad_mii_jni.cpp
    retro_rewind_archive_jni.cpp
    "${KARTPAD_REPO_ROOT}/runtime/src/android/touch_input.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/android/runtime_settings.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/android/controller_mapping.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/android/scalar_fenv.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/android/diagnostic_log.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/android/gpu_driver.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/hle/net/android_mbedtls.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/retro_rewind/archive_extract.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/retro_rewind/archive_path.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/retro_rewind/archive_scan.cpp")
  target_include_directories(${KARTPAD_GAME_RUNTIME_TARGET} PRIVATE
    "${KARTPAD_REPO_ROOT}/runtime/include")
  target_link_libraries(${KARTPAD_GAME_RUNTIME_TARGET} PRIVATE
    MINIZIP::minizip mbedtls)
  # Player-installed Vulkan drivers (Turnip, newer Qualcomm builds) through libadrenotools,
  # BSD-2-Clause (runtime/src/android/gpu_driver.cpp). Dawn's own dlopen("libvulkan.so") is
  # redirected to the loaded driver, so the shared Dawn package stays unchanged. The four hook
  # libraries ship in the APK (app/build.gradle.kts adds them to the native targets) and carry
  # their own C++ runtime so they load in adrenotools' namespace whatever else is loaded.
  add_subdirectory("${KARTPAD_REPO_ROOT}/android/third_party/adrenotools" adrenotools EXCLUDE_FROM_ALL)
  foreach(hook IN ITEMS hook_impl main_hook file_redirect_hook gsl_alloc_hook)
    target_link_options(${hook} PRIVATE "-static-libstdc++")
  endforeach()
  target_link_libraries(${KARTPAD_GAME_RUNTIME_TARGET} PRIVATE adrenotools)
  target_link_options(${KARTPAD_GAME_RUNTIME_TARGET} PRIVATE "-Wl,--wrap=dlopen" "-Wl,--wrap=dlclose")
else()
  add_library(main SHARED
    fixture_main.cpp
    guest_memory_fixture.cpp
    scheduler_fixture.cpp
    android_tls_fixture.cpp
    android_tls_loopback_fixture.cpp
    fiber_switch_android.S
    kartpad_controller_slots_fixture_jni.cpp
    kartpad_runtime_settings_fixture_jni.cpp
    kartpad_touch_input_jni.cpp
    retro_rewind_archive_jni.cpp
    "${KARTPAD_REPO_ROOT}/runtime/src/android/touch_input.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/hle/net/android_mbedtls.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/scheduler/guest_scheduler.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/retro_rewind/archive_extract.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/retro_rewind/archive_path.cpp"
    "${KARTPAD_REPO_ROOT}/runtime/src/retro_rewind/archive_scan.cpp")
  target_compile_features(main PRIVATE cxx_std_20)
  target_compile_options(main PRIVATE -Wall -Wextra -Werror -fvisibility=hidden)
  target_include_directories(main PRIVATE
    "${KARTPAD_REPO_ROOT}/runtime/include")
  target_link_libraries(main PRIVATE SDL3::SDL3 dawn::webgpu_dawn
    MINIZIP::minizip mbedtls android log dl)

  set_target_properties(main PROPERTIES
    CXX_VISIBILITY_PRESET hidden
    VISIBILITY_INLINES_HIDDEN YES)
endif()
