_ARCHIVE_PATTERN="_[0-9]{12}-[[:alnum:]]{40}.t[xg]z"
_ARCHIVE_DATE()
{
  date +%Y%m%d%H%M
}

# archive file with maximum compression and checksum.
# usage: archive [FILES]
archive()
{
  local targets=("${@}")      # target file(s).
  local count=0               # processed count.
  local total=${#}            # total to process.
  local date=$(_ARCHIVE_DATE) # date stamp.

  # set dafult value to target all supported archives.
  if [[ "${targets}" = "" ]]; then
    targets=(*)
    total=${#targets[@]}
  fi

  # iterate each target.
  for target in "${targets[@]}"; do
    # increment counter.
    ((count++))

    # status info.
    local status="[${count}/${total}] ${target}"
    echo "${status}"

    # create archive.
    tar -c "${target}" | pv -s $(du -sb "${target}" | awk '{print $1}') | xz -9e > "${target%/*}".txz
    
    # append hash to target name.
    mv "${target%/*}".txz "${target%/*}"_${date}-$(pv "${target%/*}".txz | sha1sum | cut -d\  -f1).txz

    # report success.
    echo -e "${color_default}${status}: Done.${color_default}"
  done
}

# archive file with minimal compression and checksum.
# usage: archive_fast [FILES]
archive_fast()
{
  local targets=("${@}")      # target file(s).
  local count=0               # processed count.
  local total=${#}            # total to process.
  local date=$(_ARCHIVE_DATE) # date stamp.

  # set dafult value to target all supported archives.
  if [[ "${targets}" = "" ]]; then
    targets=(*)
    total=${#targets[@]}
  fi

  # iterate each target.
  for target in "${targets[@]}"; do
    # increment counter.
    ((count++))

    # status info.
    local status="[${count}/${total}] ${target}"
    echo "${status}"

    # create archive.
    tar -c "${target}" | pv -s $(du -sb "${target}" | awk '{print $1}') | gzip -1 > "${target%/*}".tgz
    
    # append hash to target name.
    mv "${target%/*}".tgz "${target%/*}"_${date}-$(pv "${target%/*}".tgz | sha1sum | cut -d\  -f1).tgz

    # report success.
    echo -e "${color_default}${status}: Done.${color_default}"
  done
}

# check archive hashes.
# usage: archive_check [FILES]
archive_check()
{
  local targets=("${@}") # target file(s).
  local total=${#}       # total to process.
  local count=0          # processed count.
  local failed=0         # total failed checks.

  # set dafult value to target all supported archives.
  if [[ "${targets}" = "" ]]; then
    targets=($(ls | grep -E ${_ARCHIVE_PATTERN}))
    total=${#targets[@]}
  fi

  # iterate each target.
  for target in "${targets[@]}"; do
    # process only files.
    [[ -f "${target}" ]] || continue

    # increment counter.
    ((count++))

    # status info.
    local status="[${count}/${total}] ${target}"

    # extract hash from name.
    local saved="${target##*-}"
    saved="${saved%%.*}"

    # calculate actual hash.
    local actual=$(pv "${target}" | sha1sum | cut -d\  -f1)

    # compare hashes, show error on mismatch.
    if [[ "${actual}" = "${saved}" ]]; then
      echo -e "${color_default}${status}: Validation OK.${color_default}"
    else
      echo -e "${color_bred}${status}: Validation failed.${color_default}"
      ((failed++))
    fi
  done

  # report result.
  if [[ ${failed} -gt 0 ]]; then
    echo -e "${color_bred}Items failed to validate: ${failed}.${color_default}"
  else
    echo -e "${color_green}All successful.${color_default}"
  fi
}

# extract previously created archive with checksum validation.
# usage: unarchive [FILES]
unarchive()
{
  local targets=("${@}") # target file(s).
  local count=0          # processed count.
  local total=${#}       # total to process.
  
  # set dafult value to target all supported archives.
  if [[ "${targets}" = "" ]]; then
    targets=($(ls | grep -E ${_ARCHIVE_PATTERN}))
    total=${#targets[@]}
  fi

  # iterate each target.
  for target in "${targets[@]}"; do
    # increment counter.
    ((count++))

    # status info.
    local status="[${count}/${total}] ${target}"

    # extract hash from name.
    local saved="${target##*-}"
    saved="${saved%%.*}"
    
    # calculate actual hash.
    local actual=$(pv "${target}" | sha1sum | cut -d\  -f1)

    # extract if hash matched or show error if not.
    if [[ "${saved}" = "${actual}" ]]; then
      echo "${status}: Validation OK."

      # figure out the compression tool.
      local compressor
      case "${target##*.}" in
        "txz")
          compressor="xz -d"
          ;;
        "tgz")
          compressor="gzip -d"
          ;;
      esac

      # extract.
      pv "${target}" | ${compressor} | tar -xf -
    else
      # report validation error & exit.
      echo -e "${color_bred}${status}: Validation failed.${color_default}"
      return 1
    fi

    # report extraction complete.
    echo -e "${color_default}${status}: Done.${color_default}"
  done
}

# rename archive. if no name specified, it simplifies archive's name.
# usage: archive_name [ARCHIVE] [NAME]
archive_name()
{
  local targets="${1}" # target archive(s).
  local name="${2}"    # new name.
  local total=1        # total targets to process.
  local count=0        # processed targets counter.

  # set dafult value to target all supported archives.
  if [[ "${targets}" = "" ]]; then
    targets=($(ls | grep -E ${_ARCHIVE_PATTERN}))
    total=${#targets[@]}
  fi

  # iterate each target.
  for target in "${targets[@]}"; do
    # only work with targets.
    [[ -f "${target}" ]] || continue

    # iterate counter.
    ((count++))
    
    # simplify name by default.
    if [[ "${name}" = "" || ${count} -gt 1 ]]; then
      name="${target%_*}"
      name="$(parse_alnum ${name})"
    fi

    # remove old name.
    local data="${target##*_}"
    local new_name="${name}_${data}"

    # prepare status.
    local status="[${count}/${total}] ${target} -> ${new_name}"

    # check for the same name.
    if [[ "${target}" = "${new_name}" ]]; then
      echo -e "${color_green}${status}: No change.${color_default}"
      continue
    fi

    # check for existing target.
    if [[ -f "${new_name}" ]]; then
      echo -e "${color_bred}${status}: Already exists.${color_default}"
      return 1
    fi

    # rename.
    mv -- "${target}" "${new_name}" && echo -e "${color_default}${status}${color_default}" || echo -e "${color_bred}${status}: Error.${color_default}"
  done
}

# convert an old archive to a new format. TODO: remove me after some time when there won't be any old archives.
archive_convert()
{
  local old_format="_[[:alnum:]]{40}.tar.[xg]z"
  local targets=($(ls | grep -E ${old_format}))

  # add timestamp.
  for target in "${targets[@]}"; do
    local stamp=$(stat --format '%w' -- "${target}" | sed -e 's/\..*//' -e 's/:..$//' -e 's/-//g' -e 's/://' -e 's/\ //')
    local name="${target%_*}"
    local old_data="${target##*_}"
    local new_name="${name}_${stamp}-${old_data}"

    echo "${target} -> ${new_name}"

    mv "${target}" "${new_name}"
  done

  # convert tar.xz and tar.gz to .tgz and .txz.
  old_format="_[0-9]{12}-[[:alnum:]]{40}.tar.[xg]z"
  targets=($(ls | grep -E ${old_format}))

  for target in "${targets[@]}"; do
    local compression="${target##*.}"
    local new_compression
    
    case "${compression}" in
      "gz")
          new_compression="tgz"
        ;;
      "xz")
          new_compression="txz"
        ;;
    esac

    local new_name="${target%.tar.*}".${new_compression}

    echo "${target} -> ${new_name}"

    mv -- "${target}" "${new_name}"
  done
}

# export everything, primarily for use with parallel..
export -f archive archive_fast archive_check unarchive archive_name _ARCHIVE_DATE
export _ARCHIVE_PATTERN

# autocomplete.
_archive_name()
{
  COMPREPLY=()

  local cur="${COMP_WORDS[COMP_CWORD]}"
  local prev="${COMP_WORDS[COMP_CWORD-1]}"
  local command="${COMP_WORDS[0]}"

  if [[ "${prev}" = "${command}" ]]; then
    COMPREPLY=( $(compgen -W "$(ls | grep -E ${_ARCHIVE_PATTERN})" -- ${cur}) )
    return 0
  else
    local name="${prev%_*}"
    COMPREPLY=( $(compgen -W "${name}" -- ${cur}) )
    return 0
  fi
}

_archive_grep()
{
  _autocomplete_grep ${_ARCHIVE_PATTERN}
}

complete -F _archive_grep archive_check unarchive
complete -F _archive_name archive_name