#!/bin/bash

declare    PMODULES_MODULEFILES_DIR='modulefiles'
declare    PMODULES_VERSION='@PMODULES_VERSION@'
declare -A GroupDepths=()

declare -a Overlays=()
declare -A OverlayInfo
declare -A Dir2OverlayMap

declare -r ol_normal='n'
declare -r ol_hiding='h'
declare -r ol_replacing='r'


#
# compute depth of modulefile directory.
#
# Args:
#       $1: absolute path of a modulefile directory
#
compute_group_depth () {
	local -n result="$1"
        local -r dir="$2"
	if [[ ! -d "${dir}" ]]; then
		${mkdir} -p "${dir}" || \
			std::die 1 "Cannot create directory -- ${dir}"
	fi
        local group=${dir%/*}
        local group=${group##*/}
	result=$(${find} "${dir}" -depth \( -type f -o -type l \) \
                          -printf "%d" -quit 2>/dev/null)
	(( result-=2 )) || :
	# if a group doesn't contain a modulefile, depth is negativ
	# :FIXME: better solution?
	(( result < 0 )) && (( result = 0 )) || :
}

#
# (Re-)Scan available groups in given overlays and compute group depth's
#
# Args:
#       $@: overlay names
#
scan_groups () {
        local ol
	local depth
        for ol in "$@"; do
		local modulefiles_root="${OverlayInfo[${ol}:modulefiles_root]}"
	        local dir
	        for dir in "${modulefiles_root}"/*/"${PMODULES_MODULEFILES_DIR}"; do
			local group="${dir%/*}"
			group="${group##*/}"
			if [[ ! -v GroupDepths[${group}] ]]; then
				compute_group_depth depth "${dir}"
				GroupDepths[$group]=${depth}
			fi
			Dir2OverlayMap[${dir%/"${PMODULES_MODULEFILES_DIR}"*}]="${ol}"
	        done
        done
}

declare -A DefaultPmodulesConfig=(
	['defaultgroups']='Tools:Programming'
	['default_groups']='Tools:Programming'
	['defaultreleasestages']='stable'
	['default_reltages']='stable'
	['tmpdir']="/opt/psi/var/tmp/${USER}"
	['tmp_dir']="/opt/psi/var/tmp/${USER}"
	['distfilesdir']='/opt/psi/var/distfiles'
	['distfiles_dir']='/opt/psi/var/distfiles'
	['download_dir']='/opt/psi/var/distfiles'
	['overlays']=''
)

declare -A OverlayConfigKeys=(
	['install_root']='/opt/psi'
	['modulefiles_root']=''
	['excludes']=''
	['type']='n'
)

pm::get_value(){
	local -- yaml_input="$1"
	local -n val="$2"
	local -- key="$3"
	local -- expected_type="$4"
	
	local -- type=''
	type=$( ${yq} -e ".${key} | type" 2>/dev/null <<<"${yaml_input}")
	if [[ "${type}" != "${expected_type}" ]]; then
		std::die 3 "%s" \
			 "Value of '${key}' must be of type '${expected_type:2}', but is '${type:2}'!"
	fi
	val=$( ${yq} -e ".${key}" \
		     2>/dev/null <<<"${yaml_input}" ) || val=''
}
pm::get_seq(){
	local -- yaml_input="$1"
	local -n val="$2"
	local -- key="$3"

	local -- type=''
	type=$( ${yq} ".${key} | type" 2>/dev/null <<<"${yaml_input}")
	if [[ "${type:2}" == 'null' ]]; then
		val=''
		return 0
	fi
	if [[ "${type}" != '!!seq' ]]; then
		std::die 3 "%s" \
			 "Value of '${key}' must be of type 'seq', but is of type '${type:2}'!"
	fi
	val=$( ${yq} -e ".${key}[]" \
		     2>/dev/null <<<"${yaml_input}" ) || val=''
}


pm::read_config(){
	: "
	Read Pmodules configuration files '${PMODULES_ROOT}/config/Pmodules.yaml'
	and '${HOME}/.Pmodules/Pmodules.yaml'.

	Args:
		none
	"
	get_config_of_overlay(){
		: "
		Get configuration of an overlay.

		Args:
			$1 YAML config
			$2 name of overlay
		"
		local -r yaml_input="$1"
		local -r ol_name="$2"
		local -- key=''
		local -a keys=()
		local -- value=''

		Overlays+=( "${ol_name}" )
		# init overlay with defaults
		for key in "${!OverlayConfigKeys[@]}"; do
			OverlayInfo[${ol_name}:${key}]="${OverlayConfigKeys[${key}]}"
		done
		# get keys in YAML input
		readarray -t keys < <( ${yq} -e ".|keys|.[]" <<<"${yaml_input}" 2>/dev/null ) || \
			std::die 3 "Oops: retrieving keys from:\n${yaml_input}"
		for key in "${keys[@]}"; do
			[[ -v OverlayConfigKeys[${key,,}] ]] || \
				std::die 3 "%s -- %s\n%s" \
					 "Invalid key in configuration" \
					 "${key}" "${yaml_input}"
			case ${key,,} in
				install_root )
					pm::get_value "${yaml_input}" value "${key}" '!!str'
					OverlayInfo[${ol_name}:install_root]=$(${envsubst} <<< "${value}")
					[[ -d ${OverlayInfo[${ol_name}:install_root]} ]] || \
						std::die 3 \
							 "Invalid installation root directory for overlay '${ol_name}' -- ${value}"
					;;
				modulefiles_root )
					pm::get_value "${yaml_input}" value "${key}" '!!str'
					OverlayInfo[${ol_name}:modulefiles_root]=$(${envsubst} <<< "${value}")
					[[ -d ${OverlayInfo[${ol_name}:modulefiles_root]} ]] || \
						std::die 3 \
							 "Invalid modulefiles root directory for overlay '${ol_name}' -- ${value}"
					;;
				type )
					pm::get_value "${yaml_input}" value "${key}" '!!str'
					case ${value} in
						"${ol_normal}" | "${ol_replacing}" | "${ol_hiding}" )
							:
							;;
						* )
							std::die 3 "Invalid type for overlay '${ol_name}' -- ${type}"
							;;
					esac
					OverlayInfo[${ol_name}:type]="${value}"
					;;
				excludes )
					pm::get_seq "${yaml_input}" value "${key}" '!!seq'
					local -a tmp_array=()
					readarray -t tmp_array <<<${value}
					local excludes=''
					printf -v excludes "%s:" "${tmp_array[@]}"
					OverlayInfo[${ol_name}:excludes]=$(${envsubst} <<<"${excludes%:}" )
					;;
					;;
			esac
		done
		OverlayInfo[${ol_name}:used]='no'
		if [[ -z "${OverlayInfo[${ol_name}:modulefiles_root]}" ]]; then
			OverlayInfo[${ol_name}:modulefiles_root]=${OverlayInfo[${ol_name}:install_root]}
		fi
		local modulefiles_root=${OverlayInfo[${ol_name}:modulefiles_root]}
		Dir2OverlayMap[${modulefiles_root}]="${ol_name}"
	}

	get_config_of_overlays(){
		: "
		Get configuration of overlays in YAML input.

		Args:
			$1 YAML input
		"
		local -r yaml_input="$1"
		local -- overlay=''
		local -a overlays=()
		readarray -t overlays < <( ${yq} -e '.|keys|.[]' <<<"${yaml_input}" )

		for overlay in "${overlays[@]}"; do
			local yaml_tmp
			pm::get_value "${yaml_input}" yaml_tmp "${overlay}" '!!map'
			get_config_of_overlay "${yaml_tmp}" "${overlay}"
		done
	}

	get_config(){
		: "
		Get Pmodules configuration.

		Args:
			$1 Pmodules configuration file
		"
		local -r config_file="$1"
		local -- yaml_input=''
		yaml_input=$( ${yq} -Ne e "." "${config_file}" 2>/dev/null ) || \
			std::die 3 "Cannot read configuration file '${config_file}'!"

		local -a keys=()
		readarray -t keys < <( ${yq} -e ".|keys().[]" <<<"${yaml_input}" 2>/dev/null ) || \
			std::die 3 "Oops: retrieving keys from:\n${yaml_input}"
		for key in "${keys[@]}"; do
			[[ -v DefaultPmodulesConfig[${key,,}] ]] || \
				std::die 3 "%s -- %s\n%s" \
					 "Invalid key in configuration" \
					 "${key}" "${yaml_input}"
			case ${key,,} in
				defaultgroups | default_groups )
					pm::get_value "${yaml_input}" DefaultGroups "${key}" '!!str'
					;;
				defaultreleasestages | default_reltages )
					pm::get_value "${yaml_input}" DefaultReleaseStages "${key}" '!!str'
					;;
				tmpdir | tmp_dir )
					pm::get_value "${yaml_input}" TmpDir "${key}" '!!str'
					;;
				distfilesdir | download_dir )
					pm::get_value "${yaml_input}" DistfilesDir "${key}" '!!str'
					;;
				overlays )
					local tmp_str=''
					pm::get_value "${yaml_input}" tmp_str "${key}" '!!map'
					get_config_of_overlays "${tmp_str}"
					;;
			esac
		done
	}

	# system config file
	local -r sys_config_file="${PMODULES_HOME%%/Tools*}/config/Pmodules.yaml"
	test -r "${sys_config_file}" || \
		std::die 3 \
			 "%s %s -- %s" \
			 "base overlay definition file" \
			 "does not exist or is not readable" \
			 "$_"
	DefaultGroups="${DefaultPmodulesConfig['default_groups']}"
	DefaultReleaseStages="${DefaultPmodulesConfig['default_reltages']}"
	TmpDir="${DefaultPmodulesConfig['tmp_dir']}"
	DistfilesDir="${DefaultPmodulesConfig['download_dir']}"

	get_config "${sys_config_file}"

	local -r usr_config_file="${HOME}/.Pmodules/Pmodules.yaml"
	if [[ -r "${usr_config_file}" ]]; then
		get_config "${usr_config_file}"
	fi
}

# Local Variables:
# mode: sh
# sh-basic-offset: 8
# tab-width: 8
# End:
