#!/usr/bin/env bash
set -eo pipefail
[[ $TRACE ]] && set -x && export TRACE=$TRACE

version() {
  local VERSION="dev-master"
  if [[ -f /var/lib/aptfile/VERSION ]]; then
    VERSION=$(cat /var/lib/aptfile/VERSION)
  fi
  echo "aptfile $VERSION"
}

usage() {
  version
  echo "Usage: aptfile <aptfile>"
}

help() {
  usage
  echo
  echo "  <aptfile> is the path to a bash file with apt instructions."
  echo
  echo "  -h, --help     Display this help message"
  echo "  -v, --version  Display the version number"
  echo
  echo "  For more information, see https://github.com/seatgeek/bash-aptfile"
  echo
}

resolve_link() {
  $(type -p greadlink readlink | head -1) "$1"
}

abs_dirname() {
  local cwd
  local path="$1"
  cwd="$(pwd)"

  while [ -n "$path" ]; do
    cd "${path%/*}"
    local name="${path##*/}"
    path="$(resolve_link "$name" || true)"
  done

  pwd
  cd "$cwd"
}

expand_path() {
  {
    cd "$(dirname "$1")" 2>/dev/null
    local dirname="$PWD"
    cd "$OLDPWD"
    echo "$dirname/$(basename "$1")"
  } || echo "$1"
}

options=()
arguments=()
for arg in "$@"; do
  if [ "${arg:0:1}" = "-" ]; then
    if [ "${arg:1:1}" = "-" ]; then
      options[${#options[*]}]="${arg:2}"
    else
      index=1
      while option="${arg:$index:1}"; do
        [ -n "$option" ] || break
        options[${#options[*]}]="$option"
        let index+=1
      done
    fi
  else
    arguments[${#arguments[*]}]="$arg"
  fi
done

for option in "${options[@]}"; do
  case "$option" in
    "h" | "help")
      help
      exit 0
      ;;
    "v" | "version")
      version
      exit 0
      ;;
    *)
      usage >&2
      exit 1
      ;;
  esac
done

if [ "${#arguments[@]}" -eq 0 ]; then
  APTFILE_PATH=$(expand_path "aptfile")
  if [[ ! -f $APTFILE_PATH ]]; then
    usage >&2
    exit 1
  fi
  set -- "aptfile" "$@"
fi

export APTFILE_COLOR_OFF="\033[0m"  # unsets color to term fg color
export APTFILE_RED="\033[0;31m"     # red
export APTFILE_GREEN="\033[0;32m"   # green
export APTFILE_YELLOW="\033[0;33m"  # yellow
export APTFILE_MAGENTA="\033[0;35m" # magenta
export APTFILE_CYAN="\033[0;36m"    # cyan

logfile=$(basename "$0")
TMP_APTFILE_LOGFILE=$(mktemp "/tmp/${logfile}.XXXXXX") || {
  log_fail "${APTFILE_RED}WARNING: Cannot create temp file using mktemp in /tmp dir ${APTFILE_COLOR_OFF}\n"
}
export TMP_APTFILE_LOGFILE="$TMP_APTFILE_LOGFILE"
trap 'rm -rf "$TMP_APTFILE_LOGFILE" > /dev/null' INT TERM EXIT

log_fail() {
  [[ $TRACE ]] && set -x
  echo -e "${APTFILE_RED}$*${APTFILE_COLOR_OFF}" 1>&2
  [[ -f "$TMP_APTFILE_LOGFILE" ]] && echo -e "verbose logs:\n" 1>&2 && sed -e 's/^/     /' "$TMP_APTFILE_LOGFILE"
  exit 1
}

log_info() {
  [[ $TRACE ]] && set -x
  echo -e "$@"
}

update() {
  [[ $TRACE ]] && set -x
  log_info "Running update"
  apt-get update >"$TMP_APTFILE_LOGFILE" 2>&1
  [[ $? -eq 0 ]] || log_fail "Failed to run update"
}

package() {
  [[ $TRACE ]] && set -x
  [[ -z $1 ]] && log_fail "Please specify a package to install"
  local pkg="$1"
  dpkg --force-confnew -s "$pkg" >"$TMP_APTFILE_LOGFILE" 2>&1 && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} package $pkg" && return 0
  apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" -qq -y install "$pkg"
  [[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} package $pkg"
  log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} package $pkg"
}

package_from_url() {
  [[ $TRACE ]] && set -x
  [[ -z $2 ]] && log_fail "Please specify a name and a download url to install the package from"
  local name=$1
  local url=$2
  if type curl >/dev/null 2>&1; then
    local dl_cmd="curl"
    local dl_options="-so"
  elif type wget >/dev/null 2>&1; then
    local dl_cmd="wget"
    local dl_options="-qO"
  else
    log_fail "Neither curl nor wget found. Unable to download $url"
  fi
  dpkg --force-confnew -s "$name" >"$TMP_APTFILE_LOGFILE" 2>&1 && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} package $name" && return 0
  tempdir=$(mktemp -d)
  $dl_cmd $dl_options $tempdir/${name}.deb $url \
    && apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" -qq -y install "$tempdir/${name}.deb"
  if [[ $? -ne 0 ]]; then
    rm -r $tempdir
    log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} package $name"
  fi
  rm -r $tempdir
  log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} package $name"
}

packagelist() {
  [[ $TRACE ]] && set -x
  [[ -z $1 ]] && log_fail "Please specify at least one package to install"
  local input_packages=$@
  local install_packages=()
  for pkg in $input_packages; do
    dpkg --force-confnew -s "$pkg" >"$TMP_APTFILE_LOGFILE" 2>&1 && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} package $pkg" && continue
    install_packages+=($pkg)
  done
  if [[ ${#install_packages[@]} -gt 0 ]]; then
    apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confnew" -qq -y install ${install_packages[@]}
    [[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} packages ${install_packages[@]}"
    log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} packages ${install_packages[@]}"
  fi
}

ppa() {
  [[ $TRACE ]] && set -x
  [[ -z $1 ]] && log_fail "Please specify a repository to setup"
  local repo="$1"
  if [[ -d /etc/apt/sources.list.d/ ]]; then
    grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep -q "$repo" && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} ppa $repo" && return 0
  fi
  repository "ppa:$1"
}

repository() {
  [[ $TRACE ]] && set -x
  [[ -z $1 ]] && log_fail "Please specify a repository to setup"
  local repo="$1"
  if [[ -d /etc/apt/sources.list.d/ ]]; then
    grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep -Fq "$repo" && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} repository $repo" && return 0
  fi
  add-apt-repository -y "$repo" >"$TMP_APTFILE_LOGFILE" 2>&1
  [[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} repository $pkg"
  update
  log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} repository $repo"
}

repository_file() {
  [[ $TRACE ]] && set -x
  [[ -z $2 ]] && log_fail "Please specify a filename and sourceline to setup"
  local repofile="$1"
  local repo="$2"
  # sourceline is not a complete repo configuration, needs modifying
  # i.e. not sourceline="deb http://domain.invalid/debian buster main extra"
  if [[ "$repo" != "deb "* ]]; then
    releasename=$(lsb_release -sc)
    if [[ "$repo" == *" "* ]]; then
      # Components given in sourceline, adding suite
      # i.e. sourceline="http://domain.invalid/debian main"
      repo="deb ${repo/ / $releasename }"
    else
      # only URL given, adding suite and component
      # i.e. sourceline="http://domain.invalid/debian"
      repo="deb ${repo} $releasename main"
    fi
  fi

  if [[ "$repofile" != *.list ]]; then
    # Adding extension to enable parsing file
    repofile=${repofile}.list
  fi
  # Adding path
  repofile="/etc/apt/sources.list.d/$repofile"

  [[ -d "/etc/apt/sources.list.d" ]] || mkdir -p /etc/apt/sources.list.d

  grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep -Fq "$repo" && log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} repository $repo" && return 0

  echo "Writing '$repo' to file '$repofile'" >"$TMP_APTFILE_LOGFILE"
  echo "$repo" >"$repofile" 2>>"$TMP_APTFILE_LOGFILE"
  [[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} repository $pkg"
  update
  log_info "${APTFILE_GREEN}[NEW]${APTFILE_COLOR_OFF} repository $repo"
}

debconf_selection() {
  [[ $TRACE ]] && set -x
  [[ -z $1 ]] && log_fail "Please specify a debconf line"
  echo "$1" | debconf-set-selections
  [[ $? -eq 0 ]] || log_fail "${APTFILE_RED}[FAIL]${APTFILE_COLOR_OFF} debconf: $1"
  log_info "${APTFILE_CYAN}[OK]${APTFILE_COLOR_OFF} set debconf line: $1"
}

if [ -z "$TMPDIR" ]; then
  APTFILE_TMPDIR="/tmp"
else
  APTFILE_TMPDIR="${TMPDIR%/}"
fi

APTFILE_INPUT=$(expand_path "$1")
APTFILE_TMPNAME="$APTFILE_TMPDIR/aptfile.$$"
APTFILE_OUTPUT="${APTFILE_TMPNAME}.out"

aptfile_preprocess_source() {
  tail -n +2 "$1" >"$APTFILE_OUTPUT"
  trap "aptfile_cleanup_preprocessed_source" err exit
  trap "aptfile_cleanup_preprocessed_source; exit 1" int
}

aptfile_cleanup_preprocessed_source() {
  rm -f "$APTFILE_TMPNAME"
  rm -f "$APTFILE_OUTPUT"
}

aptfile_preprocess_source "$APTFILE_INPUT"

export -f update
export -f package
export -f package_from_url
export -f packagelist
export -f ppa
export -f repository
export -f repository_file
export -f debconf_selection
export -f log_fail
export -f log_info

chmod +x "$APTFILE_OUTPUT"
exec "$APTFILE_OUTPUT"
