#!/usr/bin/env bash

# Undo a BagIt archive, as defined in RFC 8493.
#
# Copyright (c) 2014-2026 by Reto Kromer <https://reto.ch/>
#
# This Bash script is released under a 3-Clause BSD License and is provided
# "as is" without warranty or support of any kind.


# initialise constants
VERSION='2026-01-25'
SCRIPT_NAME="$(basename "$0")"
CONFIG_FILE="${HOME}/.config/AVpres/Bash_AVpres/${SCRIPT_NAME}.txt"
RED='\033[1;31m'
BLUE='\033[1;34m'
NC='\033[0m'

# load configuration file if any and initialise default values
[[ -f "${CONFIG_FILE}" ]] && . "${CONFIG_FILE}"
confirmation="${confirmation:-yes}"
force="${force:-no}"

# initialise variables
unset input_folder
unset algorithm


# get date-and-time stamp
date_time() {
  TZ='UTC' date +'[%F %T %Z]'
}


# print an error message and exit with status 1
abort() {
  echo -e "${RED}${1:-An unknown error occurred.\a}${NC}"
  echo "$(date_time) ${1:-An unknown error occurred.}" >> "${LOG_FILE}"
  echo "$(date_time) END" >> "${LOG_FILE}"
  exit 1
}


# print a minimal help message and exit with status 1
print_prompt() {
  echo "$(date_time) print prompt" >> "${LOG_FILE}"
  cat << EOF
Help:
  ${SCRIPT_NAME} -h
EOF
  echo "$(date_time) END" >> "${LOG_FILE}"
  exit 1
}


# print the help message and exit with status 0
print_help() {
  local tmp
  if [[ -f "${CONFIG_FILE}" ]]; then
    tmp="local configuration file found and loaded"
  else
    tmp="no local configuration file found on this computer"
  fi

  echo "$(date_time) print help" >> "${LOG_FILE}"
  cat << EOF

Usage:
  ${SCRIPT_NAME} (-i|-b) <input_folder>
  ${SCRIPT_NAME} -h | -x
Options:
  -i  BagIt archive folder
  -b  is an alias of -i
  -h  this help
  -x  advanced options with their default arguments
      (${tmp})
See also:
  man ${SCRIPT_NAME}
  https://avpres.net/Bash_AVpres/
About:
  Abstract: Undo a BagIt archive folder, as defined in RFC 8493
  Version:  ${VERSION}

EOF
  echo "$(date_time) END" >> "${LOG_FILE}"
  exit 0
}


# print advanced options with their default arguments and exit with status 0
print_options() {
  echo "$(date_time) print options" >> "${LOG_FILE}"
  if [[ -f "${CONFIG_FILE}" ]]; then
    cat << EOF

Local configuration file
  '${CONFIG_FILE}'
found and loaded.
EOF
  else
    cat << EOF

No local configuration file for '${SCRIPT_NAME}' found on this computer.
EOF
  fi
  cat << EOF

Advanced options with their default arguments:
  --confirmation='${confirmation}'
  --force='${force}'

EOF
  echo "$(date_time) END" >> "${LOG_FILE}"
  exit 0
}


# verify that the input is a BagIt archive
verify_input() {
  local in_folder="${1}"

  in_folder="${in_folder%%/}"
  if [[ ! -d "${in_folder}" ]]; then
    abort "Error: '${in_folder}' is not a directory."
  elif [[ ! -d "${in_folder}/data" ]]; then
    abort "Error: The 'data/' folder is missing."
  fi

  # verify that the BagIt archive is 'complete'
  if [[ "${force}" != 'yes' ]]; then
    if [[ ! -f "${in_folder}/bagit.txt" ]]; then
      abort "Error: The 'bagit.txt' file is missing."
    elif ! compgen -G "${in_folder}/manifest-*.txt" > /dev/null; then
      abort "Error: The 'manifest-<algorithm>.txt' file is missing."
    fi
  fi

  return 0
}


# undo BagIt archive
undo_bagit_archive() {
  local in_folder="${1}"
  local kbd

  in_folder="${in_folder%%/}"
  if [[ "${confirmation}" != 'no' ]]; then
    echo -en "${BLUE}Undo the BagIt archive? (y|N) ${NC}"
    read -n 1 kbd
    echo
    if [[ "${kbd}" != [Yy] ]]; then
      abort "Aborted by user."
    fi
  fi

  echo -e "${BLUE}Deleting files...${NC}"
  echo "$(date_time) input='${in_folder}'" >> "${LOG_FILE}"
  echo "$(date_time) deleting files..." >> "${LOG_FILE}"
  if [[ -f "${in_folder}/bagit.txt" ]]; then
    rm -v "${in_folder}/bagit.txt" | tee -a "${LOG_FILE}"
  fi
  if [[ -f "${in_folder}/bag-info.txt" ]]; then
    rm -v "${in_folder}/bag-info.txt" | tee -a "${LOG_FILE}"
  fi
  if [[ -f "${in_folder}/fetch.txt" ]]; then
    rm -v "${in_folder}/fetch.txt" | tee -a "${LOG_FILE}"
  fi
  for algorithm in {md5,sha1,sha256,sha512,crc32,xxh32,xxh64,xxh128}; do
    if [[ -f "${in_folder}/manifest-${algorithm}.txt" ]]; then
      rm -v "${in_folder}/manifest-${algorithm}.txt" | tee -a "${LOG_FILE}"
    fi
    if [[ -f "${in_folder}/tagmanifest-${algorithm}.txt" ]]; then
      rm -v "${in_folder}/tagmanifest-${algorithm}.txt" | tee -a "${LOG_FILE}"
    fi
  done
  if [[ -f "${in_folder}/make_bagit.txt" ]]; then
    rm -v "${in_folder}/make_bagit.txt" | tee -a "${LOG_FILE}"
  fi
  if [[ -f "${in_folder}/verify_bagit.txt" ]]; then
    rm -v "${in_folder}/verify_bagit.txt" | tee -a "${LOG_FILE}"
  fi
  if [[ -f "${in_folder}/undo_bagit.txt" ]]; then
    rm -v "${in_folder}/undo_bagit.txt" | tee -a "${LOG_FILE}"
  fi

  echo -e "${BLUE}Moving files...${NC}"
  echo "$(date_time) moving files..." >> "${LOG_FILE}"
  find "${in_folder}/data" -mindepth 1 -maxdepth 1 -exec mv -vn {} "${in_folder}" \; | tee -a "${LOG_FILE}"
  echo -e "${BLUE}Deleting empty 'data/' folder.${NC}"
  rmdir "${in_folder}/data"

  return 0
}


# start log file
[[ -d '/tmp/AVpres' ]] || mkdir -p '/tmp/AVpres'
LOG_FILE="$(mktemp -q "/tmp/AVpres/${SCRIPT_NAME}.XXXXXXXXXX")"
echo "$(date_time) ${SCRIPT_NAME} ${VERSION}" > "${LOG_FILE}"
echo "$(date_time) $0 $*" >> "${LOG_FILE}"
echo "$(date_time) START" >> "${LOG_FILE}"

# check that Bash 3.2 or later is running
bash_version="$(bash -c 'echo ${BASH_VERSION}')"
echo "$(date_time) running bash version = '${bash_version}'" >> "${LOG_FILE}"
if ! printf '%s\n%s\n' "${bash_version}" "3.2" | sort -rVC; then
  echo -e "${BLUE}Warning: This 'bash' binary is very old."
  echo -e "We strongly recommend that you use the current version 5.3.${NC}"
fi

# parse and process provided input
(( $# == 0 )) && print_prompt
while getopts ":i:b:-:hx" opt; do
  case "${opt}" in
    i) if [[ "${OPTARG:0:1}" == '-' ]]; then
         abort "Error: The option '-i' requires an argument."
       else
         input_folder="${OPTARG}"
       fi ;;
    b) if [[ "${OPTARG:0:1}" == '-' ]]; then
         abort "Error: The option '-b' requires an argument."
       else
         input_folder="${OPTARG}"
       fi ;;
    -) case "${OPTARG}" in
         input=?*) input_folder="${OPTARG#*=}" ;;
         bagit=?*) input_folder="${OPTARG#*=}" ;;
         confirmation?*) confirmation="${OPTARG#*=}" ;;
         force?*) force="${OPTARG#*=}" ;;
         help) print_help ;;
         options) print_options ;;
         *) abort "Error: The option '--${OPTARG}' is not valid." ;;
       esac ;;
    h) print_help ;;
    x) print_options ;;
    :) abort "Error: The option '-${OPTARG}' requires an argument." ;;
    *) abort "Error: The option '-${OPTARG}' is not valid." ;;
  esac
done

# check that provided input folder is valid
verify_input "${input_folder}"

# undo BagIt archive
undo_bagit_archive "${input_folder}"

# end log file
echo "$(date_time) END" >> "${LOG_FILE}"
