#!/usr/bin/env bash
[[ " receive deploy build release trace delete ls run cleanup url urls report version help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
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/nginx-vhosts/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/proxy/functions"

dokku_receive() {
  local APP="$2"; local IMAGE=$(get_app_image_name "$APP"); local IMAGE_SOURCE_TYPE="$3"; local TMP_WORK_DIR="$4"
  if [[ -z "$DOKKU_SKIP_CLEANUP" ]]; then
    dokku_log_info1 "Cleaning up..."
    docker_cleanup
  else
    dokku_log_info1 "DOKKU_SKIP_CLEANUP set. Skipping dokku cleanup"
  fi
  dokku_log_info1 "Building $APP from $IMAGE_SOURCE_TYPE..."
  dokku build "$APP" "$IMAGE_SOURCE_TYPE" "$TMP_WORK_DIR"
  release_and_deploy "$APP"
}

dokku_deploy() {
  [[ -z $2 ]] && dokku_log_fail "Please specify an app to deploy"
  local APP="$2"; local IMAGE_TAG="$3"; local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG")
  verify_app_name "$APP"
  plugn trigger pre-deploy "$APP" "$IMAGE_TAG"

  is_image_herokuish_based "$IMAGE" && local DOKKU_HEROKUISH=true
  local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE"
  local oldids=$(get_app_container_ids "$APP")

  local DOKKU_DEFAULT_DOCKER_ARGS=$(: | plugn trigger docker-args-deploy "$APP" "$IMAGE_TAG")
  local DOKKU_IS_APP_PROXY_ENABLED="$(is_app_proxy_enabled "$APP")"

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

    while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do
      local id=""; local port=""; local ipaddr=""
      local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX"
      local DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX"
      local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX"

      # start the app
      local DOCKER_ARGS="$DOKKU_DEFAULT_DOCKER_ARGS"
      local DOCKER_ARGS+=" -e DYNO='$PROC_TYPE.$CONTAINER_INDEX' "
      [[ "$DOKKU_TRACE" ]] && local DOCKER_ARGS+=" -e TRACE=true "

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

      if [[ -z "$DOKKU_HEROKUISH" ]]; then
        local DOKKU_DOCKERFILE_PORTS=($(dokku config:get "$APP" DOKKU_DOCKERFILE_PORTS || true))
        local DOKKU_DOCKERFILE_PORT=$(dokku config:get "$APP" DOKKU_DOCKERFILE_PORT || true)
        local DOKKU_DOCKERFILE_START_CMD=$(dokku config:get "$APP" DOKKU_DOCKERFILE_START_CMD || true)
        local DOKKU_PROCFILE_START_CMD=$(get_cmd_from_procfile "$APP" "$PROC_TYPE")
        local START_CMD=${DOKKU_DOCKERFILE_START_CMD:-$DOKKU_PROCFILE_START_CMD}
      fi

      if [[ "$PROC_TYPE" == "web" ]]; then
        if [[ -z "${DOKKU_DOCKERFILE_PORTS[*]}" ]]; then
          local port=5000
          local DOKKU_DOCKER_PORT_ARGS+="-p $port"
        else
          local p
          for p in ${DOKKU_DOCKERFILE_PORTS[*]};do
            if [[ ! "$p" =~ .*udp.* ]]; then
              # set port to first non-udp port
              local p=${p//\/tcp}
              local port=${port:="$p"}
            fi
            local DOKKU_DOCKER_PORT_ARGS+=" -p $p "
          done
        fi
        if [[ "$DOKKU_IS_APP_PROXY_ENABLED" = "true" ]]; then
          # shellcheck disable=SC2086
          local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD)
          local ipaddr=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$id")
          # Docker < 1.9 compatibility
          if [[ -z $ipaddr ]]; then
            local ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$id")
          fi
        else
          # shellcheck disable=SC2086
          local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD)
          local port=$(docker port "$id" "$port" | sed 's/[0-9.]*://')
          local ipaddr=127.0.0.1
        fi
      else
        # shellcheck disable=SC2086
        local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD)
      fi

      # if we can't post-deploy successfully, kill new container
      kill_new() {
        local desc="wrapper function to kill newly started app container"
        docker inspect "$id" &> /dev/null && docker stop "$id" > /dev/null && docker kill "$id" > /dev/null
        trap - INT TERM EXIT
        kill -9 $$
      }

      # run checks first, then post-deploy hooks, which switches Nginx traffic
      if [[ "$(is_app_checks_enabled "$APP")" == "false" ]]; then
        dokku_log_info1 "zero downtime is disabled for app ($APP). skipping pre-flight checks"
      else
        trap kill_new INT TERM EXIT
        dokku_log_info1 "Running pre-flight checks"
        plugn trigger check-deploy  "$APP" "$id" "$PROC_TYPE" "$port" "$ipaddr"
        trap -        INT TERM EXIT
      fi

      # now using the new container
      [[ -n "$id" ]] && echo "$id" > "$DOKKU_CONTAINER_ID_FILE"
      [[ -n "$ipaddr" ]] && echo "$ipaddr" > "$DOKKU_IP_FILE"
      [[ -n "$port" ]] && echo "$port" > "$DOKKU_PORT_FILE"

      # 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 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=$(dokku config:get "$APP" DOKKU_WAIT_TO_RETIRE || true)
      local DOKKU_GLOBAL_DOKKU_WAIT_TO_RETIRE=$(dokku 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_info2 "$oldid"
    done
    (
      exec >/dev/null 2>/dev/null </dev/null
      trap '' INT HUP
      sleep "$WAIT"
      for oldid in $oldids; do
        # 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.
        docker stop "$oldid" \
        || docker kill "$oldid" \
        || plugn trigger retire-container-failed "$APP" # 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
}

dokku_build() {
  local APP="$2"; local IMAGE_SOURCE_TYPE="$3"; local TMP_WORK_DIR="$4"; local IMAGE=$(get_app_image_name "$APP")
  verify_app_name "$APP"

  local CACHE_DIR="$DOKKU_ROOT/$APP/cache"

  eval "$(config_export app "$APP")"
  pushd "$TMP_WORK_DIR" &> /dev/null

  case "$IMAGE_SOURCE_TYPE" in
    herokuish)
      local id=$(tar -c . | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$DOKKU_IMAGE" /bin/bash -c "mkdir -p /app && tar -xC /app")
      test "$(docker wait "$id")" -eq 0
      docker commit "$id" "$IMAGE" > /dev/null
      [[ -d $CACHE_DIR ]] || mkdir "$CACHE_DIR"
      plugn trigger pre-build-buildpack "$APP"

      local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$IMAGE_SOURCE_TYPE")
      [[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true "
      # shellcheck disable=SC2086
      local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -v $CACHE_DIR:/cache -e CACHE_PATH=/cache $DOCKER_ARGS $IMAGE /build)
      docker attach "$id"
      test "$(docker wait "$id")" -eq 0
      docker commit "$id" "$IMAGE" > /dev/null

      plugn trigger post-build-buildpack "$APP"
      ;;

    dockerfile)
      # extract first port from Dockerfile
      local DOCKERFILE_PORTS=$(get_dockerfile_exposed_ports Dockerfile)
      [[ -n "$DOCKERFILE_PORTS" ]] && config_set --no-restart "$APP" DOKKU_DOCKERFILE_PORTS="$DOCKERFILE_PORTS"
      plugn trigger pre-build-dockerfile "$APP"

      [[ "$DOKKU_DOCKERFILE_CACHE_BUILD" == "false" ]] && DOKKU_DOCKER_BUILD_OPTS="$DOKKU_DOCKER_BUILD_OPTS --no-cache"
      local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$IMAGE_SOURCE_TYPE")
      # shellcheck disable=SC2086
      docker build $DOCKER_ARGS $DOKKU_DOCKER_BUILD_OPTS -t $IMAGE .

      plugn trigger post-build-dockerfile "$APP"
      ;;

    *)
      dokku_log_fail "Building image source type $IMAGE_SOURCE_TYPE not supported!"
      ;;
  esac
}

dokku_release() {
  local APP="$2"; local IMAGE_SOURCE_TYPE="$3"; local IMAGE_TAG="$4"; local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG")
  verify_app_name "$APP"

  case "$IMAGE_SOURCE_TYPE" in
    herokuish)
      plugn trigger pre-release-buildpack "$APP" "$IMAGE_TAG"
      if [[ -n $(config_export global) ]]; then
        local id=$(config_export global | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/00-global-env.sh")
        test "$(docker wait "$id")" -eq 0
        docker commit "$id" "$IMAGE" > /dev/null
      fi
      if [[ -n $(config_export app "$APP") ]]; then
        local id=$(config_export app "$APP" | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/01-app-env.sh")
        test "$(docker wait "$id")" -eq 0
        docker commit "$id" "$IMAGE" > /dev/null
      fi
      plugn trigger post-release-buildpack "$APP" "$IMAGE_TAG"
      ;;

    dockerfile)
      # buildstep plugins don't necessarily make sense for dockerfiles. call the new breed!!!
      plugn trigger pre-release-dockerfile "$APP" "$IMAGE_TAG"
      plugn trigger post-release-dockerfile "$APP" "$IMAGE_TAG"
      ;;

    *)
      dokku_log_fail "Releasing image source type $IMAGE_SOURCE_TYPE not supported!"
      ;;
  esac
}

dokku_trace() {
  [[ -d $DOKKU_ROOT/.dokkurc ]] || mkdir -p "$DOKKU_ROOT/.dokkurc"
  [[ "$2" == "on" ]] || [[ "$2" == "off" ]] || {
    dokku_log_fail "Valid trace options are [on/off]"
  }

  if [[ "$2" == "on" ]]; then
    echo "Enabling dokku trace"
    echo "export DOKKU_TRACE=1" > "$DOKKU_ROOT/.dokkurc/DOKKU_TRACE"
  fi

  if [[ "$2" == "off" ]]; then
    echo "Disabling dokku trace"
    rm -f "$DOKKU_ROOT/.dokkurc/DOKKU_TRACE"
  fi
}

dokku_ls() {
  local installed_apps=$(dokku_apps)
  local dokku_app

  dokku_col_log_info1_quiet "App Name" "Container Type" "Container Id" "Status"

  for dokku_app in $installed_apps; do
    local APP=$(basename "$dokku_app")
    local DOKKU_APP_CIDS=$(get_app_container_ids "$APP")
    local DOCKER_RUNNING_CONTAINERS=$(docker ps -q --no-trunc)
    if [[ -n $DOKKU_APP_CIDS ]]; then
      local DOKKU_APP_CID
      for DOKKU_APP_CID in $DOKKU_APP_CIDS; do
        local DOKKU_APP_CONTAINER_STATUS="stopped"
        [[ $DOCKER_RUNNING_CONTAINERS =~ $DOKKU_APP_CID ]] && local DOKKU_APP_CONTAINER_STATUS="running"
        local DOKKU_APP_CONTAINER_TYPE=$(grep -l "$DOKKU_APP_CID" "$DOKKU_ROOT/$APP"/CONTAINER.* | awk -F '/' '{ print $5 }' | awk -F '.' '{ print $2 }')
        dokku_col_log_msg "$APP" "$DOKKU_APP_CONTAINER_TYPE" "${DOKKU_APP_CID:0:12}" "$DOKKU_APP_CONTAINER_STATUS"
      done
    else
      dokku_col_log_msg "$APP" "NOT_DEPLOYED" "NOT_DEPLOYED" "NOT_DEPLOYED"
    fi
  done
}

dokku_run() {
  [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on"
  local APP="$2"; local IMAGE_TAG=$(get_running_image_tag "$APP"); local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG")
  verify_app_name "$APP"

  shift 2

  if [[ -z "$DOKKU_RM_CONTAINER" ]]; then
    local DOKKU_APP_RM_CONTAINER=$(dokku config:get "$APP" DOKKU_RM_CONTAINER || true)
    local DOKKU_GLOBAL_RM_CONTAINER=$(dokku config:get --global DOKKU_RM_CONTAINER || true)
    local DOKKU_RM_CONTAINER=${DOKKU_APP_RM_CONTAINER:="$DOKKU_GLOBAL_RM_CONTAINER"}
  fi

  local DOCKER_ARGS=$(: | plugn trigger docker-args-run "$APP" "$IMAGE_TAG")
  [[ "$DOKKU_TRACE" ]] && local DOCKER_ARGS+=" -e TRACE=true "
  [[ "$DOKKU_RM_CONTAINER" ]] && local DOKKU_RUN_OPTS="--rm"
  has_tty && local DOKKU_RUN_OPTS+=" -i -t"
  is_image_herokuish_based "$IMAGE" && EXEC_CMD="/exec"
  # shellcheck disable=SC2086
  docker run $DOKKU_GLOBAL_RUN_ARGS $DOKKU_RUN_OPTS $DOCKER_ARGS $IMAGE $EXEC_CMD "$@"
}

case "$1" in
  receive)
    dokku_receive "$@"
    ;;

  deploy)
    dokku_deploy "$@"
    ;;

  build)
    dokku_build "$@"
    ;;

  release)
    dokku_release "$@"
    ;;

  trace)
    dokku_trace "$@"
    ;;

  delete)
    dokku apps:destroy "$2"
    ;;

  ls)
    dokku_ls "$@"
    ;;

  run)
    dokku_run "$@"
    ;;

  cleanup)
    docker_cleanup
    ;;

  url | urls)
    [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on"
    get_app_urls "$@"
  ;;

  report)
    dokku_log_info1 "uname: $(uname -a)"
    dokku_log_info1 "memory: "
    free -m
    dokku_log_info1 "docker version: "
    docker version
    dokku_log_info1 "docker daemon info: "
    docker -D info
    dokku_log_info1 "sigil version: $(sigil -v)"
    dokku_log_info1 "herokuish version: "
    docker run --rm -ti gliderlabs/herokuish:latest herokuish version
    dokku_log_info1 "dokku version: $(dokku version)"
    dokku_log_info1 "dokku plugins: "
    dokku plugin
    ;;

  version)
    cat "$DOKKU_ROOT/VERSION" || dokku_log_fail "Unable to determine dokku's version"
    ;;

  help)
    cat<<EOF
    ls, Pretty listing of deployed applications and containers
    run <app> <cmd>, Run a command in the environment of an application
    trace [on/off], Enable dokku tracing
    url <app>, Show the first URL for an application (compatibility)
    urls <app>, Show all URLs for an application
    version, Print dokku's version
EOF
    ;;

  *)
    exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
    ;;

esac
