#!/usr/bin/env bash

set -e
set -o pipefail

NZ_DEPS=("curl" "jq" "basename" "tar" "find" "readlink")
NZ_DEP_ERR=100
NZ_ARCH_OS="unknown"
NZ_PLATFORM_ERR=101
NZ_JSON_URL="https://ziglang.org/download/index.json"
NZ_DIR="$HOME/.night.zig"
NZ_VERSION="0.2.0"
NZ_USAGE="
USAGE:
    nz <SUBCOMMAND>

SUBCOMMANDS:
    update            Update both zig nightly toolchain & nz itself
    update nightly    Update zig nightly toolchain only
    update nz         Update nz itself only
    cleanup           Remove outdated nightly toolchains
    version           Show nz version info
    help              Show this message
"

#https://stackoverflow.com/a/33297950
function check_dep {
    local dep="$1"
    if command -v "$dep" >/dev/null 2>&1 ; then
        return 0
    else
        return "$NZ_DEP_ERR"
    fi
}

function script_echo {
    printf '[night.zig] %b\n' "$1"
}

function check_deps {
    for dep in "${NZ_DEPS[@]}"; do
        if check_dep "$dep"; then
            :
        else
            >&2 script_echo "Dependency ${dep} is not installed. Exiting now..."
            exit "$NZ_DEP_ERR"
        fi
    done
}

function arch_info {
    local arch
    local uname_m
    uname_m="$(uname -m)"
    if [[ "$uname_m" == "arm64" ]]; then
        arch="aarch64"
    elif [[ "$uname_m" == "amd64" ]]; then
        arch="x86_64"
    elif [[ "$uname_m" == "x86_64" ]]; then
        arch="x86_64"
    elif [[ "$uname_m" == "i386" ]]; then
        arch="x86"
    elif [[ "$uname_m" == "riscv64" ]]; then
        arch="riscv64"
    elif [[ "$uname_m" == "powerpc" ]]; then
        arch="powerpc"
    elif [[ "$uname_m" == "ppc64el" ]]; then
        arch="powerpc64le"
    else
        arch="unknown"
    fi
    echo "$arch"
}

# https://stackoverflow.com/a/8597411
function os_info {
    local os
    if [[ "$OSTYPE" == "linux-gnu"* ]]; then
        os="linux"
    elif [[ "$OSTYPE" == "darwin"* ]]; then
        os="macos"
    elif [[ "$OSTYPE" == "cygwin" ]]; then
        # POSIX compatibility layer and Linux environment emulation for Windows
        os="windows"
    elif [[ "$OSTYPE" == "msys" ]]; then
        # Lightweight shell and GNU utilities compiled for Windows (part of MinGW)
        os="windows"
    elif [[ "$OSTYPE" == "win32" ]]; then
        # I'm not sure this can happen.
        os="windows"
    elif [[ "$OSTYPE" == "freebsd"* ]]; then
        os="unknown"
    else
        os="unknown"
    fi
    echo "$os"
}

function validate_platform {
    NZ_ARCH_OS="$(arch_info)-$(os_info)"
    if [[ "$NZ_ARCH_OS" == *"unknown"* ]]; then
        >&2 script_echo "No prebuilt nightly binary available for ${NZ_ARCH_OS}. Exiting now..."
        exit "$NZ_PLATFORM_ERR"
    else
        :
    fi
}

function nightly_needs_update {
    local upstream_ver="$1"
    local latest_dir_name="$(basename "$(readlink -f "$NZ_DIR"/latest)")"
    if [[ "$latest_dir_name" == *"$upstream_ver"* ]]; then
        return 1
    else
        return 0
    fi
}

# works for both macOS and linux
# https://unix.stackexchange.com/a/84980
function mktempdir {
    mktemp -d 2>/dev/null || mktemp -d -t 'tmpdir'
}

function fetch_tar {
    local tar_url="$1"
    local tar_name="$(basename "$tar_url")"
    local temp_dir="$(mktempdir)"

    script_echo "Fetching nightly tar ball...\n"
    curl --output-dir "$temp_dir" --output "$tar_name" "$tar_url"
    script_echo "Fetch completed.\n"

    echo "$temp_dir/$tar_name"
}

function unpack_tar {
    local tar_path="$1"

    mkdir -p "$NZ_DIR"

    local containing_dir="$(tar -tf "$tar_path" | head -1)"
    local new_install_dir_name="${containing_dir%/}"

    script_echo "Extracting nightly tar ball...\n"
    tar -xf "$tar_path" -C "$NZ_DIR"
    script_echo "Extraction completed.\n"

    echo "$new_install_dir_name"
}

function config_latest {
    script_echo "Configuring latest nightly...\n"
    local new_install_dir_name="$1"
    (
        cd "$NZ_DIR"
        rm -f latest
        ln -s "$new_install_dir_name" latest
    )
    script_echo "Latest nightly configured.\n"
}

function install_nightly {
    local tar_url="$1"

    local tar_path="$(fetch_tar "$tar_url" | tee /dev/tty | tail -n 1)"

    local new_install_dir_name="$(unpack_tar "$tar_path" | tee /dev/tty | tail -n 1)"

    config_latest "$new_install_dir_name"
}

function update_nightly_if_necessary {
    script_echo "Checking upstream nightly version...\n"
    local json="$(curl "$NZ_JSON_URL")"
    local upstream_ver="$(jq -n "$json" | jq -r ".master.version")"
    if nightly_needs_update "$upstream_ver"; then
        script_echo "Your nighly is outdated, updating now...\n"
        local tar_url="$(jq -n "$json" | jq -r ".master.\"$NZ_ARCH_OS\".tarball")"
        install_nightly "$tar_url"
        script_echo "Nightly update completed.\n"
    else
        script_echo "Your nightly is already up to date. Skip updating...\n"
    fi
}

function deploy_nz {
    # I know it's confusing naming
    # so the full path of `nz` is:
    # ~/.night.zig/nz/nz
    local nz_bin_dir="$NZ_DIR"/nz
    mkdir -p "$nz_bin_dir"
    rm -f "$nz_bin_dir"/nz
    script_echo "Deploying latest nz...\n"
    curl --output-dir "$nz_bin_dir" --output "nz" "https://raw.githubusercontent.com/jsomedon/night.zig/main/nz"
    chmod +x "$nz_bin_dir"/nz
    script_echo "Latest nz deployed successfully.\n"
}

function rm_outdated_nightly {
    script_echo "Cleaning up outdated nightly bins...\n"
    (
        cd "$NZ_DIR"
        local to_be_removed=$(find "." -maxdepth 1 ! -name "nz" ! -name "latest" ! -name "$(basename "$(readlink -f latest)")" ! -name ".")
        for tbr in $to_be_removed; do
            echo "removing ${NZ_DIR}/$(basename "$tbr")..."
            rm -rf "$tbr"
        done
    )
    script_echo "Cleanup completed.\n"
}

function show_version {
    script_echo "version $NZ_VERSION"
}

function deploy_nightly {
    local json="$(curl "$NZ_JSON_URL")"
    local tar_url="$(jq -n "$json" | jq -r ".master.\"$NZ_ARCH_OS\".tarball")"
    script_echo "Deploying latest nightly...\n"
    install_nightly "$tar_url"
    script_echo "Latest nightly deployed successfully.\n"
}

function path_needs_config {
    local path=$1
    if [[ "$PATH" == *"$path"* ]]; then
        return 1
    else
        return 0
    fi
}

function config_path {
    local path=$1
    script_echo "Adding ${path} to \$PATH...\n"
    # script_echo "You should config your \$PATH; put this line in your shell's profile file:"
    # script_echo "export PATH=${path}:\$PATH\n"
    local os="$(os_info)"
    local bash_profile
    if [[ "$os" == "macos" ]];then
        bash_profile="${HOME}/.bash_profile"
    elif [[ "$os" == "linux" ]];then
        bash_profile="${HOME}/.bashrc"
    else
        script_echo "\$PATH auto configuration is not supported on this system: ${os}"
        script_echo "You should manually configure your \$PATH; put this line in your shell's profile file:"
        script_echo "export PATH=${path}:\$PATH\n"
        return 0
    fi
    echo "export PATH=${path}:\$PATH" >> "$bash_profile"
    script_echo "Added ${path} to \$PATH.\n"
}

function config_path_if_necessary {
    local path=$1
    if path_needs_config "$path"; then
        config_path "$path"
    else
        :
    fi
}

function show_help {
    show_version
    printf "%s" "$NZ_USAGE"
}

function main {
    check_deps
    validate_platform

    if [[ $# -eq 0 ]]; then
        show_help
    elif [[ $1 == "update" ]]; then
        if [[ $# -eq 1 ]]; then
            update_nightly_if_necessary
            deploy_nz
        elif [[ $# -eq 2 ]]; then
            if [[ $2 == "nightly" ]]; then
                update_nightly_if_necessary
            elif [[ $2 == "nz" ]]; then
                deploy_nz
            else
                show_help
            fi
        else
            show_help
        fi
    elif [[ $1 == "cleanup" ]]; then
        rm_outdated_nightly
    elif [[ $1 == "version" ]]; then
        show_version
    elif [[ $1 == "_bootstrap" ]]; then
        # hidden option, only used upon first time install
        script_echo "Starting fresh installation...\n"
        deploy_nightly
        deploy_nz
        script_echo "Installation completed.\n"
        config_path_if_necessary "$NZ_DIR"/latest
        config_path_if_necessary "$NZ_DIR"/nz
    else
        show_help
    fi
}

main "$@"