#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_AVAILABLE_PATH/checks/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_AVAILABLE_PATH/scheduler-docker-local/internal-functions"

trigger-scheduler-docker-local-scheduler-deploy() {
  declare desc="deploys an image tag for a given application"
  declare trigger="scheduler-deploy"
  declare DOKKU_SCHEDULER="$1" APP="$2" IMAGE_TAG="$3"

  if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then
    return
  fi

  rm -f "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/failed-containers"

  local DOCKER_RUN_LABEL_ARGS="--label=com.dokku.app-name=$APP --label=com.dokku.container-type=deploy"
  local DOKKU_DOCKER_STOP_TIMEOUT DOKKU_HEROKUISH DOKKU_NETWORK_BIND_ALL IMAGE
  DOKKU_HEROKUISH=false
  DOKKU_CNB=false
  IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG")
  plugn trigger pre-deploy "$APP" "$IMAGE_TAG"

  is_image_cnb_based "$IMAGE" && DOKKU_CNB=true
  is_image_herokuish_based "$IMAGE" "$APP" && DOKKU_HEROKUISH=true
  local IMAGE_SOURCE_TYPE="dockerfile"
  [[ "$DOKKU_HEROKUISH" == "true" ]] && IMAGE_SOURCE_TYPE="herokuish"
  [[ "$DOKKU_CNB" == "true" ]] && IMAGE_SOURCE_TYPE="pack"
  local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE"
  local oldids=$(get_app_container_ids "$APP")

  DOKKU_NETWORK_BIND_ALL="$(plugn trigger network-get-property "$APP" bind-all-interfaces)"
  DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)"
  [[ $DOKKU_DOCKER_STOP_TIMEOUT ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}"

  DOKKU_START_CMD="$(config_get "$APP" DOKKU_START_CMD || true)"

  local line
  local PROC_TYPE
  local PROC_COUNT
  local CONTAINER_INDEX
  while read -r line || [[ -n "$line" ]]; do
    [[ "$line" =~ ^#.* ]] && continue
    line="$(strip_inline_comments "$line")"
    PROC_TYPE=${line%%=*}
    PROC_COUNT=${line#*=}
    CONTAINER_INDEX=1

    if [[ "$(is_app_proctype_checks_disabled "$APP" "$PROC_TYPE")" == "true" ]]; then
      dokku_log_info1 "zero downtime is disabled for app ($APP.$PROC_TYPE). stopping currently running containers"
      local cid proctype_oldids="$(get_app_running_container_ids "$APP" "$PROC_TYPE" 2>/dev/null)"
      for cid in $proctype_oldids; do
        dokku_log_info2 "stopping $APP.$PROC_TYPE ($cid)"

        # Disable the container restart policy
        "$DOCKER_BIN" container update --restart=no "$cid" &>/dev/null || true

        # shellcheck disable=SC2086
        "$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG "$cid" &>/dev/null
        # remove cid from oldids to skip the old container finish processing
        oldids="$(remove_val_from_list "$cid" "$oldids" " ")"
      done
    fi

    while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do
      local cid=""
      local port=""
      local ipaddr=""
      local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX"
      local DYNO="$PROC_TYPE.$CONTAINER_INDEX"

      # start the app
      local DOCKER_ARGS
      DOCKER_ARGS=$(: | plugn trigger docker-args-deploy "$APP" "$IMAGE_TAG" "$PROC_TYPE" "$CONTAINER_INDEX")
      DOCKER_ARGS+=" --label=com.dokku.process-type=$PROC_TYPE --label=com.dokku.dyno=$DYNO "
      DOCKER_ARGS+=" --env=DYNO=$DYNO "
      DOCKER_ARGS+=" --init "
      DOCKER_ARGS+=" $DOCKER_RUN_LABEL_ARGS $DOKKU_GLOBAL_RUN_ARGS "
      DOCKER_ARGS+=$(: | plugn trigger docker-args-process-deploy "$APP" "$IMAGE_SOURCE_TYPE" "$IMAGE_TAG" "$PROC_TYPE" "$CONTAINER_INDEX")
      [[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" --env=TRACE=true "

      local START_CMD
      [[ "$DOKKU_HEROKUISH" == "true" ]] && START_CMD="/start $PROC_TYPE"
      [[ "$DOKKU_CNB" == "true" ]] && START_CMD=""
      [[ -n "$DOKKU_START_CMD" ]] && START_CMD="$DOKKU_START_CMD"

      local DOKKU_PORT=""
      if [[ "$PROC_TYPE" == "web" ]]; then
        ports=($(plugn trigger network-compute-ports "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$CONTAINER_INDEX"))
        local DOKKU_DOCKER_PORT_ARGS=""
        for p in "${ports[@]}"; do
          if [[ ! "$p" =~ .*udp.* ]]; then
            DOKKU_PORT=${DOKKU_PORT:="$p"}
          fi

          if [[ "$DOKKU_NETWORK_BIND_ALL" == "true" ]]; then
            DOCKER_ARGS+=" -p $p "
          fi
        done
      fi

      DOCKER_ARGS+=" --env=PORT=$DOKKU_PORT "

      START_CMD=$(fn-scheduler-docker-local-extract-start-cmd "$APP" "$PROC_TYPE" "$START_CMD" "$DOKKU_HEROKUISH" "$DOKKU_PORT")
      DOCKER_ARGS+=" $IMAGE "
      DOCKER_ARGS+=" $START_CMD "

      cid=$(fn-start-app-container "$DOCKER_ARGS")

      plugn trigger post-container-create "app" "$cid" "$APP" "deploy" "$PROC_TYPE"
      "$DOCKER_BIN" container start "$cid" >/dev/null || true

      ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$cid")
      port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$cid" "$DOKKU_HEROKUISH")

      kill_new() {
        declare desc="wrapper function to kill newly started app container"
        declare CID="$1" PROC_TYPE="$2" CONTAINER_INDEX="$3"
        mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP"
        echo "${CID} ${PROC_TYPE}.${CONTAINER_INDEX}" >>"${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/failed-containers"
        "$DOCKER_BIN" container inspect "$CID" &>/dev/null && {
          # Disable the container restart policy
          "$DOCKER_BIN" container update --restart=no "$CID" &>/dev/null || true
          "$DOCKER_BIN" container stop "$CID" >/dev/null && "$DOCKER_BIN" container kill "$CID" &>/dev/null
        }
        trap - INT TERM EXIT
        kill -9 $$
      }

      # run checks first, then post-deploy hooks, which switches proxy traffic
      trap 'kill_new $cid $PROC_TYPE $CONTAINER_INDEX' INT TERM EXIT
      if [[ "$(is_app_proctype_checks_disabled "$APP" "$PROC_TYPE")" == "false" ]]; then
        dokku_log_info1 "Attempting pre-flight checks ($PROC_TYPE.$CONTAINER_INDEX)"
        plugn trigger check-deploy "$APP" "$cid" "$PROC_TYPE" "$port" "$ipaddr"
      fi
      trap - INT TERM EXIT

      # now using the new container
      [[ -n "$cid" ]] && echo "$cid" >"$DOKKU_CONTAINER_ID_FILE"
      [[ -n "$ipaddr" ]] && plugn trigger network-write-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$ipaddr"
      [[ -n "$port" ]] && plugn trigger network-write-port "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$port"

      # cleanup pre-migration files
      rm -f "$DOKKU_ROOT/$APP/CONTAINER" "$DOKKU_ROOT/$APP/IP" "$DOKKU_ROOT/$APP/PORT"

      local CONTAINER_INDEX=$((CONTAINER_INDEX + 1))
    done
    # cleanup when we scale down
    if [[ "$PROC_COUNT" == 0 ]]; then
      local CONTAINER_IDX_OFFSET=0
    else
      local CONTAINER_IDX_OFFSET=$((PROC_COUNT + 1))
    fi
    local container_state_filetype
    for container_state_filetype in CONTAINER IP PORT; do
      cd "$DOKKU_ROOT/$APP"
      find . -maxdepth 1 -name "$container_state_filetype.$PROC_TYPE.*" -printf "%f\n" | sort -t . -k 3 -n | tail -n +$CONTAINER_IDX_OFFSET | xargs rm -f
    done
  done <"$DOKKU_SCALE_FILE"

  dokku_log_info1 "Running post-deploy"
  plugn trigger core-post-deploy "$APP" "$port" "$ipaddr" "$IMAGE_TAG"
  plugn trigger post-deploy "$APP" "$port" "$ipaddr" "$IMAGE_TAG"

  # kill the old container
  if [[ -n "$oldids" ]]; then

    if [[ -z "$DOKKU_WAIT_TO_RETIRE" ]]; then
      local DOKKU_APP_DOKKU_WAIT_TO_RETIRE=$(config_get "$APP" DOKKU_WAIT_TO_RETIRE || true)
      local DOKKU_GLOBAL_DOKKU_WAIT_TO_RETIRE=$(config_get --global DOKKU_WAIT_TO_RETIRE || true)
      local DOKKU_WAIT_TO_RETIRE=${DOKKU_APP_DOKKU_WAIT_TO_RETIRE:="$DOKKU_GLOBAL_DOKKU_WAIT_TO_RETIRE"}
    fi

    # Let the old container finish processing requests, before terminating it
    local WAIT="${DOKKU_WAIT_TO_RETIRE:-60}"
    dokku_log_info1 "Shutting down old containers in $WAIT seconds"
    local oldid
    for oldid in $oldids; do
      dokku_log_verbose_quiet "$oldid"
      plugn trigger scheduler-register-retired "$APP" "$oldid" "$WAIT"
    done
    (
      exec >/dev/null 2>/dev/null </dev/null
      trap '' INT HUP
      sleep "$WAIT"
      for oldid in $oldids; do
        # Disable the container restart policy
        "$DOCKER_BIN" container update --restart=no "$oldid" &>/dev/null || true

        # Attempt to stop, if that fails, then force a kill as docker seems
        # to not send SIGKILL as the docs would indicate. If that fails, move
        # on to the next.
        # shellcheck disable=SC2086
        "$DOCKER_BIN" container stop $DOCKER_STOP_TIME_ARG "$oldid" \
          || "$DOCKER_BIN" container kill "$oldid" \
          || plugn trigger retire-container-failed "$APP" "$oldid" # plugin trigger for event logging
      done
    ) &
    disown -a
    # Use trap since disown/nohup don't seem to keep child alive
    # Give child process just enough time to set the traps
    sleep 0.1
  fi
}

fn-scheduler-docker-local-extract-start-cmd() {
  declare APP="$1" PROC_TYPE="$2" START_CMD="$3" DOKKU_HEROKUISH="$4" PORT="$5"
  local DOKKU_DOCKERFILE_START_CMD DOKKU_PROCFILE_START_CMD START_CMD
  if [[ "$DOKKU_HEROKUISH" != "false" ]] && [[ -n "$START_CMD" ]]; then
    echo "$START_CMD"
    return
  fi

  DOKKU_DOCKERFILE_START_CMD=$(config_get "$APP" DOKKU_DOCKERFILE_START_CMD || true)
  DOKKU_PROCFILE_START_CMD=$(plugn trigger procfile-get-command "$APP" "$PROC_TYPE" "$PORT")
  START_CMD=${DOKKU_DOCKERFILE_START_CMD:-$DOKKU_PROCFILE_START_CMD}
  echo "$START_CMD"
}

fn-start-app-container() {
  declare desc="starts a single app container"
  declare DOCKER_ARGS="$1"

  declare -a __DOKKU_DOCKER_ARG_ARRAY
  eval "__DOKKU_DOCKER_ARG_ARRAY=($DOCKER_ARGS)"
  eval "$(config_export app "$APP")"
  "$DOCKER_BIN" container create "${__DOKKU_DOCKER_ARG_ARRAY[@]}"
}

trigger-scheduler-docker-local-scheduler-deploy "$@"
