#!/usr/bin/env bash

NAME="diffuzzy"
CODENAME="diffuzzy"
COPYRIGHT="Copyright (C) 2018 Nathan Shearer"
LICENSE="GNU General Public License 2.0"
VERSION="2.3.0.0"

# \brief Compare paths recursively with a probabilistic diff
# \param "$@" The list of paths to compare
function diffuzzy_compare
{
	if [ $VERBOSE -ge 1 ]; then
		echo "Comparing:"
		for P in "$@"; do
			echo "  '$P'"
		done
	fi

	if $COMPARE_EXISTENTIAL; then
		diffuzzy_compare_existential "$@" || return
	fi

	if $COMPARE_MODE; then
		diffuzzy_compare_mode "$@" || return
	fi

	if $COMPARE_TYPE; then
		diffuzzy_compare_type "$@" || return
	fi

	case $(stat -c %F "$1") in
		"block special file")
			if ! $COMPARE_BLOCKDATA; then
				return 0
			fi
			;;
		"directory")
			diffuzzy_compare_directories "$@"
			return $?
			;;
		"regular file") ;;
		"regular empty file") ;;
		*)
			return 0
			;;
	esac

	if $COMPARE_SIZE; then
		diffuzzy_compare_size "$@" || return
	fi
	
	if $COMPARE_DATA; then
		diffuzzy_compare_data "$@" || return
	fi
}

# \brief Compare file data
# \param "$@" The list of paths to compare
function diffuzzy_compare_data
{
	local SIZE_MAX=0
	local SIZE=0
	for P in "$@"; do
		SIZE=$(diffuzzy_sizeof "$P")
		if [ $SIZE_MAX -lt $SIZE ]; then
			SIZE_MAX=$SIZE
		fi
	done
	if [ $SIZE_MAX -le 4096 ]; then
		diffuzzy_compare_full "$@"
		return $?
	else
		if $COMPARE_HEADER; then
			diffuzzy_compare_header "$@" || return
		fi
		if $COMPARE_FOOTER; then
			diffuzzy_compare_footer "$@" || return
		fi
		if $COMPARE_OFFSETS; then
			diffuzzy_compare_offsets "$@" || return
		fi
	fi
}

# \brief Compare directories
# \param "$@" The list of paths to compare
function diffuzzy_compare_directories
{
	local PATHS=("$@")
	# build a list of files in each directory
	{
		for P in "${PATHS[@]}"; do
			pushd "$P" >/dev/null 2>/dev/null
			find -mindepth 1 -maxdepth 1 -print0
			popd >/dev/null 2>/dev/null
		done
	} | \
	sort -z | uniq -z | \
	{
		local STATUS=0
		while IFS= read -r -d $'\0' FILE; do
			# strip the leading ./ that find prepends to each string
			FILE=$(echo -n "$FILE" | sed -r 's/^\.\///')

			local SUBPATHS=()
			for P in "${PATHS[@]}"; do
				SUBPATHS+=("$P/$FILE")
			done

			diffuzzy_compare "${SUBPATHS[@]}"
			if [ $? -ne 0 ]; then
				STATUS=1
			fi
		done
		return $STATUS
	}
	return $?
}

# \brief Compare and detect missing objects
# \param "$@" The list of paths to compare
function diffuzzy_compare_existential
{
	local STATUS=0
	for P in "$@"; do
		if [ ! -e "$P" ]; then
			# test will follow symlinks so verify the file actually doesn't exist
			if [ -L "$P" ]; then
				continue
			fi
			echo "Path '$P' does not exist."
			STATUS=1
		fi
	done
	return $STATUS
}

# \brief Compare the last 512 bytes
# \param "$@" The list of paths to compare
function diffuzzy_compare_footer
{
	local STATUS=0
	local P1="$1"
	shift
	local P1_SIZE=$(diffuzzy_sizeof "$P1")
	local P1_DATA=$(set -o pipefail; dd status=none if="$P1" bs=1 count=512 skip=$(($P1_SIZE-512)) | base64 -w 0) || return
	for P2 in "$@"; do
		local P2_SIZE=$(diffuzzy_sizeof "$P2")
		P2_DATA=$(set -o pipefail; dd status=none if="$P2" bs=1 count=512 skip=$(($P2_SIZE-512)) | base64 -w 0) || return
		if [ $VERBOSE -ge 2 ]; then
			printf '%s\n' "  Comparing '$P1' and '$P2' footer at offset $(($P2_SIZE-512))"
		fi
		if [ "$P1_DATA" != "$P2_DATA" ]; then
			echo "The last 512 bytes differ between '$P1' and '$P2'."
			STATUS=1
		fi
	done
	return $STATUS
}

# \brief Perform a full comparison with diff
# \param "$@" The list of paths to compare
function diffuzzy_compare_full
{
	local STATUS=0
	local P1="$1"
	shift
	local P1_DATA=$(set -o pipefail; dd status=none if="$P1" bs=1 | base64 -w 0) || return
	for P2 in "$@"; do
		P2_DATA=$(set -o pipefail; dd status=none if="$P2" bs=1 | base64 -w 0) || return
		if [ "$P1_DATA" != "$P2_DATA" ]; then
			echo "File data differs between '$P1' and '$P2'."
			STATUS=1
		fi
	done
	return $STATUS
}

# \brief Compare the first 512 bytes
# \param "$@" The list of paths to compare
function diffuzzy_compare_header
{
	local STATUS=0
	local P1="$1"
	shift
	local P1_DATA=$(set -o pipefail; dd status=none if="$P1" bs=1 count=512 | base64 -w 0) || return
	for P2 in "$@"; do
		P2_DATA=$(set -o pipefail; dd status=none if="$P2" bs=1 count=512 | base64 -w 0) || return
		if [ $VERBOSE -ge 2 ]; then
			printf '%s\n' "  Comparing '$P1' and '$P2' header at offset 0"
		fi
		if [ "$P1_DATA" != "$P2_DATA" ]; then
			echo "The first 512 bytes differ between '$P1' and '$P2'."
			STATUS=1
		fi
	done
	return $STATUS
}

# \brief Compare file mode bits
# \param "$@" The list of paths to compare
function diffuzzy_compare_mode
{
	local STATUS=0
	local P1="$1"
	shift
	local P1_MODE=$(stat -c '%a %A' "$P1")
	for P2 in "$@"; do
		P2_MODE=$(stat -c '%a %A' "$P2")
		if [ "$P1_MODE" != "$P2_MODE" ]; then
			echo "File mode mismatch between '$P1' ($P1_MODE) and '$P2' ($P2_MODE)."
			STATUS=1
		fi
	done
	return $STATUS
}

# \brief Compare floor(log(2,file_size)) 512 byte blocks at random offsets
# \param "$@" The list of paths to compare
function diffuzzy_compare_offsets
{
	local STATUS=0

	local P1="$1"
	shift
	local P1_SIZE=$(diffuzzy_sizeof "$P1")
	local OFFSETS=$(diffuzzy_compute_offsets $P1_SIZE "$OFFSETS")
	for P2 in "$@"; do
		for OFFSET in $OFFSETS; do
			P1_DATA=$(set -o pipefail; dd status=none if="$P1" bs=512 count=1 skip=$OFFSET | base64 -w 0) || return
			P2_DATA=$(set -o pipefail; dd status=none if="$P2" bs=512 count=1 skip=$OFFSET | base64 -w 0) || return
			if [ $VERBOSE -ge 2 ]; then
				printf '%s\n' "  Comparing '$P1' and '$P2' at offset $OFFSET"
			fi
			if [ "$P1_DATA" != "$P2_DATA" ]; then
				echo "File data differs between '$P1' and '$P2' at offset $OFFSET."
				STATUS=1
				break
			fi
		done
	done

	return $STATUS
}

# \brief Compare and detect size mismatches
# \param "$@" The list of paths to compare
function diffuzzy_compare_size
{
	local STATUS=0
	if [ $# -ne 0 ]; then
		local P1="$1"
		shift
		P1_SIZE=$(diffuzzy_sizeof "$P1") || return
	fi
	for P2 in "$@"; do
		P2_SIZE=$(diffuzzy_sizeof "$P2") || return
		if [ "$P1_SIZE" != "$P2_SIZE" ]; then
			echo "File size mismatch between '$P1' and '$P2'."
			STATUS=1
		fi
	done
	return $STATUS
}

# \brief Compare and detect object type mismatches
# \param "$@" The list of paths to compare
function diffuzzy_compare_type
{
	local STATUS=0
	if [ $# -ne 0 ]; then
		local P1="$1"
		shift
		local P1_TYPE=$(stat -c %F "$P1")
	fi
	for P2 in "$@"; do
		P2_TYPE=$(stat -c %F "$P2")
		if [ "$P1_TYPE" != "$P2_TYPE" ]; then
			echo "File type mismatch between '$P1' ($P1_TYPE) and '$P2' ($P2_TYPE)."
			STATUS=1
		fi
	done
	return $STATUS
}

# \brief Compute a sorted list of offsets
# \param $1 The size of the object
# \param $2 The various offsets to compute
function diffuzzy_compute_offsets
{
	local SIZE="$1"
	local SECTORS=$(($SIZE/512))
	local OFFSETS_COMPUTED=()
	for OFFSET in $(printf %s "$2" | tr ',' ' '); do
		case "$OFFSET" in
			"log2")
				local ITERATOR=1
				while [ $ITERATOR -lt $SECTORS ]; do
					OFFSETS_COMPUTED+=( $ITERATOR )
					ITERATOR=$(($ITERATOR*2))
				done
				;;
			"log2log")
				local ITERATOR=1
				while [ $ITERATOR -lt $SECTORS ]; do
					OFFSETS_COMPUTED+=( $ITERATOR )
					OFFSETS_COMPUTED+=( $(($SECTORS-1-$ITERATOR)) )
					ITERATOR=$(($ITERATOR*2))
				done
				;;
			"rlog2")
				local ITERATOR=1
				while [ $ITERATOR -lt $SIZE ]; do
					OFFSETS_COMPUTED+=( $(( ($RANDOM*$RANDOM*$RANDOM)%$SECTORS )) )
					ITERATOR=$(($ITERATOR*2))
				done
				;;
			*)
				if ! printf "$OFFSET" | grep -E -q "[0-9]+"; then
					echo "error: unsupported offset \"$OFFSET\"."
					return 1
				fi
				local ITERATOR=1
				while [ $ITERATOR -le $OFFSET ]; do
					OFFSETS_COMPUTED+=( $(( ($RANDOM*$RANDOM*$RANDOM)%$SECTORS )) )
					ITERATOR=$(($ITERATOR+1))
				done
				;;
		esac
	done
	echo ${OFFSETS_COMPUTED[@]} | tr ' ' '\n' | sort -n | uniq
}

# \brief Verify an executable in $PATH
# \param $1 The executable
# \param $2 An optional hint to display if the executable is not found
function diffuzzy_check_executable
{
	if ! type "$1" >/dev/null 2>/dev/null; then
		echo "$CODENAME: \"$1\" is required for this script to work correctly." >&2
		if [ "$2" != "" ]; then
			echo "$2"
		fi
		return 1
	fi
}

# \brief Output the help documentation then exit
function diffuzzy_help
{
	#     01234567890123456789012345678901234567890123456789012345678901234567890123456789
	echo "Description:"
	echo "  Compare multiple paths recursively with adjustable accuracy"
	echo
	echo "Usage:"
	echo "  $CODENAME [OPTION]... PATH1 PATH2 [PATH3]..."
	echo
	echo "Options:"
	echo "  -h, --help"
	echo "    Output this help message and exit."
	echo "  -m,--method method1,method2,..."
	echo "    A comma-separated list of which comparisons to run:"
	echo "      existential  default  Detect missing objects"
	echo "      mode                  Compare file mode bits"
	echo "      type         default  Compare file types"
	#echo "      symlink      default  Compare symlink target paths"
	#echo "      device       default  Compare major and minor device type"
	echo "      size         default  Compare file size"
	echo "      data         default  Compare header, footer, and offsets"
	echo "      header       default  Compare the first 512 bytes"
	echo "      footer       default  Compare the last 512 bytes"
	echo "      offsets      default  Compare a set of 512 byte offsets"
	echo "      blockdata             Compare block device data"
	echo "  --offsets rlog2"
	echo "    A comma-spearated list of 512 byte offsets to compare:"
	echo "      rlog2    A set of floor(log(2,size)) random 512 byte offsets. Default."
	echo "      log2     1,2,4,8,... up to floor(log(2,size))"
	echo "      log2log  1,2,4,8,...,last-1,last-2,last-4,last-8..."
	echo "      n        Specify how many random 512 byte offests."
	echo "  --unittest"
	echo "    Perform unit tests to verify functionality of this script."
	echo "  -v"
	echo "    Output each set of files that are compared."
	echo "  --verbose #"
	echo "    Use more or less verbose output. Valid values are:"
	echo "      0  Default. No output."
	echo "      1  Show compared files."
	echo "      2  Show detailed comparison information."
	echo
	echo "Examples:"
	echo "  Compare three different paths"
	echo "    $CODENAME /mnt/storage /mnt/backup_local /mnt/backup_offsite"
	echo "  Compare two paths with additinal offsets for higher accuracy"
	echo "    $CODENAME --offsets log2log,rlog2 /mnt/storage /mnt/backup_local"
	echo "  Compare only one random offset to minimize load"
	echo "    $CODENAME --offsets 1 /mnt/storage /mnt/backup_offsite"
	echo
	echo "Version:"
	echo "  $NAME $VERSION"
	echo "  $COPYRIGHT"
	echo "  Licensed under $LICENSE"
}

function diffuzzy_sizeof
{
	local TYPE=$(stat -c %F "$1")
	local SIZE=0
	if $COMPARE_BLOCKDATA && [ "$TYPE" = "block special file" ]; then
		SIZE=$(blockdev --getsize64 "$1") || return
	else
		SIZE=$(stat -c %s "$1") || return
	fi
	printf %s "$SIZE"
}

# \brief Perform unit tests to verify functionality of this script
function diffuzzy_unittest
{
	local TMP="/tmp/diffuzzy.$$"
	mkdir "$TMP"

	echo "Compare a file with a missing file..."
	touch "$TMP/01.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin"

	echo "Compare two files with different mode bits..."
	touch "$TMP/01.bin"
	touch "$TMP/02.bin"
	chmod a+x "$TMP/01.bin"
	chmod a-x "$TMP/02.bin"
	COMPARE_MODE_OLD=$COMPARE_MODE
	COMPARE_MODE=true
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin"
	COMPARE_MODE=$COMPARE_MODE_OLD

	echo "Compare two files with different types: regular empty file vs. directory..."
	touch "$TMP/01.bin"
	mkdir "$TMP/02.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin"
	rmdir "$TMP/02.bin"

	echo "Compare two files with different types: regular empty file vs. fifo..."
	touch "$TMP/01.bin"
	mkfifo "$TMP/02.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin"

	echo "Compare and warn of existance of two unsupported file types..."
	mkfifo "$TMP/01.bin"
	mkfifo "$TMP/02.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 1 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin"

	dd status=none if=/dev/urandom bs=10MiB count=1 of="$TMP/1.random"
	dd status=none if=/dev/urandom bs=10MiB count=1 of="$TMP/2.random"
	dd status=none if=/dev/urandom bs=10MiB count=1 of="$TMP/3.random"

	echo "Compare two files with different sizes..."
	dd status=none if=/dev/urandom bs=1MiB count=4 of="$TMP/01.bin"
	cp "$TMP/01.bin" "$TMP/02.bin"
	dd status=none if=/dev/urandom bs=1MiB count=2 of="$TMP/02.bin" seek=4
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin"

	echo "Compare three files with different sizes..."
	dd status=none if=/dev/urandom bs=1MiB count=4 of="$TMP/01.bin"
	cp "$TMP/01.bin" "$TMP/02.bin"
	cp "$TMP/01.bin" "$TMP/03.bin"
	dd status=none if=/dev/urandom bs=1MiB count=2 of="$TMP/03.bin" seek=4
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"

	echo "Compare two small files..."
	dd status=none if=/dev/urandom bs=768 count=1 of="$TMP/01.bin"
	dd status=none if=/dev/urandom bs=768 count=1 of="$TMP/02.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	cp "$TMP/01.bin" "$TMP/02.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -ne 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin"

	echo "Compare three small files..."
	dd status=none if=/dev/urandom bs=768 count=1 of="$TMP/01.bin"
	dd status=none if=/dev/urandom bs=768 count=1 of="$TMP/02.bin"
	dd status=none if=/dev/urandom bs=768 count=1 of="$TMP/03.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	cp "$TMP/01.bin" "$TMP/03.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	cp "$TMP/01.bin" "$TMP/02.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"
	if [ $? -ne 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"

	echo "Compare two files with a different header..."
	dd status=none if=/dev/urandom bs=512 count=1 of="$TMP/01.bin"
	cat "$TMP/1.random" >> "$TMP/01.bin"
	dd status=none if=/dev/urandom bs=512 count=1 of="$TMP/02.bin"
	cat "$TMP/1.random" >> "$TMP/02.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin"

	echo "Compare three files with a different header..."
	dd status=none if=/dev/urandom bs=512 count=1 of="$TMP/01.bin"
	cat "$TMP/1.random" >> "$TMP/01.bin"
	cp "$TMP/01.bin" "$TMP/02.bin"
	dd status=none if=/dev/urandom bs=512 count=1 of="$TMP/03.bin"
	cat "$TMP/1.random" >> "$TMP/03.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"

	echo "Compare two files with a different footer..."
	cat "$TMP/1.random" >> "$TMP/01.bin"
	cat "$TMP/1.random" >> "$TMP/02.bin"
	dd status=none if=/dev/urandom bs=512 count=1 >> "$TMP/01.bin"
	dd status=none if=/dev/urandom bs=512 count=1 >> "$TMP/02.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin"

	echo "Compare three files with a different footer..."
	cat "$TMP/1.random" >> "$TMP/01.bin"
	cat "$TMP/1.random" >> "$TMP/02.bin"
	cat "$TMP/1.random" >> "$TMP/03.bin"
	dd status=none if=/dev/urandom bs=512 count=1 >> "$TMP/01.bin"
	cp "$TMP/01.bin" "$TMP/02.bin"
	dd status=none if=/dev/urandom bs=512 count=1 >> "$TMP/03.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin" "$TMP/03.bin"

	echo "Simulate a preallocated failed transfer..."
	dd status=none if=/dev/urandom bs=10MiB count=1 of="$TMP/01.bin"
	cp "$TMP/01.bin" "$TMP/02.bin"
	dd status=none if=/dev/urandom bs=10MiB count=2 >> "$TMP/01.bin"
	truncate -s 30MiB "$TMP/02.bin"
	diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin"

	echo -n "Simulate bitrot"
	dd status=none if=/dev/urandom bs=1MiB count=100 >> "$TMP/01.bin"
	cp "$TMP/01.bin" "$TMP/02.bin"
	local SECTOR=0
	while [ $SECTOR -lt 10000 ]; do
		echo -n '.'
		dd status=none if=/dev/urandom bs=512 count=1 seek=$(($RANDOM*$RANDOM%204800)) conv=notrunc of="$TMP/02.bin"
		diffuzzy_compare "$TMP/01.bin" "$TMP/02.bin"
		local RESULT=$?
		if [ $RESULT -eq 1 ]; then
			break
		fi
		SECTOR=$(($SECTOR+1))
	done
	if [ $SECTOR -eq 10000 ]; then
		echo "error: test failed."
		return 1
	else
		echo "bitrot detected after $SECTOR corrupted sectors."
	fi
	rm -f "$TMP/01.bin" "$TMP/02.bin"

	echo "Compare directiries containing files..."
	mkdir "$TMP/01"
	cat "$TMP/1.random" >> "$TMP/01/1.random"
	cat "$TMP/2.random" >> "$TMP/01/2.random"
	cat "$TMP/3.random" >> "$TMP/01/3.random"
	mkdir "$TMP/02"
	cat "$TMP/1.random" >> "$TMP/02/1.random"
	cat "$TMP/2.random" >> "$TMP/02/2.random"
	cat "$TMP/3.random" >> "$TMP/02/3.random"
	diffuzzy_compare "$TMP/01" "$TMP/02"
	if [ $? -ne 0 ]; then
		echo "error: test failed."
		return 1
	fi
	cat "$TMP/1.random" > "$TMP/02/3.random"
	diffuzzy_compare "$TMP/01" "$TMP/02"
	if [ $? -eq 0 ]; then
		echo "error: test failed."
		return 1
	fi
	rm -rf "$TMP/01" "$TMP/02"

	rm -rf "$TMP"
}

#------------------------------------------------------------------------------
# default configuration

COMPARE_EXISTENTIAL=true
COMPARE_MODE=false
COMPARE_TYPE=true
COMPARE_SIZE=true
COMPARE_DATA=true
COMPARE_HEADER=true
COMPARE_FOOTER=true
COMPARE_OFFSETS=true
COMPARE_BLOCKDATA=false
OFFSETS=rlog2
VERBOSE=0

#------------------------------------------------------------------------------
# command line arguments

THIS="$0"

if [ $# -eq 0 ]; then
	diffuzzy_help
	exit 1
fi
if [ $# -eq 1 ]; then
	case "$1" in
		"-h"|"--help")
			diffuzzy_help
			exit
			;;
		"--unittest")
			diffuzzy_unittest
			exit
			;;
		*)
			diffuzzy_help
			exit 1
			;;
	esac
fi
while [ $# -ne 0 ]; do
	case "$1" in
		"-m"|"--method")
			COMPARE_EXISTENTIAL=false
			COMPARE_MODE=false
			COMPARE_TYPE=false
			COMPARE_SIZE=false
			COMPARE_DATA=false
			COMPARE_HEADER=false
			COMPARE_FOOTER=false
			COMPARE_OFFSETS=false
			COMPARE_BLOCKDATA=false
			for M in $(echo -n "$2" | tr ',' ' '); do
				case "$M" in
					"existential") COMPARE_EXISTENTIAL=true ;;
					"mode") COMPARE_MODE=true ;;
					"type") COMPARE_TYPE=true ;;
					"size") COMPARE_SIZE=true ;;
					"data") COMPARE_DATA=true ;;
					"header")
						COMPARE_DATA=true
						COMPARE_HEADER=true
						;;
					"footer")
						COMPARE_DATA=true
						COMPARE_FOOTER=true
						;;
					"offsets")
						COMPARE_DATA=true
						COMPARE_OFFSETS=true
						;;
					"blockdata") COMPARE_BLOCKDATA=true ;;
				esac
			done
			shift 2
			;;
		"--offsets")
			OFFSETS="$2"
			shift 2
			;;
		"-v")
			VERBOSE=$(($VERBOSE+1))
			shift
			;;
		"--verbose")
			VERBOSE="$2"
			shift 2
			;;
		*)
			break;;
	esac
done
if [ $# -lt 2 ]; then
	echo "error: a minimum of two paths must be provided for comparison."
	exit 1
fi

#------------------------------------------------------------------------------
# prepare environment

diffuzzy_check_executable base64 "hint: on Gentoo this is provided by sys-apps/coreutils" || exit
if $COMPARE_BLOCKDATA; then
	diffuzzy_check_executable blockdev "hint: on Gentoo this is provided by sys-apps/util-linux" || exit
fi
diffuzzy_check_executable find "hint: on Gentoo this is provided by sys-apps/findutils" || exit
diffuzzy_check_executable sort "hint: on Gentoo this is provided by sys-apps/coreutils" || exit
diffuzzy_check_executable stat "hint: on Gentoo this is provided by sys-apps/coreutils" || exit
diffuzzy_check_executable uniq "hint: on Gentoo this is provided by sys-apps/coreutils" || exit

#------------------------------------------------------------------------------
# begin execution

diffuzzy_compare "$@"
