#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x

has_tty() {
  if [[ "$(/usr/bin/tty || true)" == "not a tty" ]]; then
    return 1
  else
    return 0
  fi
}

dokku_apps() {
  local INSTALLED_APPS=$(find $DOKKU_ROOT -maxdepth 1 -mindepth 1 -type d ! -name 'tls' ! -name '.*' -printf "%f\n") || (dokku_log_fail "You haven't deployed any applications yet")
  [[ $INSTALLED_APPS ]] && echo $INSTALLED_APPS
}

dokku_log_info1() {
  echo "-----> $*"
}

dokku_log_info2() {
  echo "=====> $*"
}

dokku_log_info1_quiet() {
  if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
    echo "-----> $*"
  else
    return 0
  fi
}

dokku_log_info2_quiet() {
  if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
    echo "=====> $*"
  else
    return 0
  fi
}

dokku_col_log_info1() {
  printf "%-6s %-18s %-25s %-25s %-25s\n" "----->" "$@"
}

dokku_col_log_info1_quiet() {
  if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
    printf "%-6s %-18s %-25s %-25s %-25s\n" "----->" "$@"
  else
    return 0
  fi
}

dokku_col_log_info2() {
  printf "%-6s %-18s %-25s %-25s %-25s\n" "=====>" "$@"
}

dokku_col_log_info2_quiet() {
  if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
    printf "%-6s %-18s %-25s %-25s %-25s\n" "=====>" "$@"
  else
    return 0
  fi
}

dokku_col_log_msg() {
  printf "%-25s %-25s %-25s %-25s\n" "$@"
}

dokku_col_log_msg_quiet() {
  if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
    printf "%-25s %-25s %-25s %-25s\n" "$@"
  else
    return 0
  fi
}

dokku_log_verbose_quiet() {
  if [[ -z "$DOKKU_QUIET_OUTPUT" ]]; then
    echo "       $*"
  else
    return 0
  fi
}

dokku_log_verbose() {
  echo "       $*"
}

dokku_log_warn() {
  echo " !     $*"
}

dokku_log_fail() {
  echo "$@" 1>&2
  exit 1
}

dokku_log_event() {
  logger -t dokku -i -- "$@"
}

dokku_log_plugn_trigger_call() {
  local l_hook

  l_hook="$1" ; shift
  dokku_log_event "INVOKED: ${l_hook}( $* ) NAME=$NAME FINGERPRINT=$FINGERPRINT"
}

dokku_container_log_verbose_quiet() {
  local CID=$1;
  shift

  OIFS=$IFS
  IFS=$'\n'
  for line in $(docker logs $CID 2>&1); do
    dokku_log_verbose_quiet "$line"
  done
  IFS=$OIFS
}

verify_app_name() {
  local APP="$1"
  [[ ! -n "$APP" ]] && dokku_log_fail "(verify_app_name) APP must not be null"
  [[ ! -d "$DOKKU_ROOT/$APP" ]] && dokku_log_fail "App $APP does not exist"

  return 0
}

verify_image() {
  local IMAGE="$1"
  if (docker inspect "$IMAGE" &>/dev/null); then
    return 0
  else
    return 1
  fi
}

get_app_image_repo() {
  # central definition of our image repo pattern
  local APP="$1"; local IMAGE_REPO="dokku/$APP"
  [[ -z "$APP" ]] && dokku_log_fail "(get_app_image_repo) APP must not be null"
  echo $IMAGE_REPO
}

get_app_image_name() {
  # return image identifier for a given app, tag tuple. validate if tag is presented
  local APP="$1"; local IMAGE_TAG="$2"; local IMAGE_REPO=$(get_app_image_repo $APP)
  [[ -z "$APP" ]] && dokku_log_fail "(get_app_image_name) APP must not be null"

  if [[ -n "$IMAGE_TAG" ]]; then
    IMAGE="$IMAGE_REPO:$IMAGE_TAG"
    verify_image "$IMAGE" || dokku_log_fail "app image ($IMAGE) not found"
  else
    IMAGE="$IMAGE_REPO:latest"
  fi
  echo $IMAGE
}

get_running_image_tag() {
  # retrieve current image tag for a given app. returns empty string if no deployed containers are found
  local APP="$1"
  [[ ! -n "$APP" ]] && dokku_log_fail "(get_running_image_tag) APP must not be null"
  verify_app_name "$APP"

  CIDS=( $(get_app_container_ids $APP) )
  RUNNING_IMAGE_TAG=$(docker ps -a --no-trunc | egrep ${CIDS[0]} 2>/dev/null | awk '{ print $2 }' | awk -F: '{ print $2 }' || echo '')
  echo $RUNNING_IMAGE_TAG
}

is_image_herokuish_based() {
  # circleci can't support --rm as they run lxc in lxc
  [[ ! -f "/home/ubuntu/.circlerc" ]] && local DOCKER_ARGS="--rm"
  docker run --entrypoint="/bin/sh" $DOCKER_ARGS "$@" -c "test -f /exec"
}

is_number() {
  local NUMBER=$1; local NUM_RE='^[0-9]+$'
  if [[ $NUMBER =~ $NUM_RE ]]; then
    return 0
  else
    return 1
  fi
}

parse_args() {
  for arg in "$@"; do
    case "$arg" in
      --quiet)
        export DOKKU_QUIET_OUTPUT=1
        ;;
      --trace)
        export DOKKU_TRACE=1
        ;;
      --rm-container|--rm)
        export DOKKU_RM_CONTAINER=1
        ;;
      --force)
        export DOKKU_APPS_FORCE_DELETE=1
        ;;
    esac
  done
  return 0
}

copy_from_image() {
  local IMAGE="$1"; local SRC_FILE="$2"; local DST_DIR="$3"
  verify_app_name $APP

  if verify_image "$IMAGE"; then
    CID=$(docker run -d $IMAGE bash)
    docker cp "$CID:$SRC_FILE" "$DST_DIR"
    docker rm -f "$CID" &> /dev/null
  else
    return 1
  fi
}

get_app_container_ids() {
  local APP="$1"; local CONTAINER_TYPE="$2"
  verify_app_name $APP
  [[ -f $DOKKU_ROOT/$APP/CONTAINER ]] && DOKKU_CIDS+=$(< $DOKKU_ROOT/$APP/CONTAINER)

  if [[ -n "$CONTAINER_TYPE" ]]; then
    CONTAINER_PATTERN="$DOKKU_ROOT/$APP/CONTAINER.$CONTAINER_TYPE.*"
    if [[ $CONTAINER_TYPE == *.* ]]; then
      CONTAINER_PATTERN="$DOKKU_ROOT/$APP/CONTAINER.$CONTAINER_TYPE"
      [[ ! -f $CONTAINER_PATTERN ]] && echo "" && return 0
    fi
  else
    CONTAINER_PATTERN="$DOKKU_ROOT/$APP/CONTAINER.*"
  fi

  shopt -s nullglob
  for DOKKU_CID_FILE in $CONTAINER_PATTERN; do
    DOKKU_CIDS+=" "
    DOKKU_CIDS+=$(< $DOKKU_CID_FILE)
    DOKKU_CIDS+=" "
  done
  shopt -u nullglob
  echo $DOKKU_CIDS
}

get_dockerfile_exposed_port() {
  local DOCKERFILE_PORT=$(grep "^EXPOSE \+[[:digit:]]\+\(\/tcp\)\? *$" $1 | head -1 | sed 's/EXPOSE \+\([[:digit:]]\+\)\(\/tcp\)\?.*/\1/' || true)
  echo "$DOCKERFILE_PORT"
}

get_app_running_container_ids() {
  local APP=$1
  verify_app_name $APP

  ! (is_deployed $APP) && dokku_log_fail "App $APP has not been deployed"
  local CIDS=$(get_app_container_ids $APP)

  for CID in $CIDS; do
    local APP_CONTAINER_STATUS=$(docker inspect -f '{{.State.Running}}' "$CID" || true)
    [[ "$APP_CONTAINER_STATUS" == "true" ]] && local APP_RUNNING_CONTAINER_IDS+="$CID "
  done

  echo "$APP_RUNNING_CONTAINER_IDS"
}

is_deployed() {
  local APP="$1"
  if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] || [[ $(ls $DOKKU_ROOT/$APP/CONTAINER.* &> /dev/null; echo $?) -eq 0 ]]; then
    return 0
  else
    return 1
  fi
}

is_container_running () {
  local CID=$1
  local CONTAINER_STATUS=$(docker inspect -f '{{.State.Running}}' "$CID" || true)

  if [[ "$CONTAINER_STATUS" == "true" ]]; then
    return 0
  else
    return 1
  fi
}

is_container_status () {
  local CID=$1
  local TEMPLATE="{{.State.$2}}"
  local CONTAINER_STATUS=$(docker inspect -f "$TEMPLATE" "$CID" || true)

  if [[ "$CONTAINER_STATUS" == "true" ]]; then
    return 0
  else
    return 1
  fi
}

is_app_running() {
  local APP="$1"
  verify_app_name $APP

  local APP_RUNNING_CONTAINER_IDS=$(get_app_running_container_ids $APP)

  if [[ -n "$APP_RUNNING_CONTAINER_IDS" ]]; then
    return 0
  else
    return 1
  fi
}

release_and_deploy() {
  local APP="$1"; local IMAGE_TAG="$2"; local IMAGE=$(get_app_image_name $APP $IMAGE_TAG)
  verify_app_name "$APP"

  if verify_image "$IMAGE"; then
    if is_image_herokuish_based "$IMAGE"; then
      IMAGE_SOURCE_TYPE="herokuish"
    else
      IMAGE_SOURCE_TYPE="dockerfile"
    fi

    DOKKU_APP_SKIP_DEPLOY="$(dokku config:get $APP DOKKU_SKIP_DEPLOY || true)"
    DOKKU_GLOBAL_SKIP_DEPLOY="$(dokku config:get --global DOKKU_SKIP_DEPLOY || true)"

    DOKKU_SKIP_DEPLOY=${DOKKU_APP_SKIP_DEPLOY:="$DOKKU_GLOBAL_SKIP_DEPLOY"}

    dokku_log_info1 "Releasing $APP ($IMAGE)..."
    dokku release "$APP" "$IMAGE_SOURCE_TYPE" "$IMAGE_TAG"

    if [[ "$DOKKU_SKIP_DEPLOY" != "true" ]]; then
      dokku_log_info1 "Deploying $APP ($IMAGE)..."
      dokku deploy "$APP" "$IMAGE_TAG"
      dokku_log_info2 "Application deployed:"
      dokku urls $APP | sed "s/^/       /"
    else
      dokku_log_info1 "Skipping deployment"
    fi

    echo
  fi
}

docker_cleanup() {
  # delete all non-running containers
  # shellcheck disable=SC2046
  docker rm $(docker ps -a -f 'status=exited' -q) &> /dev/null || true

  # delete all dead containers
  # shellcheck disable=SC2046
  docker rm $(docker ps -a -f 'status=dead' -q) &> /dev/null || true

  # delete unused images
  # shellcheck disable=SC2046
  docker rmi $(docker images -f 'dangling=true' -q) &> /dev/null &
}

get_available_port() {
  while true; do
    local port=$(shuf -i 1025-65535 -n 1)
    if ! nc -z 0.0.0.0 $port; then
      echo $port
      return 0
    else
      continue
    fi
  done
}

dokku_auth() {
  export SSH_USER=${SSH_USER:=$USER}
  export SSH_NAME=${NAME:="default"}
  if ! plugn trigger user-auth $SSH_USER $SSH_NAME "$@" ; then
    return 1
  fi
  return 0
}

_ipv4_regex() {
  echo "([0-9]{1,3}[\.]){3}[0-9]{1,3}"
}

_ipv6_regex() {
  local RE_IPV4="$(_ipv4_regex)"
  local RE_IPV6="([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|"              # TEST: 1:2:3:4:5:6:7:8
  RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,7}:|"                         # TEST: 1::                              1:2:3:4:5:6:7::
  RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|"         # TEST: 1::8             1:2:3:4:5:6::8  1:2:3:4:5:6::8
  RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|"  # TEST: 1::7:8           1:2:3:4:5::7:8  1:2:3:4:5::8
  RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|"  # TEST: 1::6:7:8         1:2:3:4::6:7:8  1:2:3:4::8
  RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|"  # TEST: 1::5:6:7:8       1:2:3::5:6:7:8  1:2:3::8
  RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|"  # TEST: 1::4:5:6:7:8     1:2::4:5:6:7:8  1:2::8
  RE_IPV6="${RE_IPV6}[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|"       # TEST: 1::3:4:5:6:7:8   1::3:4:5:6:7:8  1::8
  RE_IPV6="${RE_IPV6}:((:[0-9a-fA-F]{1,4}){1,7}|:)|"                     # TEST: ::2:3:4:5:6:7:8  ::2:3:4:5:6:7:8 ::8       ::
  RE_IPV6="${RE_IPV6}fe08:(:[0-9a-fA-F]{1,4}){2,2}%[0-9a-zA-Z]{1,}|"     # TEST: fe08::7:8%eth0      fe08::7:8%1                                      (link-local IPv6 addresses with zone index)
  RE_IPV6="${RE_IPV6}::(ffff(:0{1,4}){0,1}:){0,1}${RE_IPV4}|"            # TEST: ::255.255.255.255   ::ffff:255.255.255.255  ::ffff:0:255.255.255.255 (IPv4-mapped IPv6 addresses and IPv4-translated addresses)
  RE_IPV6="${RE_IPV6}([0-9a-fA-F]{1,4}:){1,4}:${RE_IPV4}"                # TEST: 2001:db8:3:4::192.0.2.33  64:ff9b::192.0.2.33
  echo "$RE_IPV6"
}

get_ipv4_regex() {
  local RE_IPV4="$(_ipv4_regex)"
  # Ensure the ip address continues to the end of the line
  # Fixes using a wildcard dns service such as xip.io which allows for *.<ip address>.xip.io
  echo "${RE_IPV4}\$"
}

get_ipv6_regex() {
  local RE_IPV6="$(_ipv4_regex)"
  # Ensure the ip address continues to the end of the line
  # Fixes using a wildcard dns service such as xip.io which allows for *.<ip address>.xip.io
  echo "${RE_IPV6}\$"
}
