setup() {
	set - --long-implicit implicit_value \
	      --long-explicit=explicit_value \
	      -i implicit \
	      -x=explicit \
	      -I1 group_implicit \
	      -X2=group_explicit \
	      positional_1 \
	      positional_2 \
	      --after-positional

	source "${LIB}/print.sh"
	source "${LIB}/opt.sh"
}

assert_opt_name() {
	assert [ "$OPT" = "$1" ]
}

assert_opt_value() {
	assert [ "$OPT_VAL" = "$1" ]
}

assert_opt_valueless() {
	assert [ -z "$OPT_VAL" ]
}


test:long() {
	description "Parse long options."

	while shiftopt; do
		if [[ "$OPT" = "--long-implicit" ]]; then
			assert_opt_valueless
			return
		fi
	done

	fail 'Failed to find option.'
}

test:long_implicit() {
	description "Parse long options in '--long value' syntax."

	while shiftopt; do
		if [[ "$OPT" = "--long-implicit" ]]; then
			shiftval
			assert_opt_value 'implicit_value'
			return
		fi
	done

	fail 'Failed to find option.'
}

test:long_explicit() {
	description "Parse long options in '--long=value' syntax."

	while shiftopt; do
		if [[ "$OPT" = "--long-explicit" ]]; then
			assert_opt_value 'explicit_value'
			shiftval
			assert_opt_value 'explicit_value'
			return
		fi
	done

	fail 'Failed to find option.'
}

test:short_default() {
	description "Parse short options in '-k' syntax."

	while shiftopt; do
		if [[ "$OPT" = "-i" ]]; then
			assert_opt_valueless
			return
		fi
	done

	fail 'Failed to find option.'
}

test:short() {
	description "Parse short options in '-k val' syntax."

	while shiftopt; do
		if [[ "$OPT" = "-i" ]]; then
			shiftopt
			assert_opt_valueless
			return
		fi
	done

	fail 'Failed to find option.'
}

test:short_implicit() {
	description "Parse short options in '-k value' syntax."

	while shiftopt; do
		if [[ "$OPT" = "-i" ]]; then
			shiftval
			assert_opt_value 'implicit'
			return
		fi
	done

	fail 'Failed to find option.'
}

test:short_explicit() {
	description "Parse short options in '-k=value' syntax."

	while shiftopt; do
		if [[ "$OPT" = "-x" ]]; then
			assert_opt_value 'explicit'
			shiftval
			assert_opt_value 'explicit'
			return
		fi
	done

	fail 'Failed to find option.'
}

test:short_default_mode() {
	description "Ensure the default mode for '-abc' is VALUE."
	assert_equal "$SHIFTOPT_SHORT_OPTIONS" "VALUE"
}

test:short_split_none() {
	description "Parse short options in '-abc' syntax with SPLIT mode."
	SHIFTOPT_SHORT_OPTIONS="SPLIT"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-I"|"-1")
				assert_opt_valueless
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 2
}
	
test:short_split_implicit() {
	description "Parse short options in '-abc val' syntax with SPLIT mode."
	SHIFTOPT_SHORT_OPTIONS="SPLIT"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-I"|"-1")
				assert_opt_valueless
				shiftval 
				assert_opt_value "group_implicit" 
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 2
}
	
test:short_split_explicit() {
	description "Parse short options in '-abc=val' syntax with SPLIT mode."
	SHIFTOPT_SHORT_OPTIONS="SPLIT"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-X"|"-2")
				assert_opt_value "group_explicit"
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 2
}

test:short_pass_none() {
	description "Parse short options in '-abc' syntax with PASS mode."
	SHIFTOPT_SHORT_OPTIONS="PASS"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-I"|"-1") fail "Short group -I1 was split." ;;
			"-I1")
				assert_opt_valueless
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 1
}
	
test:short_pass_implicit() {
	description "Parse short options in '-abc val' syntax with PASS mode."
	SHIFTOPT_SHORT_OPTIONS="PASS"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-I"|"-1") fail "Short group -I1 was split." ;;
			"-I1")
				assert_opt_valueless
				shiftval 
				assert_opt_value "group_implicit" 
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 1
}
	
test:short_pass_explicit() {
	description "Parse short options in '-abc=val' syntax with PASS mode."
	SHIFTOPT_SHORT_OPTIONS="PASS"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-X"|"-2") fail "Short group -X2 was split." ;;
			"-X2")
				assert_opt_value "group_explicit"
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 1
}

test:short_conv_none() {
	description "Parse short options in '-abc' syntax with CONV mode."
	SHIFTOPT_SHORT_OPTIONS="CONV"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-I"|"-1") fail "Short group -I1 was split." ;;
			"-I1") fail "Short group -I1 was not converted." ;;
			"--I1")
				assert_opt_valueless
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 1
}
	
test:short_conv_implicit() {
	description "Parse short options in '-abc val' syntax with CONV mode."
	SHIFTOPT_SHORT_OPTIONS="CONV"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-I"|"-1") fail "Short group -I1 was split." ;;
			"-I1") fail "Short group -I1 was not converted." ;;
			"--I1")
				assert_opt_valueless
				shiftval 
				assert_opt_value "group_implicit" 
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 1
}
	
test:short_conv_explicit() {
	description "Parse short options in '-abc=val' syntax with CONV mode."
	SHIFTOPT_SHORT_OPTIONS="CONV"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-X"|"-2") fail "Short group -X2 was split." ;;
			"-X2") fail "Short group -X2 was not converted." ;;
			"--X2")
				assert_opt_value "group_explicit"
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 1
}

test:short_value_none() {
	description "Parse short options in '-abc' syntax with VALUE mode."
	SHIFTOPT_SHORT_OPTIONS="VALUE"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-I1") fail "Short group -I1 was not truncated." ;;
			"-I")
				assert_opt_value "1"
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 1
}
	
test:short_value_implicit() {
	description "Parse short options in '-abc val' syntax with VALUE mode."
	SHIFTOPT_SHORT_OPTIONS="VALUE"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-I1") fail "Short group -I1 was not truncated." ;;
			"-I")
				shiftval 
				assert_opt_value "1" 
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 1
}
	
test:short_value_explicit() {
	description "Parse short options in '-abc=val' syntax with VALUE mode."
	SHIFTOPT_SHORT_OPTIONS="VALUE"

	local found=0
	while shiftopt; do
		case "$OPT" in
			"-X2") fail "Short group -X2 was not truncated." ;;
			"-X")
				assert_opt_value "2=group_explicit"
				((found++)) || true
				;;
		esac
	done

	assert_equal "$found" 1
}

test:hook() {
	description "Option hooks."
	
	SHIFTOPT_HOOKS=("example_hook")
	
	found=false
	example_hook() {
		if [[ "$OPT" = "--long-implicit" ]]; then
			found=true
			return 0
		fi
		return 1
	}

	while shiftopt; do
		if [[ "$OPT" = "--long-implicit" ]]; then
			fail "Option was not filtered by hook."
		fi
	done
	
	if ! "$found"; then
		fail "Option was not found by hook."
	fi
}

test:fn_setargs() {
	description "Function setargs."

	setargs "--setarg=true"
	shiftopt || true
	shiftval
	
	assert_opt_name "--setarg"
	assert_opt_value "true"
}

test:fn_resetargs() {
	description "Function resetargs."

	setargs "--setarg=true"
	resetargs
	shiftopt || true
	
	assert_opt_name "--long-implicit"
}

# shellcheck disable=SC2154
test:fn_getargs() {
	description "Function getargs."

	setargs "--one=two" "three" "--four"
	shiftopt
	
	getargs args
	
	assert_equal 2 "${#args[@]}"
	assert_equal "three" "${args[0]}"
	assert_equal "--four" "${args[1]}"
	
	# Ensure getargs doesn't remove remaining arguments.
	shiftopt
	assert_opt_name "three"
	
	# Ensure it doesn't set an empty string.
	args=(one two)
	setargs
	getargs args
	assert_equal 0 "${#args[@]}"
}

test:fn_getargs_append() {
	description "Function getargs -a."

	setargs "--two=three" "four"

	args=(zero one)
	getargs -a args

	assert_equal 4 "${#args[@]}"
	assert_equal "zero" "${args[0]}"
	assert_equal "one" "${args[1]}"
	assert_equal "--two=three" "${args[2]}"
	assert_equal "four" "${args[3]}"

	# Ensure it doesn't append empty strings.
	args=()
	setargs "zero" "one"
	getargs -a args
	assert_equal 2 "${#args[@]}"	
	
	args=(zero one)
	setargs
	getargs -a args
	assert_equal 2 "${#args[@]}"	
}
