#!/usr/bin/env bash

__version() {
  echo -e "\n${txtbld}edeliver v$DELIVER_VERSION${txtrst} | $HOMEPAGE\n"
}

__help() {
  __version

  echo -e "${txtbld}Usage:${txtrst}
  edeliver <build-command|deploy-command|node-command|local-command> command-info [Options]
  edeliver --help|--version
  edeliver help <command>

  ${txtbld}Build Commands:${txtrst}
  edeliver build release [--revision=<git-revision>|--tag=<git-tag>] [--branch=<git-branch>] [Options]
  edeliver build upgrade --from=<git-tag-or-revision>|--with=<release-version-from-store>
                        [--to=<git-tag-or-revision>] [--branch=<git-branch>] [Options]

  ${txtbld}Deploy Commands:${txtrst}
  edeliver deploy release|upgrade [[to] staging|production] [--version=<release-version>] [Options]
  edeliver upgrade [staging|production] [--to=<git-tag-or-revision>] [--branch=<git-branch>] [Options]
  edeliver update  [staging|production] [--to=<git-tag-or-revision>] [--branch=<git-branch>] [Options]

  ${txtbld}Node Commands:${txtrst}
  edeliver start|stop|restart|ping|version [staging|production] [Options]
  edeliver migrate [staging|production] [up|down] [--version=<migration-version>]
  edeliver [show] migrations [on] [staging|production]

  ${txtbld}Local Commands:${txtrst}
  edeliver check release|config [--version=<release-version>]
  edeliver show releases|appups
  edeliver show relup <xyz.upgrade.tar.gz>
  edeliver edit relup|appups [--version=<release-version>]
  edeliver upload|download [release|upgrade <release-version>]|<src-file-name> [<dest-file-name>]
  edeliver increase [major|minor] versions [--from=<git-tag-or-revision>] [--to=<git-tag-or-revision>]
  edeliver unpack|pack release|upgrade [--version=<release-version>]

"
  __exec_if_defined "print_custom_commands_help"
  echo -e "${txtbld}Options:${txtrst}
  -C, --compact         Displays every task as it's run, silences all output. (default mode)
  -V, --verbose         Same as above, does not silence output.
  -P, --plain           Displays every task as it's run, silences all output. No colouring. (CI)
  -D, --debug           Runs in shell debug mode, displays everything.
  -S, --skip-existing   Skip copying release archives if they exist already on the deploy hosts.
  -F, --force           Do not ask, just do, overwrite, delete or destroy everything
      --clean-deploy    Delete the release, lib and erts-* directories before deploying the release
      --skip-git-clean  Don't build from a clean state for faster builds. See $GIT_CLEAN_PATHS env
      --skip-mix-clean  Skip the 'mix clean step' for faster builds. Use in addition to --skip-git-clean
      --skip-relup-mod  Skip modification of relup file. Custom relup instructions are not added
      --relup-mod=<module-name> The name of the module to modify the relup
      --auto-version=revision|commit-count|branch|date Automatically append metadata to release version.
      --increment-version=major|minor|patch Increment the version for the current build.
      --set-version=<release-version> Set the release version for the current build.
      --start-deploy    Starts the deployed release. If release is running, it is restarted!
      --host=[u@]vwx.yz Run command only on that host, even if different hosts are configured
      --mix-env=<env>   Build with custom mix env \$MIX_ENV. Default is 'prod'

${txtbld}Miscellaneous:${txtrst}
  Sometimes you will be asked, if you omit a required argument (e.g --from for the build upgrade task).
  You can overwrite any config at runtime:

  BUILD_HOST=build-2.acme.com edeliver build release
  GIT_CLEAN_PATHS='_build rel priv/generated' edeliver build release
"
}

__build_help() {
  echo -e "
${bldwht}Usage:${txtrst}
  edeliver build release|upgrade [Options]

${bldylw}Info:${txtrst}
  Builds either a release or and upgrade on the build host and copies it
  to the release store. A release can be used by the edeliver
  ${bldwht}deploy release${txtrst} command to deploy it on staging or production hosts.
  ${txtgrn}The built release can be used after a restart${txtrst} of the node.
  An upgrade can be used by the edeliver ${bldwht}deploy upgrade${txtrst} command
  to deploy it onstaging or production hosts. ${txtgrn}The built upgrade
  can be used for hot code upgrades without a restart${txtrst} of the node.

  For more information try ${bldwht}edeliver help build release${txtrst}
  or ${bldwht}edeliver help build upgrade${txtrst}.
"
}


__deploy_help() {
  echo -e "
${bldwht}Usage:${txtrst}
  edeliver deploy release [[to] staging|production] [Options]
  edeliver deploy upgrade [[to] staging|production] [Options]

${bldylw}Info:${txtrst}
  Deploys either a release or an upgrade on all staging (default)
  or production hosts. A release must have been built before by
  the ${bldwht}build release${txtrst} command while an ${txtylw}upgrade must have been built
  before${txtrst} by the ${bldwht}build upgrade${txtrst} command ${txtylw}from the version
  that is running on that nodes${txtrst}.

  Deploying a release requires a restart of the node
  while deploying an upgrade updates the node by using
  ${txtgrn}hot code upgrade${txtrst} and no restart of the nodes is required.

  For more information try ${bldwht}edeliver help deploy release${txtrst}
  or ${bldwht}edeliver help deploy upgrade${txtrst}.
"
}

__available_strategies() {
  __version

  echo -e "${txtbld}Available strategies:${txtrst}"

  for strategy in $STRATEGIES_NAME
  do
    echo "  * $strategy"
  done

  echo ""
}

[[ "$1" == "help" ]] && PRINT_COMMAND_HELP=true && shift

# parse command
if (( $# )); then
  arg="$1" && shift
  case "${arg}" in
    (build)
      COMMAND="${arg}"
      if (( $# )); then
        arg="$1" && shift
        case "${arg}" in
          (release|upgrade)
            COMMAND_INFO="${arg}"
          ;;
          (appups|appup)
            COMMAND_INFO="appup"
          ;;
          (-*)
            [[ "$PRINT_COMMAND_HELP" = "true" ]] && __build_help && exit 2
            __help; error_message "No build type found. Use release|upgrade\n"; exit 2
          ;;
          (*)
            __help; error_message "Unknown build type ${arg}. Use release|upgrade\n"; exit 2
          ;;
        esac
      else # no build type found
        [[ "$PRINT_COMMAND_HELP" = "true" ]] && __build_help && exit 2
        __help; error_message "No build type found. Use release|upgrade\n"; exit 2
      fi
    ;;
    (deploy)
      COMMAND="${arg}"
      if (( $# )); then
        arg="$1" && shift
        case "${arg}" in
          (release|upgrade)
            COMMAND_INFO="${arg}"
          ;;
          (-*)
            [[ "$PRINT_COMMAND_HELP" = "true" ]] && __deploy_help && exit 2
            __help; error_message "No deploy type found. Use release|upgrade\n"; exit 2
          ;;
          (*)
            __help; error_message "Unknown deploy type ${arg}. Use release|upgrade\n"; exit 2
          ;;
        esac
      else # no build type found
        [[ "$PRINT_COMMAND_HELP" = "true" ]] && __deploy_help && exit 2
        __help; error_message "No deploy type found. Use release|upgrade\n"; exit 2
      fi
    ;;
    (start|stop|restart|ping|version|migrate|migrations)
      COMMAND="${arg}"
      if (( $# )); then
        [[ "$1" == "on" ]] && shift
        arg="$1" && shift
        case "${arg}" in
          (*)
            if [[ "${arg}" =~ ^-- ]]; then
              NODE_ENVIRONMENT="staging"
            else
              NODE_ENVIRONMENT="$arg"
            fi
          ;;
        esac
      else # no environment found, use staging as default
        NODE_ENVIRONMENT="staging"
      fi
    ;;
    (check)
      COMMAND="${arg}"
      if (( $# )); then
        arg="$1" && shift
        case "${arg}" in
          (release|config)
            CHECK_CMD="${arg}"
          ;;
          (-*)
            __help; error_message "No check type found. Use release|config\n"; exit 2
          ;;
          (*)
            __help; error_message "Unknown check type ${arg}. Use release|config\n"; exit 2
          ;;
        esac
      else
        __help; error_message "No check type found. Use release|config\n"; exit 2
      fi
    ;;
    (unpack|pack)
      COMMAND="${arg}"
      if (( $# )); then
        arg="$1" && shift
        case "${arg}" in
          (release|upgrade)
            COMMAND_INFO="${arg}"
          ;;
          (-*)
            __help; error_message "No release type found. Use release|upgrade\n"; exit 2
          ;;
          (*)
            __help; error_message "Unknown release type ${arg}. Use release|upgrade\n"; exit 2
          ;;
        esac
      else # no build type found
        __help; error_message "No release type found. Use release|upgrade\n"; exit 2
      fi
    ;;
    (show)
      COMMAND="${arg}"
      if (( $# )); then
        arg="$1" && shift
        case "${arg}" in
          (release|releases)
            COMMAND_INFO="releases"
          ;;
          (appup|appups)
            COMMAND_INFO="appups"
          ;;
          (relup|relups)
            COMMAND_INFO="relup"
            if (( $# )); then
              RELEASE_FILE="$1" && shift
            else
              __help; error_message "No upgrade tar.gz passed as argument.\n"; exit 2
            fi
          ;;
          (migrations)
            COMMAND="${arg}"
            if (( $# )); then
              [[ "$1" == "on" ]] && shift
              arg="$1" && shift
              case "${arg}" in
                (*)
                  if [[ "${arg}" =~ ^-- ]]; then
                    NODE_ENVIRONMENT="staging"
                  else
                    NODE_ENVIRONMENT="$arg"
                  fi
                ;;
              esac
            else # no environment found, use staging as default
              NODE_ENVIRONMENT="staging"
            fi
          ;;
          (-*)
            __help; error_message "No show command found. Use releases|appups|relups\n"; exit 2
          ;;
          (*)
            __help; error_message "Unknown show command ${arg}. Use releases|appups|relups\n"; exit 2
          ;;
        esac
      else
        __help; error_message "No show command found. Use releases|appups|relups\n"; exit 2
      fi
    ;;
    (upload|download)
      COMMAND="${arg}"
      if (( $# )); then
        arg="$1" && shift
        case "${arg}" in
          (release|upgrade)
            COMMAND_INFO="${arg}"
            if (( $# )); then
              arg="$1" && shift
              VERSION="${arg}"
              (( $# )) && ! [[ "$1" =~ ^- ]] && DESTINATION_FILE="$1" && shift
            else
              __help; error_message "No $COMMAND_INFO version given\n"; exit 2
            fi
          ;;
          (*)
            RELEASE_FILE="${arg}"
            (( $# )) && ! [[ "$1" =~ ^- ]] && DESTINATION_FILE="$1" && shift
          ;;
        esac
      else # no build type found
        __help; error_message "No file to $COMMAND found. Use release|upgrade <version> or file name\n"; exit 2
      fi
    ;;
    (edit)
      COMMAND="${arg}"
      if (( $# )); then
        arg="$1" && shift
        case "${arg}" in
          (appup|appups)
            COMMAND_INFO="appups"
          ;;
          (relup|relups)
            COMMAND_INFO="relup"
          ;;
          (-*)
            __help; error_message "No edit command found. Use appup|relup\n"; exit 2
          ;;
          (*)
            __help; error_message "Unknown edit command ${arg}. Use appup|relup\n"; exit 2
          ;;
        esac
      else
        __help; error_message "No edit command found. Use appup|relup\n"; exit 2
      fi
    ;;
    (increase)
      COMMAND="${arg}"
      if (( $# )); then
        arg="$1" && shift
        case "${arg}" in
          (versions|version)
            INCREASE_VERSION_TYPE="version"
            COMMAND_INFO="versions"
          ;;
          (minor|major)
            INCREASE_VERSION_TYPE="$arg"
            COMMAND_INFO="versions"
          ;;
          (-*)
            __help; error_message "Increase type not found. Use versions|major|minor\n"; exit 2
          ;;
          (*)
            __help; error_message "Unknown increase type ${arg}. Use versions|major|minor\n"; exit 2
          ;;
        esac
      else
        INCREASE_VERSION_TYPE="minor"
        COMMAND_INFO="versions"
      fi
    ;;
    (-h|--help)
      __help
      exit 0
    ;;
    (-v|--version)
      __version
      exit 0
    ;;
    (*)
      COMMAND="${arg}";
    ;;
  esac
else # no command found
  __help
  [[ "$PRINT_COMMAND_HELP" != "true" ]] && error_message "No command found.\n";
  exit 2
fi

while (( $# ))
do
  arg="$1" && shift
  case "${arg}" in
    (-C|--compact)
      MODE="compact"
    ;;
    (-D|--debug)
      MODE="debug"
    ;;
    (-V|--verbose)
      MODE="verbose"
    ;;
    (-S|--skip-existing)
      SKIP_COPYING_EXISTING_FILES="skip"
    ;;
    (-P|--plain)
      # this was already captured in libexec/output
      # noop
    ;;
    (-F|--force)
      FORCE="true"
    ;;
    (--clean-deploy)
      CLEAN_DEPLOY="true"
    ;;
    (--skip-git-clean)
      SKIP_GIT_CLEAN="true"
    ;;
    (--skip-mix-clean)
      SKIP_MIX_CLEAN="true"
      [[ -n "$SET_RELEASE_VERSION" ]] && __help && error_message "Option --set-version= cannot be used together with the --skip-mix-clean option.\n" && exit 2
      [[ -n "$INCREMENT_RELEASE_VERSION" ]] && __help && error_message "Option --increment-version= cannot be used together with the --skip-mix-clean option.\n" && exit 2
      [[ -n "$AUTO_RELEASE_VERSION" ]] && __help && error_message "Option --auto-version= cannot be used together with the --skip-mix-clean option.\n" && exit 2
    ;;
    (--start-deploy)
      START_DEPLOY="true"
    ;;
    (--skip-relup-mod|--skip-relup-modification)
      SKIP_RELUP_MODIFICATIONS="true"
    ;;
    (--runs-as-mix-task)
      RUNS_AS_MIX_TASK="true"
    ;;
    (-h|--help)
      __help
      exit 0
    ;;
    (to)
    ;;
    (staging)
      DEPLOY_ENVIRONMENT="staging"
    ;;
    (production)
      DEPLOY_ENVIRONMENT="production"
    ;;
    (check)
      CHECK=true
    ;;
    (*)
      if [[ "${arg}" =~ ^--from= ]]; then
        FROM="${arg##--from=}"
      elif [[ "${arg}" =~ ^--with= ]]; then
        WITH="${arg##--with=}"
      elif [[ "${arg}" =~ ^--to= ]]; then
        TO="${arg##--to=}"
      elif [[ "${arg}" =~ ^--revision= ]]; then
        REVISION="${arg##--revision=}"
      elif [[ "${arg}" =~ ^--tag= ]]; then
        TAG="${arg##--tag=}"
        REVISION="$(git rev-parse $TAG)"
      elif [[ "${arg}" =~ ^--branch= ]]; then
        BRANCH=${arg##--branch=}
      elif [[ "${arg}" =~ ^--version= ]]; then
        VERSION=${arg##--version=}
      elif [[ "${arg}" =~ ^--host= ]]; then
        HOST=${arg##--host=}
      elif [[ "${arg}" =~ ^--mix-env= ]]; then
        TARGET_MIX_ENV=${arg##--mix-env=}
      elif [[ "${arg}" =~ ^--relup-mod= ]]; then
        RELUP_MODIFICATION_MODULE=${arg##--relup-mod=}
      elif [[ "${arg}" =~ ^--auto-version= ]]; then
        AUTO_RELEASE_VERSION=${arg##--auto-version=}
        IFS='+' read -ra AUTO_VERSION_ARGS <<< "$AUTO_RELEASE_VERSION"
        for AUTO_VERSION_ARG in "${AUTO_VERSION_ARGS[@]}"; do
          ORIGINAL_AUTO_VERSION_ARG=$AUTO_VERSION_ARG
          AUTO_VERSION_ARG=${AUTO_VERSION_ARG#append-*}
          AUTO_VERSION_ARG=${AUTO_VERSION_ARG#git-*}
          AUTO_VERSION_ARG=${AUTO_VERSION_ARG#build-*}
          if ! [[ "$AUTO_VERSION_ARG" =~  ^(revision|commit-count|commit-count-branch|commit-count-all|commit-count-all-branches|branch|branch-unless-master|date)$ ]]; then
            __help; error_message "Illegal value for --auto-version= option: '${ORIGINAL_AUTO_VERSION_ARG}'.\n"; exit 2
          fi
        done
        unset AUTO_VERSION_ARGS AUTO_VERSION_ARG ORIGINAL_AUTO_VERSION_ARG
        [[ "$SKIP_MIX_CLEAN" = "true" ]] && __help && error_message "Option --auto-version= cannot be used together with the --skip-mix-clean option.\n" && exit 2
      elif [[ "${arg}" =~ ^--increment-version= ]]; then
        INCREMENT_RELEASE_VERSION=${arg##--increment-version=}
        ! [[ "$INCREMENT_RELEASE_VERSION" =~  ^(patch|minor|major)$ ]] && __help && error_message "Value for --increment-version= option must be either patch, minor or major.\n" && exit 2
        [[ -n "$SET_RELEASE_VERSION" ]] && __help && error_message "Option --increment-version= cannot be used together with the --set-version= option.\n" && exit 2
        [[ "$SKIP_MIX_CLEAN" = "true" ]] && __help && error_message "Option --set-version= cannot be used together with the --skip-mix-clean option.\n" && exit 2
      elif [[ "${arg}" =~ ^--set-version= ]]; then
        SET_RELEASE_VERSION=${arg##--set-version=}
        [[ -z "$SET_RELEASE_VERSION" ]] && __help && error_message "Value for --set-version= option must not be empty.\n" && exit 2
        [[ -n "$INCREMENT_RELEASE_VERSION" ]] && __help && error_message "Option --set-version= cannot be used together with the --increment-version= option.\n" && exit 2
        [[ "$SKIP_MIX_CLEAN" = "true" ]] && __help && error_message "Option --increment-version= cannot be used together with the --skip-mix-clean option.\n" && exit 2
      else
        [[ -n "${COMMAND}" ]] && __exec_if_defined "accepts_custom_command_argument" "${COMMAND}" "${arg}" || hint_message "Unknown argument ${arg} ignored"
      fi
    ;;
  esac
done

case "${MODE}" in
  (compact)
    VERBOSE=""
    SILENCE="&> /dev/null"
  ;;
  (verbose)
    VERBOSE=true
    SILENCE=""
  ;;
  (debug)
    set -x
    VERBOSE=true
    SILENCE=""
  ;;
esac



case "${COMMAND}" in
  (build)
    STRATEGY="erlang-${COMMAND}-${COMMAND_INFO}"
    if [[ ! ${COMMAND_INFO} = "release" ]] && [[ "$PRINT_COMMAND_HELP" != "true" ]] && [[ -z "$FROM" ]] && [[ -z "$WITH" ]]; then
      __help; error_message "build command requires --from= or --with argument.\n"; exit 2
    fi
  ;;
  (deploy)
    STRATEGY="erlang-deploy-${COMMAND_INFO}"
  ;;
  (start|stop|restart|ping|version|migrate|migrations)
    STRATEGY="erlang-node-execute"
    NODE_ACTION="${COMMAND}"
  ;;
  (upload|download)
    STRATEGY="erlang-release-store-copy"
  ;;
  (unpack|pack)
    STRATEGY="erlang-unpack-pack"
  ;;
  (increase)
    STRATEGY="erlang-increase-versions"
  ;;
  (*)
    STRATEGY="erlang-${COMMAND}"
    [[ -n "${COMMAND_INFO}" ]] && STRATEGY="${STRATEGY}-${COMMAND_INFO}"
    if ! [[ -f "${BASE_PATH}/strategies/${STRATEGY}" ]]; then
      if ! [[ -f "$ORIGIN_DIR/.deliver/strategies/${STRATEGY}" ]]; then
        if [[ "${COMMAND}" = "publish" ]] && [[ -f "${BASE_PATH}/strategies/publish-edeliver" ]]; then
          STRATEGY="publish-edeliver"
        elif [[ "$PRINT_COMMAND_HELP" = "true" ]]; then
          __help; exit 0
        else
          __help; error_message "Unknown command ${COMMAND}.\n"; exit 2
        fi
      fi
    fi
  ;;
esac

