cmake_minimum_required(VERSION 3.20)
project(SuperCanRecovery VERSION 0.1.0 LANGUAGES C CXX)

if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
  message(FATAL_ERROR "supercan-assets currently requires Linux secure filesystem primitives")
endif()
find_package(OpenSSL REQUIRED COMPONENTS Crypto)
find_package(SDL3 3.4 CONFIG REQUIRED)
find_package(OpenGL REQUIRED)
find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(OGG REQUIRED IMPORTED_TARGET ogg)
add_executable(supercan-assets src/assets/main.cpp)
pkg_check_modules(FREETYPE REQUIRED IMPORTED_TARGET freetype2)
pkg_check_modules(FONTCONFIG REQUIRED IMPORTED_TARGET fontconfig)
pkg_check_modules(CAPSTONE REQUIRED IMPORTED_TARGET capstone)
target_compile_features(supercan-assets PRIVATE cxx_std_20)
target_link_libraries(supercan-assets PRIVATE OpenSSL::Crypto)
add_library(supercan-formats STATIC src/assets/model.cpp src/assets/image.cpp src/assets/resource.cpp src/assets/effect.cpp src/assets/d3d9_shader.cpp src/assets/d3d9_glsl.cpp)
target_compile_features(supercan-formats PUBLIC cxx_std_20)
target_include_directories(supercan-formats PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/src/assets")
target_link_libraries(supercan-assets PRIVATE supercan-formats)
add_library(supercan-game-data STATIC src/game/save.cpp src/game/numeric.cpp src/game/timing.cpp src/game/skeleton.cpp src/game/camera.cpp src/game/collision.cpp src/game/collision_mesh.cpp src/game/collision_mesh_sphere_line.cpp src/game/collision_mesh_box_axial.cpp src/game/collision_mesh_pair.cpp src/game/screen.cpp src/game/preshader.cpp src/game/preshader_stage.cpp src/game/actor.cpp src/game/actor_memory.cpp src/game/actor_canonical.cpp src/game/actor_resource.cpp src/game/actor_world.cpp src/game/actor_transform.cpp src/game/actor_runtime.cpp src/game/actor_terrain.cpp src/game/actor_level_environment.cpp src/game/actor_level.cpp src/game/actor_level_spatial.cpp src/game/continuation.cpp src/game/input.cpp)
target_compile_features(supercan-game-data PUBLIC cxx_std_20)
target_compile_options(supercan-game-data PRIVATE -frounding-math -ffp-contract=off -fno-fast-math)
target_include_directories(supercan-game-data PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(supercan-game-data PUBLIC supercan-formats SDL3::SDL3)
target_link_libraries(supercan-assets PRIVATE supercan-game-data)
set(original_vorbis_root "${CMAKE_CURRENT_SOURCE_DIR}/src/audio/vendor/original_numeric_vorbis")
add_library(supercan-original-vorbis STATIC
  "${original_vorbis_root}/lib/block.c"
  "${original_vorbis_root}/lib/codebook.c"
  "${original_vorbis_root}/lib/sharedbook.c"
  "${original_vorbis_root}/lib/info.c"
  "${original_vorbis_root}/lib/synthesis.c"
  "${original_vorbis_root}/lib/floor0.c"
  "${original_vorbis_root}/lib/floor1.c"
  "${original_vorbis_root}/lib/res0.c"
  "${original_vorbis_root}/lib/mapping0.c"
  "${original_vorbis_root}/lib/registry.c"
  "${original_vorbis_root}/lib/mdct.c"
  "${original_vorbis_root}/lib/window.c"
  "${original_vorbis_root}/lib/lsp.c"
  "${original_vorbis_root}/lib/vorbisfile.c")
set_target_properties(supercan-original-vorbis PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED YES)
target_compile_definitions(supercan-original-vorbis PRIVATE HAVE_ALLOCA_H=1)
target_compile_options(supercan-original-vorbis PRIVATE -frounding-math -ffp-contract=off -fno-fast-math)
target_include_directories(supercan-original-vorbis PRIVATE "${original_vorbis_root}/include" "${original_vorbis_root}/lib")
target_link_libraries(supercan-original-vorbis PRIVATE PkgConfig::OGG Threads::Threads m)
add_library(supercan-audio-data STATIC src/audio/decode.cpp src/audio/software_mixer.cpp)
target_compile_features(supercan-audio-data PUBLIC cxx_std_20)
target_compile_options(supercan-audio-data PRIVATE -frounding-math -ffp-contract=off)
target_include_directories(supercan-audio-data PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_include_directories(supercan-audio-data PRIVATE "${original_vorbis_root}/include")
target_link_libraries(supercan-audio-data PRIVATE SDL3::SDL3 supercan-original-vorbis)
add_executable(supercan-audio tools/asset-inspector/audio.cpp)
target_link_libraries(supercan-audio PRIVATE supercan-audio-data supercan-formats SDL3::SDL3)
add_executable(supercan-scene tools/asset-inspector/scene.cpp)
target_link_libraries(supercan-scene PRIVATE supercan-formats supercan-game-data SDL3::SDL3 OpenGL::GL)
add_library(supercan-engine STATIC src/engine/script_text.cpp src/engine/startup.cpp src/engine/engine_image.cpp
  src/engine/engine_core.cpp src/engine/engine_objects.cpp src/engine/file_loader.cpp src/engine/wdf.cpp
  src/engine/platform.cpp src/engine/engine.cpp src/engine/engine_vars.cpp src/engine/engine_state.cpp
  src/engine/bmap.cpp src/engine/script_image.cpp src/engine/metadata.cpp src/engine/engine_level.cpp src/engine/script_vm.cpp src/engine/engine_frame.cpp)
target_compile_features(supercan-engine PUBLIC cxx_std_20)
target_include_directories(supercan-engine PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(supercan-engine PUBLIC supercan-render supercan-game-data supercan-audio-data SDL3::SDL3 OpenGL::GL OpenSSL::Crypto PRIVATE PkgConfig::FREETYPE PkgConfig::FONTCONFIG PkgConfig::CAPSTONE)
add_executable(supercan src/main.cpp)
target_link_libraries(supercan PRIVATE supercan-engine)
add_library(supercan-render STATIC src/render/d3d9_device.cpp src/render/entity_render.cpp)
target_compile_features(supercan-render PUBLIC cxx_std_20)
target_compile_options(supercan-render PRIVATE -frounding-math -ffp-contract=off -fno-fast-math)
target_include_directories(supercan-render PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(supercan-render PUBLIC supercan-game-data OpenGL::GL SDL3::SDL3)
set(SUPERCAN_TARGETS supercan-assets supercan-formats supercan-game-data supercan-original-vorbis supercan-audio-data supercan-scene supercan-audio supercan-engine supercan supercan-render)
option(SUPERCAN_SANITIZERS "Enable address and undefined-behavior sanitizers" OFF)
install(TARGETS supercan-assets supercan-scene supercan-audio supercan RUNTIME DESTINATION bin)
install(FILES "${original_vorbis_root}/COPYING" "${original_vorbis_root}/COPYING.candidate" "${original_vorbis_root}/NOTICE"
  DESTINATION share/licenses/supercan/original-numeric-vorbis)

include(CTest)
if(BUILD_TESTING)
  find_package(Python3 REQUIRED COMPONENTS Interpreter)
  add_test(NAME native_assets
    COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/native_assets.py"
      "$<TARGET_FILE:supercan-assets>"
      "${CMAKE_CURRENT_SOURCE_DIR}/Suepercan_Win_TR_pre-installed/SuperCan/Game"
      "${CMAKE_CURRENT_SOURCE_DIR}/docs/manifests/game-preinstalled.sha256"
      "${CMAKE_CURRENT_SOURCE_DIR}/Suepercan_Win_TR_Setup/supercan_setup.exe")
  add_executable(world_boundary tests/world_boundary.cpp)
  add_executable(model_parser_test tests/model_parser_test.cpp)
  add_executable(model_animation_test tests/model_animation_test.cpp)
  add_executable(model_skeleton_parser_test tests/model_skeleton_parser_test.cpp)
  add_executable(model_image_test tests/model_image_test.cpp)
  add_executable(resource_boundary tests/resource_boundary.cpp)
  add_executable(effect_test tests/effect_test.cpp)
  add_executable(d3d9_shader_test tests/d3d9_shader_test.cpp)
  add_executable(preshader_test tests/preshader_test.cpp)
  target_compile_options(preshader_test PRIVATE -frounding-math -ffp-contract=off -fno-fast-math)
  add_executable(preshader_stage_test tests/preshader_stage_test.cpp)
  target_compile_options(preshader_stage_test PRIVATE -frounding-math -ffp-contract=off -fno-fast-math)
  add_executable(actor_test tests/actor_test.cpp)
  add_executable(actor_runtime_test tests/actor_runtime_test.cpp)
  target_link_libraries(actor_runtime_test PRIVATE supercan-game-data)
  target_compile_options(actor_runtime_test PRIVATE -UNDEBUG -frounding-math -ffp-contract=off -fno-fast-math)
  add_executable(actor_terrain_test tests/actor_terrain_test.cpp)
  add_executable(render_menu_test tests/render_menu_test.cpp)
  target_link_libraries(render_menu_test PRIVATE supercan-render)
  target_compile_options(render_menu_test PRIVATE -UNDEBUG -frounding-math -ffp-contract=off -fno-fast-math)
  target_link_libraries(actor_terrain_test PRIVATE supercan-game-data)
  target_compile_options(actor_terrain_test PRIVATE -UNDEBUG -frounding-math -ffp-contract=off -fno-fast-math)
  add_executable(actor_level_test tests/actor_level_test.cpp)
  target_link_libraries(actor_level_test PRIVATE supercan-game-data)
  target_compile_options(actor_level_test PRIVATE -UNDEBUG -frounding-math -ffp-contract=off -fno-fast-math)
  add_executable(actor_sprite_formats_test tests/actor_sprite_formats_test.cpp)
  target_link_libraries(actor_sprite_formats_test PRIVATE supercan-game-data)
  target_compile_options(actor_sprite_formats_test PRIVATE -UNDEBUG)
  add_executable(continuation_test tests/continuation_test.cpp)
  add_executable(input_test tests/input_test.cpp)
  add_executable(save_codec_test tests/save_codec_test.cpp)
  target_link_libraries(save_codec_test PRIVATE supercan-game-data)
  target_compile_options(save_codec_test PRIVATE -UNDEBUG)
  add_executable(numeric_test tests/numeric_test.cpp)
  add_executable(timing_test tests/timing_test.cpp)
  add_executable(skeleton_pose_test tests/skeleton_pose_test.cpp)
  add_executable(camera_path_test tests/camera_path_test.cpp)
  add_executable(primitive_collision_test tests/primitive_collision_test.cpp)
  add_executable(mesh_collision_test tests/mesh_collision_test.cpp)
  target_link_libraries(mesh_collision_test PRIVATE supercan-game-data)
  target_compile_options(mesh_collision_test PRIVATE -UNDEBUG -frounding-math -ffp-contract=off -fno-fast-math)
  add_executable(screen_test tests/screen_test.cpp)
  target_compile_options(screen_test PRIVATE -frounding-math -ffp-contract=off -fno-fast-math)
  target_link_libraries(primitive_collision_test PRIVATE supercan-game-data)
  target_compile_options(primitive_collision_test PRIVATE -UNDEBUG -frounding-math -ffp-contract=off -fno-fast-math)
  foreach(target numeric_test timing_test skeleton_pose_test camera_path_test screen_test preshader_test preshader_stage_test actor_test continuation_test input_test)
    target_link_libraries(${target} PRIVATE supercan-game-data)
    target_compile_options(${target} PRIVATE -UNDEBUG)
  endforeach()
  add_executable(audio_decode_test tests/audio_decode_test.cpp)
  target_link_libraries(audio_decode_test PRIVATE supercan-audio-data)
  target_compile_options(audio_decode_test PRIVATE -UNDEBUG)
  add_executable(software_mixer_test tests/software_mixer_test.cpp)
  target_link_libraries(software_mixer_test PRIVATE supercan-audio-data)
  target_compile_options(software_mixer_test PRIVATE -UNDEBUG -frounding-math -ffp-contract=off)
  foreach(target model_parser_test model_animation_test model_skeleton_parser_test model_image_test resource_boundary effect_test d3d9_shader_test)
    target_link_libraries(${target} PRIVATE supercan-formats)
    target_compile_options(${target} PRIVATE -UNDEBUG)
  endforeach()
  list(APPEND SUPERCAN_TARGETS world_boundary model_parser_test model_animation_test model_skeleton_parser_test model_image_test resource_boundary effect_test d3d9_shader_test save_codec_test numeric_test timing_test skeleton_pose_test camera_path_test primitive_collision_test mesh_collision_test screen_test audio_decode_test software_mixer_test preshader_test preshader_stage_test actor_test continuation_test input_test)
  list(APPEND SUPERCAN_TARGETS actor_runtime_test actor_terrain_test actor_level_test actor_sprite_formats_test render_menu_test)
  add_executable(engine_startup_test tests/engine_startup_test.cpp)
  target_link_libraries(engine_startup_test PRIVATE supercan-engine)
  target_compile_options(engine_startup_test PRIVATE -UNDEBUG)
  list(APPEND SUPERCAN_TARGETS engine_startup_test)
  target_link_libraries(world_boundary PRIVATE supercan-game-data)
  target_compile_options(world_boundary PRIVATE -UNDEBUG -frounding-math -ffp-contract=off -fno-fast-math)
  add_test(NAME model_parser COMMAND model_parser_test)
  add_test(NAME model_animation COMMAND model_animation_test)
  add_test(NAME model_skeleton_parser COMMAND model_skeleton_parser_test)
  add_test(NAME skeleton_pose COMMAND skeleton_pose_test)
  add_test(NAME native_resources COMMAND resource_boundary)
  add_test(NAME model_images COMMAND model_image_test)
  add_test(NAME native_effects COMMAND effect_test)
  add_test(NAME native_d3d9_shaders COMMAND d3d9_shader_test)
  add_test(NAME native_fxlc COMMAND preshader_test)
  add_test(NAME native_pres_stage COMMAND preshader_stage_test)
  add_test(NAME native_actor_skills COMMAND actor_test)
  set(actor_assets "${CMAKE_CURRENT_SOURCE_DIR}/analysis/native-preinstalled-assets")
    add_test(NAME native_render_menu COMMAND render_menu_test "${actor_assets}")
  if(EXISTS "${actor_assets}/Data/model_v0.gen")
    add_test(NAME world_boundary COMMAND world_boundary "${actor_assets}")
    add_test(NAME native_actor_runtime COMMAND actor_runtime_test "${actor_assets}")
    add_test(NAME native_actor_terrain COMMAND actor_terrain_test "${actor_assets}")
    add_test(NAME native_actor_level COMMAND actor_level_test "${actor_assets}")
    add_test(NAME native_actor_sprite_formats COMMAND actor_sprite_formats_test "${actor_assets}")
  else()
    add_test(NAME world_boundary COMMAND world_boundary)
    add_test(NAME native_actor_runtime COMMAND actor_runtime_test)
    add_test(NAME native_actor_terrain COMMAND actor_terrain_test)
    add_test(NAME native_actor_level COMMAND actor_level_test)
    add_test(NAME native_actor_sprite_formats COMMAND actor_sprite_formats_test)
  set_tests_properties(native_render_menu PROPERTIES SKIP_RETURN_CODE 77)
  endif()
  set_tests_properties(world_boundary native_actor_runtime native_actor_terrain native_actor_level
                       native_actor_sprite_formats PROPERTIES SKIP_RETURN_CODE 77)
  add_test(NAME native_continuations COMMAND continuation_test)
  set(game_root "${CMAKE_CURRENT_SOURCE_DIR}/Suepercan_Win_TR_pre-installed/SuperCan/Game")
  if(EXISTS "${game_root}/supercan.exe" AND EXISTS "${game_root}/acknex.dll")
    add_test(NAME engine_startup COMMAND engine_startup_test "${game_root}")
  else()
    add_test(NAME engine_startup COMMAND engine_startup_test)
  endif()
  set_tests_properties(engine_startup PROPERTIES SKIP_RETURN_CODE 77)
  add_test(NAME native_keyboard COMMAND input_test)
  add_test(NAME save_codec COMMAND save_codec_test)
  add_test(NAME native_numeric COMMAND numeric_test)
  add_test(NAME native_timing COMMAND timing_test)
  add_test(NAME camera_paths COMMAND camera_path_test)
  add_test(NAME primitive_collision COMMAND primitive_collision_test)
  add_test(NAME mesh_collision COMMAND mesh_collision_test)
  add_test(NAME screen_unprojection COMMAND screen_test)
  add_test(NAME audio_decode COMMAND audio_decode_test)
  add_test(NAME software_audio_mixer COMMAND software_mixer_test)
  add_test(NAME scene_math COMMAND supercan-scene --self-check)
  add_test(NAME resource_boundaries COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/tools/assets/check_recover.py")
endif()
foreach(target IN LISTS SUPERCAN_TARGETS)
  target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic -Wconversion -Wshadow)
  if(SUPERCAN_SANITIZERS)
    if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
      message(FATAL_ERROR "SUPERCAN_SANITIZERS requires GCC or Clang")
    endif()
    target_compile_options(${target} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
    target_link_options(${target} PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
  endif()
endforeach()
