#!/usr/bin/env bash
# Build a Mizu program.
#
#   ./koicc hello.c                  -> HELLO.EXE
#   ./koicc -m noteedit.c            -> NOTEEDIT.APP  (a loadable module)
#   ./koicc -d images.c png.c ...    -> IMAGES.DLL    (a library, see koidll.h)
#   ./koicc hello.c mytool           -> MYTOOL.EXE
#   ./koicc main.c board.c ai.c      -> MAIN.EXE
#   ./koicc main.c board.c -o games  -> GAMES.EXE
#   ./koicc -c tool.c                -> TOOL.EXE, with the C library (musl)
#   ./koicc -s game.c                -> GAME.EXE, with SDL 2 and the C library
#
# Several sources make one program.
set -euo pipefail

cd "$(dirname "$0")"

SOURCES=()
NAME=""
# Module: same image, entry returns to the loader (koimod.c).
MODULE=0
LIBC=0
SDL=0
CXX_SOURCES=0

while [ $# -gt 0 ]; do
    case "$1" in
        -m|--module)
            MODULE=1
            ;;
        -d|--library)
            # Library: module whose entry returns an export table (koidll.h).
            MODULE=2
            ;;
        -c|--libc)
            # With musl (ports/musl).
            LIBC=1
            ;;
        -s|--sdl)
            # With SDL 2 (ports/sdl2) and musl.
            LIBC=1
            SDL=1
            ;;
        -o)
            shift
            [ $# -gt 0 ] || { echo "koicc: -o needs a name" >&2; exit 1; }
            NAME=$1
            ;;
        *.c)
            SOURCES+=("$1")
            ;;
        *.cpp|*.cc|*.cxx)
            # C++: musl plus libstdc++.
            SOURCES+=("$1")
            LIBC=1
            CXX_SOURCES=1
            ;;
        *)
            # Bare word after the sources: output name.
            NAME=$1
            ;;
    esac
    shift
done

if [ ${#SOURCES[@]} -eq 0 ]; then
    echo "usage: koicc <source.c> [more.c ...] [-o name]" >&2
    exit 1
fi

[ -n "$NAME" ] || NAME=$(basename "${SOURCES[0]}" | sed 's/\.[^.]*$//')
if [ "$MODULE" = "1" ]; then
    OUTPUT=$(echo "$NAME" | tr 'a-z' 'A-Z').APP
    ENTRY=koimod.c
elif [ "$MODULE" = "2" ]; then
    OUTPUT=$(echo "$NAME" | tr 'a-z' 'A-Z').DLL
    ENTRY=koimod.c
else
    OUTPUT=$(echo "$NAME" | tr 'a-z' 'A-Z').EXE
    ENTRY=start.c
fi

CC=${CC:-gcc}

# Flags come from koiflags, written by `make sdk`.
#   -ffreestanding     no hosted environment
#   -fpie -pie         loaded anywhere; see program.ld
#   -mno-red-zone      interrupts may use this stack
#   -nostdlib          no startup files; start.c is the entry
if [ ! -f koiflags ]; then
    echo "koicc: koiflags is missing - this SDK copy is incomplete." >&2
    echo "       It is written by \`make sdk\` and holds the build flags." >&2
    exit 1
fi
. ./koiflags

if [ "$LIBC" = "1" ]; then
    if [ ! -f libc/lib/libc.a ]; then
        echo "koicc: this SDK has no C library - build it with ports/musl/build.sh" >&2
        exit 1
    fi
    # Hosted C: musl headers and startup, libgcc.
    EXTRA_INCLUDE=()
    EXTRA_LIBS=()
    if [ "$SDL" = "1" ]; then
        [ -f libc/lib/libSDL2.a ] || { echo "koicc: this SDK has no SDL - build it with ports/sdl2/build.sh" >&2; exit 1; }
        EXTRA_INCLUDE=(-isystem libc/include/SDL2)
        EXTRA_LIBS=(libc/lib/libSDL2.a)
    fi
    # Compiler helpers from the SDK if present (no red zone), else the host's.
    LIBGCC=libc/lib/libgcc.a
    [ -f "$LIBGCC" ] || LIBGCC=$($CC -print-libgcc-file-name)
    DRIVER=$CC
    CXX_LIBS=()
    if [ "$CXX_SOURCES" = "1" ]; then
        [ -f libc/lib/libstdc++.a ] || { echo "koicc: this SDK has no C++ library - build it with ports/libstdcxx/build.sh" >&2; exit 1; }
        DRIVER=${CXX:-g++}
        CXX_VERSION=$(ls libc/include/c++ | head -1)
        EXTRA_INCLUDE+=(-nostdinc++ -isystem "libc/include/c++/$CXX_VERSION"
                        -isystem "libc/include/c++/$CXX_VERSION/x86_64-linux-musl")
        CXX_LIBS=(libc/lib/libstdc++.a libc/lib/libsupc++.a libc/lib/libgcc_eh.a)
    fi
    "$DRIVER" -O2 -fpie -mno-red-zone -fno-stack-protector "${EXTRA_INCLUDE[@]}" \
        -fno-asynchronous-unwind-tables -ffunction-sections -fdata-sections \
        -DKOI_HOSTED -nostdinc -isystem libc/include \
        -isystem "$($CC -print-file-name=include)" -iquote . \
        -nostdlib -pie -Wl,--no-dynamic-linker -Wl,-T,libc/lib/program-libc.ld -Wl,--eh-frame-hdr \
        -Wl,--build-id=sha1 -Wl,-z,max-page-size=0x1000 -Wl,-z,noexecstack \
        -Wl,--gc-sections \
        -Wl,-u,pthread_mutexattr_init -Wl,-u,pthread_mutexattr_settype \
        -Wl,-u,pthread_mutexattr_destroy -Wl,-u,pthread_once \
        -Wl,-u,pthread_key_create -Wl,-u,pthread_getspecific -Wl,-u,pthread_setspecific \
        -o "$OUTPUT" libc/lib/koi_crt.o "${SOURCES[@]}" \
        -Wl,--start-group "${EXTRA_LIBS[@]}" "${CXX_LIBS[@]}" libc/lib/libc.a "$LIBGCC" -Wl,--end-group
else
"$CC" $KOI_CFLAGS $KOI_LDFLAGS \
    -o "$OUTPUT" "${SOURCES[@]}" "$ENTRY" koilib.c
fi

ABI=$(sed -n 's/^#define KOI_ABI_VERSION \([0-9]*\).*/\1/p' syscall.h)
ALPHA=$(sed -n 's/^#define KOI_ABI_IS_ALPHA \([0-9]*\).*/\1/p' syscall.h)

echo "$OUTPUT  (interface version $ABI)"
if [ "${ALPHA:-0}" = "1" ]; then
    echo
    echo "NOTE: the interface is ALPHA. Function numbers can still change, and"
    echo "      a kernel with a different interface version will refuse to run"
    echo "      this binary rather than misinterpret it. Rebuild after updating."
fi
echo
if [ "$MODULE" = "1" ]; then
    echo "This is a module: it is loaded by a program, not typed at a prompt."
    echo "Put it beside the program that loads it."
    exit 0
fi
if [ "$MODULE" = "2" ]; then
    echo "This is a library: programs open it with koi_dll_open (koidll.h)."
    echo "Put it beside the program that uses it, or in \\BIN for everyone."
    echo "\`dllinfo $OUTPUT\` lists what it exports."
    exit 0
fi

echo "Copy it to a Mizu drive and run it from the Run box or the Command Prompt."
echo "With mtools, and the disk image at esp.img:"
echo
echo "    mcopy -o -i esp.img $OUTPUT ::/"
