#!/usr/bin/env sh
# MIT License, Copyright (c) 2020 Marvin Borner

set -e

cd "$(dirname "$0")"

MAKE=make
NPROC=nproc
SUDO=sudo
TAGS=ctags
if [ "$(uname -s)" = "OpenBSD" ]; then
        NPROC="sysctl -n hw.ncpuonline"
        SUDO="doas"
        TAGS="ectags"
        export MAKE=gmake
        export CC="egcc"
        export CXX="eg++"
        export LDFLAGS=-Wl,-z,notext
fi

mode="${1}"
no_ask="${2}"

# TODO: Support q35 chipset ('-machine q35') - loops in ide_wait
qemu_with_flags() {
    network="rtl8139"
    qemu-system-i386 -cpu max -no-reboot -vga std -rtc base=localtime -m 256M -smp 4 -netdev user,id=net0,hostfwd=tcp:127.0.0.1:8000-10.0.2.15:8000 -device $network,netdev=net0 -object filter-dump,id=dump,netdev=net0,file=dump.pcap "$@"
}

make_cross() {
    if [ ! -d "./cross/" ]; then
        if [ "$no_ask" != "-y" ]; then
            echo -n "Do you want to compile a cross compiler (this can take up to 20 minutes)? [yn] "
            read -r answer
            if ! [ "$answer" != "${answer#[Yy]}" ]; then
                echo "The compilation of melvix requires a cross compiler!"
                exit 1
            fi
        fi

        # Create directory
        mkdir -p cross
        cd cross
        DIR=$(pwd)

        # Get sources
        mkdir "${DIR}/src" && cd "${DIR}/src"
        echo "Downloading..."
        curl "https://ftp.gnu.org/gnu/binutils/binutils-2.33.1.tar.gz" >binutils.tar.gz
        tar xzf binutils.tar.gz
        curl "https://ftp.gnu.org/gnu/gcc/gcc-9.2.0/gcc-9.2.0.tar.gz" >gcc.tar.gz
        tar xzf gcc.tar.gz

        # Prepare compiling
        mkdir -p "${DIR}/opt/bin"
        export PREFIX="${DIR}/opt"
        export TARGET=i686-elf
        export PATH="$PREFIX/bin:$PATH"

        if [ "$(uname -s)" = "OpenBSD" ]; then
                export with_gmp=/usr/local
                sed -i 's/-no-pie/-nopie/g' "${DIR}/src/gcc-9.2.0/gcc/configure"
        fi

        # Compile binutils
        mkdir "${DIR}/src/build-binutils" && cd "${DIR}/src/build-binutils"
        ../binutils-2.33.1/configure --target="$TARGET" --prefix="$PREFIX" --with-sysroot --disable-nls --disable-werror
        $MAKE -j $($NPROC)
        $MAKE install

        # Compile GCC
        mkdir "${DIR}/src/build-gcc" && cd "${DIR}/src/build-gcc"
        ../gcc-9.2.0/configure --target="$TARGET" --prefix="$PREFIX" --disable-nls --enable-languages=c --without-headers
        $MAKE -j $($NPROC) all-gcc all-target-libgcc
        $MAKE install-gcc install-target-libgcc

        # Fix things
        if [ "$(uname -s)" = "OpenBSD" ]; then
                cd "${DIR}/opt/libexec/gcc/i686-elf/9.2.0/" && ln -sf liblto_plugin.so.0.0 liblto_plugin.so
        fi

        cd "${DIR}/.."
    fi
}

make_disk() {
    rm -rf disk && mkdir -p disk/font/

    cp -r res/ disk/

    cd disk/font/
    VERSION="1.8.2"
    wget -q "https://github.com/fcambus/spleen/releases/download/$VERSION/spleen-$VERSION.tar.gz"
    tar xzf "spleen-$VERSION.tar.gz"
    mv spleen-"$VERSION"/*.psfu .
    rm -rf "spleen-$VERSION"*

    cd ../../
}

make_build() {
    if ! [ -d "disk/" ]; then
        echo "Creating disk..."
        make_disk
    fi

    mkdir -p build/
    rm -rf build/*

    printf "\nBuilding...\n"
    if [ "$mode" = "debug" ]; then
        $MAKE -j $($NPROC) debug
    else
        $MAKE -j $($NPROC)
    fi

    # Create disk image
    dd if=/dev/zero of=build/disk.img bs=1k count=32k status=none
    if [ "$(uname -s)" = "OpenBSD" ]; then
        VND=$($SUDO vnconfig build/disk.img)
        (echo "e 0"; echo 83; echo n; echo 0; echo "*"; echo "quit") | $SUDO fdisk -e $VND >/dev/null
        $SUDO mkfs.ext2 -F /dev/${VND}i >/dev/null

        if [ "$(cd ext2util/ && git diff --name-only | wc -l | xargs)" -ne "5" ]; then
            cd ext2util
            git stash
            find ./ -type f -exec sed -i -e 's/sync/fs_sync/g' {} \;
            sed -i 's/gcc/egcc/g' Makefile
            $MAKE
            cd ..
        fi

        $SUDO dd if=build/boot.bin of=/dev/${VND}i conv=notrunc status=none
        cp build/load.bin . # For nicer disk img
        $SUDO ./ext2util/ext2util -x /dev/${VND}i -wf load.bin -i 5 >/dev/null
        rm load.bin
    else
        $SUDO mke2fs -q build/disk.img
        dd if=build/boot.bin of=build/disk.img conv=notrunc status=none
        cp build/load.bin . # For nicer disk img
        ./ext2util/ext2util -x build/disk.img -wf load.bin -i 5 >/dev/null
        rm load.bin
    fi

    # Set test app as init
    if [ "$mode" = "test" ]; then
        cp build/apps/test build/apps/init
    fi

    mkdir -p mnt/
    if [ "$(uname -s)" = "OpenBSD" ]; then
        $SUDO mount -t ext2fs /dev/${VND}i mnt/
    else
        $SUDO mount build/disk.img mnt/
    fi
    $SUDO cp -r disk/* mnt/
    $SUDO cp -r build/apps/ mnt/bin/
    $SUDO cp build/kernel.bin mnt/
    $SUDO umount mnt/
    rm -rf mnt/

    if [ "$(uname -s)" = "OpenBSD" ]; then
        $SUDO vnconfig -u $VND
    fi

    printf "Build finshed successfully!\n\n"
}

make_test() {
    if [ "$mode" = "test" ]; then
        qemu_with_flags -serial file:test.log -nographic -drive file=build/disk.img,format=raw,index=1,media=disk
        echo
        grep -E 'PASS|FAIL' test.log
        if grep -q "All tests passed" test.log; then exit 0; else exit 1; fi
    else
        qemu_with_flags -serial stdio -drive file=build/disk.img,format=raw,index=1,media=disk
    fi
}

make_debug() {
    qemu_with_flags -serial stdio -drive file=build/disk.img,format=raw,index=1,media=disk -s -S
}

make_disasm() {
    objdump -drwC -Mintel build/kernel.elf | less -R
    #hexdump -C build/kernel.bin | less -R
}

make_append_commands() {
    s=""
    while read -r data; do
        s="${s}
${data}"
    done

    echo "$s" |
        sed -n "/Compiled $1/,/Compiled $2/p" |
        grep -wE 'gcc' |
        grep -w '\-c' |
        jq -nR '[inputs|{directory:"'"$(pwd)/$3"'/", command:., file: match(" [^ ]+$").string[1:]}]' \
            >>compile_commands.json
}

make_sync() {
    $TAGS -R --exclude=.git --exclude=build --exclude=disk --exclude=cross --exclude=ext2util --exclude=boot .

    rm -f compile_commands.json
    output=$($MAKE --always-make --dry-run)
    echo "$output" | make_append_commands libc libk libc
    echo "$output" | make_append_commands libk libgui libgui
    echo "$output" | make_append_commands libgui libtxt libtxt
    echo "$output" | make_append_commands libtxt libnet libnet
    echo "$output" | make_append_commands libnet kernel kernel
    echo "$output" | make_append_commands kernel boot boot
    echo "$output" | make_append_commands boot apps apps
    tr <compile_commands.json '\n' '\r' | sed -e 's/\r]\r\[/,/g' | tr '\r' '\n' >tmp
    mv tmp compile_commands.json
}

make_clean() {
    rm -rf build/
    $MAKE clean
}

if [ "${mode}" = "cross" ]; then
    make_cross
elif [ "${mode}" = "build" ]; then
    make_cross
    make_clean
    make_build
elif [ "${mode}" = "clean" ]; then
    make_clean
elif [ "${mode}" = "again" ]; then
    make_test
elif [ "${mode}" = "disasm" ]; then
    make_cross
    make_clean
    make_build
    make_disasm
    make_clean
elif [ "${mode}" = "sync" ]; then
    make_sync
elif [ "${mode}" = "disk" ]; then
    make_disk
elif [ "${mode}" = "debug" ]; then
    make_cross
    make_clean
    make_build
    make_sync &
    make_debug
elif [ "${mode}" = "test" ] || [ "${mode}" = "" ]; then
    make_cross
    make_clean
    make_build
    make_sync &
    make_test
    make_clean
else
    echo "Usage: ./run {cross | clean | build | test | debug | again | disasm | sync | disk} [-y]"
    printf "\nDescription of options:\n"
    printf "cross\t\tBuilds the cross compiler\n"
    printf "clean\t\tRemoves the compiled files\n"
    printf "build\t\tBuilds the whole project (cross+clean)\n"
    printf "test\t\tRuns the Melvix unit tests with QEMU (cross+clean+build)\n"
    printf "debug\t\tEmulates Melvix with QEMU and debug options (cross+clean+build)\n"
    printf "again\t\tOpens QEMU again using the previous build\n"
    printf "disasm\t\tDisassembles the main kernel binary\n"
    printf "sync\t\tSyncs the 'tags' and 'compile_commands.json' file\n"
    printf "disk\t\tPrepares the userspace disk (e.g. fonts)\n"
    printf "nothing\t\tWhen no option is set, Melvix gets built and emulated using QEMU (cross+clean+build)\n"
    printf "*\t\tAnything else prints this help\n\n"
fi