_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 IFS=$'\n'
  local targets=("${@}")      # target file(s).
  local count=0               # processed count.
  local total=${#}            # total to process.
  local date=$(_ARCHIVE_DATE) # date stamp.
  local failed=0

  # Set dafult value to target all directories.
  if [[ "${targets}" = "" ]]; then
    targets=($(ls --classify | grep /\$))
    total=${#targets[@]}
  fi

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

    # status info.
    local status="[${count}/${total}] ${target}"
    echo -e "${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

    # Show error.
    if [[ ${?} != 0 ]]; then
      ((failed++))
      echo -e "${color_bred}${status}: Failed.${color_default}"
    fi
  done

  # Show error.
  if [[ ${failed} != 0 ]]; then
    echo -e "${color_bred}Failed: ${failed}.${color_default}"
    false
  fi
}

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

  # Set dafult value to target all directories.
  if [[ "${targets}" = "" ]]; then
    targets=($(ls --classify | grep /$))
    total=${#targets[@]}
  fi

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

    # status info.
    local status="[${count}/${total}] ${target}"
    echo -e "${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

    # Show error.
    if [[ $? != 0 ]]; then
      ((failed++))
      echo -e "${color_bred}${status}: Failed.${color_default}"
    fi
  done

  # Show error.
  if [[ ${failed} != 0 ]]; then
    echo -e "${color_bred}Failed: ${failed}.${color_default}"
    false
  fi
}

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

  # 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}"
    echo -e "${status}"

    # 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
      ((failed++))
      echo -e "${color_bred}${status}: Failed.${color_default}"
    fi
  done

  if [[ ${failed} != 0 ]]; then
    echo -e "${color_bred}Failed: ${failed}.${color_default}"
    false
  fi
}

# Delete old versions of an archives.
# Usage: archive_prune [NAME]
archive_prune()
{
  local IFS=$'\n'
  local targets=("${@}")
  local count=0
  local total=${#}
  local failed=0

  # All archives by default.
  if [[ "${targets}" = "" ]]; then
    targets=($(ls | grep -E ${_ARCHIVE_PATTERN}))
    total=${#targets[@]}
  fi

  # Iterate each target.
  for target in "${targets[@]}"; do
    # Only work with existing files.
    [[ -f "${target}" ]] || continue

    # Iterate counter.
    ((count++))
    
    local name="${target%_*}"
    local data="${target##*_}"
    local time="${data%%-*}"
    local copies=($(ls ${name}_*))

    # Iterate each copy.
    for copy in "${copies[@]}"; do
      local copy_data="${copy##*_}"
      local copy_time="${copy_data%%-*}"

      if [[ "${copy_time}" -lt "${time}" ]]; then
        echo -e "${name}: prune ${copy_time}."
        rm -- "${copy}"

        if [[ ${?} != 0 ]]; then
          echo -e "${color_bred}${target}: Failed.${color_default}"
          ((failed++))
        fi
      fi
    done
  done

  if [[ ${failed} != 0 ]]; then
    echo -e "${color_bred}Failed: ${failed}.${color_default}"
    false
  fi
}

# extract previously created archive with checksum validation.
# usage: unarchive [FILES]
unarchive()
{
  local IFS=$'\n'
  local targets=("${@}") # target file(s).
  local count=0          # processed count.
  local total=${#}       # total to process.
  local failed=0
  
  # 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}"
    echo -e "${status}"

    # 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
      # figure out the compression tool.
      local compressor
      case "${target##*.}" in
        "txz")
          compressor="xz -d"
          ;;
        "tgz")
          compressor="gzip -d"
          ;;
      esac

      # extract.
      unset IFS
      pv "${target}" | ${compressor} | tar -xf -

      if [[ ${?} != 0 ]]; then
        echo -e "${color_bred}${status}: Failed.${color_default}"
        ((failed++))
      fi
    else
      # report validation error & continue.
      echo -e "${color_bred}${status}: Validation failed.${color_default}"
      ((failed++))
      continue
    fi
  done

  if [[ ${failed} != 0 ]]; then
    echo -e "${color_bred}Failed: ${failed}.${color_default}"
    false
  fi
}

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

  # 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
    # 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 "${status}"
      continue
    fi

    # check for existing target.
    if [[ -f "${new_name}" ]]; then
      echo -e "${color_bred}${status}: Already exists.${color_default}"
      ((failed++))
      continue
    fi

    echo -e "${status}"

    # rename.
    mv -- "${target}" "${new_name}"

    if [[ ${?} != 0 ]]; then
      echo -e "${color_bred}${status}: Failed.${color_default}"
      ((failed++))
    fi
  done

  if [[ ${failed} != 0 ]]; then
    echo -e "${color_bred}Failed: ${failed}.${color_default}"
    false
  fi
}

# 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 IFS=$'\n'
  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()
{
  local IFS=$'\n'
  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 -o filenames -F _archive_grep archive_check unarchive
complete -o filenames -F _archive_name archive_name