#!/usr/bin/env bash
# -----------------------------------------------------------------------------
# bat-extras | Copyright (C) 2019-2020 eth-p | MIT License
#
# Repository: https://github.com/eth-p/bat-extras
# Issues:     https://github.com/eth-p/bat-extras/issues
# -----------------------------------------------------------------------------
source "${LIB}/constants.sh"

# An array of functions to call before returning from `shiftopt`.
#
# If one of these functions returns a successful exit code, the
# option will be transparently skipped instead of handled.
SHIFTOPT_HOOKS=()

# A setting to change how `shiftopt` will interpret short options that consist
# of more than one character.
#
# Values:
#
#     SPLIT  -- Splits the option into multiple single-character short options.
#               "-abc" -> ("-a" "-b" "-c")
#     
#     VALUE  -- Uses the remaining characters as the value for the short option.
#               "-abc" -> ("-a=bc")
#
#     CONV   -- Converts the argument to a long option.
#               "-abc" -> ("--abc")
#
#     PASS   -- Pass the argument along as-is.
#               "-abc" -> ("-abc")
#
SHIFTOPT_SHORT_OPTIONS="VALUE"

# Sets the internal _ARGV, _ARGV_INDEX, and _ARGV_LAST variables used when
# parsing options with the shiftopt and shiftval functions.
#
# Arguments:
#     ... -- The program arguments.
# 
# Example:
#     setargs "--long=3" "file.txt"
setargs() {
	_ARGV=("$@")
	_ARGV_LAST="$((${#_ARGV[@]} - 1))"
	_ARGV_INDEX=0
	_ARGV_SUBINDEX=1
}

# Gets all the remaining unparsed arguments and saves them to a variable.
#
# Arguments:
#     "-a" -- Append the arguments to the variable instead of replacing it.
#     $1   -- The variable to save the args to.
# 
# Example:
#     getargs remaining_args
getargs() {
	if [[ "$1" = "-a" || "$1" = "--append" ]]; then
		if [[ "${_ARGV_INDEX}" -ne "$((_ARGV_LAST+1))" ]]; then
			eval "$2=(\"\${$2[@]}\" $(printf '%q ' "${_ARGV[@]:$_ARGV_INDEX}"))"
		fi
	else
		if [[ "${_ARGV_INDEX}" -ne "$((_ARGV_LAST+1))" ]]; then
			eval "$1=($(printf '%q ' "${_ARGV[@]:$_ARGV_INDEX}"))"
		else
			eval "$1=()"
		fi
	fi
}

# Resets the internal _ARGV* variables to the original script arguments.
# This is the equivalent of storing the top-level $@ and using setargs with it.
resetargs() {
	setargs "${_ARGV_ORIGINAL[@]}"
}

# INTERNAL.
#
# Increments the argv index pointer used by `shiftopt`.  
_shiftopt_next() {
	_ARGV_SUBINDEX=1
	((_ARGV_INDEX++)) || true
}

# Gets the next option passed to the script.
#
# Variables:
#     OPT  -- The option name.
#
# Returns:
#     0  -- An option was read.
#     1  -- No more options were read.
#
# Example:
#     while shiftopt; do
#         shiftval
#         echo "$OPT = $OPT_VAL"
#     done
shiftopt() {
	# Read the top of _ARGV.
	[[ "$_ARGV_INDEX" -gt "$_ARGV_LAST" ]] && return 1
	OPT="${_ARGV[$_ARGV_INDEX]}"
	unset OPT_VAL

	if [[ "$OPT" =~ ^-[a-zA-Z0-9_-]+=.* ]]; then
		OPT_VAL="${OPT#*=}"
		OPT="${OPT%%=*}"
	fi
	
	# Handle short options.
	if [[ "$OPT" =~ ^-[^-]{2,} ]]; then
		case "$SHIFTOPT_SHORT_OPTIONS" in
		 	# PASS mode: "-abc=0" -> ("-abc=0")
			PASS) _shiftopt_next ;;

			# CONV mode: "-abc=0" -> ("--abc=0")
			CONV) OPT="-${OPT}"; _shiftopt_next ;; 

			# VALUE mode: "-abc=0" -> ("-a=bc=0")
			VALUE) {
				OPT="${_ARGV[$_ARGV_INDEX]}"
				OPT_VAL="${OPT:2}"
				OPT="${OPT:0:2}"
				_shiftopt_next
			} ;; 

			# SPLIT mode: "-abc=0" -> ("-a=0" "-b=0" "-c=0")
			SPLIT) {
				OPT="-${OPT:$_ARGV_SUBINDEX:1}"
				((_ARGV_SUBINDEX++)) || true
				if [[ "$_ARGV_SUBINDEX" -gt "${#OPT}" ]]; then
					_shiftopt_next
				fi
			} ;;

			# ????? mode: Treat it as pass.
			*)
				printf "shiftopt: unknown SHIFTOPT_SHORT_OPTIONS mode '%s'" \
					"$SHIFTOPT_SHORT_OPTIONS" 1>&2
				_shiftopt_next
				;;
		esac
	else
		_shiftopt_next
	fi

	# Handle hooks.
	local hook
	for hook in "${SHIFTOPT_HOOKS[@]}"; do
		if "$hook"; then
			shiftopt
			return $?
		fi
	done

	return 0
}

# Gets the value for the current option.
#
# Variables:
#     OPT_VAL  -- The option value.
#
# Returns:
#     0       -- An option value was read.
#     EXIT 1  -- No option value was available.
shiftval() {
	# Skip if a value was already provided.
	if [[ -n "${OPT_VAL+x}" ]]; then
		return 0
	fi
	
	if [[ "$_ARGV_SUBINDEX" -gt 1 && "$SHIFTOPT_SHORT_OPTIONS" = "SPLIT" ]]; then
		# If it's a short group argument in SPLIT mode, we grab the next argument.
		OPT_VAL="${_ARGV[$((_ARGV_INDEX+1))]}"
	else
		# Otherwise, we can handle it normally.
		OPT_VAL="${_ARGV[$_ARGV_INDEX]}"
		_shiftopt_next
	fi

	# Error if no value is provided.
	if [[ "$OPT_VAL" =~ -.* ]]; then
		printc "%{RED}%s: '%s' requires a value%{CLEAR}\n" "$PROGRAM" "$ARG"
		exit 1
	fi
}

# -----------------------------------------------------------------------------
setargs "$@"
_ARGV_ORIGINAL=("$@")
