#!/bin/bash

#   ____  ____
#  / __ \/ __ \
# / /_/ / /_/ /
# \____/\____/
#
# Simple Go Version Manager.
#
# https://github.com/hit9/oo
#
# Chao Wang, hit9@icloud.com

#
# Settings
#
MIRROR_PATTERN=${OO_MIRROR_PATTERN-https://codeload.github.com/golang/go/tar.gz/go%s}
BUILD_CMD=${OO_BUILD_CMD-./make.bash}

if [ ! -z "${OO_BOOTSTRAP_VERSION}" ]; then
    BOOTSTRAP_VERSION=${OO_BOOTSTRAP_VERSION}
fi

PREBUILT_PKG_URL_PATTERN=${PREBUILT_PKG_URL_PATTERN-https://dl.google.com/go/go%s.%s-amd64.tar.gz}

#
# Global Vars
#
ROOT_DIR=$(cd -P -- "$(dirname -- "${BASH_SOURCE:-$_}")/.." && pwd -P)
VERSIONS_DIR=${ROOT_DIR}/versions
GOROOT_DIR=${ROOT_DIR}/go
GOBIN_PATH=${GOROOT_DIR}/bin/go

VERSION=$(cat ${ROOT_DIR}/VERSION)

#
# Help
#
show_help() {
    cat <<-EOF
Usage: oo [COMMAND]

Commands:
  oo                         Output current go version
  oo ls                      Output versions installed
  oo <version>               Use go <version>
  oo use <version>           Use go <version>
  oo get <version>           Get go <version>
  oo rm <version>            Remove the given version
  oo as <version>            Run go from oo on a specific version
  oo dir [<version>]         Show go directory by version
  oo bin [<version>]         Show go binary path by version
  oo build <version>         Build go by version
  oo env                     Output current go env
  oo upgrade                 Upgrade oo to latest version

Options:
  -v, --version              Output oo's version
  -h, --help                 Output this help message

Environment Variables:
  OO_MIRROR_PATTERN          Mirror url pattern to download go source tarball
                             default: https://codeload.github.com/golang/go/tar.gz/go%s
  OO_BUILD_CMD               Command to build go from source, default: ./make.bash
  OO_BOOTSTRAP_VERSION       Bootstrap go version to compile the target version,
                             default: the latest installed version

  PREBUILT_PKG_URL_PATTERN   Url pattern to download pre-built go package
                             default: https://dl.google.com/go/go%s.%s-amd64.tar.gz
                             The first parameter here is version, the second is platform.
                             Use https://dl.google.com/go/go%s.%s-arm64.tar.gz for arm64 platforms (e.g. Mac M1).

Version: ${VERSION}
EOF
    exit 0
}

# https://gist.github.com/livibetter/1861384
source "vercmp.sh"

show_version() {
    echo ${VERSION}
    exit 0
}

abort() {
    echo $1 && exit 1
}

#
# Show current version
#
current() {
    if [ -f ${GOROOT_DIR}/VERSION ]; then
        printf '%s\n' $(cat ${GOROOT_DIR}/VERSION)
    else
        abort 'go? (no version installed)'
    fi
}

#
# List all versions installed
#
list() {
    test ! -f ${GOROOT_DIR}/VERSION && abort 'no version installed'

    current_version=$(cat ${GOROOT_DIR}/VERSION)
    for file in ${VERSIONS_DIR}/*
    do
        if [ -d ${file} ]; then
            version_listing=$(basename ${file})
            if [ ${current_version} = $(printf "go%s" ${version_listing}) ]; then
                printf "=> %s\n" ${version_listing}
            else
                printf "   %s\n" ${version_listing}
            fi
        fi
    done
}

#
# Use a version
#
use() {
    test -z $1 && show_help
    target_dir=${VERSIONS_DIR}/$1
    test -d ${target_dir} || abort 'not installed'
    # create soft link
    test -f ${GOROOT_DIR} && rm ${GOROOT_DIR}
    test -d ${GOROOT_DIR} && rm -r ${GOROOT_DIR}
    ln -fs ${target_dir} ${GOROOT_DIR}
    printf '=> ' && echo $(current)
}

#
# Remove a version
#
remove() {
    test -z $1 && show_help
    target_dir=${VERSIONS_DIR}/$1
    test -d ${target_dir} || abort 'not installed'

    if [ -d ${GOROOT_DIR} ] && \
        [ $(readlink ${GOROOT_DIR}) = ${target_dir} ]; then
        rm ${GOROOT_DIR} && printf 'go@%s deused\n' $1
    fi

    rm -r ${target_dir}
    printf 'go@%s removed\n' $1
}

#
# Get a version
#
handle_get_sigint() {
    printf '\ncanceled, clean up..\n' $1
    remove $1
    exit 1
}

handle_get_sigtstp() {
    printf '\nstopped, clean up..\n' $1
    remove $1
    exit 1
}

#
# Get new version installed
#
get() {
    test -z $1 && show_help
    version=$1

    target_dir=${VERSIONS_DIR}/${version}
    test -d ${target_dir} && abort 'already installed'
    command -v curl > /dev/null || abort 'curl is required'

    use_prebuilt=false
    test -z $2 || use_prebuilt=true
    if [ ! -d "${VERSIONS_DIR}" ] || [ -z "$(ls -A ${VERSIONS_DIR})" ]; then
        printf 'no existing go versions installed, using prebuilt package\n'
        use_prebuilt=true
    fi

    if [ "$use_prebuilt" = false ]; then
        tarball_url=$(printf ${MIRROR_PATTERN} ${version})
    else
        if [[ "$OSTYPE" == "linux-gnu" ]]; then
            printf 'linux detected\n'
            tarball_url=$(printf ${PREBUILT_PKG_URL_PATTERN} ${version} 'linux')
        elif [[ "$OSTYPE" == "darwin"* ]]; then
            printf 'darwin/osx detected\n'
            tarball_url=$(printf ${PREBUILT_PKG_URL_PATTERN} ${version} 'darwin')
        else
            abort 'unsupported os'
        fi
    fi

    test -d ${target_dir} || mkdir -p ${target_dir}
    trap 'handle_get_sigint ${version}' INT
    trap 'handle_get_sigtstp ${version}' SIGTSTP
    printf 'get %s..\n' ${tarball_url}
    curl -# -L ${tarball_url} | tar xz -C ${target_dir} --strip 1 &> /dev/null

    if [ ${?} != 0 ]; then
        rm -rf ${target_dir} && abort 'fetch tarball failed, maybe bad version'
    fi

    if [ "$use_prebuilt" = false ]; then
        build ${version}
    fi
    use ${version}
}


#
# Build go
#
build() {
    if [ -z $1 ]; then
        abort 'require argument version'
    else
        version=$1
    fi

    target_dir=${VERSIONS_DIR}/${version}
    test ! -d $target_dir && abort 'not installed'

    printf 'build %s..\n' ${version}

    # go1.5 requires go>=1.4 as build tool (no more gcc)
    vercmp ${version} '1.5'
    ret=$?

    if [ "${ret}" == 0 ] || [ "${ret}" == 1 ] ; then
        if [ -z "${BOOTSTRAP_VERSION}" ]; then
            bootstrap_version=0
            for d in $(ls -d ${VERSIONS_DIR}/* | xargs -n 1 basename); do
                vercmp ${d} ${bootstrap_version}
                if [ "${?}" == 1 ] && [ "${d}" != "${version}" ]; then
                    bootstrap_version=${d}
                fi
            done
        else
            bootstrap_version=${BOOTSTRAP_VERSION}
        fi

        vercmp ${bootstrap_version} '1.4'
        if [ "${?}" == 2 ]; then
            abort 'bootstrap go required to be >= 1.4'
        fi

        test ! -d ${VERSIONS_DIR}/${bootstrap_version} && abort "bootstrap version not installed"

        printf "using go%s to bootstrap go%s..\n" ${bootstrap_version} ${version}..
        export GOROOT_BOOTSTRAP=${VERSIONS_DIR}/${bootstrap_version}
    fi

    cd ${target_dir}/src && ${BUILD_CMD} &> /dev/null || abort 'build failed'
    printf "build successfully\n"
}


#
# Show go dir
#
dir() {
    if [ -z $1 ]; then
        printf ${GOROOT_DIR}
    else
        printf ${VERSIONS_DIR}/$1
    fi
}

#
# Show go bin
#
bin() {
    if [ -z $1 ]; then
        printf ${GOROOT_DIR}/bin/go
    else
        printf ${VERSIONS_DIR}/$1/bin/go
    fi
}


#
# Run as go version
#
as() {
    test -z $1 && show_help
    version=$1
    target_dir=${VERSIONS_DIR}/${version}
    test ! -d $target_dir && abort 'not installed'
    target_bin=${target_dir}/bin/go
    exec "${target_bin}" "${@:2}"
}

#
# Show current go env
#
env() {
    exec "${GOBIN_PATH}" env
}

#
# Upgrade oo
#
upgrade() {
    command -v git > /dev/null 2>&1 || abort 'git not installed'
    cd ${ROOT_DIR} && git pull git://github.com/hit9/oo.git master
    printf '=> %s\n' $(cat ${ROOT_DIR}/VERSION)
}

#
# Main
#
if test $# -eq 0; then
    current
else
    case $1 in
        -v|--version) show_version ;;
        -h|--help|help) show_help ;;
        ls) list ;;
        get) get $2 $3;;
        rm) remove $2 ;;
        as) as ${@:2} ;;
        use) use $2 ;;
        dir) dir $2 ;;
        bin) bin $2 ;;
        build) build $2 ;;
        env) env ;;
        upgrade) upgrade ;;
        *) use $1 ;;
    esac
fi
