#!/usr/bin/env bash
#
# sprinter-cc — wrapper around SDCC for building Sprinter .EXE files.
#
# Usage:
#   sprinter-cc -o foo.exe foo.c [more.c ...] [options]
#
# Options:
#   -o NAME          output executable name (required)
#   --crt0=TYPE      crt0 variant: default | minimal | banked   (default: default)
#   -I PATH          additional include path (repeatable)
#   -L 0xADDR        code load address (default: 0x4100)
#   -E 0xADDR        entry address       (default: same as -L)
#   -S 0xADDR        stack address       (default: 0xBFFE)
#   --code-loc 0xN   forwarded to SDCC --code-loc       (default: 0x4100)
#   --data-loc 0xN   forwarded to SDCC --data-loc       (default: 0x8000)
#   -Wl FLAG         extra linker flag (repeatable)
#   --bank N=FILE.c  compile FILE.c as bank N; repeatable; pulls crt0_banked
#                    automatically and adds -Wl-b_BANKN=0x{N}C000
#   --mkexe FLAG     extra mkexe flag (repeatable; e.g. --mkexe -p --mkexe 0)
#   -v               verbose (echo every command)
#   -h, --help       show this help
#
# Example:
#   sprinter-cc -o hello.exe hello.c
#   sprinter-cc -o app.exe main.c --bank 1=engine.c
#   sprinter-cc -o tiny.exe tiny.c --crt0=minimal

set -eo pipefail

# ------- Locate the toolchain ------------------------------------------------
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PROJ_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"

SDCC_BIN="$PROJ_ROOT/third_party/sdcc/bin"
SDCC="$SDCC_BIN/sdcc"
SDASZ80="$SDCC_BIN/sdasz80"
MKEXE="$PROJ_ROOT/toolchain/mkexe/mkexe"
CHECK_BANKS="$PROJ_ROOT/toolchain/check_banks.py"

RUNTIME="$PROJ_ROOT/runtime"
LIB_DIR="$PROJ_ROOT/lib"
INC_DIR="$PROJ_ROOT/libc/include"

# ------- Defaults ------------------------------------------------------------
OUT=""
CRT0_TYPE="default"
CODE_LOC="0x8100"   # TINY mode default — everything in W2 (single page)
DATA_LOC="0"         # 0 = let linker auto-place after code areas
LOAD_ADDR=""
ENTRY_ADDR=""
STACK_ADDR="0xBFFE"
VERBOSE=0
USER_INCS=()
SOURCES=()
LD_EXTRA=()
MKEXE_EXTRA=()
BANK_SPECS=()    # entries like "1=engine.c"

# ------- Parse args ----------------------------------------------------------
usage() {
    sed -n '3,28p' "$0" | sed 's/^# \{0,1\}//'
    exit "${1:-0}"
}

while [[ $# -gt 0 ]]; do
    case "$1" in
        -o)            OUT="$2";          shift 2;;
        --crt0=*)      CRT0_TYPE="${1#*=}"; shift;;
        -I)            USER_INCS+=("-I" "$2"); shift 2;;
        -L)            LOAD_ADDR="$2";    shift 2;;
        -E)            ENTRY_ADDR="$2";   shift 2;;
        -S)            STACK_ADDR="$2";   shift 2;;
        --code-loc)    CODE_LOC="$2";     shift 2;;
        --data-loc)    DATA_LOC="$2";     shift 2;;
        -Wl)           LD_EXTRA+=("$2");  shift 2;;
        --bank)        BANK_SPECS+=("$2"); shift 2;;
        --mkexe)       MKEXE_EXTRA+=("$2"); shift 2;;
        -v)            VERBOSE=1;         shift;;
        -h|--help)     usage 0;;
        -*)            echo "sprinter-cc: unknown option: $1" >&2; usage 1;;
        *)             SOURCES+=("$1");   shift;;
    esac
done

[[ -z "$OUT" ]]         && { echo "sprinter-cc: -o NAME is required" >&2; exit 1; }
[[ ${#SOURCES[@]} -eq 0 ]] && { echo "sprinter-cc: no input files" >&2; exit 1; }

LOAD_ADDR="${LOAD_ADDR:-$CODE_LOC}"
ENTRY_ADDR="${ENTRY_ADDR:-$LOAD_ADDR}"

# Pick crt0 source.
case "$CRT0_TYPE" in
    default) CRT0_SRC="$RUNTIME/crt0.s";        ;;
    minimal) CRT0_SRC="$RUNTIME/crt0_minimal.s";;
    banked)  CRT0_SRC="$RUNTIME/crt0_banked.s"; ;;
    *) echo "sprinter-cc: bad --crt0 type: $CRT0_TYPE" >&2; exit 1;;
esac

# Banked builds always need crt0_banked, regardless of what the user asked.
if [[ ${#BANK_SPECS[@]} -gt 0 ]] && [[ "$CRT0_TYPE" != "banked" ]]; then
    [[ $VERBOSE -eq 1 ]] && echo "  --bank present → switching to crt0_banked"
    CRT0_SRC="$RUNTIME/crt0_banked.s"
fi

# ------- Build in a per-output workdir ---------------------------------------
WORK="$(dirname "$OUT")/.sprinter-cc-$(basename "$OUT" .exe)"
mkdir -p "$WORK"

run() {
    [[ $VERBOSE -eq 1 ]] && echo "  $*"
    "$@"
}

# 1. crt0
CRT0_REL="$WORK/$(basename "$CRT0_SRC" .s).rel"
run "$SDASZ80" -o "$CRT0_REL" "$CRT0_SRC"

# 2. user sources → .rel (HOME)
USER_RELS=()
CC_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size -I "$INC_DIR" "${USER_INCS[@]}")
for src in "${SOURCES[@]}"; do
    rel="$WORK/$(basename "$src" .c).rel"
    run "$SDCC" "${CC_FLAGS[@]}" -c -o "$rel" "$src"
    USER_RELS+=("$rel")
done

# 3. bank sources
BANK_RELS=()
BANK_LD_FLAGS=()
for spec in "${BANK_SPECS[@]}"; do
    bank_n="${spec%%=*}"
    bank_src="${spec#*=}"
    rel="$WORK/bank${bank_n}_$(basename "$bank_src" .c).rel"
    run "$SDCC" "${CC_FLAGS[@]}" \
        --codeseg "BANK${bank_n}" --constseg "BANK${bank_n}" --dataseg "BANK${bank_n}" \
        -c -o "$rel" "$bank_src"
    BANK_RELS+=("$rel")
    # virtual address: bank_n in upper byte, 0xC000 low
    addr=$(printf "0x%X" $(( (bank_n << 16) | 0xC000 )))
    BANK_LD_FLAGS+=("-Wl-b_BANK${bank_n}=${addr}")
done

# 4. link → .ihx
IHX="$WORK/$(basename "$OUT" .exe).ihx"
LINK_FLAGS=(-mz80 --no-std-crt0 --std-c99 --opt-code-size
            --code-loc "$CODE_LOC" --data-loc "$DATA_LOC")
LINK_FLAGS+=("${BANK_LD_FLAGS[@]}")
for f in "${LD_EXTRA[@]}"; do LINK_FLAGS+=("$f"); done
# libsprinter.lib via -l/-L (sdcc passes -lsprinter through to sdldz80)
run "$SDCC" "${LINK_FLAGS[@]}" -o "$IHX" \
    "$CRT0_REL" "${USER_RELS[@]}" "${BANK_RELS[@]}" \
    "-L$LIB_DIR" "-lsprinter"

# Quick bank-size check (only meaningful if there are banks).
if [[ ${#BANK_SPECS[@]} -gt 0 ]] && [[ -f "${IHX%.ihx}.map" ]]; then
    python3 "$CHECK_BANKS" "${IHX%.ihx}.map" || true
fi

# 5. mkexe → .exe
MK_FLAGS=(-L "$LOAD_ADDR" -E "$ENTRY_ADDR" -S "$STACK_ADDR" -o "$OUT" "$IHX")
[[ $VERBOSE -eq 1 ]] && MK_FLAGS=(-v "${MK_FLAGS[@]}")
for f in "${MKEXE_EXTRA[@]}"; do MK_FLAGS=("$f" "${MK_FLAGS[@]}"); done
run "$MKEXE" "${MK_FLAGS[@]}"

echo "sprinter-cc: wrote $OUT"
