#!/usr/bin/env bash

VERSION=0.3.4
URL_PRE_MIGRATE="http://download.tumbleweed.boombatower.com"
URL_POST_MIGRATE="http://download.opensuse.org/history"
CONFIG_DIR="/etc/zypp"
VARS_DIR="$CONFIG_DIR/vars.d"
VAR_NAME="snapshotVersion"
VAR_FILE="$VARS_DIR/$VAR_NAME"
SNAPSHOT_HISTORY="$VARS_DIR/.snapshotVersion.history"
REPOS_DIR="/etc/zypp/repos.d"
# REPO_PATTERN="https?://download.opensuse.org/(?:[^u][^/]+/)?tumbleweed/[^$]+"
# Disable matching debug and source URLs for the time being.
REPO_PATTERN="https?://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 "$VAR_FILE" ] ; then
    return 0
  fi
  return 1
}

tumbleweed_init()
{
  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 "$VAR_FILE" > /dev/null
    if [ $? -ne 0 ] ; then
      echo "failed to set version to $version in $VARS_DIR/$VAR_NAME"
      exit 1
    fi
    tumbleweed_history "$version"
  else
    cat "$VAR_FILE"
  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
    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 [ ! -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 "$VAR_FILE"
}

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 "- unofficial, $URL_PRE_MIGRATE: $count_pre snapshots"
  echo "- official, $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.
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 boombatower hosting to download.opensuse.org.
unmigrate             Revert migration to official hosting.
_EOF_
}

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)
      command="$1" ; ;;
    version)
      command="installed" ; ;;
    upgrade)
      command="update" ; ;;
    switch)
      command="$1" ; args_expected=1 ;
      ;;
    -*) echo "unknown option $1" ; exit 1 ; ;;
    *)
      if [ $args_expected -eq 0 ] ; then
        return 1
      fi
      args+=("$1")
      ((args_expected--))
      ;;
  esac
  return 0
}

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

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[@]}

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