#!/usr/bin/env sh

# Ensure this file is executable via `chmod a+x deken`, then place it
# somewhere on your ${PATH}, like ~/bin. The rest of Deken will self-install
# on first run into the ~/.deken/ directory.

# Much of this code is pilfered from Clojure's Leiningen tool

script="$0"
script_args=$*

##############################################################################
# variable declarations
##############################################################################

export DEKEN_VERSION="0.10.4"

: "${DEKEN_HOME:=${HOME}/.deken}"
: "${DEKEN_GIT_BRANCH:=main}"
: "${DEKEN_BASE_URL:=https://raw.githubusercontent.com/pure-data/deken/${DEKEN_GIT_BRANCH}/developer}"
# allow the user to override the default python
: "${PYTHON_BIN:=python3}"

export DEKEN_HOME

VIRTUALENV_URL="https://bootstrap.pypa.io/virtualenv/@pyversion@/virtualenv.pyz"

case "$(uname -s)" in
    CYGWIN*)
        DEKEN_HOME=$(cygpath -w "${DEKEN_HOME}")
        ;;
    *)
        ;;
esac

# This needs to be defined before we call HTTP_CLIENT below
if [ "${HTTP_CLIENT}" = "" ]; then
    if which curl >/dev/null; then
        if [ -n "${https_proxy}" ]; then
            CURL_PROXY="-x ${https_proxy}"
        fi
        HTTP_CLIENT="curl ${CURL_PROXY} -f -L -o"
    else
        HTTP_CLIENT="wget -O"
    fi
fi

if test -z "${systeminstalled}"; then
 # if this script resides in /usr/ and there's a deken.hy relative to it in the
 # share/ folder, we consider it system-installed
 if test -z "${0##/usr/*}" && test -e "${0%/*}/../share/deken/deken.hy"; then
   systeminstalled=true
 else
   systeminstalled=false
 fi
fi

if ${systeminstalled}; then
    DEKEN_HOME="${0%/*}/../share/deken"
fi

: "${DEKEN_HY:=${DEKEN_HOME}/deken.hy}"
: "${DEKENHY:=${DEKEN_HOME}/virtualenv/bin/hy}"
: "${SYSTEMHY:=$(command -v hy3 hy | head -1)}"
: "${SYSTEMHY:=$(which hy3 hy 2>/dev/null| head -1)}"

##############################################################################
# helper functions
##############################################################################

error() {
    echo "$@" 1>&2
}

countdown() {
  _countdown_t=$1
  _countdown_t=$((_countdown_t))
  while [ $_countdown_t -gt 0 ]; do
     printf "\r%2d" $((_countdown_t))
     sleep 1
     _countdown_t=$((_countdown_t-1))
  done
  printf "\r  \r"
  unset _countdown_t
}

fetch_file() {
  _fetch_file_dst=${1#file://}
  _fetch_file_src=${2#file://}
  if test -e "${_fetch_file_src}"; then
     cp -v "${_fetch_file_src}" "${_fetch_file_dst}"
  else
     ${HTTP_CLIENT} "${_fetch_file_dst}" "${_fetch_file_src}"
  fi
  unset _fetch_file_dst
  unset _fetch_file_src
}

uninstall_deken() {
    if ${systeminstalled}; then
      # on Debian we disallow uninstalling
      error "Uninstalling is disabled for system-provided deken!"
      error "Instead, use your package manager to remove deken."
      exit 1
    fi
    error "I'm going to uninstall myself and my dependencies from ${DEKEN_HOME} now."
    error "Feel free to Ctrl-C now if you don't want to do this."
    countdown 5
    error "Uninstalling deken."
    rm -rf "${DEKEN_HOME}" "$0"
    exit 0
}

bail_install() {
    error "Self-installation of Deken failed."
    error "Please paste any errors in the bug tracker at https://github.com/pure-data/deken/issues"
    # remove all traces of our attempts to install.
    rm -rf "${DEKEN_HOME}"
    # bail from this script.
    exit 1
}
bail_install_msg() {
    error "$@"
    bail_install
}

tell_reinstall() {
cat <<EOF
You can run 'deken install --self' or 'deken upgrade --self' anytime to
 re-install (or upgrade) your Deken installation.
EOF

}

bail_requirements() {
    rm -f "${DEKEN_HOME}/requirements.txt"
    cat >/dev/stderr <<EOF
Installation of requirements failed.
You probably should install the following packages first:
 - 'python3-dev'
 - 'libffi-dev'
 - 'libssl-dev'
EOF
    tell_reinstall >/dev/stderr
    exit 1
}

bail_upgrade() {
    if ${systeminstalled}; then
      # on Debian we don't want the user to run upgrades themselves
      return
    fi
    cat >/dev/stderr <<EOF
It seems your version of deken is out of sync.
($script has version ${DEKEN_VERSION}, but your installation is $1)
EOF
    tell_reinstall >/dev/stderr
    echo >/dev/stderr
}

install_virtualenv() {
  if which virtualenv >/dev/null; then
    virtualenv --system-site-packages "${DEKEN_HOME}/virtualenv"
  else
    _install_virtualenv_pyversion=$("${PYTHON_BIN}" -c "import sys; print('%s.%s' % (sys.version_info.major, sys.version_info.minor))")
    _install_virtualenv_url=$(echo "${VIRTUALENV_URL}" | sed -e "s|@pyversion@|${_install_virtualenv_pyversion}|g")
    echo "Downloading & installing Virtualenv for ${_install_virtualenv_pyversion} using ${_install_virtualenv_url}"
    rm -rf "${DEKEN_HOME}/virtualenv.pyz"
    mkdir -p "${DEKEN_HOME}"
    fetch_file "${DEKEN_HOME}/virtualenv.pyz" "${_install_virtualenv_url}" && \
            "${PYTHON_BIN}" "${DEKEN_HOME}/virtualenv.pyz" "${DEKEN_HOME}/virtualenv"
    rm -rf "${DEKEN_HOME}/virtualenv.pyz"
    unset _install_virtualenv_pyversion
    unset _install_virtualenv_url
  fi

    [ -d "${DEKEN_HOME}/virtualenv" ] || \
            bail_install
}

install_deken() {
    if ${systeminstalled}; then
       # on Debian, we can skip installation
       return
    fi

    which "${PYTHON_BIN}" >/dev/null || \
        bail_install_msg "Oops, no Python found! You need Python3 to run Deken: ${PYTHON_BIN}
You can specify an alternative Python interpreter via the PYTHON_BIN envvar"
    error "This is your first time running deken on this machine."
    error "I'm going to install myself and my dependencies into ${DEKEN_HOME} now."
    error "Feel free to Ctrl-C now if you don't want to do this."
    countdown 3
    error "Installing deken."

    mkdir -p "${DEKEN_HOME}"
    [ -e "${DEKEN_HOME}/requirements.txt" ] || (\
        ( echo "Fetching Python requirements file: ${DEKEN_BASE_URL}/requirements.txt" && \
        fetch_file "${DEKEN_HOME}/requirements.txt" "${DEKEN_BASE_URL}/requirements.txt" ) || bail_install)
    [ -e "${DEKEN_HOME}/requirements.txt" ] || bail_install
    [ -e "${DEKEN_HY}" ] || (\
        ( echo "Fetching main hylang file: ${DEKEN_BASE_URL}/deken.hy" && \
        fetch_file "${DEKEN_HY}" "${DEKEN_BASE_URL}/deken.hy" ) || bail_install)
    [ -e "${DEKEN_HY}" ] || bail_install
    [ -d "${DEKEN_HOME}/virtualenv" ] || install_virtualenv
    if ! [ -x "${DEKENHY}" ]; then
        echo "Installing deken library dependencies."
        "${DEKEN_HOME}/virtualenv/bin/pip" install -r "${DEKEN_HOME}/requirements.txt" || bail_requirements
    fi
    echo "${DEKEN_VERSION}" > "${DEKEN_HOME}/version.txt"
}

upgrade_deken() {
    if ${systeminstalled}; then
      # on Debian we disallow upgrading
      error "Direct upgrading is disabled for system-provided deken!"
      error "Instead, use your package manager to install newer versions."
      exit 1
    fi
    # first upgrade this script itself
    echo "Upgrading ${script}."
    fetch_file "${DEKEN_HOME}/.upgrade-deken" "${DEKEN_BASE_URL}/deken" || ( error "Error upgrading deken"; exit 1; )
    if diff -q "${DEKEN_HOME}/.upgrade-deken" "$script" >/dev/null; then
        # launcher script is already up-to-date
        rm "${DEKEN_HOME}/.upgrade-deken"
    else
        if test -w "${script}"; then
            cat "${DEKEN_HOME}/.upgrade-deken" > "${script}"
            rm "${DEKEN_HOME}/.upgrade-deken"
            error "The deken-installer has changed."
            error "Please re-run the last command"
            error ""
            error "Hint: ${script} ${script_args}"
            exit
	else
            rm "${DEKEN_HOME}/.upgrade-deken"
	    error "Unable to update '${script}', proceeding anyhow..."
        fi
    fi
    # next upgrade our dependencies
    for f in requirements.txt deken.hy
    do
        echo "Fetching ${f} file: ${DEKEN_BASE_URL}/${f}"
        fetch_file "${DEKEN_HOME}/.upgrade-${f}" "${DEKEN_BASE_URL}/${f}" || ( error "Error upgrading ${f}"; exit 1; )
        mv "${DEKEN_HOME}/.upgrade-${f}" "${DEKEN_HOME}/${f}"
    done
    # finally update the python dependencies
    "${DEKEN_HOME}/virtualenv/bin/pip" install -r "${DEKEN_HOME}/requirements.txt" || bail_requirements

    echo "${DEKEN_VERSION}" > "${DEKEN_HOME}/version.txt"
    echo "Successfully upgraded."
}

tryrun_deken() {
    if ${systeminstalled}; then
       "${SYSTEMHY}" "${DEKEN_HY}" "$@"
       exit $?
    fi

    # check if the 'deken' script and the actual implementation match
    _tryrun_deken_version=$(cat "${DEKEN_HOME}/version.txt" 2>/dev/null)
    if test -n "${_tryrun_deken_version}" && test "x${_tryrun_deken_version}" != "x${DEKEN_VERSION}"; then
        bail_upgrade "${_tryrun_deken_version}"
    fi
    unset _tryrun_deken_version

    if [ ! -x "${DEKENHY}" ]; then
       error "Unable to find '${DEKENHY}'"
       error "Try running '$0 install --self' or '$0 upgrade --self'"
       exit 1
    fi
    if [ ! -e "${DEKEN_HY}" ]; then
       error "Unable to find '${DEKEN_HY}'"
       error "Try running '$0 install --self' or '$0 upgrade --self'"
       exit 1
    fi

    "${DEKENHY}" "${DEKEN_HY}" "$@"
}

##############################################################################
# here starts the code
##############################################################################


# catch 'uninstall --self' early, so we don't run into the "installed" checks
if [ $# -eq 2  ] && [ "$1" = "uninstall" ] && [ "$2" = "--self" ]; then
  uninstall_deken
  exit
fi

if [ $# -eq 1  ] && [ "$1" = "--version" ]; then
  echo "${DEKEN_VERSION}"
  exit
fi


if [ "$(id -u)" -eq 0 ] && [ "${DEKEN_ROOT}" = "" ]; then
    error "WARNING: You're currently running as root; probably by accident."
    error "Press Control-C to abort or Enter to continue as root."
    error "Set DEKEN_ROOT=yes to disable this warning."
    read -r _
fi

# make sure we are deployed
[ -d "${DEKEN_HOME}" ] || install_deken

# last check to make sure we can bootstrap
[ -d "${DEKEN_HOME}" ] || bail_install

# catch the special "upgrade" command
if [ $# -eq 2  ] && [ "$2" = "--self" ]; then
	case "$1" in
		install)
			install_deken
			exit
			;;
		update|upgrade)
			upgrade_deken
			exit
			;;
	esac
fi

# run the real deken command with args passed through
tryrun_deken "$@"
