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

set -e

cd "$(dirname "$0")"

MELVIX_CONFIG="${MELVIX_CONFIG:-dev}"

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')
# TODO: Support -enable-kvm: GPF?!
qemu_with_flags() {
    network="rtl8139"
    qemu-system-i386 -d guest_errors,unimp,pcall -cpu max -no-reboot -vga std -rtc base=localtime -m 256M -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.36.1.tar.gz" >binutils.tar.gz
        tar xzf binutils.tar.gz
        curl "https://ftp.gnu.org/gnu/gcc/gcc-11.1.0/gcc-11.1.0.tar.gz" >gcc.tar.gz
        tar xzf gcc.tar.gz

        # Prepare compiling
        mkdir -p "${DIR}/opt/bin"
        export CFLAGS="-g0 -O2"
        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-11.1.0/gcc/configure"
        fi

        # Compile binutils
        mkdir "${DIR}/src/build-binutils" && cd "${DIR}/src/build-binutils"
        ../binutils-2.36.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-11.1.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/11.1.0/" && ln -sf liblto_plugin.so.0.0 liblto_plugin.so
        fi

        # Remove sources
        rm -rf "${dir}/src/"

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

make_disk() {
    rm -rf disk && mkdir -p disk/font/ && mkdir -p disk/icons/ && mkdir -p disk/conf/ && mkdir -p disk/boot/
    echo "Hallo" >disk/conf/test

    cp -r res/ disk/

    echo "Getting font"
    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 ../../

    icons="cursor-default
    cursor-default-outline
    chess-bishop
    chess-king
    chess-knight
    chess-pawn
    chess-queen
    chess-rook"

    [ -d icons/ ] || git clone --depth=1 https://github.com/Templarian/MaterialDesign.git icons/
    inkscape_version="$(inkscape --version | awk '{print $2}' | cut -c1)"
    echo "$icons" | while read icon; do
        echo "Converting $icon"
        if [ "$inkscape_version" = "0" ]; then
            inkscape -z -w 18 -h 18 icons/svg/"$icon".svg -e disk/icons/"$icon"-18.png
            inkscape -z -w 24 -h 24 icons/svg/"$icon".svg -e disk/icons/"$icon"-24.png
            inkscape -z -w 36 -h 36 icons/svg/"$icon".svg -e disk/icons/"$icon"-36.png
            inkscape -z -w 48 -h 48 icons/svg/"$icon".svg -e disk/icons/"$icon"-48.png
        else
            inkscape -w 18 -h 18 icons/svg/"$icon".svg -o disk/icons/"$icon"-18.png
            inkscape -w 24 -h 24 icons/svg/"$icon".svg -o disk/icons/"$icon"-24.png
            inkscape -w 36 -h 36 icons/svg/"$icon".svg -o disk/icons/"$icon"-36.png
            inkscape -w 48 -h 48 icons/svg/"$icon".svg -o disk/icons/"$icon"-48.png
        fi
    done

    echo "Done!"
}

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

    mkdir -p build/
    rm -rf build/*

    printf "\nBuilding...\n"
    $MAKE -j $($NPROC) CONFIG="$MELVIX_CONFIG" $MELVIX_MAKE

    # Prepare Grub
    mkdir -p disk/boot/grub/
    cp build/apps/kernel/exec disk/boot/melvix
    cp boot/grub.cfg disk/boot/grub/

    # Create disk image # TODO: Fix build for OpenBSD
    dd if=/dev/zero of=build/disk.img bs=1k count=32k status=none
    DEV=$($SUDO losetup --find --partscan --show build/disk.img)
    PART="p1"
    $SUDO parted -s "$DEV" mklabel msdos mkpart primary ext2 32k 100% -a minimal set 1 boot on
    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
    else
        $SUDO mke2fs -b 1024 -q "$DEV$PART"
    fi

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

    mkdir -p mnt/
    if [ "$(uname -s)" = "OpenBSD" ]; then
        $SUDO mount -t ext2fs /dev/${VND}i mnt/
    else
        $SUDO mount "$DEV$PART" mnt/
    fi
    $SUDO cp -r disk/* mnt/
    $SUDO chmod -R 0 mnt/conf/
    $SUDO cp -r build/apps/ mnt/apps/
    $SUDO grub-install --boot-directory=mnt/boot --target=i386-pc --modules="ext2" "$DEV"
    $SUDO umount mnt/ || (sleep 1 && sync && sudo umount mnt)
    $SUDO rm -rf mnt/

    if [ "$(uname -s)" = "OpenBSD" ]; then
        $SUDO vnconfig -u $VND
    else
        $SUDO losetup -d "$DEV"
    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() {
    if [ -z "$1" ]; then
        echo "Usage: './run disasm {kernel, wm, ...} [-S]'"
        exit 1
    fi
    objdump -drwC "$2" -Mintel build/apps/"$1"/exec | less -R
    #hexdump -C build/kernel.bin | less -R
}

make_addr() {
    printf "Info: Make sure that you've turned the debug build on (e.g. with MELVIX_CONFIG=debug)\n\n"
    if [ -z "$2" ]; then
        echo "Usage: './run addr kernel 0x50042'"
        exit 1
    fi
    addr2line -e build/apps/"$1"/exec -f -p "$2"
}

make_cloc() {
    cloc . --exclude-dir=build,iso,disk,res,cross,icons
}

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=boot .

    rm -f compile_commands.json
    output=$($MAKE --always-make --dry-run)
    echo "$output" | make_append_commands libc libk libs/libc
    echo "$output" | make_append_commands libk libgui libs/libgui
    echo "$output" | make_append_commands libgui libtxt libs/libtxt
    echo "$output" | make_append_commands libtxt kernel kernel
    echo "$output" | make_append_commands kernel 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_disasm "$2" "$3"
elif [ "${mode}" = "addr" ]; then
    make_addr "$2" "$3"
elif [ "${mode}" = "cloc" ]; then
    make_cloc
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 | addr | 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 a given part of Melvix\n"
    printf "addr\t\tResolves an address to a line of code\n"
    printf "cloc\t\tCount the total lines of code\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 "*\t\tAnything else prints this help\n"
    printf "\t\tWhen no option is set, Melvix gets built and emulated using QEMU (cross+clean+build)\n\n"
    echo "You can set the environment variable 'MELVIX_CONFIG' to a config group defined in '.build.mk'"
    echo "Set the environment variable 'MELVIX_MAKE=\"CONFIG_FOO=bar\"' to override build configs"
fi