# shellcheck disable=SC2016,SC2207

_get_help() {
	"$@" -h 2>&1
}

_get_help_opt() {
	# Fetch all the optional parameters with help from _parse_help()
	_parse_help - < <(printf '%s\n' "$@")
}

_get_help_pos() {
	local pos

	# Fetch all the positional parameters, i.e. get first word prefixed
	# with 20h x 2. This may not be 100% accurate. Also, it won't return
	# any usable strings, it's just meant to point out what type of
	# mandatory argument given method depends on, like bdev_name, etc.
	# TODO: separate completion for such arguments, e.g., get all bdevs
	# for parameter like bdev_name?
	while read -r; do
		[[ $REPLY =~ ^\ {2}[^\ -] ]] || continue
		read -r pos _ <<< "$REPLY" && echo "$pos"
	done < <(printf '%s\n' "$@")
}

_get_default_rpc_methods() {
	local aliases method names
	# Don't squash whitespaces, slurp the entire line
	while read -r; do
		# Each method name seems to be prefixed with 20h x 4. Then it can
		# be followed with list of aliases enclosed inside (). Example:
		#    ioat_scan_accel_engine (ioat_scan_copy_engine, scan_ioat_copy_engine)
		[[ $REPLY =~ ^\ {4}([a-z]+(_[a-z]+)*)(\ *\((.+)\))? ]] || continue

		names=("${BASH_REMATCH[1]}")
		if [[ $SPDK_RPC_ALIASES == yes ]] && [[ -n ${BASH_REMATCH[4]} ]]; then
			IFS=", " read -ra aliases <<< "${BASH_REMATCH[4]}"
			names+=("${aliases[@]}")
		fi

		for method in "${names[@]}"; do
			rpc_methods["$method"]=1
		done
	done < <(_get_help "$1" 2> /dev/null)
}

_get_supported_methods() {
	local method methods

	if ! methods=($("$1" -s "$rpc_sock" rpc_get_methods 2> /dev/null)); then
		_get_default_rpc_methods "$1"
		return 0
	fi
	((${#methods[@]} > 0)) || return 0

	# Kill the json flavor
	methods=("${methods[@]//+(\"|,| )/}")
	unset -v "methods[0]" "methods[-1]" # []

	for method in "${methods[@]}"; do
		rpc_methods["$method"]=1
	done
}

_get_help_rpc_method() {
	local rpc=$1
	local method=$2
	local rpc_help opt

	mapfile -t rpc_help < <(_get_help "$rpc" "$method")

	_get_help_pos "${rpc_help[@]}"
	_get_help_opt "${rpc_help[@]}"
}

_is_rpc_method() {
	local word=$1

	[[ -v rpc_methods["$word"] ]]
}

_method_in_words() {
	for word in "${words[@]}"; do
		if _is_rpc_method "$word"; then
			echo "$word"
			return 0
		fi
	done
	return 1
}

_set_rpc_sock() {
	# Look for unix sock each app creates upon its execution. In
	# first instance, check the cmdline for an -s arg, if it's
	# followed by the path to the sock, use it.

	local word
	for ((word = 0; word < ${#words[@]}; word++)); do
		if [[ ${words[word]} == -s && -S ${words[word + 1]} ]]; then
			rpc_sock=${words[word + 1]}
			return 0
		fi
	done

	# default .sock
	[[ -S /var/tmp/spdk.sock ]] && rpc_sock=/var/tmp/spdk.sock

	return 0
}

_spdk_opt_to_complete() {
	local opt=$1

	case "$opt" in
		--pci-blocked | -B | --pci-allowed | -A)
			local pcis
			if [[ -e /sys/bus/pci/devices ]]; then
				pcis=(/sys/bus/pci/devices/*)
				pcis=("${pcis[@]##*/}")
			fi
			COMPREPLY=($(compgen -W '${pcis[*]}' -- "$cur"))
			compopt -o filenames
			;;
		--main-core | -p) # FIXME: Is this meant to be an actual core id or thread id? Assume the latter
			local cpus
			if [[ -e /sys/devices/system/cpu ]]; then
				cpus=(/sys/devices/system/cpu/cpu+([0-9]))
				cpus=("${cpus[@]##*cpu}")
			fi
			COMPREPLY=($(compgen -W '${cpus[*]}' -- "$cur"))
			;;
		--iova-mode)
			COMPREPLY=($(compgen -W 'pa va' -- "$cur"))
			;;
		--tpoint-group-mask | -e)
			COMPREPLY=($(compgen -W '$(_get_tpoint_g_masks)' -- "$cur"))
			compopt -o nosort
			;;
		--logflag)
			COMPREPLY=($(compgen -W '$(_get_log_flags)' -- "$cur"))
			;;
		--huge-dir)
			COMPREPLY=($(compgen -W '$(_get_fs_mounts "hugetlbfs")' -- "$cur"))
			compopt -o filenames
			;;
		--iflag | --oflag) # spdk_dd specific
			if [[ ${app##*/} == spdk_dd ]]; then
				COMPREPLY=($(compgen -W '$(_get_help_pos "${app_help[@]}")' -- "$cur"))
			fi
			;;
		*) return 1 ;;
	esac
	return 0
}

_get_fs_mounts() {
	[[ $(< /proc/filesystems) == *"$1"* ]] || return 0

	local mount fs mounts
	while read -r _ mount fs _; do
		[[ $fs == "$1" ]] && mounts+=("$mount")
	done < /proc/mounts

	if ((${#mounts[@]} > 0)); then
		printf '%s\n' "${mounts[@]}"
	fi
}

_get_from_spdk_help() {
	_get_help "$app" |& grep "$1"
}

_get_tpoint_g_masks() {
	local g_masks

	g_masks=$(_get_from_spdk_help "tracepoint group mask for spdk trace buffers") || return 0
	[[ $g_masks =~ \((.+)\) ]] || return 0

	IFS=", " read -ra g_masks <<< "${BASH_REMATCH[1]}"
	printf '%s\n' "${g_masks[@]}"
}

_get_log_flags() {
	local logflags

	logflags=$(_get_from_spdk_help "enable debug log flag") || return 0
	[[ $logflags =~ \((.+)\) ]] || return 0

	if [[ -n ${BASH_REMATCH[1]} && ${BASH_REMATCH[1]} != "not supported"* ]]; then
		IFS=", " read -ra logflags <<< "${BASH_REMATCH[1]}"
		printf '%s\n' "${logflags[@]}"
	fi
}

_is_app() {
	type -P "$1" > /dev/null
}

_rpc() {
	local cur prev words

	_init_completion || return
	_is_app "$1" || return

	local rpc=$1 rpc_sock="" method=""
	local -A rpc_methods=()

	_set_rpc_sock
	if [[ -S $rpc_sock ]]; then
		_get_supported_methods "$rpc"
	else
		_get_default_rpc_methods "$rpc"
	fi

	if method=$(_method_in_words); then
		COMPREPLY=($(compgen -W '$(_get_help_rpc_method "$rpc" "$method")' -- "$cur"))
		compopt -o nosort
	elif [[ $cur == -* ]]; then
		COMPREPLY=($(compgen -W '$(_parse_help "$rpc")' -- "$cur"))
	elif [[ $prev == --verbose ]]; then
		COMPREPLY=($(compgen -W 'DEBUG INFO ERROR' -- "$cur"))
	elif [[ $prev == -s ]]; then
		_filedir
	else
		COMPREPLY=($(compgen -W '${!rpc_methods[*]}' -- "$cur"))
	fi
}

_spdk_app() {
	local cur prev

	_init_completion || return
	_is_app "$1" || return

	local app=$1 app_help

	mapfile -t app_help < <(_get_help "$app")

	if [[ $cur == -* ]]; then
		COMPREPLY=($(compgen -W '$(_get_help_opt "${app_help[@]}")' -- "$cur"))
	else
		_spdk_opt_to_complete "$prev" || _filedir
	fi
}

# Build simple completion for some common spdk apps|tools
_spdk_apps() {
	local apps

	apps=(
		iscsi_tgt
		iscsi_top
		nvmf_tgt
		spdk_dd
		spdk_tgt
		spdk_top
		spdk_trace_record
		vhost
		create_vbox.sh
		pkgdep.sh
		run-autorun.sh
		vm_setup.sh
	) # TODO: Add more?

	complete -o default -F _spdk_app "${apps[@]}"
	complete -o default -F _rpc rpc.py
}

_spdk_apps

# Look for _configure(). If it exists, include default completions for path lookups
if [[ $(type -t _configure) == function ]]; then
	complete -o default -F _configure configure
fi
