#!/usr/bin/env bash

###  ------------------------------- ###
###  Helper methods for BASH scripts ###
###  ------------------------------- ###

realpath () {
(
  TARGET_FILE="$1"

  cd "$(dirname "$TARGET_FILE")"
  TARGET_FILE=$(basename "$TARGET_FILE")

  COUNT=0
  while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ]
  do
      TARGET_FILE=$(readlink "$TARGET_FILE")
      cd "$(dirname "$TARGET_FILE")"
      TARGET_FILE=$(basename "$TARGET_FILE")
      COUNT=$(($COUNT + 1))
  done

  if [ "$TARGET_FILE" == "." -o "$TARGET_FILE" == ".." ]; then
    cd "$TARGET_FILE"
    TARGET_FILEPATH=
  else
    TARGET_FILEPATH=/$TARGET_FILE
  fi

  # make sure we grab the actual windows path, instead of cygwin's path.
  if ! is_cygwin; then
    echo "$(pwd -P)/$TARGET_FILE"
  else
    echo $(cygwinpath "$(pwd -P)/$TARGET_FILE")
  fi
)
}

# TODO - Do we need to detect msys?

# Uses uname to detect if we're in the odd cygwin environment.
is_cygwin() {
  local os=$(uname -s)
  case "$os" in
    CYGWIN*) return 0 ;;
    *)  return 1 ;;
  esac
}

# This can fix cygwin style /cygdrive paths so we get the
# windows style paths.
cygwinpath() {
  local file="$1"
  if is_cygwin; then
    echo $(cygpath -w $file)
  else
    echo $file
  fi
}

# Make something URI friendly
make_url() {
  url="$1"
  local nospaces=${url// /%20}
  if is_cygwin; then
    echo "/${nospaces//\\//}"
  else
    echo "$nospaces"
  fi
}

# Detect if we should use JAVA_HOME or just try PATH.
get_java_cmd() {
  if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]];  then
    echo "$JAVA_HOME/bin/java"
  else
    echo "java"
  fi
}

echoerr () {
  echo 1>&2 "$@"
}
vlog () {
  [[ $verbose || $debug ]] && echoerr "$@"
}
dlog () {
  [[ $debug ]] && echoerr "$@"
}
execRunner () {
  # print the arguments one to a line, quoting any containing spaces
  [[ $verbose || $debug ]] && echo "# Executing command line:" && {
    for arg; do
      if printf "%s\n" "$arg" | grep -q ' '; then
        printf "\"%s\"\n" "$arg"
      else
        printf "%s\n" "$arg"
      fi
    done
    echo ""
  }

  exec "$@"
}
addJava () {
  dlog "[addJava] arg = '$1'"
  java_args=( "${java_args[@]}" "$1" )
}
addApp () {
  dlog "[addApp] arg = '$1'"
  sbt_commands=( "${app_commands[@]}" "$1" )
}
addResidual () {
  dlog "[residual] arg = '$1'"
  residual_args=( "${residual_args[@]}" "$1" )
}
addDebugger () {
  addJava "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1"
}
addConfigOpts () {
  dlog "[addConfigOpts] arg = '$*'"
  for item in $*
  do
    addJava "$item"
  done
}
# a ham-fisted attempt to move some memory settings in concert
# so they need not be messed around with individually.
get_mem_opts () {
  local mem=${1:-1024}
  local meta=$(( $mem / 4 ))
  (( $meta > 256 )) || meta=256
  (( $meta < 1024 )) || meta=1024

  # default is to set memory options but this can be overridden by code section below
  memopts="-Xms${mem}m -Xmx${mem}m"
  if [[ "${java_version}" > "1.8" ]]; then
    extmemopts="-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=${meta}m"
  else
    extmemopts="-XX:PermSize=64m -XX:MaxPermSize=${meta}m"
  fi

  if [[ "${java_opts}" == *-Xmx* ]] || [[ "${java_opts}" == *-Xms* ]] || [[ "${java_opts}" == *-XX:MaxPermSize* ]] || [[ "${java_opts}" == *-XX:ReservedCodeCacheSize* ]] || [[ "${java_opts}" == *-XX:MaxMetaspaceSize* ]]; then
    # if we detect any of these settings in ${java_opts} we need to NOT output our settings.
    # The reason is the Xms/Xmx, if they don't line up, cause errors.
    memopts=""
    extmemopts=""
  fi

  echo "${memopts} ${extmemopts}"
}
require_arg () {
  local type="$1"
  local opt="$2"
  local arg="$3"
  if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
    die "$opt requires <$type> argument"
  fi
}
is_function_defined() {
  declare -f "$1" > /dev/null
}

# If we're *not* running in a terminal, and we don't have any arguments, then we need to add the 'ui' parameter
detect_terminal_for_ui() {
  [[ ! -t 0 ]] && [[ "${#residual_args}" == "0" ]] && {
    addResidual "ui"
  }
  # SPECIAL TEST FOR MAC
  [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]] && [[ "${#residual_args}" == "0" ]] && {
    echo "Detected MAC OSX launched script...."
    echo "Swapping to UI"
    addResidual "ui"
  }
}

# Processes incoming arguments and places them in appropriate global variables.  called by the run method.
process_args () {
  while [[ $# -gt 0 ]]; do
    case "$1" in
       -h|-help) usage; exit 1 ;;
    -v|-verbose) verbose=1 && shift ;;
      -d|-debug) debug=1 && shift ;;
           -mem) require_arg integer "$1" "$2" && app_mem="$2" && shift 2 ;;
     -jvm-debug) 
        if echo "$2" | grep -E ^[0-9]+$ > /dev/null; then 
            addDebugger "$2" && shift 
        else
            addDebugger 9999
        fi 
        shift ;;
     -java-home) require_arg path "$1" "$2" && java_cmd="$2/bin/java" && shift 2 ;;
            -D*) addJava "$1" && shift ;;
            -J*) addJava "${1:2}" && shift ;;
              *) addResidual "$1" && shift ;;
    esac
  done

  is_function_defined process_my_args && {
    myargs=("${residual_args[@]}")
    residual_args=()
    process_my_args "${myargs[@]}"
  }
}

# Actually runs the script.
run() {
  # TODO - check for sane environment

  # process the combined args, then reset "$@" to the residuals
  process_args "$@"
  detect_terminal_for_ui
  set -- "${residual_args[@]}"
  argumentCount=$#

  #check for jline terminal fixes on cygwin
  if is_cygwin; then
    stty -icanon min 1 -echo > /dev/null 2>&1
    addJava "-Djline.terminal=jline.UnixTerminal"
    addJava "-Dsbt.cygwin=true"
  fi

  # run sbt
  execRunner "$java_cmd" \
    "-Dactivator.home=$(make_url "$activator_home")" \
    $(get_mem_opts $app_mem) \
    ${java_opts[@]} \
    ${java_args[@]} \
    -jar "$app_launcher" \
    "${app_commands[@]}" \
    "${residual_args[@]}"
    
  local exit_code=$?
  if is_cygwin; then
    stty icanon echo > /dev/null 2>&1
  fi
  exit $exit_code
}

# Loads a configuration file full of default command line options for this script.
loadConfigFile() {
  cat "$1" | sed '/^\#/d'
}

###  ------------------------------- ###
###  Start of customized settings    ###
###  ------------------------------- ###
usage() {
 cat <<EOM
Usage: $script_name <command> [options]

  Command:
  ui                 Start the Activator UI
  new [name] [template-id]  Create a new project with [name] using template [template-id]
  list-templates     Print all available template names
  -h | -help         Print this message

  Options:
  -v | -verbose      Make this runner chattier
  -d | -debug        Set sbt log level to debug
  -mem <integer>     Set memory options (default: $sbt_mem, which is $(get_mem_opts $sbt_mem))
  -jvm-debug <port>  Turn on JVM debugging, open at the given port.

  # java version (default: java from PATH, currently $(java -version 2>&1 | grep version))
  -java-home <path>  Alternate JAVA_HOME

  # jvm options and output control
  -Dkey=val          Pass -Dkey=val directly to the java runtime
  -J-X               Pass option -X directly to the java runtime
                     (-J is stripped)

  # environment variables (read from context)
  JAVA_OPTS          Environment variable, if unset uses ""
  SBT_OPTS           Environment variable, if unset uses ""
  ACTIVATOR_OPTS     Environment variable, if unset uses ""

In the case of duplicated or conflicting options, the order above
shows precedence: environment variables lowest, command line options highest.
EOM
}

###  ------------------------------- ###
###  Main script                     ###
###  ------------------------------- ###

declare -a residual_args
declare -a java_args
declare -a app_commands
declare -r real_script_path="$(realpath "$0")"
declare -r activator_home="$(realpath "$(dirname "$real_script_path")")"
declare -r app_version="1.3.3"

declare -r app_launcher="${activator_home}/activator-launch-${app_version}.jar"
declare -r script_name=activator
java_cmd=$(get_java_cmd)
declare -r java_opts=( "${ACTIVATOR_OPTS[@]}" "${SBT_OPTS[@]}" "${JAVA_OPTS[@]}" "${java_opts[@]}" )
userhome="$HOME"
if is_cygwin; then
  # cygwin sets home to something f-d up, set to real windows homedir
  userhome="$USERPROFILE"
fi
declare -r activator_user_home_dir="${userhome}/.activator"
declare -r java_opts_config_home="${activator_user_home_dir}/activatorconfig.txt"
declare -r java_opts_config_version="${activator_user_home_dir}/${app_version}/activatorconfig.txt"

# Now check to see if it's a good enough version
declare -r java_version=$("$java_cmd" -version 2>&1 | awk -F '"' '/version/ {print $2}')
if [[ "$java_version" == "" ]]; then
  echo
  echo No java installations was detected.
  echo Please go to http://www.java.com/getjava/ and download
  echo
  exit 1
elif [[ ! "$java_version" > "1.6" ]]; then
  echo
  echo The java installation you have is not up to date
  echo Activator requires at least version 1.6+, you have
  echo version $java_version
  echo
  echo Please go to http://www.java.com/getjava/ and download
  echo a valid Java Runtime and install before running Activator.
  echo
  exit 1
fi

# if configuration files exist, prepend their contents to the java args so it can be processed by this runner
# a "versioned" config trumps one on the top level
if [[ -f "$java_opts_config_version" ]]; then
  addConfigOpts $(loadConfigFile "$java_opts_config_version")
elif [[ -f "$java_opts_config_home" ]]; then
  addConfigOpts $(loadConfigFile "$java_opts_config_home")
fi

run "$@"
