#!/usr/bin/env bash

REQUIRED_CONFIGS+=("APP")
REQUIRED_CONFIGS+=("NODE_ACTION")
REQUIRED_CONFIGS+=("NODE_ENVIRONMENT")


require_node_config

help() {
  case "$NODE_ACTION" in
    (start|stop|restart|ping|version)
      echo -e "
${bldwht}Usage:${txtrst}
  edeliver $NODE_ACTION [staging|production] [Options]

${txtbld}Options:${txtrst}
  --host=[u@]vwx.yz Run command only on that host,
                    even if different hosts are configured"
    case "$NODE_ACTION" in
        (start)    echo -e "  --verbose Displays progress of started applications\n            if the startup progress takes some time.\n\n${bldylw}Info:${txtrst} Starts the node(s) on the deploy hosts.\n" ;;
        (stop)     echo -e "\n${bldylw}Info:${txtrst} Stops the node(s) on the deploy hosts.\n" ;;
        (restart)  echo -e "  --verbose Displays progress of started applications\n            if the startup progress takes some time.\n\n${bldylw}Info:${txtrst} Restarts the application on the deploy host(s) with the most recent version.\n" ;;
        (ping)     echo -e "\n${bldylw}Info:${txtrst} Checks whether the node(s) on the deploy host(s)\n      are running.\n" ;;
        (version)  echo -e "\n${bldylw}Info:${txtrst} Displays the running version(s) on the deploy host(s).\n" ;;
    esac
    ;;
    (migrate)
      echo -e "
${bldwht}Usage:${txtrst}
  edeliver migrate [staging|production] [up|down] [--version=<migration-version>]

${txtbld}Options:${txtrst}
  --host=[u@]vwx.yz Run migrations only on that host,
                    even if different hosts are configured
  --version=<migration-version> The version to migrate to.

${bldylw}Info:${txtrst}
  Migrates the database schema on the deploy host(s) either
  ${bldwht}up${txtrst} or ${bldwht}down${txtrst}. Make sure the release / upgrade containing
  the migrations is installed before.
    "
    ;;
    (migrations)
      echo -e "
${bldwht}Usage:${txtrst}
  edeliver [show] migrations [on] [staging|production]

${txtbld}Options:${txtrst}
  --host=[u@]vwx.yz Show migrations only from that host,
                    even if different hosts are configured

${bldylw}Info:${txtrst} Shows the pending migration on the deploy host(s).
    "
    ;;
    (*)
      [[ -n "$COMMAND_INFO" ]] && local _command="$COMMAND $COMMAND_INFO" || local _command="$COMMAND"
      error "\nNo custom help provided for command '$_command'. Try --help option.\n"
    ;;
  esac
}

run() {
  [[ "$NODE_ACTION" = version ]] && status "getting release versions from $NODE_ENVIRONMENT servers" || status "${NODE_ACTION}ing $NODE_ENVIRONMENT servers"
  authorize_hosts
  if [[ "$USING_DISTILLERY" = "true" ]]; then
    local _rpc_command="rpcterms"
    local _rpc_open_brackets="["
    local _rpc_close_brackets="]"
  else
    local _rpc_command="rpc"
    local _rpc_open_brackets="[["
    local _rpc_close_brackets="]]"
  fi

  if [[ "$NODE_ACTION" = version ]] && [[ "$RELEASE_CMD" = "mix" ]]; then
    NODE_ACTION="${_rpc_command} Elixir.Edeliver run_command '${_rpc_open_brackets}release_version, \"$APP\"${_rpc_close_brackets}.' | tr -d \\\""
  elif [[ "$NODE_ACTION" = ping ]] && [[ "$RELEASE_CMD" = "mix" ]]; then
    NODE_ACTION="ping"
  elif [[ "$NODE_ACTION" = migrations ]] && [[ "$RELEASE_CMD" = "mix" ]]; then
    NODE_ACTION="${_rpc_command} Elixir.Edeliver run_command '${_rpc_open_brackets}list_pending_migrations, \"$APP\", \"$ECTO_REPOSITORY\"${_rpc_close_brackets}.'"
  elif [[ "$NODE_ACTION" = migrations ]] && [[ "$RELEASE_CMD" != "mix" ]]; then
    error "Showing migrations is only supported when using mix as release command."
  elif [[ "$NODE_ACTION" = migrate ]] && [[ "$RELEASE_CMD" = "mix" ]]; then
    local __up_or_down="up"
    for arg in $ARGS; do [[ "$arg" = "down" ]] && local __up_or_down="down"; done
    [[ -n "$VERSION" ]] && local __to_version=", \"$VERSION\"" || local __to_version=""
    NODE_ACTION="${_rpc_command} Elixir.Edeliver run_command '${_rpc_open_brackets}migrate, \"$APP\", \"$ECTO_REPOSITORY\", ${__up_or_down}${__to_version}${_rpc_close_brackets}.'"
  elif [[ "$NODE_ACTION" = migrate ]] && [[ "$RELEASE_CMD" != "mix" ]]; then
    error "Executing migrations is only supported when using mix as release command."
  elif [[ "$RELEASE_CMD" = "mix" ]]; then
    NODE_ACTION="$NODE_ACTION"
  fi
  __exec_if_defined execute_custom_node_command "$NODE_ACTION" || execute_node_command "$NODE_ACTION"
  [[ "$?" != 0 ]] && exit 1
}

# executes a node command asynchronously on all
# remote nodes and prints the result for each node.
# if there is only one single node and the node command
# is start, the progress of the start command output
# is printed continuously to the screen.
execute_node_command() {
  local _node_command=$1
  if [[ $(echo $NODES | wc -w) -eq 1 ]]; then
    __execute_node_command_synchronously "$_node_command"
  else
    __execute_node_command_asynchronously "$_node_command"
  fi
}

# executes a node command asynchronously on all
# remote nodes and prints the result for each node.
__execute_node_command_asynchronously() {
  local _node_command=$1
  echo
  background_jobs_pids=()
  local i=0;
  for _node in $NODES;
  do
    {
      local _output=""
      local _lines=-1
      local _result
      while read line; do
        _result=$line
        _output="${_output}${line}\n"
        _lines=$(( $_lines + 1 ))
      done
      _output=$(echo -e "$_output" | head -n $_lines)
      __print_node_command_result "$i" "$_result" "$_node" "$_output"
      exit $result
    } < <(__execute_node_command $i "$_node" "$_node_command") &
    background_jobs_pids+=("$!")
    i=$((i+1))

  done

  local _had_errors=0
  for (( i = 0 ; i < ${#background_jobs_pids[@]} ; i++ ))
  do
    wait ${background_jobs_pids[$i]}
    [[ "$?" = 0 ]] || _had_errors=1
  done
  return $_had_errors
}


# executes a node command synchronously on a single
# remote node and prints the output of the command
# continuously.
__execute_node_command_synchronously() {
  local _node_command=$1
  local _node=${NODES# *}
  local _status_code
  local _response
  __print_node_command_result "single_node" "0" "$_node" ""
  if [[ "$MODE" = "verbose" ]] &&  [[ "${_node_command}" = start* || "${_node_command}" = restart* ]]; then
    # display (re)start progress (= started applications)
    echo -en "  response: ${txtylw}"
    __execute_node_command "single_node" "$_node" "$_node_command"
    _status_code=$?
    echo -e "${txtrst}"
  else
    echo -n "  response: "
    _response=$(__execute_node_command "single_node" "$_node" "$_node_command")
    _status_code=$?
    __format_response "$_response"
  fi
  return $_status_code
}

# formats the response when node command is executed
# synchronously. If version is displayed and --verbose option passed
# the last git revisions are displayed in addition if the version
# contains the git revsion.
__format_response() {
  local _response="$@"
  if [[ "$COMMAND" = "version" ]] && [[ "$_response" =~ "+" ]]; then
    local _is_first_line="true" _line _revision="$(grep -oe '[0-9a-fA-F]\{7\}' <<< "$_response")"
    if [[ -n "$_revision" ]]; then
      echo -e "${txtylw}$_response${txtrst}"
      local _branch="$(git branch -a --contains "$_revision" 2>/dev/null | head -n 1 | cut -b3-)"
      [[ -n "$_branch" ]] && echo -e "  branch  : ${_branch}"
      local _commit_date="$(git show -s --format=%ci "$_revision" 2>/dev/null)"
      [[ -n "$_commit_date" ]] && echo -e "  date    : $_commit_date (git commit)"
      local _last_commits=${VERSION_INFO_LAST_COMMITS:-5}
      (IFS='
'
      for _line in $(git log "$_revision" -"$_last_commits" --pretty=oneline 2>/dev/null | cut -d" " -f2-); do
        _line="$(trim_string "$_line")"
        [[ "$_is_first_line" = "true" ]] && echo -n "  commits : " || echo -n "            "
        echo -e "$_line"
        _is_first_line="false"
      done
      [[ "$_is_first_line" = "false" ]] && [[ "$_last_commits" != "1" ]] && echo "            ..."
      )
      return 0
    fi
  fi
  echo "${bldylw}$_response${txtrst}"

}



# executes a node command on a given node.
__execute_node_command() {
  local _node_index=$1
  local _node_name=$2
  local _node_command=$3

  local _config; local _config_arg
  local _user=${_node_name%@*}
  local _host=${_node#*@}
  local _path=${_host#*:}
  local _status_code
  [[ "$_path" =~ .*\|.* ]] && _config=${_path#*|} || _config=""
  _path=${_path%|*}
  _host=${_host%:*}

  [[ -n "$_config" ]] && _config_arg="--config=${_config}" || _config_arg=""
  [ "${_node_index}" != "single_node" ] && [ "${_node_command}" = "start" ] &&  _config_arg="$_config_arg --short"

  [[ -n "$ECTO_REPOSITORY" ]] && local _node_env="ECTO_REPOSITORY='$ECTO_REPOSITORY' "

  _remote_job="$(__get_node_command "$_node_command" "$_path" "$_config_arg")"

  [ "${_node_index}" = "single_node" ] && local _terminal_option="-t -q" || local _terminal_option=
  ssh $_terminal_option -o ConnectTimeout="$SSH_TIMEOUT" "${_user}@${_host}" "$_remote_job"
  _status_code="$?"
  [ "${_node_index}" != "single_node" ] && echo -e "\n${_status_code}"
  [[ "$_status_code" = "0" ]] && return 0 || return 1
}

# prints the result from the execution of a node command on a remote node.
__print_node_command_result() {
  local _node_index=$1
  local _status_code=$2
  local _node_name=$3
  local _node_action_response=$4
  local _config; local _config_arg
  local _message=""
  _message="${bldgrn}$NODE_ENVIRONMENT${txtrst} node: $_node_index\n\n"
  [[ "$_node_index" = "single_node" ]] && _message="${bldgrn}$NODE_ENVIRONMENT${txtrst} node:\n\n"
  echo
  local _user=${_node_name%@*}
  local _host=${_node_name#*@}
  local _path=${_host#*:}
  [[ "$_path" =~ .*\|.* ]] && _config=${_path#*|} || _config=""
  _path=${_path%|*}
  _host=${_host%:*}

  _message="${_message}  user    : $_user\n"
  _message="${_message}  host    : $_host\n"
  _message="${_message}  path    : $_path\n"
  [[ -n "$_config" ]] && _message="${_message}  config  : $_config\n"

  if [[ "$_node_action_response" =~ ": " ]]; then
    local _node=${_node_action_response%: *}
    _node_action_response=${_node_action_response#*: }
    _node=$(echo ${_node} | grep -oe "[^ @]\+@.*")
    _message="${_message}  node    : $_node\n"
  fi

  if [[ "$_node_index" = "single_node" ]]; then
    echo -en "${_message}" && return 0
  elif [[ "$_status_code" = "0" ]] && [[ -n "$_node_index" ]]; then
    _node_action_response="$(__format_response "$_node_action_response")"
    _message="${_message}  response: ${_node_action_response}\n"
  elif [[ -n "$_node_index" ]]; then
    _message="${_message}  response: ${bldred}failed${txtrst}\n";
    [[ -n "$_node_action_response" ]] && {
      local lines=0
      while read line; do
        [[ "$lines" = "0" ]] && _message="${_message}  error   : ${line}\n" || _message="${_message}            ${line}\n"
        lines=$(($lines+1))
      done < <(echo -e "$_node_action_response")
    }
    _message="$_message\n${txtrst}"
  fi
  echo -e "${_message}"
  [[ "$_status_code" = "0" ]] && return 0 || return 1
}



