#!/usr/bin/env bash

NAME="mvregex"
CODENAME="mvregex"
COPYRIGHT="Copyright (C) 2007 Nathan Shearer"
LICENSE="GNU General Public License 2.0"
VERSION="2.4.0.0"

# \brief Ensures dependencies are present
# \param $@ The dependencies to check for
function mvregex_check_dependencies
{
	for TOOL in "$@"; do
		if ! type "$TOOL" >/dev/null 2>/dev/null; then
			printf '%s\n' "$CODENAME: \"$TOOL\" is required for this application to work correctly." >&2
			exit
		fi
	done
}

function mvregex_help
{
	#              01234567890123456789012345678901234567890123456789012345678901234567890123456789
	printf '%s\n' "Description:"
	printf '%s\n' "  Move or rename files that match the given regular expression."
	printf '%s\n'
	printf '%s\n' "Usage:"
	printf '%s\n' "  $CODENAME [options] SOURCE_REGEX DESTINATION_REGEX FILE..."
	printf '%s\n' "  $CODENAME [options] --transliterate windows FILE..."
	printf '%s\n'
	printf '%s\n' "Options:"
	printf '%s\n' "  -f, --force"
	printf '%s\n' "    Do not prompt before overwriting files. Disabled by default, but writable"
	printf '%s\n' "    files are still overwritten by default."
	printf '%s\n' "    Without this option, overwrite files that are writable, and only prompt for"
	printf '%s\n' "    files that are not writable."
	printf '%s\n' "    With this option, overwrite all files without prompting."
	printf '%s\n' "    If you specify more than one of -f, -i, -n, only the final one takes effect."
	printf '%s\n' "    See -i or -n to disable overwriting existing files."
	printf '%s\n' "  -h, --help"
	printf '%s\n' "    Display this help message and exit."
	printf '%s\n' "  -i, --interactive"
	printf '%s\n' "    Prompt before overwriting files. Disabled by default."
	printf '%s\n' "    If you specify more than one of -f, -i, -n, only the final one takes effect."
	printf '%s\n' "  --merge"
	printf '%s\n' "    Merge files into existing destination folders. Disabled by default."
	printf '%s\n' "  -n, --no-clobber"
	printf '%s\n' "    Do not overwrite files. Disabled by default."
	printf '%s\n' "    If you specify more than one of -f, -i, -n, only the final one takes effect."
	printf '%s\n' "  --no-merge"
	printf '%s\n' "    Disable --merge."
	printf '%s\n' "  --no-recursive"
	printf '%s\n' "    Disable --recursive."
	printf '%s\n' "  -p, --pretend"
	printf '%s\n' "    Perform a simulation only. No files are actually moved ore renamed."
	printf '%s\n' "    This option will increase the verbosity level to 2."
	printf '%s\n' "  -r, --recursive"
	printf '%s\n' "    Process files in directories recursively. Disabled by default."
	printf '%s\n' "  --transliterate windows"
	printf '%s\n' "    Convert strings into a format supported by windows:"
	printf '%s\n' "      double quote '\"' (ASCII 0x22) -> two single quotes \"''\" (ASCII 0x2727)"
	printf '%s\n' "      colon ':' -> ratio '∶' U+2236 (UTF-8 0xE288B6)"
	printf '%s\n' "      asterisk '*' -> heavy asterisk '✱' U+2731 (UTF-8 0xE29CB1)"
	printf '%s\n' "      backslash '\\' -> underscore '_'"
	printf '%s\n' "      question mark '?' -> underscore '_'"
	printf '%s\n' "      pipe '|' -> underscore '_'"
	printf '%s\n' "      less than '<' -> underscore '_'"
	printf '%s\n' "      greater than '>' -> underscore '_'"
	printf '%s\n' "      remove trailing spaces (ASCII 0x20)"
	printf '%s\n' "      remove trailing spaces (cygwin UTF-8 0xEF80A8)"
	printf '%s\n' "      remove trailing period (ASCII 0x2E)"
	printf '%s\n' "      remove trailing period (cygwin UTF-8 0xEF80A9)"
	printf '%s\n' "  -v"
	printf '%s\n' "    Increase the verbosity level by 1."
	printf '%s\n' "  --verbose #"
	printf '%s\n' "    Use more or less verbose output. Valid values are:"
	printf '%s\n' "      0  Default. No output."
	printf '%s\n' "      1  Show only renamed files."
	printf '%s\n' "      2  Show subfiles that are moved during a folder merge."
	printf '%s\n' "      3  Show all files."
	printf '%s\n'
	printf '%s\n' "Examples:"
	printf '%s\n' "  Convert \"JPG\" extension to \"jpg\""
	printf '%s\n' "    $CODENAME -p -v '[jJ][pP][eE]?[gG]\$' 'jpg' *"
	printf '%s\n' "  Convert \"###\" to \"S#E##\""
	printf '%s\n' "    $CODENAME -p -v '^([0-9])([0-9][0-9])' 'S\1E\2' *.mkv"
	printf '%s\n' "  Convert upper case to lower case"
	printf '%s\n' "    $CODENAME -p -v '(.*)' '\L\1' *"
	printf '%s\n' "  Convert lower case to upper case"
	printf '%s\n' "    $CODENAME -p -v '(.*)' '\U\1' *"
	printf '%s\n' "  Remove trailing spaces on all files recursively"
	printf '%s\n' "    $CODENAME -p -r '^(.*) +$' '\1' *"
	printf '%s\n' "  Remove trailing spaces (cygwin encoded byte sequence EF80A8) on all files recursively"
	printf '%s\n' "    $CODENAME -p -r '^(.*)\\xEF\\x80\\xA8$' '\1' *"
	printf '%s\n' "  Remove trailing . on all files recursively"
	printf '%s\n' "    $CODENAME -p -r '^(.*)\\.+$' '\1' *"
	printf '%s\n' "  Remove trailing . (cygwin encoded byte sequence EF80A9) on all files recursively"
	printf '%s\n' "    $CODENAME -p -r '^(.*)\\xEF\\x80\\xA9$' '\1' *"
	printf '%s\n'
	printf '%s\n' "Version:"
	printf '%s\n' "  $NAME $VERSION"
	printf '%s\n' "  $COPYRIGHT"
	printf '%s\n' "  Licensed under $LICENSE"
}

function mvregex_main
{
	for SRC in "$@"; do
		if $RECURSIVE && [ "$(stat -c %F "$SRC")" = "directory" ]; then
			find "$SRC" -mindepth 1 -maxdepth 1 -print0 | \
				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=$(printf "%s" "$FILE" | sed -r 's/^\.\///')
						mvregex_main "$FILE"
						if [ $? -ne 0 ]; then
							STATUS=1
						fi
					done
					return $STATUS
				}
		fi

		local SRC_PATH=$(dirname "$SRC")
		if [ "$SRC_PATH" = '.' ]; then
			SRC_PATH=""
		else
			SRC_PATH="$SRC_PATH/"
		fi
		local SRC_NAME=$(basename "$SRC")
		local DST_NAME=
		if [ "$TRANSLITERATE" = "windows" ]; then
			DST_NAME="$SRC_NAME"
			# double quote '\"' -> two single quotes \"''\"
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/\"/''/g")
			# colon ':' -> ratio '∶' U+2236 (UTF-8 0xE288B6)
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/:/∶/g")
			# asterisk '*' -> heavy asterisk '✱' U+2731 (UTF-8 0xE29CB1)
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/\*/✱/g")
			# backslash '\\' -> underscore '_'
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/\\//_/g")
			# question mark '?' -> underscore '_'
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/\\?/_/g")
			# pipe '|' -> underscore '_'
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/\\|/_/g")
			# less than '<' -> underscore '_'
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/</_/g")
			# greater than '>' -> underscore '_'
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/>/_/g")
			# remove trailing spaces (ASCII 0x20)
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/^(.*[^ ]) +$/\\1/")
			# remove trailing spaces (cygwin UTF-8 0xEF80A8)
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/^(.*[^\xEF\x80\xA8])\xEF\x80\xA8+$/\\1/")
			# remove trailing period (ASCII 0x2E)
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/^(.*[^.])\\.+$/\\1/")
			# remove trailing period (cygwin UTF-8 0xEF80A9)
			DST_NAME=$(printf %s "$DST_NAME" | sed -r "s/^(.*[^\xEF\x80\xA9])\xEF\x80\xA9+$/\\1/")
		else
			DST_NAME=$(printf %s "$SRC_NAME" | sed -r "s/$SOURCE_REGEX/$DESTINATION_REGEX/")
		fi
		if [ \( $VERBOSE -ge 3 \) -a \( "$SRC_NAME" = "$DST_NAME" \) ]; then
			printf '%s\n' "'$SRC_PATH$SRC_NAME' -> '$SRC_PATH$DST_NAME'"
		fi
		if [ "$SRC_NAME" != "$DST_NAME" ]; then
			mvregex_mv "$SRC_PATH$SRC_NAME" "$SRC_PATH$DST_NAME"
			if [ \( $? -eq 0 \) -a  \( $VERBOSE -ge 1 \) ]; then
				printf '%s\n' "'$SRC_PATH$SRC_NAME' -> '$SRC_PATH$DST_NAME'"
			fi
		fi
	done
}

function mvregex_mv
{
	local SRC="$1"
	local DST="$2"
	
	if $MERGE && [ -d "$SRC" -a -d "$DST" ]; then
		find "$SRC" -mindepth 1 -maxdepth 1 -print0 | \
			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=$(printf "%s" "$FILE" | sed -r 's/^\.\///')
					local FILE_BASENAME=$(basename "$FILE")
					if [ $VERBOSE -ge 2 ]; then
						printf '%s\n' "'$FILE' -> '$DST/$FILE_BASENAME'"
					fi
					mvregex_mv "$FILE" "$DST/$FILE_BASENAME"
					if [ $? -ne 0 ]; then
						STATUS=1
					fi
				done
				return $STATUS
			}
		$PRETEND || rmdir "$SRC"
		return
	fi
	if ! $PRETEND && [ "$SRC" != "$DST" ]; then
		mv $MV_ARGS -- "$SRC" "$DST"
	fi
}

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

MV_ARGS="-T"
MERGE=false
PRETEND=false
RECURSIVE=false
TRANSLITERATE=false
VERBOSE=0

#------------------------------------------------------------------------------
# config files

if [ -r /etc/$CODENAME.conf ]; then
	. /etc/$CODENAME.conf
fi
if [ -r ~/.$CODENAME.conf ]; then
	. ~/.$CODENAME.conf
fi

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

if [ $# -eq 0 ]; then
	mvregex_help
	exit 1
fi

THIS="$0"
while [ $# -ne 0 ]; do
	case "$1" in
		"-f"|"--force")
			MV_ARGS="$MV_ARGS -f"
			shift
			;;
		"-h"|"--help")
			mvregex_help
			exit
			;;
		"-i"|"--interactive")
			MV_ARGS="$MV_ARGS -i"
			shift
			;;
		"--merge")
			MERGE=true
			shift
			;;
		"-n"|"--no-clobber")
			MV_ARGS="$MV_ARGS -n"
			shift
			;;
		"--no-merge")
			MERGE=false
			shift
			;;
		"--no-recursive")
			RECURSIVE=false
			shift
			;;
		"-p"|"--pretend")
			PRETEND=true
			if [ "$VERBOSE" -lt 2 ]; then
				VERBOSE=2
			fi
			shift
			;;
		"-r"|"--recursive")
			RECURSIVE=true
			shift
			;;
		"--transliterate")
			TRANSLITERATE="$2"
			shift 2
			;;
		"-v")
			VERBOSE=$(($VERBOSE+1))
			shift
			;;
		"--verbose")
			VERBOSE="$2"
			shift 2
			;;
		*)
			break;;
	esac
done

if [ "$TRANSLITERATE" = "false" ]; then
	SOURCE_REGEX="$1"
	shift
	DESTINATION_REGEX="$1"
	shift
else
	if [ "$TRANSLITERATE" != "windows" ]; then
		printf '%s\n' "error: unrecognized transliterate option \"$TRANSLITERATE\""
		exit 1
	fi
fi

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

mvregex_check_dependencies find sed mv

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

mvregex_main "$@"
