export _archive_pattern="_[0-9]{12}-[[:alnum:]]{40}.t[xg]z"

# Archive directories.
# All directories by default.
# Usage: archive [DIRS]
function archive() {
	local IFS=$'\n'
	local targets=("${@}")
	local count=0
	local total=${#}
	local date=$(_archive_date)
	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}"

		local name=$(parse_camel "${target}")

		# create archive.
		tar -c "${target}" | pv -s $(du -sb "${target}" | awk '{print $1}') | xz -9e > "${name}".txz
		
		# append hash to target name.
		mv "${name}".txz "${name}"_${date}-$(pv "${name}".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 directories with fast compression.
# All directories by default.
# Usage: archive_fast [DIRS]
function archive_fast() {
	local IFS=$'\n'
	local targets=("${@}")
	local count=0
	local total=${#}
	local date=$(_archive_date)
	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}"

		local name=$(parse_camel "${target}")

		# create archive.
		tar -c "${target}" | pv -s $(du -sb "${target}" | awk '{print $1}') | gzip -1 > "${name}".tgz
		
		# append hash to target name.
		mv "${name}".tgz "${name}"_${date}-$(pv "${name}".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 archives integrity.
# Checks all archives by default.
# Usage: archive_check [FILES]
function archive_check() {
	local IFS=$'\n'
	local targets=("${@}")
	local total=${#}
	local count=0
	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 data=($(_archive_parse ${target}))
		local saved=${data[2]}

		# 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 archives.
# All archives by default.
# Usage: archive_prune [NAME]
function 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 data=($(_archive_parse ${target}))
		local name="${data[0]}"
		local time="${data[1]}"
		local copies=($(ls ${name}_*))

		# Iterate each copy.
		for copy in "${copies[@]}"; do
			local copy_data=($(_archive_parse ${copy}))
			local copy_time="${copy_data[1]}"

			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]
function unarchive() {
	local IFS=$'\n'
	local targets=("${@}")
	local count=0
	local total=${#}
	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 data=($(_archive_parse "${target}"))
		local saved="${data[2]}"
		
		# 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 archives. 
# If no name specified, it simplifies archive's name.
# If no archives specified, apply to all archives.
# Usage: archive_name [ARCHIVE] [NAME]
function archive_name() {
	local IFS=$'\n'
	local targets="${1}"
	local name="${2}"
	local total=1
	local count=0
	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_camel ${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 old archives to a new format. TODO: remove me after some time when there won't be any old archives.
function 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
}

# Parse archive file name to get: name, date, hash and format.
# Usage: _archive_parse <FILENAME>
function _archive_parse() {
	local input="${1}"
	local name="${input%_*}"
	local format="${input##*.}"
	local data="${input##*_}"; data="${data%.*}"
	local date="${data%%-*}"
	local hash="${data##*-}"

	echo "${name}"
	echo "${date}"
	echo "${hash}"
	echo "${format}"
}

# Autocomplete for archive_name function.
# First arg is the archives list, second one is selected archive's current name.
function _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
}

# Autocomplete with archives in current dir.
function _archive_grep() {
	_autocomplete_grep ${_archive_pattern}
}

# Get date for a new archive.
function _archive_date() {
	date +%Y%m%d%H%M
}

complete -o filenames -F _archive_grep archive_check unarchive
complete -o filenames -F _archive_name archive_name