#!/bin/sh


## global variables ##

VERSION=0.0.1
SCRIPT="$(basename "$0")"
VERBOSE=false
FORCE=false
INSTALL_BASE="$HOME/.ghcup"


## print help ##

usage() {
    (>&2 echo "ghcup ${VERSION}
GHC up toolchain installer

USAGE:
    ${SCRIPT} [FLAGS] <SUBCOMMAND>

FLAGS:
    -v, --verbose    Enable verbose output
    -h, --help       Prints help information
    -V, --version    Prints version information

SUBCOMMANDS:
    install          Install GHC
    show             Show current/installed GHC
    set              Set currently active GHC version
    self-update      Update this script in-place
")
    exit 1
}

install_usage() {
    (>&2 echo "ghcup-install
Install the specified GHC version

USAGE:
    ${SCRIPT} install [FLAGS] <VERSION>

FLAGS:
    -h, --help       Prints help information
    -f, --force      Overwrite already existing installation

ARGS:
    <VERSION>        E.g. \"8.4.3\" or \"8.6.1\"
")
    exit 1
}

set_usage() {
    (>&2 echo "ghcup-set
Set the currently active GHC to the specified version

USAGE:
    ${SCRIPT} set [FLAGS] <VERSION>

FLAGS:
    -h, --help       Prints help information

ARGS:
    <VERSION>        E.g. \"8.4.3\" or \"8.6.1\"
")
    exit 1
}

self_update_usage() {
    (>&2 echo "ghcup-self-update
Update the ghcup.sh script in-place

USAGE:
    ${SCRIPT} self-update [FLAGS] [TARGET-LOCATION]

FLAGS:
    -h, --help         Prints help information

ARGS:
    [TARGET-LOCATION]  Where to place the updated script (defaults to ~/.local/bin).
                       Must be an absolute path!
")
    exit 1
}


show_usage() {
    (>&2 echo "ghcup-show
Show the installed/current GHC versions

USAGE:
    ${SCRIPT} show [FLAGS]

FLAGS:
    -h, --help         Prints help information
    -i, --installed    Show installed GHC version only
")
    exit 1
}


## utilities ##

die() {
    (>&2 echo "$1")
    exit 2
}

edo()
{
    if ${VERBOSE} ; then
        echo "$@" 1>&2
    fi
    "$@" || exit 2
}

echov() {
    if ${VERBOSE} ; then
        echo "$1"
    else
        if [ -n "$2" ] ; then
            echov "$2"
        fi
    fi
}

printf_green() {
    printf "\\033[0;32m%s\\033[0m\\n" "$1"
}

get_distro_name() {
    if [ -f /etc/os-release ]; then
        # freedesktop.org and systemd
        # shellcheck disable=SC1091
        . /etc/os-release
        printf "%s" "$NAME"
    elif command -V lsb_release >/dev/null 2>&1; then
        # linuxbase.org
        printf "%s" "$(lsb_release -si)"
    elif [ -f /etc/lsb-release ]; then
        # For some versions of Debian/Ubuntu without lsb_release command
        # shellcheck disable=SC1091
        . /etc/lsb-release
        printf "%s" "$DISTRIB_ID"
    elif [ -f /etc/debian_version ]; then
        # Older Debian/Ubuntu/etc.
        printf "Debian"
    else
        # Fall back to uname, e.g. "Linux <version>", also works for BSD, etc.
        printf "%s" "$(uname -s)"
    fi
}

get_distro_ver() {
    if [ -f /etc/os-release ]; then
        # freedesktop.org and systemd
        # shellcheck disable=SC1091
        . /etc/os-release
        printf "%s" "$VERSION_ID"
    elif command -V lsb_release >/dev/null 2>&1; then
        # linuxbase.org
        printf "%s" "$(lsb_release -sr)"
    elif [ -f /etc/lsb-release ]; then
        # For some versions of Debian/Ubuntu without lsb_release command
        # shellcheck disable=SC1091
        . /etc/lsb-release
        printf "%s" "$DISTRIB_RELEASE"
    elif [ -f /etc/debian_version ]; then
        # Older Debian/Ubuntu/etc.
        printf "%s" "$(cat /etc/debian_version)"
    else
        # Fall back to uname, e.g. "Linux <version>", also works for BSD, etc.
        printf "%s" "$(uname -r)"
    fi
}


get_arch() {
    myarch=$(uname -m)

    case "${myarch}" in
    x86_64)
        printf "x86_64"  # or AMD64 or Intel64 or whatever
        ;;
    i*86)
        printf "i386"  # or IA32 or Intel32 or whatever
        ;;
    *)
        die "Cannot figure out architecture (was: ${myarch})"
        ;;
    esac

    unset myarch
}

get_download_url() {
    myghcver=$1
    myarch=$(get_arch)
    mydistro=$(get_distro_name)
    mydistrover=$(get_distro_ver)
    baseurl="https://downloads.haskell.org/~ghc"

    # TODO: awkward, restructure
    case "${mydistro},${mydistrover},${myarch},${myghcver}" in
    Debian,7,i386,8.2.2)
        printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb${mydistrover}-linux.tar.xz"
        ;;
    *,*,i386,*)
        printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
        ;;
    Debian,*,*,8.2.2)
        printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
        ;;
    Debian,8,*,*)
        printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
        ;;
    Debian,*,*,*)
        printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb9-linux.tar.xz"
        ;;
    Ubuntu,*,*,8.2.2)
        printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
        ;;
    Ubuntu,*,*,*)
        printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb9-linux.tar.xz"
        ;;
    *,*,*,8.2.2)
        printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-deb8-linux.tar.xz"
        ;;
    *,*,*,*) # this is our best guess
        printf "%s" "${baseurl}/${myghcver}/ghc-${myghcver}-${myarch}-fedora27-linux.tar.xz"
        ;;
    esac

    unset myghcver myarch mydistro mydistrover baseurl
}


## subcommand install ##

install_ghc() {
    myghcver=$1
    downloader=curl
    downloader_opts="--fail -O"
    inst_location=${INSTALL_BASE}/ghc/${myghcver}
    target_location=${INSTALL_BASE}/bin
    download_url=$(get_download_url "${myghcver}")
    download_tarball_name=$(basename "${download_url}")

    [ -e "${target_location}" ] || mkdir "${target_location}"

    if [ -e "${inst_location}" ] ; then
        if ${FORCE} ; then
            echo "GHC already installed in ${inst_location}, overwriting!"
        else
            die "GHC already installed in ${inst_location}, use --force to overwrite"
        fi
    fi

    printf_green "Installing GHC for $(get_distro_name) on architecture $(get_arch)"
    tmp_dir=$(mktemp -d)
    [ -z "${tmp_dir}" ] && die "Failed to create temporary directory"
    (
        edo cd "${tmp_dir}"

        echov "Downloading ${download_url}"
        # shellcheck disable=SC2086
        edo ${downloader} ${downloader_opts} "${download_url}"

        edo tar -xf ghc-*-linux.tar.xz
        edo cd "ghc-${myghcver}"

        echov "Installing GHC into ${inst_location}"

        edo ./configure --prefix="${inst_location}"
        edo make install

        # clean up
        edo cd ..
        [ -e "${tmp_dir}/${download_tarball_name}" ] && rm "${tmp_dir}/${download_tarball_name}"
        [ -e "${tmp_dir}/ghc-${myghcver}" ] && rm -r "${tmp_dir}/ghc-${myghcver}"
    ) || {
        [ -e "${tmp_dir}/${download_tarball_name}" ] && rm "${tmp_dir}/${download_tarball_name}"
        [ -e "${tmp_dir}/ghc-${myghcver}" ] && rm -r "${tmp_dir}/ghc-${myghcver}"
        die "Failed to install"
    }

    for f in "${inst_location}"/bin/*-"${myghcver}" ; do
        [ -e "${f}" ] || die "Something went wrong, ${f} does not exist!"
        fn=$(basename "${f}")
        # shellcheck disable=SC2046
        edo ln $(echov "-v") -sf ../ghc/"${myghcver}/bin/${fn}" "${target_location}/${fn}"
        unset fn
    done
    # shellcheck disable=SC2046
    edo ln $(echov "-v") -sf ../ghc/"${myghcver}"/bin/runhaskell "${target_location}/runhaskell-${myghcver}"

    printf_green "Done installing, run \"ghci-${myghcver}\" or set up your current GHC via: ${SCRIPT} set-ghc ${myghcver}"

    unset myghcver downloader downloader_opts inst_location target_location f download_url download_tarball_name
}


## subcommand set-ghc ##

set_ghc() {
    myghcver=$1
    target_location=${INSTALL_BASE}/bin
    inst_location=${INSTALL_BASE}/ghc/${myghcver}

    [ -e "${inst_location}" ] || die "GHC ${myghcver} not installed yet, use: ${SCRIPT} install ${myghcver}"
    [ -e "${target_location}" ] || edo mkdir "${target_location}"

    printf_green "Setting GHC to ${myghcver}"

    for f in "${inst_location}"/bin/*-"${myghcver}" ; do
        [ -e "${f}" ] || die "Something went wrong, ${f} does not exist!"
        source_fn=$(basename "${f}")
        target_fn=$(echo "${source_fn}" | sed "s#-${myghcver}##")
        # shellcheck disable=SC2046
        edo ln $(echov "-v") -sf ../ghc/"${myghcver}/bin/${source_fn}" "${target_location}/${target_fn}"
        unset source_fn target_fn
    done
    # shellcheck disable=SC2046
    edo ln $(echov "-v") -sf runghc "${target_location}"/runhaskell

    printf_green "Done, make sure \"${target_location}\" is in your PATH!"

    unset myghcver target_location inst_location f
}


## self-update subcommand ##

self_update() {
    target_location=$1
    source_url="https://raw.githubusercontent.com/hasufell/ghcup/master/ghcup.sh"
    downloader=curl
    downloader_opts="--fail -O"

    [ -e "${target_location}" ] || die "Destination \"${target_location}\" does not exist, cannot update script"

    printf_green "Updating ${SCRIPT}"

    (
        edo cd "$(mktemp -d)"

        echov "Downloading ${source_url}"
        # shellcheck disable=SC2086
        edo ${downloader} ${downloader_opts} "${source_url}"
        edo mv ghcup.sh "${target_location}"/ghcup.sh
        edo chmod +x "${target_location}"/ghcup.sh
    )

    printf_green "Done, make sure \"${target_location}\" is in your PATH!"

    unset target_location source_url downloader downloader_opts
}

## show subcommand ##

show_ghc() {
    ghc_location=${INSTALL_BASE}/ghc
    current_ghc=$(show_ghc_installed)

    echo "Installed GHCs:"
    for i in "${ghc_location}"/* ; do
        [ -e "${i}" ] || die "Something went wrong, ${i} does not exist!"
        echo "    $(basename "${i}")"
    done

    if [ -n "${current_ghc}" ] ; then
        echo
        echo "Current GHC"
        echo "    ${current_ghc}"
    fi

    unset target_location i
}

show_ghc_installed() {
    target_location=${INSTALL_BASE}/bin
    real_ghc=$(realpath "${target_location}/ghc")

    if [ -e "${real_ghc}" ] ; then
        real_ghc="$(basename "${real_ghc}" | sed 's#ghc-##')"
        printf "%s" "${real_ghc}"
    fi

    unset target_location real_ghc
}


## command line parsing and entry point ##

# sanity checks
if [ -z "$HOME" ] ; then
    die "HOME env not set, cannot operate"
fi

[ $# -lt 1 ] && usage

while [ $# -gt 0 ] ; do
    case $1 in
    -v|--verbose)
        VERBOSE=true
        shift 1;;
    -V|--version)
        printf "%s" "${VERSION}"
        exit 0;;
    -h|--help)
        usage;;
    *) case $1 in
       install)
           shift 1
           while [ $# -gt 0 ] ; do
               case $1 in
                   -h|--help) install_usage;;
                   -f|--force) FORCE=true
                       shift 1;;
                   *) GHC_VER=$1
                      break;;
               esac
           done
           [ "${GHC_VER}" ] || install_usage
           install_ghc "${GHC_VER}"
           break;;
       set)
           shift 1
           while [ $# -gt 0 ] ; do
               case $1 in
                   -h|--help) set_usage;;
                   *) GHC_VER=$1
                      break;;
               esac
           done
           [ "${GHC_VER}" ] || set_usage
           set_ghc "${GHC_VER}"
           break;;
       self-update)
           shift 1
           while [ $# -gt 0 ] ; do
               case $1 in
                   -h|--help) self_update_usage;;
                   *) TARGET_LOCATION=$1
                       break;;
               esac
           done
           if [ "${TARGET_LOCATION}" ] ; then
               self_update "${TARGET_LOCATION}"
           else
               self_update "${HOME}/.local/bin"
           fi
           break;;
       show)
           SHOW_INSTALLED=false
           shift 1
           while [ $# -gt 0 ] ; do
               case $1 in
                   -h|--help) show_usage;;
                   -i|--installed) SHOW_INSTALLED=true
                       break;;
                   *) show_usage;;
               esac
           done
           if ${SHOW_INSTALLED} ; then
               show_ghc_installed
           else
               show_ghc
           fi
           break;;
       *) usage;;
       esac
       break;;
    esac
done