# UI POSIX shell options library
# Note: This currently needs "bash" as shell, so it's not that "POSIX"ish after all

# Recommended way to source this in your shell scripts:
### PATH="${PATH}:/usr/local/share/ui-auto:/usr/share/ui-auto"
### . ui-libopt.sh

# For documentation of function parameters, refer to the local
# variables at the very beginning of each implementation.

# For debug output on error only
UI_OPT_CMDLINE="$(basename "${0}") ${@}"

ui_opt_init()
{
	local desc="${1}"
	local doc="${2}"

	UI_OPT_SYNTAX=""
	UI_OPT_NAME=$(basename "${0}")
	UI_OPT_DESC="${desc}"
	UI_OPT_DOC="${doc}"
	UI_OPT_POSITIONAL=0
}

ui_opt_add()
{
	local id="${1}"
	local desc="${2}"
	local default="${3}"
	local implies="${4}"
	local doc="${5}"
	local expert="${6}"

	local idLetter="${id:0:1}"
	case ${idLetter} in h|H) ui_opt_error "ui-libopt.sh usage: Options -h and -H are reserved (fix your sript)";; esac
	local syntax="-${idLetter}$(if [ "${id:1:1}" = ":" ]; then echo -n " arg"; fi)"

	UI_OPT_SYNTAX="${UI_OPT_SYNTAX}${id}"
	eval "UI_OPT_DESC_${idLetter}=\"${desc}\""
	eval "UI_OPT_DEFAULT_${idLetter}=\"${default}\""
	eval "UI_OPT_SYNTAX_${idLetter}=\"${syntax}\""
	eval "UI_OPT_IMPLIES_${idLetter}=\"${implies}\""
	eval "UI_OPT_DOC_${idLetter}=\"${doc}\""
	eval "UI_OPT_EXPERT_${idLetter}=\"${expert}\""
}

ui_opt_addPos()
{
	local id="${1}"
	local desc="${2}"
	local default="${3}"
	local doc="${4}"

	eval "UI_OPT_ID_POSITIONAL_${UI_OPT_POSITIONAL}=\"${id}\""
	eval "UI_OPT_DESC_POSITIONAL_${UI_OPT_POSITIONAL}=\"${desc}\""
	eval "UI_OPT_DEFAULT_POSITIONAL_${UI_OPT_POSITIONAL}=\"${default}\""
	eval "UI_OPT_DOC_POSITIONAL_${UI_OPT_POSITIONAL}=\"${doc}\""
	UI_OPT_POSITIONAL="$((UI_OPT_POSITIONAL+1))"
}

ui_opt_parse()
{
	local OPTIND=1 OPTARG OPTERR=1 o

	# Options
	while getopts "hH${UI_OPT_SYNTAX}" o; do
		if [ "${o}" = "?" ]; then
			ui_opt_help >&2
			exit 1
		elif [ "${o}" = "h" ]; then
			ui_opt_help
			exit 0
		elif [ "${o}" = "H" ]; then
			ui_opt_help long
			exit 0
		fi
		eval "UI_OPT_${o}=\"${OPTARG}\""
		local impliesVar="UI_OPT_IMPLIES_${o}"
		for implied in ${!impliesVar}; do
			if ! ui_opt_given ${implied}; then
				ui_opt_set ${implied}
			fi
		done
	done

	# Positionals
	for ((i=0; i <= $((${#}-OPTIND)); ++i)) do
		local j=$((i+OPTIND))
		UI_OPT_positional[${i}]="${!j}"
	done
}

# Show usage
ui_opt_usage()
{
	echo -n "Usage: ${UI_OPT_NAME} [-h|-H]"
	for ((i=0; i < ${#UI_OPT_SYNTAX}; ++i)); do
		local o="${UI_OPT_SYNTAX:${i}:1}"
		if [ "${o}" != ":" ]; then
			local syntax="UI_OPT_SYNTAX_${o}"
			local expert="UI_OPT_EXPERT_${o}"
			if [ "${!expert}" != "expert" -o "${1}" = "long" ]; then
				echo -n " ${!syntax}"
			fi
		fi
	done
	for ((i=0; i < $((UI_OPT_POSITIONAL)); ++i)); do
		local id="UI_OPT_ID_POSITIONAL_${i}"
		echo -n " ${!id}"
	done
	echo
}

# Show help
ui_opt_help()
{
	[ -z "${UI_OPT_DESC}" ] || echo -e "${UI_OPT_DESC}\n"
	[ -z "${UI_OPT_DOC}" -o "${1}" != "long" ] || echo -e "${UI_OPT_DOC}\n" | sed -e 's/.*/ &/'

	ui_opt_usage "${1}"

	echo -e "\n -h|-H : Get usage help; short (-h) or complete (-H)."

	if [ ${#UI_OPT_SYNTAX} -gt 0 ]; then
		echo -e "\nOptions:"
		for ((i=0; i < ${#UI_OPT_SYNTAX}; ++i)); do
			local o="${UI_OPT_SYNTAX:${i}:1}"
			if [ "${o}" != ":" ]; then
				local expert="UI_OPT_EXPERT_${o}"
				if [ "${!expert}" != "expert" -o "${1}" = "long" ]; then
					local syntax="UI_OPT_SYNTAX_${o}"
					local desc="UI_OPT_DESC_${o}"
					local default="UI_OPT_DEFAULT_${o}"
					local implies="UI_OPT_IMPLIES_${o}"
					local doc="UI_OPT_DOC_${o}"

					local def=""
					[ -z "${!default}" ] || def=" ['${!default}']"
					local imp=""
					[ -z "${!implies}" ] || imp=" (implies ${!implies})"
					local exp=""
					[ -z "${!expert}" ] || exp=" {${!expert}}"
					printf " %-6s: %s%s%s%s\n" "${!syntax}" "${!desc}" "${def}" "${imp}" "${exp}"
					[ -z "${!doc}" -o "${1}" != "long" ] || echo "${!doc}" | sed -e 's/.*/           &/'
				fi
			fi
		done
	fi

	if [ ${UI_OPT_POSITIONAL} -gt 0 ]; then
		echo -e "\nPositionals:"
		for ((i=0; i < $((UI_OPT_POSITIONAL)); ++i)); do
			local id="UI_OPT_ID_POSITIONAL_${i}"
			local desc="UI_OPT_DESC_POSITIONAL_${i}"
			local default="UI_OPT_DEFAULT_POSITIONAL_${i}"
			local doc="UI_OPT_DOC_POSITIONAL_${i}"
			printf " %-6s: %s\n" "${!id}" "${!desc}"
			[ -z "${!default}" ] || echo -e "         Default='${!default}'."
			[ -z "${!doc}" -o "${1}" != "long" ] || echo "${!doc}" | sed -e 's/.*/           &/'
		done
	fi
}

# Check whether an option is given
ui_opt_given()
{
	local varName="UI_OPT_${1}"
	[ -n "${!varName+set}" ]
}

# Get option value from char identifier. Returns 1 if the option is
# not given and there is no default, 0 on success.
ui_opt_get()
{
	local varName="UI_OPT_${1}"
	local defaultName="UI_OPT_DEFAULT_${1}"

	if [ "${!varName+set}" ]; then
		echo -n "${!varName}"
	elif [ "${!defaultName}" ]; then
		echo -n "${!defaultName}"
	else
		ui_opt_error "Option ${1} not given, and has no default"
	fi
}

# Set option value from char identifier. You may use this if some
# option indirectly sets another one, for example.
ui_opt_set()
{
	local varName="UI_OPT_${1}"
	local varValue="${2}"

	eval "${varName}=\"${varValue}\""
}

# Check whether a positional argument is given
ui_opt_givenPos()
{
	local index="${1}"
	[ $((index)) -lt ${#UI_OPT_positional[*]} ]
}

# Get non-option options from index. Other behaviour like ui_opt_get.
ui_opt_getPos()
{
	local index="${1}"

	local defaultName="UI_OPT_DEFAULT_POSITIONAL_${index}"
	if [ $((index)) -lt ${#UI_OPT_positional[*]} ]; then
		echo -n "${UI_OPT_positional[${index}]}"
	elif [ "${!defaultName}" ]; then
		echo -n "${!defaultName}"
	else
		local idName="UI_OPT_ID_POSITIONAL_${index}"
		ui_opt_error "Positional arg ${!idName} not given, and has no default"
	fi
}

# Little helper to (re-)assmeble option line as given
ui_opt_assemble()
{
	for o in ${1}; do
		if ui_opt_given ${o}; then
			echo -n "-${o} $(ui_opt_get $o) "
		fi
	done
}

ui_opt_error()
{
	echo -e "E: ${1}.\nE: Failing command line: ${UI_OPT_CMDLINE}\n" >&2
	case "${2}" in
		HELP)
			ui_opt_help >&2
			;;
		QUIET)
			true
			;;
		*)
			ui_opt_usage >&2
			echo -e "\nUse '-h|-H' for full usage help." >&2
	esac
	exit 9
}

#
# Some non-opt common tools (too few yet to create another lib)
#
ui_check_installed()
{
	local chk="${1}"
	local wrn="${2}"

	local cmd=$(basename $(echo "${chk}" | cut -d" "  -f1))
	if ! ${chk} >/dev/null 2>&1; then
		local msg="${cmd} not installed, or needs an updated version. Failed test: \"${chk}\""
		if [ "${wrn}" = "warnonly" ]; then
			echo "W: ${msg}." >&2
		else
			ui_opt_error "${msg}"
		fi
	fi
}

ui_run_alt()
{
	if [ -x ${1} ]; then
		${1}
	else
		echo "W: Preferred tool \"${1}\" not found, running alternate \"${2}\""
		${2}
	fi
}

# [reproducible builds, man page fix]:
# - At build time, we need to use consistent HOME string (as we use -H to produce man pages).
# - At runtime, we must use the actual HOME
ui_reproducible_builds_home()
{
	if [ -n "${SOURCE_DATE_EPOCH}" ]; then
		printf "%s" "<user-home>"
	else
		printf "%s" "${HOME}"
	fi
}
