# Global variables of our script
COMPRESSION="gzip -1"
ARCHIVE_EXTENSION="gz"
BASE_DIRECTORY="/var/lib/heavyai"
STORAGE_DIR="/var/lib/heavyai/storage"
BACKUP_TYPE="full"
DUMP_DIR=""
ESTIMATOR="false"

declare -A list_of_compressors compressor_extension
list_of_compressors=( ["gzip"]="gzip -1" ["zstd"]="zstd -T0" ["pigz"]="pigz -1" ["pixz"]="pixz -1")
compressor_extension=( ["gzip"]="gz" ["zstd"]="zst" ["pigz"]="gz" ["pixz"]="xz")
types_of_backups="full incremental cumulative"

function showUsageAndExit() {
  if ! [ -z "${2}" ]; then
    echo -n "${2}"
  fi;
  echo -e "Usage: heavyvackup.sh backup|restore OPTIONS"
  echo -e "Actions: backup restore (restore isn't implemented yet.)"
  echo -e "Options:"
  echo -e "-dn|--database-name=logical name of the backup"
  echo -e "-dd|--dump-dir=destination path of backup archives containing storage and additional files"
  echo -e "[-bt|backup-type=backup type (${types_of_backups})] default: Full"
  echo -e "[-sc|--set-compressor=compression to use (${!list_of_compressors[@]})] default: gzip"
  echo -e "[-sd|--storage-dir=path haeavydb storage] default: ${STORAGE_DIR}"
  echo -e "[-ap|--addition-paths=additional paths to be backed up]"
  exit ${1}
}

#list of severity
INFO_S="Info"
WARN_S="Warning"
ERROR_S="Error"
function log() {
  severity=$1
  message=$(echo ${2} | sed 's/ *$//g')
  echo -e $(date +"%Y-%M-%d %H:%m:%S ${severity}: ${message}.")
}

function setCompressor() {
  compressor="${1}"

  if [ "${compressor}" == "" ]; then
    log $WARN_S "Setting no compression for backups"
    ARCHIVE_EXTENSION=""
    COMPRESSION=""
  else
    COMPRESSION="${list_of_compressors[${compressor}]}"
    ARCHIVE_EXTENSION=".${compressor_extension[${compressor}]}"
    if [ "$(which ${compressor})" == "" ] || [ -z "${COMPRESSION}" ]; then
      log $WARN_S "Compression program "${compressor}" not found. Using gzip instead"
      COMPRESSION="gzip -1"
      ARCHIVE_EXTENSION=".gz"
    fi
  fi
}

function checkAdditionalDir() {
  dir_to_check=$1
  if ! [ -d "${dir_to_check}" ]; then
    log $ERROR_S "The specified additional directory "${dir_to_check}" doesn't exists or it's not readable"
    exit 1
  else
    let size=$(du -ks ${dir_to_check} 2>&1 | cut -f 1) 2>/dev/null
    let retvalue=$?
    if [ "${retvalue}" != "0" ]; then
      # the du command has falied. It's likely to be a permission problem
      log $ERROR_S "The specified additional directory "${dir_to_check}" it's not fully readable"
      exit 1
    fi
  fi

}
function checkStorageDir() {
  storage_dir_to_check=$1
  isnot_storage_dir=true

  if ! [ -d "${storage_dir_to_check}" ]; then
    log $ERROR_S "The specified storage directory "${storage_dir_to_check}" doesn't exists or it's not readable"
    exit 1
  fi

  for i in data catalogs disk_cache export log lockfiles 
  do 
    if ! [ -d "${storage_dir_to_check}/$i" ]; then
      isnot_storage_dir=false; 
      break; 
    fi 
  done;

  if [ "${isnot_storage_dir}" == "false" ]; then
    log $ERROR_S "The specified storage directory "${storage_dir_to_check}" doesn't contains heavydb data"
    exit -1
  fi
  STORAGE_DIR="${storage_dir_to_check}"
}

function checkStorageDirInUse() {
  if [ -f "${STORAGE_DIR}/heavydb_pid.lck" ]; then
    if lsof -w "${STORAGE_DIR}/heavydb_pid.lck" | grep heavydb_pid.lck >/dev/null; then
      log $ERROR_S "Storage directory "${STORAGE_DIR}" in use. The ${action} could be inconsistent. Exiting"
      if [ -z "${verbose_output}" ]; then
        IFS=""; log $ERROR_S "The Processes locking the storage directory are \n"$(lsof -w "${STORAGE_DIR}"/heavydb_pid.lck | grep heavydb_pid.lck)
      fi
      exit -1
    fi
  fi
}

function checkDumpDirExistsAndEmpty() {
  check=$(ls -1a ${STORAGE_DIR} 2>/dev/null | wc -l)
  case $check in 
    2)
      log $INFO_S "The path "${STORAGE_DIR}" is a valid restore path"
      ;;
    0)
      log $ERROR_S "Restore path "${STORAGE_DIR}" doesn't exists or it's not writable"
      exit -1
      ;;
    *)
      log $ERROR_S "Restore path "${STORAGE_DIR}" isn't empty. Please clear all contents before restoring a backup"
      exit -1
      ;;
  esac
  exit 0
}

function checkDumpDirExists() {
  if [ -z "${DUMP_DIR}" ]; then
    showUsageAndExit 1 "You must specify a dump dir with -dd or --dump-dir option"
  fi
  if [ -w "${DUMP_DIR}" ]; then 
    log $INFO_S "Using ${DUMP_DIR} as destinaton for the backup"
  else
    log $ERROR_S "Dump path ${DUMP_DIR} doesn't exists or it's not writable"
    exit -1
  fi
}

function checkDumpFilesExist() {
  # We aren't checking anything
  echo ""
}

function doAllChecks() {
  if [ -z "${action}" ]; then
    showUsageAndExit 2 "You must specify on of backup or restore"
  fi
  if [ "${action}" == "backup" ]; then
    checkStorageDir ${STORAGE_DIR}
    if [ "${ADDITIONAL_PATH}" != "" ]; then
      checkAdditionalDir ${ADDITIONAL_PATH}
    fi
    checkDumpDirExists
  elif [ "${action}" == "restore" ]; then
    checkDumpFilesExist
    checkDumpDirExistsAndEmpty
  fi
  if [ -z "${ESTIMATOR+x}" ]; then
    checkStorageDirInUse
  fi
}

function setDumpDir() {
  if ! [ -w "${1}" ]; then
    log $ERROR_S "Dump dir ${1} doesn't exist or it's not writable"
    exit -1
  fi;
  DUMP_DIR="${1}"
}
 
function setBackupType() {
  echo "${types_of_backups}" | grep -q "$1" 
  if [ "$?" != "0" ]; then
    log $ERROR_S "The requested type of backup ${1} isn't supported. Please use one of ${types_of_backup}"
    exit -1
  fi
  BACKUP_TYPE="${1}"
  if [ "${BACKUP_TYPE}" == "full" ]; then
    BACKUP_TYPE_LEVEL="--level 0"
  else
    BACKUP_TYPE_LEVEL="--level 1"
  fi
}

function doBackup() {
  estimator=${1}
  type_of_data=${2}
  path_to_backup=${3}
  additional_command=""

  let size=$(du -ks ${STORAGE_DIR} 2>&1 | cut -f 1) 2>/dev/null
  let retvalue=$?
  if [ "${retvalue}" != "0" ]; then
    # the du command has falied. It's likely to be a permission problem
    echo -e "Check the files and directories permissions on ${path_to_backup}"
    return ${retvalue}
  fi

  snar_file_name="${DUMP_DIR}/bkp_${DATABASE_NAME}_${type_of_data}"
  if [ -f "${snar_file_name}.snar" ]; then 
    cp ${snar_file_name}.snar ${snar_file_name}.snar.old
  fi;
  if [ "${estimator}" == "true" ]; then
    file_name="-"
    log_message="Run a size and time estimation of ${BACKUP_TYPE} backup. No output will be produced"
    snar_file_name=${snar_file_name}.estimate_only
  else
    file_name=$(date +"${DUMP_DIR}/bkp_${DATABASE_NAME}_${type_of_data}_%Y_%U_%u_%H%M_${BACKUP_TYPE}.tar${ARCHIVE_EXTENSION}")
    log_message="Running ${BACKUP_TYPE} backup."
  fi

  let start_time=$(date +%s)

  if ! [ "${COMPRESSION}" == "" ]; then
  status=$(tar --use-compress-program "${COMPRESSION}" \
    ${BACKUP_TYPE_LEVEL} --listed-incremental ${snar_file_name}.snar \
    -cf ${file_name} ${path_to_backup} ${additional_command} 2>&1 | wc -c )
  else
  status=$(tar \
    ${BACKUP_TYPE_LEVEL} --listed-incremental ${snar_file_name}.snar \
    -cf ${file_name} ${path_to_backup} ${additional_command} 2>&1 | wc -c)
  fi

  let retvalue=$?
  let elapsed_seconds=$(date +%s)-${start_time}+1
  let tp=${size}/${elapsed_seconds}/1024

  if [ "${retvalue}" != "0" ]; then
  # the creation of archive is failed. remove the newly created files 
  # and restore the original snar
    rm -f ${file_name}*
    rm -f ${snar_file_name}.snar
    if [ -f "${snar_file_name}.snar.old" ]; then 
      mv ${snar_file_name}.snar.old ${snar_file_name}.snar
    fi   
  else
    status="${elapsed_seconds} seconds" 
    if [ "${BACKUP_TYPE}" == "full" ]; then 
      status="${status} with a throughput of ${tp} MB/sec"
    fi

    if [ -f "${snar_file_name}.snar.old" ]; then 
      rm -f ${snar_file_name}.snar.old
    fi
  fi
  echo -e "${status}"
  return $retvalue

}

# Start of the shell
for i in "$@"
do
  case $i in
    restore)
      action="restore"
      shift
      ;;
    backup)
      action="backup"
      shift
      ;;
    -u=*|--user=*)
      username=${i#*=}
      shift
      ;;
    -p=*|--password=*)
      password=${i#*=}
      shift
      ;;
    -d=*|--database=*)
      database=${i#*=}
      shift
      ;;
    -dn=*|--database-name=*)
      DATABASE_NAME=${i#*=}
      shift
      ;;
    -sc=*|--set-compressor=*)
      setCompressor ${i#*=}
      shift
      ;;
    -no|--no-compression)
      setCompressor
      shift
      ;;
    -sd=*|--storage-dir=*)
      STORAGE_DIR=${i#*=}
      shift
      ;;
    -ap=*|--additional-paths=*)
      if [ "${action}" == "backup" ]; then
        ADDITIONAL_PATH=${i#*=}
      else
        log $WARN_S "Additional paths are specified while restoring a backup. Ignoring the request"
      fi
      shift
      ;;
    -dd=*|--dump-dir=*)
      setDumpDir "${i#*=}"
      shift
      ;;
    -bt=*|backup-type=*)
      if [ "${action}" == "backup" ]; then
        setBackupType ${i#*=}
      else
        log $WARN_S "A backup type is specified while restoring a backup. Ignoring the request"
      fi
      shift
      ;;
    -e|--estimator)
      ESTIMATOR="true"
      ESTIMATOR_LOG="estimation of"
      log $WARN_S "Running an estimate only. No backupset will be produced"
      shift
      ;;
    -h|--help)
      showUsageAndExit 0
      shift
      ;;
     *)
      showUsageAndExit 2 "Unknown parameter ${@}"
      ;;
  esac;
done;

if [ "${action}" == "backup" ]; then
  log $INFO_S "Starting ${ESTIMATOR_LOG} ${BACKUP_TYPE} backup of database ${DATABASE_NAME} with storage dir ${STORAGE_DIR}"
  doAllChecks
  let start_time=$(date +%s)
  status=$(doBackup ${ESTIMATOR} data ${STORAGE_DIR})
  if [ ${?} != 0 ]; then
    log $ERROR_S "The ${ESTIMATOR_LOG} ${BACKUP_TYPE} backup of database storage ${DATABASE_NAME} has failed. Reason: ${status}"
    exit 1
  else
    log $INFO_S "The ${ESTIMATOR_LOG} ${BACKUP_TYPE} backup of database ${DATABASE_NAME} has succeded in ${status}" 
  fi

  if [ -n "${ADDITIONAL_PATH}" ]; then
    status=$(doBackup ${ESTIMATOR} additional ${ADDITIONAL_PATH})
    if [ ${?} != 0 ]; then
      log $ERROR_S "The ${BACKUP_TYPE} backup of additional storage ${DATABASE_NAME} has failed.\n Reason: ${status}"
    else
      log $INFO_S "The ${BACKUP_TYPE} backup of additional storage ${DATABASE_NAME} has succeded in ${status}" 
    fi
  fi
else
  log $WARN_S "Automated Restore not yet implemented. Running checks only"
  doAllChecks
fi