#!/usr/bin/env bash

if ! [ -z "$DEBUG" ]; then
  export PS4='Line ${LINENO}: BASH_LINENO: ${BASH_LINENO[*]}": '
  set -x
fi


VERSION=0.3.6.0
URL_PRE_MIGRATE="http://download.opensuse.org/history"
URL_POST_MIGRATE="http://cdn.opensuse.org/history"
ZYPP_CONFIG_DIR="/etc/zypp"
ZYPP_VARS_DIR="$ZYPP_CONFIG_DIR/vars.d"
DNF_CONFIG_DIR="/etc/dnf"
DNF_VARS_DIR="$DNF_CONFIG_DIR/vars"
VAR_NAME="snapshotVersion"
ZYPP_VAR_PATH="$ZYPP_VARS_DIR/$VAR_NAME"
# resolves https://github.com/boombatower/tumbleweed-cli/issues/23
DNF_VAR_LINK="$DNF_VARS_DIR/$VAR_NAME"

SNAPSHOT_HISTORY="$VARS_DIR/.snapshotVersion.history"
REPOS_DIR="/etc/zypp/repos.d"
# REPO_PATTERN="https?://cdn.opensuse.org/(?:[^u][^/]+/)?tumbleweed/[^$]+"
# Disable matching debug and source URLs for the time being.
REPO_PATTERN="https?://(?:cdn)|(?:download).opensuse.org/tumbleweed/[^$]+"

tumbleweed_sudo()
{
  # Obtain sudo before issuing real commands to avoid half completion.
  if ! sudo cat /dev/null ; then
    echo "unable to obtain sudo"
    exit 1
  fi
}


tumbleweed_inited()
{
  if [ -f "$ZYPP_VAR_PATH" ] ; then
    return 0
  fi
  return 1
}

tumbleweed_init()
{
  if [[ -d "$DNF_VARS_DIR" && ! -e "$DNF_VAR_LINK" ]]; then
    echo Creating link:
    ln -vs "$ZYPP_VAR_PATH" "$DNF_VAR_LINK"
  fi
  if tumbleweed_inited && [ "$force" != "1" ] ; then
    echo "already initialized"
    return
  fi

  tumbleweed_sudo
  tumbleweed_history_init
  tumbleweed_variable "$(tumbleweed_installed)"
  tumbleweed_repo_init
}

tumbleweed_variable()
{
  if [ $# -eq 1 ] ; then
    local version="$1"
    echo "$version" | sudo tee "$ZYPP_VAR_PATH" > /dev/null
    if [ $? -ne 0 ] ; then
      echo "failed to set version to $version in $ZYPP_VAR_PATH"
      exit 1
    fi
    tumbleweed_history "$version"
  else
    cat "$ZYPP_VAR_PATH"
  fi
}

tumbleweed_repo_init()
{
  if [ ! -d "$REPOS_DIR/.previous" ] ; then
    sudo mkdir "$REPOS_DIR/.previous"
  fi

  local IFS=$'\n' # Handle repo files with space in name. :(
  local files=($(grep -lP "$REPO_PATTERN" "$REPOS_DIR"/*))
  local file
  for file in "${files[@]}" ; do
    echo "backup $file"
    sudo cp --backup=numbered "$file" "$REPOS_DIR/.previous/$(basename "$file")"
    sudo sed -i -r 's|(name=.*)|\1 ($snapshotVersion)|' "$file"
    sudo sed -i -r 's|(baseurl=).*tumbleweed/(.*)|\1'$URL'/$snapshotVersion/tumbleweed/\2|' "$file"
  done
}

tumbleweed_history_init()
{
  if [ ! -f "$SNAPSHOT_HISTORY" ] ; then
    # Need an actual line for sed insert to work.
    echo | sudo tee "$SNAPSHOT_HISTORY" > /dev/null
  fi
}

tumbleweed_history()
{
  if [ $# -eq 1 ] ; then
    if [ "$(head -n1 "$SNAPSHOT_HISTORY")" != "$1" ] ; then
      sudo sed -i "1i$1" "$SNAPSHOT_HISTORY"
    fi
  else
    # Exclude empty lines necessary for data structure.
    grep -v -e '^$' "$SNAPSHOT_HISTORY"
  fi
}

tumbleweed_history_pop()
{
  if [ $# -eq 1 ] ; then
    sudo sed -i "1d" "$SNAPSHOT_HISTORY"
  else
    head -n2 "$SNAPSHOT_HISTORY" | tail -n1
  fi
}

tumbleweed_status()
{
  echo "latest   : $(tumbleweed_latest)"
  echo "target   : $(tumbleweed_target)"
  echo "installed: $(tumbleweed_installed)"
}

tumbleweed_latest()
{
  curl --fail --silent -L "$URL/latest"
}

tumbleweed_target()
{
  tumbleweed_variable
}

tumbleweed_installed()
{
  cat /etc/os-release | grep -oP "VERSION_ID=\"\K(\d+)"
}

tumbleweed_list()
{
  curl --fail --silent -L "$URL/list"
}

tumbleweed_update()
{
  tumbleweed_switch
  install=1
}

tumbleweed_prompt()
{
  echo -n "$1 [y/n] (y): "
  local response
  read response
  if [ "$response" != "" ] && [ "$response" != "y" ] ; then
    echo "exiting"
    exit 1
  fi
}

tumbleweed_switch()
{
  if [ $# -eq 1 ] ; then
    local version="$1"
    if ! tumbleweed_list | grep -Fx "$version" > /dev/null ; then
      echo "invalid version $version, not in available list"
      return 1
    fi
  else
    local version="$(tumbleweed_latest)"
    if [ -z "$version" ] ; then
      echo "Unable to determine latest version (likely due to network connection issue)"
      exit 1
    fi
    echo "choosing latest version"
  fi

  if [ "$version" == "$(tumbleweed_target)" ] && [ "$force" != "1" ] ; then
    echo "already on $version"
    exit
  fi

  tumbleweed_prompt "switching from $(tumbleweed_target) to $version?"
  tumbleweed_sudo
  tumbleweed_variable "$version"
}

tumbleweed_revert()
{
  local version="$(tumbleweed_history_pop)"
  if [ "$version" == "" ] ; then
    echo "no previous version in history"
    exit 1
  fi

  tumbleweed_prompt "switching from $(tumbleweed_target) to $version?"
  tumbleweed_sudo
  tumbleweed_history_pop "indeed"
  tumbleweed_variable "$version"
}

tumbleweed_uninit()
{
  if [[ -L "$DNF_VAR_LINK" && "$(readlink "$DNF_VAR_LINK")" == "$ZYPP_VAR_PATH" ]]; then
    echo Removing link: "$DNF_VAR_LINK" '->' "$ZYPP_VAR_PATH"
    rm -v "$DNF_VAR_LINK"
  fi
  if [ ! -d "$REPOS_DIR/.previous" ] ; then
    echo "nothing to revert"
    exit 1
  fi

  # Prompt for confirmation.
  local repos=($(ls "$REPOS_DIR/.previous"))
  tumbleweed_prompt "revert ${#repos[@]} repos?"

  tumbleweed_sudo
  sudo mv "$REPOS_DIR/.previous"/* $REPOS_DIR/
  sudo rm -r "$REPOS_DIR/.previous"
  sudo rm "$ZYPP_VAR_PATH"
}

tumbleweed_install()
{
  sudo zypper ref
  sudo zypper dup
}

tumbleweed_migrate_check()
{
  if grep -l "$URL_PRE_MIGRATE" "$REPOS_DIR"/* > /dev/null ; then
    MIGRATED=0
    URL="$URL_PRE_MIGRATE"
  else
    MIGRATED=1
    URL="$URL_POST_MIGRATE"
  fi

  # # Issue notice of official hosting and migration if target is available.
  # if [ $MIGRATED -eq 0 ] && \
  #   curl --fail --silent -L "$URL_POST_MIGRATE/list" | grep -Fx "$(tumbleweed_target)" > /dev/null ; then
  #   echo "NOTICE: Official snapshot hosting is now available. The unofficial hosting is" >&2
  #   echo "        deprecated and will be discontinued at a future date. Consider" >&2
  #   echo "        migrating to the official hosting via \`tumbleweed migrate\`." >&2
  # fi
}

tumbleweed_migrate()
{
  if [ $MIGRATED -eq 1 ] ; then
    echo "already migrated, exiting"
    exit 1
  fi

  echo "A backup of repos to be migrated will be kept in $REPOS_DIR/.migrated"
  echo "which may be restored by invoking the unmigrate command."
  echo

  # echo "Be aware that the official hosting snapshot count differs from the"
  # echo "unofficial hosting that you are currently using."
  # echo

  local count_pre=$(curl --fail --silent "$URL_PRE_MIGRATE/list" | wc -l)
  local count_post=$(curl --fail --silent -L "$URL_POST_MIGRATE/list" | wc -l)
  echo "- non-CDN, $URL_PRE_MIGRATE: $count_pre snapshots"
  echo "- CDN, $URL_POST_MIGRATE: $count_post snapshots"
  echo

  # echo "Eventually the unofficial hosting will be phased out."
  echo

  tumbleweed_prompt "Do you wish to continue?"
  tumbleweed_sudo
  tumbleweed_repo_migrate
}

tumbleweed_repo_migrate()
{
  if [ ! -d "$REPOS_DIR/.migrated" ] ; then
    sudo mkdir "$REPOS_DIR/.migrated"
  fi

  local IFS=$'\n' # Handle repo files with space in name. :(
  local files=($(grep -l "\$snapshotVersion" "$REPOS_DIR"/*))
  for file in "${files[@]}" ; do
    sudo cp "$file" "$REPOS_DIR/.migrated"
    sudo sed -i -r 's|(baseurl=).*(\$snapshotVersion)/(.*)|\1'$URL_POST_MIGRATE'/\2/tumbleweed/\3|' "$file"
    echo "migrated $file"
  done
}

tumbleweed_unmigrate()
{
  if [ ! -d "$REPOS_DIR/.migrated" ] ; then
    echo "nothing to unmigrate"
    exit 1
  fi

  # Prompt for confirmation.
  local repos=($(ls "$REPOS_DIR/.migrated"))
  tumbleweed_prompt "unmigrate ${#repos[@]} repos?"

  tumbleweed_sudo
  sudo mv "$REPOS_DIR/.migrated"/* $REPOS_DIR/
  sudo rm -r "$REPOS_DIR/.migrated"
}

tumbleweed_usage()
{
  cat <<_EOF_
Usage: $0 [options] command [arguments]

Options:
    --version         Print version string and exit
    --force           Force on operation to occur regardless of checks.
    --install         Initiate install after command.
-h, --help            Display this message and exit

Commands:
init                  Initialize repos to point to snapshot repos. If DNF is installed, also enables DNF support.
status                Show status information (latest, target, and installed).
latest                Show latest snapshot available.
installed|version     Show current installed snapshot.
target                Show the target of the repositories.
list                  List available snapshots.
history               List history of snapshots targetted.
update|upgrade        Switch to and install the latest available snapshot.
switch %version       Switch to a new snapshot (none for latest).
revert                Revert to the previous snapshot or repo state.
uninit                Revert back to a snapshotless repository setup.
migrate               Migrate from download.opensuse.org to new cdn.opensuse.org.
unmigrate             Revert migration to download.opensuse.org.
_EOF_
}

command=""

try_update_command(){
  if [ "$command" != "" ]; then
    echo "ERROR: Got second command '$command' when processing '$1'!"
    echo "Run multiple commands separately, e.g."
    echo -e "\t$0 $1"
    echo -e "\t$0 $command"
    exit 1
  fi
  command="$1"
}

tumbleweed_handle()
{
  case "$1" in
    --version) echo "$VERSION" ; exit 0 ; ;;
    --force) force=1 ; ;;
    --install) install=1 ; ;;
    -h|--help) command="usage" ; ;;
    history|init|installed|latest|list|revert|status|target|uninit|update|migrate|unmigrate)
      try_update_command "$1" ; ;;
    version)
      try_update_command "installed" ; ;;
    upgrade)
      try_update_command "update" ; ;;
    switch)
      try_update_command "$1" ; args_expected=1 ;
      ;;
    -*) echo "unknown option $1" ; exit 1 ; ;;
    *)
      if [ $args_expected -eq 0 ] ; then
        if [ -z "$1" ]; then
          return 1
        fi
        echo "Unexpected argument $1"
        exit 1
      fi
      args+=("$1")
      ((args_expected--))
      ;;
  esac
  return 0
}


args_expected=0
args=()
while tumbleweed_handle $1 ; do
  shift
done

if [ -z "$command" ]; then
  command="usage"
fi

if [ "$command" != "usage" ] && [ "$command" != "init" ] && ! tumbleweed_inited ; then
  echo "repositories have not been initialized for snapshots"
  echo "  Try $0 init"
  exit 1
fi

tumbleweed_migrate_check
tumbleweed_$command "${args[@]}"
result=$?

if [ $result != "0" ]; then
  echo "Command failed with result $result"
  exit 1
fi

if [[ "$install" == "1" ]] ; then
  tumbleweed_install
fi
