cmake_minimum_required(VERSION 3.16)
project(magic8_sim C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

# lv_conf.h lives next to this file; LV_CONF_INCLUDE_SIMPLE tells LVGL to
# find it as a plain #include "lv_conf.h" rather than needing a specific
# relative path baked in.
add_compile_definitions(LV_CONF_INCLUDE_SIMPLE)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})

# lv_conf.h turns on LV_USE_SDL (for the interactive sim, added later),
# which makes LVGL's own CMakeLists compile the SDL driver files
# unconditionally as part of the `lvgl` target -- so even the headless
# screenshot tool below needs SDL2's headers on the include path to build,
# whether or not it links against SDL2 itself.
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2)
include_directories(${SDL2_INCLUDE_DIRS})
link_directories(${SDL2_LIBRARY_DIRS})

add_subdirectory(third_party/lvgl)

# ui/theme.h, ui/screen_swirl.c etc. use lround/cos/sin (math.h). On macOS
# these resolve through libSystem with no extra flag, which is why this
# was missing and still built fine here -- but Linux's libc doesn't fold
# libm in, and linking fails there with "undefined reference to `lround'"
# and similar. find_library (not a bare `m`) so this stays a no-op rather
# than an error on a platform where there's no separate libm to find.
find_library(MATH_LIBRARY m)

# generated/ (firmware/main/generated/) is committed to the repo, not
# gitignored -- deterministic output from content/moves.json and
# assets/fonts/*.ttf, both also committed. That makes it possible to edit
# either input and build without regenerating, silently using the stale
# committed copy. This target runs on every build (no OUTPUT, so ninja
# always re-checks it, not just on the first configure) and fails the
# build rather than warning -- see tools/check-generated-fresh.sh for why
# that matters more here than it sounds like it should.
add_custom_target(check_generated_fresh ALL
    COMMAND bash ${CMAKE_SOURCE_DIR}/../tools/check-generated-fresh.sh
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/..
    COMMENT "Checking firmware/main/generated/ was produced from the current content/moves.json and assets/fonts/*.ttf"
    VERBATIM
)

# The release guard (flashing/build_release.py refusing a dev build) has
# host tests of its own. Same treatment: every build, fails loudly.
add_custom_target(check_release_guard ALL
    COMMAND python3 -m unittest -q flashing/test_build_release.py tools/test_import_moves.py
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/..
    COMMENT "Running flashing/test_build_release.py (the release guard must refuse a dev build)"
    VERBATIM
)

# Documentation drift fails the build too: reversed decisions restated,
# dead links to deleted files, future dates. See the script for the list
# and the 2026-09-10 audits for why.
add_custom_target(check_docs_consistent ALL
    COMMAND bash ${CMAKE_SOURCE_DIR}/../tools/check-docs-consistent.sh
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/..
    COMMENT "Checking the live documents for reversed decisions, dead links and future dates"
    VERBATIM
)

# The generated documentation sections (README's motion, timeout and shake
# tables; PROJECT-CONTEXT's content counts, URL scheme and toolchain) must
# match the code they are generated from. See tools/gen-docs.py.
add_custom_target(check_generated_docs ALL
    COMMAND python3 ${CMAKE_SOURCE_DIR}/../tools/gen-docs.py --check
    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/..
    COMMENT "Checking the generated documentation sections match their sources"
    VERBATIM
)

# CONFIGURE_DEPENDS re-runs these globs on every build, not just at
# configure time. Without it a new shared source is silently absent until
# someone re-runs cmake by hand: adding ui/router.c and rebuilding failed
# to link here, with the file sitting in the directory. The firmware side
# needs a script-mode guard for the same fix (firmware/main/CMakeLists.txt);
# this is a plain CMake project, so it does not.
file(GLOB MAGIC8_UI_SOURCES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/../ui/*.c)
file(GLOB MAGIC8_FONT_SOURCES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/../firmware/main/generated/fonts/*.c)
# The move table + QR bit-arrays are definitions, compiled exactly once
# (tools/generate-qr.mjs emits a .c next to the .h for that reason).
set(MAGIC8_CONTENT_SOURCE ${CMAKE_SOURCE_DIR}/../firmware/main/generated/content_data.c)

include_directories(
    ${CMAKE_SOURCE_DIR}/../ui
    ${CMAKE_SOURCE_DIR}/../firmware/main/generated
)

# Headless screenshot tool: no SDL, no window, just LVGL core + our screens,
# rendered to a snapshot buffer and dumped as a .ppm per screen. This is
# what proves layout/wrapping/colors match README without needing a real
# display or a person to look at a window.
add_executable(magic8_screenshot
    screenshot_main.c
    platform_sim.c
    ${MAGIC8_UI_SOURCES}
    ${MAGIC8_FONT_SOURCES}
    ${MAGIC8_CONTENT_SOURCE}
)
target_link_libraries(magic8_screenshot PRIVATE lvgl)
if(MATH_LIBRARY)
    target_link_libraries(magic8_screenshot PRIVATE ${MATH_LIBRARY})
endif()
add_dependencies(magic8_screenshot check_generated_fresh)

# Plain logic test for the state machine -- no LVGL, no fonts, just
# state_machine.c + the content table. Deliberately its own tiny target so
# it builds in seconds and can run in CI later without pulling in LVGL at all.
add_executable(magic8_test_state_machine
    test_state_machine.c
    platform_sim.c
    ${CMAKE_SOURCE_DIR}/../ui/state_machine.c
    ${CMAKE_SOURCE_DIR}/../ui/deck.c
    ${CMAKE_SOURCE_DIR}/../ui/rng.c
    ${MAGIC8_CONTENT_SOURCE}
)
target_include_directories(magic8_test_state_machine PRIVATE
    ${CMAKE_SOURCE_DIR}/../ui
    ${CMAKE_SOURCE_DIR}/../firmware/main/generated
)
if(MATH_LIBRARY)
    target_link_libraries(magic8_test_state_machine PRIVATE ${MATH_LIBRARY})
endif()
add_dependencies(magic8_test_state_machine check_generated_fresh)

# Pure logic test for the touch press-edge detector (ui/touch_edge.c):
# boolean in, one tap event per press out. No LVGL, no fonts, no content.
add_executable(magic8_test_input_edge
    test_input_edge.c
    ${CMAKE_SOURCE_DIR}/../ui/touch_edge.c
)
target_include_directories(magic8_test_input_edge PRIVATE ${CMAKE_SOURCE_DIR}/../ui)

# Pure logic test for the shake detector (ui/shake_detect.c): acceleration
# samples in, one shake event out. No LVGL, no fonts, no content.
add_executable(magic8_test_shake_detect
    test_shake_detect.c
    ${CMAKE_SOURCE_DIR}/../ui/shake_detect.c
)
target_include_directories(magic8_test_shake_detect PRIVATE ${CMAKE_SOURCE_DIR}/../ui)

add_executable(magic8_test_battery_sleep test_battery_sleep.c
    ../firmware/main/battery_monitor.c ../ui/battery_policy.c)
target_include_directories(magic8_test_battery_sleep PRIVATE sleep_stubs ../firmware/main ../ui)

add_executable(magic8_test_battery_policy test_battery_policy.c ../ui/battery_policy.c)
target_include_directories(magic8_test_battery_policy PRIVATE ../ui)

add_executable(magic8_test_battery_dot test_battery_dot.c platform_sim.c
    ${MAGIC8_UI_SOURCES} ${MAGIC8_FONT_SOURCES} ${MAGIC8_CONTENT_SOURCE})
target_link_libraries(magic8_test_battery_dot PRIVATE lvgl)
if(MATH_LIBRARY)
    target_link_libraries(magic8_test_battery_dot PRIVATE ${MATH_LIBRARY})
endif()

# The interactive walkthrough -- a real SDL window, real state machine,
# real timing. Run this one, don't just read the screenshots.
add_executable(magic8_walkthrough
    main_interactive.c
    platform_sim.c
    ${MAGIC8_UI_SOURCES}
    ${MAGIC8_FONT_SOURCES}
    ${MAGIC8_CONTENT_SOURCE}
)
target_link_libraries(magic8_walkthrough PRIVATE lvgl ${SDL2_LIBRARIES})
target_link_directories(magic8_walkthrough PRIVATE ${SDL2_LIBRARY_DIRS})
if(MATH_LIBRARY)
    target_link_libraries(magic8_walkthrough PRIVATE ${MATH_LIBRARY})
endif()
add_dependencies(magic8_walkthrough check_generated_fresh)

# The four guards run whenever any product binary is built by name, not
# only on a bare `ninja`: sim/README.md's documented command names the five
# targets, and a guard that a documented workflow skips is not a guard.
foreach(t magic8_screenshot magic8_test_state_machine magic8_test_input_edge magic8_test_shake_detect magic8_walkthrough magic8_test_battery_policy magic8_test_battery_dot magic8_test_battery_sleep)
    add_dependencies(${t} check_generated_fresh check_release_guard check_docs_consistent check_generated_docs)
endforeach()
