#!/usr/bin/env bash
set -euo pipefail

function die() {
    echo -e "! ERROR:" "$@" >&2
    exit 1
}

function check_tool() {
    if ! command -v "$1" &>/dev/null; then
        echo "!! Required tool '$1' is not installed."
        exit 1
    fi
}

function check_bash() {
    if ! declare -n _DUMMY &>/dev/null; then
        die "You need bash at least 4.3 to run this script."
    fi
}

function get_platform_os() {
    local -n _platform_os="$1"

    if [[ "$OSTYPE" == "linux"* ]]; then
        _platform_os="linux"
    elif [[ "$OSTYPE" == "darwin"* ]]; then
        _platform_os="darwin"
    elif [[ "$OSTYPE" == "freebsd"* ]]; then
        _platform_os="freebsd"
    else
        # Resort to `uname` for windows stuff.
        local name
        name=$(uname -a)
        case "$name" in
        CYGWIN*) _platform_os="windows" ;;
        MINGW*) _platform_os="windows" ;;
        *Msys) _platform_os="windows" ;;
        *) die "Platform: '$name' not supported." ;;
        esac
    fi

    return 0
}

function get_platform_arch() {
    local -n _arch="$1"

    _arch=""
    if uname -m | grep -q "x86_64" &>/dev/null; then
        _arch="amd64"
        return 0
    elif uname -m | grep -q -E "aarch64|arm64" &>/dev/null; then
        _arch="arm64"
        return 0
    elif uname -a | grep -q -E "aarch64|arm64" &>/dev/null; then
        _arch="arm64"
    else
        die "Architecture: '$(uname -m)' not supported."
    fi
}

check_bash
check_tool "grep"
check_tool "jq"
check_tool "curl"
check_tool "sha256sum"
check_tool "tar"
check_tool "unzip"
check_tool "uname"

org=gabyx
repo=githooks

unInstall="false"
installerArgs=()
versionTag=""

# Compare a and b as version strings. Rules:
# $1-a $2-op $3-b
# R1: a and b : dot-separated sequence of items. Items are numeric. The last item can optionally end with letters, i.e., 2.5 or 2.5a.
# R2: Zeros are automatically inserted to compare the same number of items, i.e., 1.0 < 1.0.1 means 1.0.0 < 1.0.1 => yes.
# R3: op can be '=' '==' '!=' '<' '<=' '>' '>=' (lexicographic).
# R4: Unrestricted number of digits of any item, i.e., 3.0003 > 3.0000004.
# R5: Unrestricted number of items.
function version_compare() {
    local a=$1 op=$2 b=$3 al=${1##*.} bl=${3##*.}
    while [[ $al =~ ^[[:digit:]] ]]; do al=${al:1}; done
    while [[ $bl =~ ^[[:digit:]] ]]; do bl=${bl:1}; done
    local ai=${a%"$al"} bi=${b%"$bl"}

    local ap=${ai//[[:digit:]]/} bp=${bi//[[:digit:]]/}
    ap=${ap//./.0} bp=${bp//./.0}

    local w=1 fmt=$a.$b x IFS=.
    for x in $fmt; do [ ${#x} -gt "$w" ] && w=${#x}; done
    fmt=${*//[^.]/}
    fmt=${fmt//./%${w}s}
    # shellcheck disable=SC2086,SC2059
    printf -v a "$fmt" $ai$bp
    printf -v a "%s-%${w}s" "$a" "$al"
    # shellcheck disable=SC2086,SC2059
    printf -v b "$fmt" $bi$ap
    printf -v b "%s-%${w}s" "$b" "$bl"

    # shellcheck disable=SC1072
    case $op in
    '<=' | '>=') test "$a" "${op:0:1}" "$b" || [ "$a" = "$b" ] ;;
    *) test "$a" "$op" "$b" ;;
    esac
}

function print_help() {
    echo -e "Usage: install.sh [options...] [-- <installer-args>...]\n\n" \
        "Options:\n" \
        "  --version <version> : The version to download (if not latest)\n" \
        "                        and install.\n" \
        "  --uninstall         : Uninstall Githooks. Uses always the latest uninstaller.\n" \
        "all other arguments are forwarded to the installer."
}

function parse_args() {
    local to_installer=false
    local prev=""

    for p in "$@"; do
        if [ "$to_installer" = "true" ]; then
            installerArgs+=("$p")
        elif [ "$p" = "--help" ] || [ "$p" = "-h" ]; then
            print_help
            exit 0
        elif [ "$p" = "--version" ]; then
            true
        elif [ "$p" = "--uninstall" ]; then
            unInstall="true"
        elif [ "$prev" = "--version" ]; then
            versionTag="v$p"

        elif [ "$p" = "--" ]; then
            to_installer="true"
        else
            echo "! Unknown argument '$p'." >&2
            return 1
        fi

        prev="$p"
    done
}

function check_old_version_v2() {
    if ! version_compare "${versionTag##v}" ">=" "2.3.4"; then
        echo "!! Can only bootstrap version tags >= 'v2.3.4' with this script. Got tag '$versionTag'."
        exit 1
    fi

    local versionTag="$1"
    local installedVersion
    installedVersion=$(git hooks --version 2>/dev/null | sed -E 's/.* ([0-9]+\..*)/\1/') || true

    if [ -n "$installedVersion" ] &&
        version_compare "$installedVersion" "<=" "3.0.0" &&
        version_compare "${versionTag##v}" ">=" "3.0.0"; then
        echo "!! You use an old Githooks version < 3 and want to install a version > 3.0.0." >&2
        echo "!! Please uninstall the version before reinstalling the major new one." >&2
        exit 1
    fi

    if [ -n "$(git config githooks.usecorehookspath)" ] &&
        version_compare "${versionTag##v}" ">=" "3.0.0"; then
        echo "!! You seem to use an old Githooks version < 3 and want to install a version > 3.0.0." >&2
        echo "!! Please uninstall the version before reinstalling the major new one." >&2
        exit 1
    fi
}

tempDir="$(mktemp -d)"
function clean_up() {
    rm -rf "$tempDir" &>/dev/null || true
}
trap clean_up EXIT

parse_args "$@"

if [ "$versionTag" = "" ] || [ "$unInstall" = "true" ]; then
    # Find the latest version using the GitHub API
    response="$tempDir/response"
    http_status=$(curl --silent --output "$response" -w "%{response_code}" \
        --location "https://api.github.com/repos/$org/$repo/releases") || {
        echo "!! Could not get releases info from github.com"
        exit 1
    }

    if [ "$http_status" != "200" ]; then
        echo "Could not get latest tag. Status code: '$http_status'."
        exit 1
    fi

    versionTag=$(
        jq --raw-output 'map(select((.assets | length) > 0)) | .[0].tag_name' <"$response"
    )
fi

check_old_version_v2 "$versionTag"

os=""
arch=""

get_platform_os os
get_platform_arch arch

# The download used `macos` for `darwin` platform.
if [ "$os" = "darwin" ]; then
    os="macos"
fi

# Download and install
response="$tempDir/response"
http_status=$(curl --silent --output "$response" -w "%{response_code}" \
    "https://api.github.com/repos/$org/$repo/releases/tags/$versionTag") || {
    echo "Could not get releases from github.com."
    exit 1
}

if [ "$http_status" != "200" ]; then
    echo "Could not get release info. Status code: '$http_status'."
    exit 1
fi

checksumFileURL=$(jq --raw-output ".assets[] | select( .name == \"githooks.checksums\") | .browser_download_url" <"$response")

url=$(
    jq --raw-output ".assets[] |
        select( (.name | contains(\"$os\"))
                    and (.name | contains(\"$arch\")) ) |
                         .browser_download_url" <"$response"
) || {
    echo "Could not get assets from tag '$versionTag'."
    exit 1
}

if [ -z "$url" ]; then
    echo -e "!! Unsupported operating system '$os' or \n" \
        "machine type '$arch': \n" \
        "Please check 'https://github.com/$org/${repo}/releases' manually."

    exit 1
fi

githooks="$tempDir/githooks"
mkdir -p "$githooks"

cd "$tempDir"

echo -e "Downloading '$checksumFileURL'..."
checksums=$(curl --progress-bar --location "$checksumFileURL")

echo -e "Downloading '$url'..."
curl --progress-bar --location "$url" -o githooks.install

checksum=$(sha256sum "githooks.install" | cut -d ' ' -f 1)
if ! echo "$checksums" | grep -q "$checksum"; then
    echo "!! Checksum sha265 '$checksum' could not be verified in 'githooks.checksums' file."
    echo "$checksums"

    exit 1
else
    echo -e "\n=============================\nChecksums verified!\n=============================\n"
fi

case "$url" in
*.tar.gz)
    tar -C "$githooks" -xzf "githooks.install" >/dev/null
    ;;

*.zip)
    unzip -d "$githooks" "$githooks.install" >/dev/null
    ;;
esac

cliExe="cli"
if [ ! -f "githooks/cli" ]; then
    # Version 3 has new names
    cliExe="githooks-cli"
fi

if [ "$os" = "windows" ]; then
    cliExe="$cliExe.exe"
fi

if [ "$unInstall" = "true" ]; then
    "githooks/$cliExe" uninstaller "${installerArgs[@]}"
else
    "githooks/$cliExe" installer "${installerArgs[@]}"
fi
