# VirtualXT -- Makefile
#
# Core library, utils, modules, and frontend compile freestanding (no libc).
# The frontend depends only on libretro function pointers.
# Tests are hosted (use libc for stdio, malloc, etc.).
#
# Targets:
#   develop  (default)  -- libretro core with debug server + test runners
#   release             -- libretro core without debug server
#   object              -- release-mode libretro object for packaging/web
#   rpi                 -- cross-compile baremetal Raspberry Pi kernel
#   web                 -- standalone Emscripten web frontend
#   test                -- run all tests (test-modules + test-cpu)
#   testdata            -- download 8088/V20 MOO CPU test data
#   bios-data           -- regenerate C headers from BIOS ROMs and disk image
#   samples             -- regenerate audio sample header
#   vxtbios/vxtx/egabios-- assemble BIOS ROMs (requires nasm)
#   run                 -- launch develop core in RetroArch
#   clean/distclean     -- remove build artifacts (+ test data)

CC       ?= gcc
AR       ?= ar
NASM     ?= nasm
PYTHON   ?= python3
BUILD_DATE := $(shell date +%m/%d/%Y)
VXT_VERSION := $(shell . tools/getversion/export.sh)
CFLAGS   := -std=c11 -Wall -Wextra -Wpedantic -Isrc
LDFLAGS  ?=
LIBRETRO_OUT ?= virtualxt_libretro.so
LIBRETRO_LINK_MODE ?= -shared
LIBRETRO_OBJECT_OUT ?= virtualxt_libretro.o
CIRCLEHOME       ?= ../circle

# Cross-compiler prefix for Raspberry Pi.
# Read the toolchain prefix from Circle's Config.mk (set by ./configure).
# Falls back to the standard baremetal prefix if Circle is not yet configured.
CROSS_PREFIX ?= $(shell \
	if [ -f "$(CIRCLEHOME)/Config.mk" ]; then \
		grep '^PREFIX64' "$(CIRCLEHOME)/Config.mk" 2>/dev/null | sed 's/^PREFIX64[[:space:]]*=[[:space:]]*//' | sed 's/[[:space:]]*$$//'; \
	fi)
ifeq ($(CROSS_PREFIX),)
CROSS_PREFIX := aarch64-none-elf-
endif

ifdef NO_RTC
CFLAGS += -DNO_RTC
endif

ifdef NO_DEFAULT_DISK
CFLAGS += -DNO_DEFAULT_DISK
endif

ifdef NO_SAMPLES
CFLAGS += -DNO_SAMPLES
endif

ifdef CRT_FILTER_EXTERNAL
CFLAGS += -DCRT_FILTER_EXTERNAL
endif

CORE_CFLAGS  := $(CFLAGS) -ffreestanding -fPIC -O2
TEST_CFLAGS  := $(CFLAGS) -O2 -DDEBUG_SERVER
FRONT_CFLAGS := $(CORE_CFLAGS) -Itools/libretro -DVXT_VERSION='"$(VXT_VERSION)"'
DEBUG_CFLAGS := $(TEST_CFLAGS) -fPIC

# ── Source lists ─────────────────────────────────────────────────────────

CORE_SRCS := src/utils/utils.c \
             src/vxt/peripheral.c src/vxt/cpu.c src/vxt/alu.c \
             src/vxt/div.c src/vxt/cpu_remap.c src/vxt/cpu_v20.c \
             src/vxt/cpu_cycles.c src/vxt/machine.c \
             src/modules/module_table.c \
             src/modules/rom/rom.c \
             src/modules/disk/disk.c \
             src/modules/chipset/chipset.c src/modules/chipset/pic.c \
             src/modules/chipset/pit.c src/modules/chipset/dma.c \
             src/modules/chipset/ppi.c \
             src/modules/cga/cga.c \
             src/modules/ega/ega.c \
             src/modules/hgc/hgc.c \
             src/modules/uart/uart.c \
             src/modules/mouse/mouse.c \
             src/modules/joystick/joystick.c \
             src/modules/hostfs/hostfs.c \
             src/modules/ems/ems.c \
             $(if $(NO_RTC),,src/modules/rtc/rtc.c)

TEST_SRCS := src/tests/moo.c src/tests/cpu_main.c

MODULE_SRCS := src/tests/module_main.c \
               src/tests/test_bus.c src/tests/test_pic.c \
               src/tests/test_pit.c src/tests/test_dma.c \
               src/tests/test_rom.c src/tests/test_ppi.c \
               src/tests/test_ems.c src/tests/test_rtc.c

# ── Object lists ─────────────────────────────────────────────────────────

CORE_OBJS         := $(CORE_SRCS:.c=.o)
TEST_OBJS         := $(TEST_SRCS:.c=.o)
MODULE_OBJS       := $(MODULE_SRCS:.c=.o)
DEBUG_TEST_OBJS   := src/tests/test_debug.o
DEBUG_SERVER_OBJS := src/modules/debug/debug_server.o
DEVELOP_FRONT_OBJS := src/frontend/core.develop.o src/frontend/ini.develop.o src/frontend/crt.develop.o src/frontend/audio_mixer.develop.o
RELEASE_FRONT_OBJS := src/frontend/core.release.o src/frontend/ini.release.o src/frontend/crt.release.o src/frontend/audio_mixer.release.o

# When NO_RTC=1, rtc.o is excluded from libvxt.a but test_rtc.c still needs it.
# Compile rtc.o separately for the module-runner link.
ifdef NO_RTC
MODULE_EXTRA_OBJS := src/modules/rtc/rtc.o
else
MODULE_EXTRA_OBJS :=
endif

# ── BIOS data generation ────────────────────────────────────────────────

BIOS_DATA := src/frontend/generated/vxtbios_bin.h \
              src/frontend/generated/vxtx_bin.h \
              src/frontend/generated/egabios_rom.h \
              src/frontend/generated/freedos_hd_img.h

$(BIOS_DATA): bios/vxtbios.bin bios/vxtx.bin bios/egabios.bin boot/freedos_hd.img
	$(PYTHON) tools/bios/gen_bios_data.py

# ── Sample data generation ──────────────────────────────────────────────

SAMPLES_DATA := src/frontend/generated/audio_samples.h

$(SAMPLES_DATA): samples/servo_seeks_eq.wav tools/samples/gen_samples.py
	$(PYTHON) tools/samples/gen_samples.py

# ── Top-level targets ───────────────────────────────────────────────────

.PHONY: all develop release object web rpi check-circle clean distclean testdata test test-cpu test-cpu-8088 test-cpu-v20 test-modules run bios-data samples vxtbios vxtx egabios

.DEFAULT_GOAL := develop

all: develop

develop: libvxt.a test-runner module-runner $(DEVELOP_FRONT_OBJS) $(DEBUG_SERVER_OBJS)
	mkdir -p $(dir $(LIBRETRO_OUT))
	$(CC) $(LIBRETRO_LINK_MODE) -o $(LIBRETRO_OUT) $(LDFLAGS) $(DEVELOP_FRONT_OBJS) $(DEBUG_SERVER_OBJS) ./libvxt.a -lpthread

release: libvxt.a $(RELEASE_FRONT_OBJS)
	mkdir -p $(dir $(LIBRETRO_OUT))
	$(CC) $(LIBRETRO_LINK_MODE) -o $(LIBRETRO_OUT) $(RELEASE_FRONT_OBJS) ./libvxt.a

object: libvxt.a $(RELEASE_FRONT_OBJS)
	mkdir -p $(dir $(LIBRETRO_OBJECT_OUT))
	$(CC) -r $(LDFLAGS) -o $(LIBRETRO_OBJECT_OUT) $(RELEASE_FRONT_OBJS) ./libvxt.a

# Cross-compile the libretro core object for Raspberry Pi (AArch64 baremetal),
# then build the Circle kernel image that wraps it.
# The baremetal target excludes the default disk image and audio samples to
# reduce binary size; the user is expected to provide a custom machine setup.
rpi: check-circle
	rm -f $(CORE_OBJS) $(RELEASE_FRONT_OBJS) libvxt.a $(LIBRETRO_OBJECT_OUT)
	$(MAKE) CC=$(CROSS_PREFIX)gcc AR=$(CROSS_PREFIX)ar object NO_DEFAULT_DISK=1 NO_SAMPLES=1 CRT_FILTER_EXTERNAL=1
	$(MAKE) -C tools/circle/kernel PREFIX64=$(CROSS_PREFIX)

# Build standalone web frontend with Emscripten.
web:
	$(MAKE) -C tools/web

check-circle:
	@command -v $(CROSS_PREFIX)gcc >/dev/null 2>&1 || { \
		echo "Error: No AArch64 cross-compiler found ($(CROSS_PREFIX)gcc)."; \
		echo "Please install aarch64-none-elf-gcc or aarch64-linux-gnu-gcc."; \
		exit 1; \
	}
	@if [ ! -f "$(CIRCLEHOME)/lib/libcircle.a" ]; then \
		echo "Error: Circle library not found at $(CIRCLEHOME)/lib/libcircle.a"; \
		echo "Please build Circle first:"; \
		echo "  cd $(CIRCLEHOME) && ./configure -r 4 --multicore && makeall"; \
		exit 1; \
	fi

bios-data: $(BIOS_DATA)
samples: $(SAMPLES_DATA)

# ── BIOS assembly (manual, requires nasm) ───────────────────────────────

vxtbios: bios/vxtbios.bin
bios/vxtbios.bin: bios/vxtbios.asm
	$(NASM) -f bin -o $@ -l bios/vxtbios.lst -dBUILD_DATE="'$(BUILD_DATE)'" $<
	$(PYTHON) tools/checksum/update_bios_checksum.py $@

vxtx: bios/vxtx.bin
bios/vxtx.bin: bios/vxtx.asm
	$(NASM) -f bin -o $@ -l bios/vxtx.lst $<
	$(PYTHON) tools/checksum/update_bios_checksum.py $@

egabios: bios/egabios.bin
bios/egabios.bin: bios/egabios.asm
	$(NASM) -f bin -o $@ -l bios/egabios.lst $<
	$(PYTHON) tools/checksum/update_bios_checksum.py $@

# ── Libraries and binaries ──────────────────────────────────────────────

libvxt.a: $(CORE_OBJS)
	$(AR) rcs $@ $^

test-runner: $(TEST_OBJS) libvxt.a
	$(CC) $(TEST_CFLAGS) -o $@ $(TEST_OBJS) ./libvxt.a

module-runner: $(MODULE_OBJS) $(MODULE_EXTRA_OBJS) $(DEBUG_TEST_OBJS) $(DEBUG_SERVER_OBJS) libvxt.a
	$(CC) $(TEST_CFLAGS) -o $@ $(MODULE_OBJS) $(MODULE_EXTRA_OBJS) $(DEBUG_TEST_OBJS) $(DEBUG_SERVER_OBJS) ./libvxt.a -lpthread

# ── Compilation rules ───────────────────────────────────────────────────

# Core: freestanding.
src/utils/%.o:          src/utils/%.c;          $(CC) $(CORE_CFLAGS) -c -o $@ $<
src/vxt/%.o:            src/vxt/%.c;            $(CC) $(CORE_CFLAGS) -c -o $@ $<
src/modules/%.o:        src/modules/%.c;        $(CC) $(CORE_CFLAGS) -c -o $@ $<
src/modules/debug/%.o:  src/modules/debug/%.c;  $(CC) $(DEBUG_CFLAGS) -c -o $@ $<

# Tests: hosted.
src/tests/%.o: src/tests/%.c
	$(CC) $(TEST_CFLAGS) -c -o $@ $<

# Frontend: develop (with debug server) and release (without).
src/frontend/%.develop.o: src/frontend/%.c
	$(CC) $(FRONT_CFLAGS) -DDEBUG_SERVER -c -o $@ $<

src/frontend/%.release.o: src/frontend/%.c
	$(CC) $(FRONT_CFLAGS) -c -o $@ $<

# ── Test targets ────────────────────────────────────────────────────────

test: test-modules test-cpu

test-modules: module-runner
	@./module-runner

test-cpu: test-cpu-8088 test-cpu-v20

test-cpu-8088: test-runner
	@if [ ! -d src/tests/testdata ] || [ -z "$$(ls src/tests/testdata/*.MOO 2>/dev/null)" ]; then \
	    echo "No 8088 test data found. Run 'make testdata' first."; exit 1; \
	fi; \
	failed=0; passed=0; total=0; \
	for f in src/tests/testdata/*.MOO; do \
	    [ -f "$$f" ] || continue; \
	    mask=213; \
	    if [ -f src/tests/testdata/masks.txt ]; then \
	        m=$$(grep "^$$(basename $$f) " src/tests/testdata/masks.txt | awk '{print $$2}'); \
	        [ -n "$$m" ] && mask=$$m; \
	    fi; \
	    total=$$((total + 1)); \
	    if ./test-runner "$$f" "$$mask"; then \
	        passed=$$((passed + 1)); \
	    else \
	        failed=$$((failed + 1)); \
	    fi; \
	done; \
	echo ""; \
	echo "=== 8088 Results: $$passed passed, $$failed failed ($$total files) ==="; \
	[ $$failed -eq 0 ]

test-cpu-v20: test-runner
	@if [ ! -d src/tests/testdata/v20 ] || [ -z "$$(ls src/tests/testdata/v20/*.MOO 2>/dev/null)" ]; then \
	    echo "No V20 test data found. Run 'make testdata' first."; exit 1; \
	fi; \
	failed=0; passed=0; total=0; \
	for f in src/tests/testdata/v20/*.MOO; do \
	    [ -f "$$f" ] || continue; \
	    mask=213; \
	    if [ -f src/tests/testdata/v20/masks.txt ]; then \
	        m=$$(grep "^$$(basename $$f) " src/tests/testdata/v20/masks.txt | awk '{print $$2}'); \
	        [ -n "$$m" ] && mask=$$m; \
	    fi; \
	    total=$$((total + 1)); \
	    if ./test-runner --v20 "$$f" "$$mask"; then \
	        passed=$$((passed + 1)); \
	    else \
	        failed=$$((failed + 1)); \
	    fi; \
	done; \
	echo ""; \
	echo "=== V20 Results: $$passed passed, $$failed failed ($$total files) ==="; \
	[ $$failed -eq 0 ]

testdata:
	$(PYTHON) tools/testdata/fetch.py

# ── Run ─────────────────────────────────────────────────────────────────

run: develop
	retroarch -v -L virtualxt_libretro.so

# ── Clean ───────────────────────────────────────────────────────────────

clean:
	rm -f $(CORE_OBJS) $(TEST_OBJS) $(MODULE_OBJS) $(MODULE_EXTRA_OBJS) \
	      $(DEBUG_TEST_OBJS) $(DEBUG_SERVER_OBJS) \
	      $(DEVELOP_FRONT_OBJS) $(RELEASE_FRONT_OBJS) \
	      libvxt.a test-runner module-runner $(LIBRETRO_OUT) \
	      $(LIBRETRO_OBJECT_OUT)
	-$(MAKE) -C tools/circle/kernel clean
	-$(MAKE) -C tools/web clean

distclean: clean
	rm -rf src/tests/testdata
