
ARCH ?= x64
MPY_ABI_VERSION ?= 6.3
MPY_DIR ?= ./dependencies/micropython
MICROPYTHON_BIN ?= micropython

# extmod settings
PORT=unix
BOARD=ESP32_GENERIC_S3

VERSION := $(shell git describe --tags --always)

MPY_DIR_ABS = $(abspath $(MPY_DIR))

C_MODULES_SRC_PATH = $(abspath ./src)

ifeq ($(PORT),unix)
    MANIFEST_PATH=$(abspath ./src/manifest_unix.py)
endif

WEBASSEMBLY_MANIFEST_PATH=$(abspath ./src/manifest_webassembly.py)

MODULES_PATH = ./dist/$(ARCH)_$(MPY_ABI_VERSION)
PORT_DIR = ./dist/ports/$(PORT)
PORT_BUILD_DIR=$(MPY_DIR)/ports/$(PORT)/build-$(BOARD)
PORT_DIST_DIR=./dist/ports/$(PORT)/$(BOARD)

UNIX_MICROPYTHON = ./dist/ports/unix/micropython
WEBASSEMBLY_MICROPYTHON = ./dist/ports/webassembly/micropython.mjs

# List of modules
MODULES = emlearn_trees \
	emlearn_neighbors \
	emlearn_iir \
	emlearn_fft \
	emlearn_kmeans \
	emlearn_iir_q15 \
	emlearn_arrayutils \
	emlearn_linreg \
	emlearn_logreg \
	emlearn_extratrees \
	emlearn_plsr \
	emlearn_cnn_int8 \
	emlearn_cnn_fp32

# Generate list of .mpy files
MODULE_MPYS = $(addprefix $(MODULES_PATH)/,$(addsuffix .mpy,$(MODULES)))

# Special cases
emlearn_cnn_int8_SRC = src/tinymaix_cnn
emlearn_cnn_int8_CONFIG = CONFIG=int8
emlearn_cnn_fp32_SRC = src/tinymaix_cnn
emlearn_cnn_fp32_CONFIG = CONFIG=fp32

# Source directories for each module
emlearn_trees_SRC = src/emlearn_trees
emlearn_neighbors_SRC = src/emlearn_neighbors
emlearn_iir_SRC = src/emlearn_iir
emlearn_fft_SRC = src/emlearn_fft
emlearn_kmeans_SRC = src/emlearn_kmeans
emlearn_iir_q15_SRC = src/emlearn_iir_q15
emlearn_arrayutils_SRC = src/emlearn_arrayutils
emlearn_linreg_SRC = src/emlearn_linreg
emlearn_logreg_SRC = src/emlearn_logreg
emlearn_extratrees_SRC = src/emlearn_extratrees
emlearn_plsr_SRC = src/emlearn_plsr

# Dependencies for each .mpy file: .c, .h, .py files, and Makefile
$(foreach mod,$(MODULES),\
  $(eval $(MODULES_PATH)/$(mod).mpy: \
    $(wildcard $($(mod)_SRC)/*.c) \
    $(wildcard $($(mod)_SRC)/*.h) \
    $(wildcard $($(mod)_SRC)/*.py) \
    $($(mod)_SRC)/Makefile))

# CNN modules share the same build directory, so they must build sequentially
# Ensure int8 builds after fp32 to avoid race conditions
$(MODULES_PATH)/emlearn_cnn_int8.mpy: $(MODULES_PATH)/emlearn_cnn_fp32.mpy

# Generate list of .mpy files
MODULE_MPYS = $(addprefix $(MODULES_PATH)/,$(addsuffix .mpy,$(MODULES)))

# Build dynamic native module
$(MODULES_PATH)/%.mpy: $(ARCH_SENTINEL)
	$(MAKE) -C $(or $($(*)_SRC),src/$*) \
		ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) CFLAGS_EXTRA=$(CFLAGS_EXTRA) \
		V=1 $($(*)_CONFIG) dist

# A file to track ARCH changes etc, to nuke old artifact
ARCH_SENTINEL := .sentinel_arch_$(ARCH)
$(ARCH_SENTINEL):
	@echo "ARCH changed or not set — cleaning old build artifacts"
	@rm -f .sentinel_arch_*
	find src -name ".mpy_ld_cache" -type d -exec rm -rf {} + ; \
	find src -name "build" -type d -exec rm -rf {} + ; \
	find src -name "*.mpy" -delete ; \
	find src -name "*.o" -delete
	@mkdir -p $(MODULES_PATH)
	@touch $@

# CNN modules need clean build due to shared build directory
# They must also build sequentially (fp32 first, then int8)
$(MODULES_PATH)/emlearn_cnn_fp32.mpy: $(ARCH_SENTINEL)
	$(MAKE) -C src/tinymaix_cnn \
		ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) CFLAGS_EXTRA=$(CFLAGS_EXTRA) \
		V=1 CONFIG=fp32 clean
	$(MAKE) -C src/tinymaix_cnn \
		ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) CFLAGS_EXTRA=$(CFLAGS_EXTRA) \
		V=1 CONFIG=fp32 dist

$(MODULES_PATH)/emlearn_cnn_int8.mpy: $(MODULES_PATH)/emlearn_cnn_fp32.mpy $(ARCH_SENTINEL)
	$(MAKE) -C src/tinymaix_cnn \
		ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) CFLAGS_EXTRA=$(CFLAGS_EXTRA) \
		V=1 CONFIG=int8 clean
	$(MAKE) -C src/tinymaix_cnn \
		ARCH=$(ARCH) MPY_DIR=$(MPY_DIR_ABS) CFLAGS_EXTRA=$(CFLAGS_EXTRA) \
		V=1 CONFIG=int8 dist

# Collect test files for dependency tracking
TEST_PY := $(wildcard tests/test_*.py)

check_unix_natmod: $(MODULE_MPYS) $(TEST_PY)
	MICROPYPATH=$(MODULES_PATH) $(MICROPYTHON_BIN) tests/test_all.py

$(PORT_DIR):
	mkdir -p $@

# Collect all source and build files under src/ for port builds
SRC_C := $(shell find src -name "*.c" 2>/dev/null)
SRC_H := $(shell find src -name "*.h" 2>/dev/null)
SRC_PY := $(shell find src -name "*.py" 2>/dev/null)
SRC_BUILD := src/micropython.cmake src/dynmodule.mk $(wildcard src/*/micropython.mk)
SRC_ALL = $(SRC_C) $(SRC_H) $(SRC_PY) $(SRC_BUILD)

$(UNIX_MICROPYTHON): $(PORT_DIR) $(SRC_ALL) src/manifest_unix.py
	$(MAKE) -C $(MPY_DIR)/ports/unix V=1 MICROPY_PY_FFI=0 USER_C_MODULES=$(C_MODULES_SRC_PATH) FROZEN_MANIFEST=$(MANIFEST_PATH) CFLAGS_EXTRA="-Wno-unused-function -Wno-unused-function $(CFLAGS_EXTRA)" -j4
	cp $(MPY_DIR)/ports/unix/build-standard/micropython $@

unix: $(UNIX_MICROPYTHON)

$(WEBASSEMBLY_MICROPYTHON): $(PORT_DIR) $(SRC_ALL) src/manifest_webassembly.py
	emcc --version
	mkdir -p $(PORT_DIR)/../webassembly
	$(MAKE) -C $(MPY_DIR)/ports/webassembly VARIANT=pyscript V=1 USER_C_MODULES=$(C_MODULES_SRC_PATH) FROZEN_MANIFEST=$(WEBASSEMBLY_MANIFEST_PATH) CFLAGS_EXTRA="-Wno-unused-function -Wno-unused-function $(CFLAGS_EXTRA)" -j4
	cp $(MPY_DIR)/ports/webassembly/build-pyscript/micropython.mjs $@
	cp $(MPY_DIR)/ports/webassembly/build-pyscript/micropython.wasm dist/ports/webassembly/


webassembly: $(WEBASSEMBLY_MICROPYTHON)


check_unix: $(UNIX_MICROPYTHON) $(TEST_PY)
	$(UNIX_MICROPYTHON) tests/test_all.py nomodules -test_cnn

rp2: $(PORT_DIR) $(SRC_ALL) src/manifest_unix.py
	$(MAKE) -C $(MPY_DIR)/ports/rp2 V=1 USER_C_MODULES=$(C_MODULES_SRC_PATH)/micropython.cmake FROZEN_MANIFEST=$(MANIFEST_PATH) CFLAGS_EXTRA='-Wno-unused-function -Wno-unused-function' -j4
	mkdir -p ./dist/ports/rp2/RPI_PICO
	cp -r $(MPY_DIR)/ports/rp2/build-RPI_PICO/firmware* ./dist/ports/rp2/RPI_PICO/


extmod: $(SRC_ALL) src/manifest_unix.py
	$(MAKE) -C $(MPY_DIR)/ports/esp32 V=1 BOARD=$(BOARD) USER_C_MODULES=$(C_MODULES_SRC_PATH)/micropython.cmake FROZEN_MANIFEST=$(MANIFEST_PATH) CFLAGS_EXTRA='-Wno-unused-function -Wno-unused-function' -j4
	mkdir -p $(PORT_DIST_DIR)
	cp -r $(PORT_BUILD_DIR)/firmware* $(PORT_DIST_DIR)
	cp -r $(PORT_BUILD_DIR)/micropython* $(PORT_DIST_DIR)
	

.PHONY: clean unix codesize

codesize:
	python3 tools/code_size.py $(MPY_DIR_ABS)/ports/unix/build-standard

clean:
	git clean -dfx src/
	rm -rf ./dist

RELEASE_NAME = emlearn-micropython-$(VERSION)
# NOTE: does not depend on dist.
release:
	mkdir $(RELEASE_NAME)
	cp -r dist/* $(RELEASE_NAME) 
	zip -r $(RELEASE_NAME).zip $(RELEASE_NAME)
	#cp $(RELEASE_NAME).zip emlearn-micropython-latest.zip

check: check_unix_natmod

dist: $(MODULE_MPYS)

# =============================================================================
# QEMU MicroPython targets
# =============================================================================

QEMU_PORT_DIR = $(MPY_DIR)/ports/qemu
QEMU_BOARD ?= MPS2_AN500
QEMU_ARCH ?= armv7emdp
QEMU_FIRMWARE = $(QEMU_PORT_DIR)/build-$(QEMU_BOARD)/firmware.elf

# Build firmware for QEMU
.PHONY: qemu_build
qemu_build:
	$(MAKE) -C $(QEMU_PORT_DIR) BOARD=$(QEMU_BOARD) MICROPY_HEAP_SIZE=1024000

# Run tests/test_all.py on QEMU using mpremote mount
# Usage: make check_qemu QEMU_BOARD=MPS2_AN500 QEMU_ARCH=armv7emdp
.PHONY: check_qemu
check_qemu: $(QEMU_FIRMWARE)
	python3 $(abspath tools/run_qemu_tests.py) --board $(QEMU_BOARD) --arch $(QEMU_ARCH) --abi-version $(MPY_ABI_VERSION) --mount $(abspath .)


