TARGET              := monitor
BUILDDIR            := build
LAYOUT_CONFIG       := rom_layout.mk
LAYOUT_BUILD        := $(BUILDDIR)/generated
LAYOUT_HEADER       := $(LAYOUT_BUILD)/rom_layout.h
LAYOUT_INCLUDE      := $(LAYOUT_BUILD)/rom_layout.inc

include $(LAYOUT_CONFIG)

METAL_C_SRCFILES    := $(wildcard *.c)
METAL_ASM_SRCFILES  := boot.asm
METAL_RELFILES      := $(METAL_ASM_SRCFILES:%.asm=$(BUILDDIR)/metal/%.rel) \
                       $(METAL_C_SRCFILES:%.c=$(BUILDDIR)/metal/%.rel)

METAL_CC            := sdcc
METAL_AS            := sdasz80
METAL_MAKEBIN       := makebin
METAL_FLAGS         := -mz80 --no-std-crt0 --code-loc $(RESIDENT_CODE_BASE) --code-size $(RESIDENT_CODE_SIZE) --data-loc $(RESIDENT_DATA_BASE) --opt-code-size
METAL_CPPFLAGS      := -I src -I $(LAYOUT_BUILD) --disable-warning 283
METAL_BIN_SIZE      := $(ROM_SIZE)
METAL_RAM_SIZE      := 16384
METAL_IMAGE_SIZE    := 65536

TINYBASIC_SRC       := apps/tinybasic/tinybasic.asm
TINYBASIC_BUILD     := $(BUILDDIR)/tinybasic
TAPE_LOADER_SRC     := apps/casette/tape_loader.asm
TAPE_LOADER_BUILD   := $(BUILDDIR)/casette
NASCOM_BASIC_SRC    := apps/nascom_basic/basic.asm
NASCOM_BASIC_BUILD  := $(BUILDDIR)/nascom_basic

.DEFAULT_GOAL       := metal

.PHONY: all metal clean

all: metal

metal: $(BUILDDIR)/metal/$(TARGET).ihx $(BUILDDIR)/metal/$(TARGET).bin $(BUILDDIR)/metal/$(TARGET).prog.bin
	@stored=$$(wc -c < $(BUILDDIR)/metal/$(TARGET).prog.bin); \
	awk -v rom_capacity=$(METAL_BIN_SIZE) -v stored=$$stored -v ram_capacity=$(METAL_RAM_SIZE) '\
		$$1 ~ /^_(CODE|HOME|GSINIT|GSFINAL|INITIALIZER)$$/ || $$1 ~ /^_ITABLE/ { code += $$5 } \
		$$1 ~ /^_(DATA|INITIALIZED|BSEG|BSS|HEAP)$$/ { data += $$5 } \
		END { \
			printf "\nMemory usage:\n"; \
			printf "  ROM storage:  %5u / %5u bytes (%5.1f%%)\n", stored, rom_capacity, 100 * stored / rom_capacity; \
			printf "  Resident BIOS+monitor: %5u bytes (%5u code/vector + %5u static data)\n", code + data, code, data; \
		}' $(BUILDDIR)/metal/$(TARGET).map
	@printf "  Tiny BASIC:   %5u ROM bytes at %04X\n" \
		$$(wc -c < $(TINYBASIC_BUILD)/tinybasic.bin) \
		$$(( $(TINYBASIC_ROM_ADDRESS) ))
	@printf "  Tape loader:  %5u ROM bytes at %04X\n" \
		$$(wc -c < $(TAPE_LOADER_BUILD)/tape_loader.bin) \
		$$(( $(TAPE_LOADER_ROM_ADDRESS) ))
	@printf "  NASCOM BASIC: %5u ROM bytes at %04X\n" \
		$$(wc -c < $(NASCOM_BASIC_BUILD)/nascom_basic.bin) \
		$$(( $(NASCOM_BASIC_ROM_ADDRESS) ))

$(BUILDDIR)/metal/$(TARGET).ihx: $(METAL_RELFILES)
	@mkdir -p $(@D)
	$(METAL_CC) $(METAL_FLAGS) -o $@ $(METAL_RELFILES)

$(BUILDDIR)/metal/$(TARGET).flat.bin: $(BUILDDIR)/metal/$(TARGET).ihx
	$(METAL_MAKEBIN) -s $(METAL_IMAGE_SIZE) $< $@

$(BUILDDIR)/metal/$(TARGET).prog.bin: $(BUILDDIR)/metal/$(TARGET).flat.bin $(TINYBASIC_BUILD)/tinybasic.bin $(TAPE_LOADER_BUILD)/tape_loader.bin $(NASCOM_BASIC_BUILD)/nascom_basic.bin
	@set -e; \
	payload_end=$$(awk '$$1 == "DEF" && $$2 == "resident_image_end" { print $$3 }' $(BUILDDIR)/metal/$(TARGET).noi); \
	payload_size=$$((payload_end - $(RESIDENT_RAM_BASE))); \
	payload_rom_end=$$(( $(RESIDENT_ROM_OFFSET) + payload_size )); \
	tiny_size=$$(wc -c < $(TINYBASIC_BUILD)/tinybasic.bin); \
	tape_size=$$(wc -c < $(TAPE_LOADER_BUILD)/tape_loader.bin); \
	nascom_size=$$(wc -c < $(NASCOM_BASIC_BUILD)/nascom_basic.bin); \
	if test $$payload_size -le 0; then \
		echo "error: resident image is empty or has an invalid end symbol" >&2; exit 1; \
	fi; \
	if test $$payload_rom_end -gt $$(( $(TINYBASIC_ROM_ADDRESS) )); then \
		printf "error: resident ROM image ends at %04X, beyond Tiny BASIC origin %04X\n" \
			$$payload_rom_end $$(( $(TINYBASIC_ROM_ADDRESS) )) >&2; exit 1; \
	fi; \
	if test $$(( $(TINYBASIC_ROM_ADDRESS) + tiny_size )) -gt $$(( $(TAPE_LOADER_ROM_ADDRESS) )); then \
		printf "error: Tiny BASIC ends at %04X, beyond tape-loader origin %04X\n" \
			$$(( $(TINYBASIC_ROM_ADDRESS) + tiny_size )) $$(( $(TAPE_LOADER_ROM_ADDRESS) )) >&2; exit 1; \
	fi; \
	if test $$(( $(TAPE_LOADER_ROM_ADDRESS) + tape_size )) -gt $$(( $(NASCOM_BASIC_ROM_ADDRESS) )); then \
		printf "error: tape loader ends at %04X, beyond NASCOM BASIC origin %04X\n" \
			$$(( $(TAPE_LOADER_ROM_ADDRESS) + tape_size )) $$(( $(NASCOM_BASIC_ROM_ADDRESS) )) >&2; exit 1; \
	fi; \
	if test $$(( $(NASCOM_BASIC_ROM_ADDRESS) + nascom_size )) -gt $$(( $(ROM_SIZE) )); then \
		printf "error: NASCOM BASIC ends at %04X, beyond ROM size %04X\n" \
			$$(( $(NASCOM_BASIC_ROM_ADDRESS) + nascom_size )) $$(( $(ROM_SIZE) )) >&2; exit 1; \
	fi; \
	dd if=$< of=$@ bs=1 count=$$(( $(NASCOM_BASIC_ROM_ADDRESS) )) status=none; \
	dd if=$< of=$@ bs=1 skip=$$(( $(RESIDENT_RAM_BASE) )) seek=$$(( $(RESIDENT_ROM_OFFSET) )) count=$$payload_size conv=notrunc status=none; \
	dd if=$(TINYBASIC_BUILD)/tinybasic.bin of=$@ bs=1 seek=$$(( $(TINYBASIC_ROM_ADDRESS) )) conv=notrunc status=none; \
	dd if=$(TAPE_LOADER_BUILD)/tape_loader.bin of=$@ bs=1 seek=$$(( $(TAPE_LOADER_ROM_ADDRESS) )) conv=notrunc status=none; \
	dd if=$(NASCOM_BASIC_BUILD)/nascom_basic.bin of=$@ bs=1 seek=$$(( $(NASCOM_BASIC_ROM_ADDRESS) )) conv=notrunc status=none

$(BUILDDIR)/metal/$(TARGET).bin: $(BUILDDIR)/metal/$(TARGET).flat.bin $(BUILDDIR)/metal/$(TARGET).prog.bin
	dd if=$< of=$@ bs=$(METAL_BIN_SIZE) count=1 status=none
	dd if=$(BUILDDIR)/metal/$(TARGET).prog.bin of=$@ conv=notrunc status=none

$(BUILDDIR)/metal/%.rel: %.c $(LAYOUT_HEADER)
	@mkdir -p $(@D)
	$(METAL_CC) $(METAL_FLAGS) $(METAL_CPPFLAGS) -o $@ -c $<

$(BUILDDIR)/metal/%.rel: %.asm $(LAYOUT_INCLUDE)
	@mkdir -p $(@D)
	$(METAL_AS) -Iinclude -I$(LAYOUT_BUILD) -plosgff -o $@ $<

$(TINYBASIC_BUILD)/tinybasic.rel: $(TINYBASIC_SRC) $(LAYOUT_INCLUDE)
	@mkdir -p $(@D)
	$(METAL_AS) -Iinclude -I$(LAYOUT_BUILD) -plosgff -o $@ $<

$(TINYBASIC_BUILD)/tinybasic.ihx: $(TINYBASIC_BUILD)/tinybasic.rel
	sdldz80 -n -i -m -j $(basename $@) $<

$(TINYBASIC_BUILD)/tinybasic.flat.bin: $(TINYBASIC_BUILD)/tinybasic.ihx
	$(METAL_MAKEBIN) -s $(METAL_IMAGE_SIZE) $< $@

$(TINYBASIC_BUILD)/tinybasic.bin: $(TINYBASIC_BUILD)/tinybasic.flat.bin
	@tiny_size=$$(awk '$$1 == "TINYBASIC0" { print $$5 + 0 }' $(TINYBASIC_BUILD)/tinybasic.map); \
	test $$tiny_size -gt 0; \
	dd if=$< of=$@ bs=1 skip=$$(( $(TINYBASIC_ROM_ADDRESS) )) count=$$tiny_size status=none

$(TAPE_LOADER_BUILD)/tape_loader.rel: $(TAPE_LOADER_SRC) $(LAYOUT_INCLUDE)
	@mkdir -p $(@D)
	$(METAL_AS) -Iinclude -I$(LAYOUT_BUILD) -plosgff -o $@ $<

$(TAPE_LOADER_BUILD)/tape_loader.ihx: $(TAPE_LOADER_BUILD)/tape_loader.rel
	sdldz80 -n -i -m -j $(basename $@) $<

$(TAPE_LOADER_BUILD)/tape_loader.flat.bin: $(TAPE_LOADER_BUILD)/tape_loader.ihx
	$(METAL_MAKEBIN) -s $(METAL_IMAGE_SIZE) $< $@

$(TAPE_LOADER_BUILD)/tape_loader.bin: $(TAPE_LOADER_BUILD)/tape_loader.flat.bin
	@loader_size=$$(awk '$$1 == "TAPELOAD0" { print $$5 + 0 }' $(TAPE_LOADER_BUILD)/tape_loader.map); \
	test $$loader_size -gt 0; \
	dd if=$< of=$@ bs=1 skip=$$(( $(TAPE_LOADER_ROM_ADDRESS) )) count=$$loader_size status=none

$(NASCOM_BASIC_BUILD)/nascom_basic.bin: $(NASCOM_BASIC_SRC)
	@mkdir -p $(@D)
	zasm --z80 $< -o $@ -l0

$(LAYOUT_HEADER): $(LAYOUT_CONFIG)
	@mkdir -p $(@D)
	@awk 'BEGIN { print "#ifndef ROM_LAYOUT_H"; print "#define ROM_LAYOUT_H"; print "" } \
		/^[A-Z][A-Z0-9_]*[[:space:]]*:=[[:space:]]*/ { print "#define " $$1 " " $$3 } \
		END { print ""; print "#endif" }' $< > $@

$(LAYOUT_INCLUDE): $(LAYOUT_CONFIG)
	@mkdir -p $(@D)
	@awk 'BEGIN { print "; Generated from rom_layout.mk; do not edit." } \
		/^[A-Z][A-Z0-9_]*[[:space:]]*:=[[:space:]]*/ { print $$1 " = " $$3 }' $< > $@

clean:
	rm -rf $(BUILDDIR)

-include $(EMU_DEPFILES)
