#!/usr/bin/env bash

#
# Setup.
#

VERSION="1.7.0"
NY_PREFIX=${NY_PREFIX-/usr/local}
VERSIONS_DIR=$NY_PREFIX/ny/versions
UP=$'\033[A'
DOWN=$'\033[B'
IOJS_MIRROR=${IOJS_MIRROR-https://iojs.org/dist/}
NODEJS_MIRROR=${NODEJS_MIRROR-https://nodejs.org/download/}

test -d $VERSIONS_DIR || mkdir -p $VERSIONS_DIR

#
# Log <type> <msg>
#

log() {
  printf "  \033[36m%10s\033[0m : \033[90m%s\033[0m\n" $1 $2
}

#
# Exit with the given <msg ...>
#

abort() {
  printf "\n  \033[31mError: $@\033[0m\n\n" && exit 1
}

#
# Ensure we have curl or wget support.
#

GET=

# wget support (Added --no-check-certificate for Github downloads)
command -v wget > /dev/null && GET="wget --no-check-certificate -q -O-"

command -v curl > /dev/null && GET="curl -# -L"

test -z "$GET" && abort "curl or wget required"

#
# Functions used when showing versions installed
#

enter_fullscreen() {
  tput smcup
  stty -echo
}

leave_fullscreen() {
  tput rmcup
  stty echo
}

handle_sigint() {
  leave_fullscreen
  exit $?
}

handle_sigtstp() {
  leave_fullscreen
  kill -s SIGSTOP $$
}

#
# Output usage information.
#

display_help() {
  cat <<-EOF

  Usage: ny [options] [COMMAND] [args]

  Commands:

    ny                            Output versions installed
    ny latest                     Install or activate the latest iojs release
    ny <version>                  Install iojs <version>
    ny use <version> [args ...]   Execute iojs <version> with [args ...]
    ny bin <version>              Output bin path for <version>
    ny rm <version ...>           Remove the given version(s)
    ny prev                       Revert to the previously activated version
    ny --latest                   Output the latest iojs version available
    ny ls                         Output the versions of iojs available
    ny c                          Output the current version of iojs installed

  Options:

    -V, --version   Output current version of n
    -h, --help      Display help information

  Aliases:

    which   bin
    use     as
    list    ls
    -       rm

EOF
  exit 0
}

#
# Hide cursor.
#

hide_cursor() {
  printf "\e[?25l"
}

#
# Show cursor.
#

show_cursor() {
  printf "\e[?25h"
}

#
# Output version after selected.
#

next_version_installed() {
  list_versions_installed | grep $selected -A 1 | tail -n 1
}

#
# Output version before selected.
#

prev_version_installed() {
  list_versions_installed | grep $selected -B 1 | head -n 1
}

#
# Output n version.
#

display_n_version() {
  echo $VERSION && exit 0
}

#
# Check for installed version, and populate $active
#

check_current_version() {
  command -v node &> /dev/null
  if test $? -eq 0; then
    active=`node --version`
    active=${active#v}
  fi
}

#
# Display sorted versions directories paths.
#

versions_paths() {
  ls -d $VERSIONS_DIR/* \
    | egrep "/[0-9]+\.[0-9]+\.[0-9]+$" \
    | sort -k 1,1n -k 2,2n -k 3,3n -t .
}

#
# Display installed versions with <selected>
#

display_versions_with_selected() {
  selected=$1
  echo
  for dir in `versions_paths`; do
    local version=${dir##*/}
    if test "$version" = "$selected"; then
      printf "  \033[36mο\033[0m $version\033[0m\n"
    else
      printf "    \033[90m$version\033[0m\n"
    fi
  done
  echo
}

#
# List installed versions.
#

list_versions_installed() {
  for dir in `versions_paths`; do
    local version=${dir##*/}
    echo $version
  done
}

#
# Display current node --version.
#

display_current_version() {
  check_current_version
  if test "$(ls -l $VERSIONS_DIR | grep ^d)" && test -n "$active" ; then
    echo $active
  fi
}

#
# Display current node --version and others installed.
#

display_versions() {
  enter_fullscreen
  check_current_version
  display_versions_with_selected $active

  trap handle_sigint INT
  trap handle_sigtstp SIGTSTP

  while true; do
    read -n 3 c
    case "$c" in
      $UP)
        clear
        display_versions_with_selected $(prev_version_installed)
        ;;
      $DOWN)
        clear
        display_versions_with_selected $(next_version_installed)
        ;;
      *)
        activate $selected
        leave_fullscreen
        exit
        ;;
    esac
  done
}

#
# Move up a line and erase.
#

erase_line() {
  printf "\033[1A\033[2K"
}

#
# Check if the HEAD response of <url> is 200.
#

is_ok() {
  curl -Is $1 | head -n 1 | grep 200 > /dev/null
}

#
# Return parameters based on given version.
#
# Usage: $(version_switch "0.10.1" "if version starts with 0. or v0." "else")
#

version_switch() {
  if  [[ $1 =~ ^"1."* ]] || [[ $1 =~ ^"2."* ]] || [[ $1 =~ ^"3."* ]];
  then
      echo $2
  else
      echo $3
  fi
}

#
# Determine mirror for <version>
#

version_mirror() {
  echo $(version_switch $1 $IOJS_MIRROR $NODEJS_MIRROR)
}

#
# Determine mirror for <version>
#

version_engine() {
  echo $(version_switch $1 "iojs" "node")
}

#
# Determine tarball url for <version>
#

tarball_url() {
  local version=$1
  local mirror=$(version_mirror $version)
  local uname="$(uname -a)"
  local engine=$(version_engine $version)
  local arch=x86
  local os=

  # f.i. rc/, etc.
  local special=

  if [[ $engine == 'node' ]];
  then
    special=release/
  fi

  # from nave(1)
  case "$uname" in
    Linux*) os=linux ;;
    Darwin*) os=darwin ;;
    SunOS*) os=sunos ;;
  esac

  case "$uname" in
    *x86_64*) arch=x64 ;;
    *armv6l*) arch=armv6l ;;
    *armv7l*) arch=armv7l ;;
  esac

  if  [[ $1 =~ ^"0."* ]] && [[ $arch =~ ^"arm"* ]];
  then
      >&2 echo ""
      >&2 echo ">> Precompiled arm binaries below 1.0.0 (iojs) resp. 4.0.0 (nodejs) are not available.";
      >&2 echo ""
      echo "invalid_value"
  fi

  if  [[ $1 == *"-rc."* ]];
  then
    special="rc/"
  fi

  echo "${mirror}${special}v${version}/${engine}-v${version}-${os}-${arch}.tar.gz"
}

#
# Activate <version>
#

activate() {
  local version=$1
  check_current_version
  if test "$version" != "$active"; then
    local dir=$VERSIONS_DIR/$version
    echo $active > $VERSIONS_DIR/.prev
    mkdir -p $dir/include
    cp -fR $dir/bin $dir/lib $dir/include $dir/share $NY_PREFIX
  fi
}

#
# Activate previous node.
#

activate_previous() {
  test -f $VERSIONS_DIR/.prev || abort "no previous versions activated"
  local prev=$(cat $VERSIONS_DIR/.prev)
  test -d $VERSIONS_DIR/$prev || abort "previous version $prev not installed"
  activate $prev
  echo
  log activate $prev
  echo
}

#
# Install <version>
#

install_node() {
  local version=${1#v}
  local mirror=$(version_mirror $version)

  local dots=`echo $version | sed 's/[^.]*//g'`

  if test ${#dots} -eq 1; then
    version=`$GET 2> /dev/null $mirror \
      | egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \
      | egrep -v '^0\.[0-7]\.' \
      | egrep -v '^0\.8\.[0-5]$' \
      | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
      | egrep ^$version \
      | tail -n1`

    test $version || abort "invalid version ${1#v}"
  fi

  local dir=$VERSIONS_DIR/$version
  local url=$(tarball_url $version)

  if test -d $dir; then
    if [[ ! -e $dir/n.lock ]] ; then
      activate $version
      exit
    fi
  fi

  echo
  log install v$version

  is_ok $url || abort "invalid version $version"

  log mkdir $dir
  mkdir -p $dir
  if [ $? -ne 0 ] ; then
    abort "sudo required"
  else
    touch $dir/n.lock
  fi

  cd $dir

  log fetch $url
  curl -L# $url | tar -zx --strip 1
  erase_line
  rm -f $dir/n.lock

  activate $version
  log installed $(node --version)
  echo
}

#
# Remove <version ...>
#

remove_versions() {
  test -z $1 && abort "version(s) required"
  while test $# -ne 0; do
    rm -rf $VERSIONS_DIR/${1#v}
    shift
  done
}

#
# Output bin path for <version>
#

display_bin_path_for_version() {
  test -z $1 && abort "version required"
  local version=${1#v}
  local bin=$VERSIONS_DIR/$version/bin/node
  if test -f $bin; then
    printf $bin
  else
    abort "$1 is not installed"
  fi
}

#
# Execute the given <version> of node with [args ...]
#

execute_with_version() {
  test -z $1 && abort "version required"
  local version=${1#v}
  local bin=$VERSIONS_DIR/$version/bin/node

  shift # remove version

  if test -f $bin; then
    $bin "$@"
  else
    abort "$version is not installed"
  fi
}

#
# Display the latest node release version.
#

display_latest_version() {
  $GET 2> /dev/null $NODEJS_MIRROR/release/ \
    | egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \
    | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
    | tail -n1
}

#
# Display the versions of node available.
#

display_remote_versions() {
  check_current_version
  local versions=""
  versions=`$GET 2> /dev/null $NODEJS_MIRROR \
    | egrep -o '[0-9]+\.[0-9]+\.[0-9]+' \
    | egrep -v '^0\.[0-7]\.' \
    | egrep -v '^0\.8\.[0-5]$' \
    | sort -u -k 1,1n -k 2,2n -k 3,3n -t . \
    | awk '{ print "  " $1 }'`

  echo
  for v in $versions; do
    if test "$active" = "$v"; then
      printf "  \033[36mο\033[0m $v \033[0m\n"
    else
      if test -d $VERSIONS_DIR/$v; then
        printf "    $v \033[0m\n"
      else
        printf "    \033[90m$v\033[0m\n"
      fi
    fi
  done
  echo
}

#
# Handle arguments.
#

if test $# -eq 0; then
  test "$(ls -l $VERSIONS_DIR | grep ^d)" || abort "no installed version"
  display_versions
else
  while test $# -ne 0; do
    case $1 in
      -V|--version) display_n_version ;;
      -h|--help|help) display_help ;;
      --latest) display_latest_version; exit ;;
      bin|which) display_bin_path_for_version $2; exit ;;
      as|use) shift; execute_with_version $@; exit ;;
      rm|-) shift; remove_versions $@; exit ;;
      latest) install_node $(display_latest_version); exit ;;
      ls|list) display_remote_versions; exit ;;
      prev) activate_previous; exit ;;
      c) display_current_version; exit ;;
      *) install_node $1; exit ;;
    esac
    shift
  done
fi
