#!@BASH@ --noprofile
#

#
# which information do we need for overlays?
# - list of used overlays by name
# - dict to map an overlay name to install_root
# - dict to map an overlay name to modulefile_root
# - dict to map an overlay name to the overlay type
# - dict to map moduledirs to overlay name

unset CDPATH	# unset CDPATH, otherwise 'cd' prints the directoy!
unset IFS	# use default IFS

shopt -s nullglob

# used in some output messages only
declare -r CMD='module'

declare -r mydir=$(cd $(dirname "$0") && pwd)
declare -- prefix=$(dirname "${mydir}")
declare -r libdir="${prefix}/lib"
declare -r libexecdir="${prefix}/libexec"

source "${libdir}/libstd.bash"
source "${libdir}/libpmodules.bash"

declare -r os_name="$(uname -s)"

path="${libexecdir}:/bin:/usr/bin"
std::def_cmds "${path}" \
	      'awk' 'base64' 'find' 'getopt' 'logger' 'mktemp' \
	      'rm' 'sed' 'sort' 'yq'

declare -rx TCL_LIBRARY="${PMODULES_HOME}/lib/tcl@TCL_VERSION@"
declare -x  TCLLIBPATH
std::prepend_path TCLLIBPATH "${PMODULES_HOME}/lib/Pmodules"

declare -r modulecmd="${libexecdir}/modulecmd.bin"

declare -- verbosity_lvl=${PMODULES_VERBOSITY:-'verbose'}

declare -- Shell=''

# the following settings are used if the config file doesn't exist

# set groups which should be available after initialization
declare -- DefaultGroups='Tools Programming'

# define available release stages
declare -- ReleaseStages=':unstable:stable:deprecated:'

# set releases which should be available after initialization
declare -- DefaultReleaseStages='stable'

# not used here but in modbuild
declare -- TmpDir="/opt/psi/var/tmp/${USER}"
declare -- DistfilesDir="/opt/psi/var/distfiles"


export_env() {
	case "${Shell}" in
		sh | bash | zsh )
			local -r fmt="export %s=\"%s\"; "
			;;
		csh | tcsh )
			local -r fmt="setenv %s \"%s\"; "
			;;
		* )
			std::die 1 "Unsupported shell -- ${Shell}"
			;;
	esac

	while (( $# > 0 )); do
		printf "${fmt}" "$1" "${!1}"
		shift
	done
	# :FIXME: UsedGroups can be modified in libmodule.tcl using
	# append-path/remove-path! But we keep the state in PMODULES_ENV
	# so we don't have to export it.
	# 
	case "${Shell}" in
	sh | bash | zsh )
		echo "unset UsedGroups; "
		;;
	csh | tcsh )
		echo "unsetenv UsedGroups; "
		;;
	esac
}

#
# Save/cache state in the environment variable PMODULES_ENV. The content is
# base64 encoded. This function is called on exit via a trap handler.
#
# Arguments:
#       none
#

declare g_env_must_be_saved='no'

save_env() {
	encode_base64(){
		case "${os_name}" in
			Linux )
				"${base64}" --wrap=0 <<< "$1"
				;;
			Darwin )
				# does not wrap if running in a script
				"${base64}"  <<< "$1"
				;;
			* )
				std::die 255 "Oops: Unsupported OS"
				;;
		esac
	}

        [[ $1 == 'no' ]] && return 0
        local vars=( Version )
	vars+=( UsedReleaseStages UsedFlags UsedGroups )
	vars+=( DefaultGroups DefaultReleaseStages )
        vars+=( ReleaseStages )
	vars+=( GroupDepths )
	vars+=( Overlays )
        vars+=( UsedOverlays )
        vars+=( OverlayInfo Dir2OverlayMap)

	local s=$(typeset -p ${vars[@]})
	declare -gx PMODULES_ENV=$( encode_base64 "$s" )
}

#
# function called on exit
#
_exit() {
	save_env "${g_env_must_be_saved}"
	export_env 'PMODULES_ENV'
	if [[ -n "${tmpfile}" ]] && [[ -e "${tmpfile}" ]]; then
		${rm} -f "${tmpfile}" || :
	fi
}
trap '_exit' EXIT

#
# get release stage of module
# Note:
# - the release stage of a module outside ${OverlayInfo[@]} is always 'stable'
# - the release stage of a module inside ${OverlayInfo[@]} without a
#   coresponding file is always 'unstable'
#
# Args:
#       $1      upvar for returned release stage
#	$2	modulefile directory (element of MODULEPATH)
# 	$3      module name/version
#
get_release_stage() {
	local "$1"
	local -r dir="$2"
	local -r modulefile="${dir}/$3"

	if [[ ! -v Dir2OverlayMap[${dir%/${PMODULES_MODULEFILES_DIR}*}] ]]; then
		std::upvar $1 'stable'
		return
	fi
        #
        # In an overlay the name of the module-file is something like
        #   dir/modulefiles/name/version
        # the corresponding file is
        #   dir/modulefiles/name/.release-version
        #
	local -r rel_stage_file="${modulefile%/*}/.release-${modulefile##*/}"
	if [[ -r ${rel_stage_file} ]]; then
		# read file, remove empty lines, spaces etc
		std::upvar $1 $( < "${rel_stage_file}" )
	else
		std::upvar $1 'unstable'
	fi
}

#
# check whether the argument in $1 is a valid release stage.
#
is_release_stage() {
	[[ :${ReleaseStages}: =~ :$1: ]]
}

#
# Check whether a given moduledir is in an used overlay.
# If yes, return 0 and the overlay with upvar of first argument
# otherwise return 1
#
# Arguments
#	$1	upvar to return overlay
#	$2	upvar to return group
#	$3	moduledir to check
#
find_overlay () {
        local "$1"
        local "$2"
	local path="${3//+(\/)/\/}"	# replace multpile '/' with one
	path="${path/%\/}"		# remove trailing slash if exist
	path="${path%/${PMODULES_MODULEFILES_DIR}*}"

	[[ -v Dir2OverlayMap[${path}] ]] || return 1

        local ol="${Dir2OverlayMap[${path}]}"
        std::upvar $1 "${ol}"

        local group="${path#${OverlayInfo[${ol}:mod_root]}/}"
        std::upvar $2 "${group}"
	return 0
}

#
# Check whether the module passed in argument $1 is loaded.
#
module_is_loaded() {
	[[ :${LOADEDMODULES}: =~ :$1: ]]
}

#
# Check shebang.
#
# Arguments:
#	$1	file name to test
#
is_modulefile() {
	local -r fname="$1"
	local -- shebang
	[[ -r ${fname} ]] || return 1
	read -n 11 shebang < "${fname}"
	[[ "${shebang:0:8}" == '#%Module' ]] || [[ "${shebang:0:9}" == '#%Pmodule' ]]
}

#
# Get the value of <module>_PREFIX.
#
# Arguments:
#	$1	upvar to return result
#	$2	modulefile
#
get_module_prefix() {
	local -n _prefix="$1"
	local -- _prefix=$("${modulecmd}" bash show "$2" 2>&1 | \
				   ${awk} '/_PREFIX |_HOME / {print $3; exit}')
}

#
# Generic wrappers of 'modulecmd':
#
# subcommand_generic0:
#	no argument allowed
# subcommand_generic1:
#	Exact one argument must be passed
# subcommand_generic1plus:
#	One or more arguments must be passed
#
# The options to output help are always accepted.
#
subcommand_generic0() {
	local -r subcommand="$1"
	shift
        local -a args=()
	while (( $# > 0 )); do
		case $1 in
			-\? | -H | --help )
				print_help "${subcommand}"
				;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift 1
	done
        if (( ${#args[@]} > 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "no arguments allowed"
        fi
	"${modulecmd}" "${Shell}" "${subcommand}"
}

subcommand_generic1() {
	local -r subcommand="$1"
	shift
	local -a args=()
	while (( $# > 0 )); do
		case $1 in
			-\? | -H | --help )
				print_help "${subcommand}"
				;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
	elif (( ${#args[@]} > 1 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "only one argument allowed"
	fi
	"${modulecmd}" "${Shell}" "${subcommand}" "${args[@]}"
}

subcommand_generic1plus() {
	local -r subcommand="$1"
	shift
	local args=()
	while (( $# > 0 )); do
		case $1 in
			-\? | -H | --help )
				print_help "${subcommand}"
				;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
	fi
	"${modulecmd}" "${Shell}" "${subcommand}" "${args[@]}"
}

##############################################################################
#
# load [-fsvw] <module>
#
# $1: module to load
#
Subcommands[add]='load'
Subcommands[load]='load'
Options[load]='-l help -o \?Hfsvw -l force -l silent -l verbose -l warn'
Help[load]='
USAGE:
        module add     modulefile...
        module load    modulefile...
                Load modulefile(s) into the shell environment. Loading a
		'group-head' will extend the MODULEPATH. E.g.: loading a
		compiler makes additional modules like openmpi and libraries
		compiled with this compiler available.
'

subcommand_load() {
        local -r subcommand='load'
	local rel_stage='undef'
	local current_modulefile=''
	local prefix=''
	local m=''

	#......................................................................
	# Test whether a given module is available.
	# The passed module-name can be
	#
	# - an absolute file- or link-name.
	#   The module can be either in- or outside our hierarchy.
	#
	# - a relative file- or link-name.
	#   The module can be either in- or outside out hierarchy.
	#
	# - specified with name and version (like gcc/5.2.0).
	#   The module can be either in- or outside our hierarchy.
	#
	# - specified with name only (without version, like gcc).
	#   The module can be either in- or outside our hierarchy.
	#
	# - directory in- or outsite our hierarchy (not supported by
        #   modulecmd.tcl!)
	#
	# arguments:
	# 	$1: module name or file
	#
	# possible return values:
	# 	0: module is loadable
	# 	1: either not a modulefile or unsused release stage
	#
	# The following variables in the enclosing function are set:
        #       current_modulefile
        #       prefix
	#	rel_stage
	#
	is_available() {
		local  m=$1
		local -a array
		#
		# the next command assigns the absolute modulefile path
		# to ${arry[0]} and - if the module is in our root - the
		# prefix of the module to ${array[1]}.
		#
		# The trick with the first line matching "_PREFIX" is not
		# 100% reliable: One of the Pmodules extensions must be
		# called before something like
		#   setenv FOO_PREFIX bar
		# can be used.
		#
		mapfile -t array  < <("${modulecmd}" 'bash' show "$m" 2>&1 | \
			${awk} 'NR == 2 {print substr($0, 1, length($0)-1)}; /_PREFIX |_HOME / {print $3; exit}')
		current_modulefile="${array[0]}"
		prefix="${array[1]}"
		test -n "${current_modulefile}" || return 1
		get_release_stage rel_stage "${current_modulefile}" "${UsedReleaseStages}"
	}

	#......................................................................
	# output load 'hints'
	#
	# Note:
	#	The variable 'm' from the parent function will be used
	#	but not changed.
	#
	# Args:
	#	none
	get_load_hints() {
		local "$1"
		local output=''
		local rel_stage=''
		while read -a line; do
                        [[ -z ${line} ]] && continue
			rel_stage=${line[1]}
			if [[ ! ":${UsedReleaseStages}:" =~ "${rel_stage}" ]]; then
				output+="module use ${rel_stage}; "
			fi
			local group=${line[2]}
	        	if [[ ! ":${UsedGroups}:" =~ ":${group}:" ]] && \
				   (( ${GroupDepths[${group}]} == 0 )); then
				output+="module use ${group}; "
			fi
			local -i n=$(( ${GroupDepths[${group}]}/2 ))
			output+="module load ${line[@]:3:$n} ${line[0]}\n"
		done < <(set +x; subcommand_search  "${m}" -a --no-header 2>&1)
		std::upvar $1 "${output}"
	}

	#......................................................................
        module_is_loaded() {
	        [[ :${LOADEDMODULES}: =~ :$1: ]]
        }

	#......................................................................
	load_dependencies() {
		local -r fname="$1"
		while read dep; do
			[[ -z ${dep} ]] && continue
			[[ ${dep:0:1} == \# ]] && continue
			module_is_loaded "${dep}" && continue
                        subcommand_load "${dep}"
		done < "${fname}"
	}

	#......................................................................
	# Check whether argument is a group.
	#
	# In the following function we test whether the group exist in
	# an overlay. It doesn't matter which overlay. If we find an
	# overlay providing modules for this group, we compute the
	# hierarchical depth of the group and save this value for later
	# use.
	#
	# Args:
	#       $1: group
	#
	# Notes:
	# This function is used with extended module names.
	#
	# Multiple overlays may provide modules for the same group. But the
	# hierarchical depth of a group must always be the same.
	#
	is_group () {
		local "$1"
		find_overlay_with_group() {
			local "$1"
			local -r group="$2/${PMODULES_MODULEFILES_DIR}"
			local ol
			for ol in "${UsedOverlays[@]}"; do
				local inst_root="${OverlayInfo[${ol}:inst_root]}"
				if [[ -d "${inst_root}/${group}" ]]; then
					std::upvar $1 "${ol}"
					return 0
				fi
			done
			return 1
		}
		local -r group="$1"
		# arg isn't emtpy and group already in cache
		[[ -n ${group} ]] && [[ -n ${GroupDepths[${group}]} ]] && return 0
		local ol=''
		find_overlay_with_group ol "${group}" || return 1
		local moduledir="${OverlayInfo[${ol}:mod_root]}/${group}/${PMODULES_MODULEFILES_DIR}"
		local -i depth
		compute_group_depth depth "${moduledir}" || return 1
		GroupDepths[${group}]=${depth}
		g_env_must_be_saved='yes'
	}

	#......................................................................
	local args=()
	local opts=()
        local overlay
	while (($# > 0)); do
		case $1 in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
			-f | --force )
				opts+=(' -f')
				;;
			-s | --silent )
				verbosity_lvl='silent'
				;;
			-v | --verbose )
				verbosity_lvl='verbose'
				;;
			-w | --warn )
				verbosity_lvl='warn'
				;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( $1 )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
                std::die 2 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
		         "No module specified"
	fi

	IFS=':'
	local -a modulepath=(${MODULEPATH})
	unset IFS

        local m=''
	for m in "${args[@]}"; do
		if [[ "$m" =~ ":" ]]; then

			# extendet module name is either
			# - group:name or
			# - group:name:rel_stage or
			# - rel_stage:name or
			# - rel_stage:group:name or
			# - name:rel_stage

			IFS=':'
			local -a toks=($m)
			unset IFS
			local group=''
			local rel_stage=''
			if is_group "${toks[0]}"; then
				group=${toks[0]}
				m=${toks[1]}
				rel_stage=${toks[2]}
			elif is_release_stage "${toks[0]}"; then
				rel_stage=${toks[0]}
				if is_group "${toks[1]}"; then
					group=${toks[1]}
					m=${toks[2]}
				else
					m=${toks[1]}
					group=${toks[2]}
				fi
			else
				m=${toks[0]}
				if is_group "${toks[1]}"; then
					group=${toks[1]}
					rel_stage=${toks[2]}
				else
					rel_stage=${toks[1]}
					group=${toks[2]}
				fi
			fi
			if [[ -n ${group} ]]; then
				is_group "${group}" || \
                                        std::die 3 "%s %s: %s -- %s" \
                                                 "${CMD}" "${subcommand}" \
                                                 "illegal group name" \
                                                 "${group}"
				local -i depth=${GroupDepths[${group}]}
				(( depth != 0 )) && \
                                        std::die 3 "%s %s: %s -- %s" \
                                                 "${CMD}" "${subcommand}" \
                                                 "illegal group name" \
                                                 "${group}"
				modulepath=()
				group+="/${PMODULES_MODULEFILES_DIR}"
                                for overlay in "${UsedOverlays[@]}"; do
					local mod_root="${OverlayInfo[${overlay}:mod_root]}"
				        modulepath=( "${mod_root}/${group}" "${modulepath[@]}" )
                                done
			fi
			if [[ -n ${rel_stage} ]]; then
			        is_release_stage "${rel_stage}" || \
                                        std::die 3 "%s %s: %s -- %s" \
                                                 "${CMD}" "${subcommand}" \
                                                 "illegal release stage" \
						 "${rel_stage}"
				std::append_path UsedReleaseStages "${rel_stage}"
                                g_env_must_be_saved='yes'
			fi
		fi # handle extended module names
                find_module current_modulefile rel_stage "${m}" "${modulepath[@]}"
                if [[ -z ${current_modulefile} ]]; then
			local text=''
			get_load_hints text
			if [[ -z "${text}" ]]; then
	                        std::die 3 "%s %s: module does not exist -- %s" \
                                          "${CMD}" 'load' "${m}"
			else
	                        std::info "%s %s: module unavailable -- %s" \
                                          "${CMD}" 'load' "${m}"
				if [[ ${verbosity_lvl} == 'verbose' ]]; then
				      	std::info ''
					std::info "Try with one of the following command(s):"
 					std::info "${text}"
				fi
	                        std::die 3 ""
                        fi
                fi
		if [[ ${m} == Pmodules/*  ]] && [[ -n ${LOADEDMODULES} ]]; then
			std::error "%s %s: %s" \
				   "${CMD}" "${subcommand}" \
				   "cannot load a Pmodules module because other modules are already load!"
			std::die 3 "%s %s: %s -- %s\n" \
                                 "${CMD}" "${subcommand}" \
                                 "failed" \
                                 "${m}"
		fi
		if [[ ":${LOADEDMODULES}:" =~ ":${m}:" ]]; then
			# already loaded
                        continue
		fi

		local prefix=''
		get_module_prefix prefix "${current_modulefile}"
                if [[ -n ${prefix} ]]; then
                        test -r "${prefix}/.info" && cat "$_" 1>&2
			test -r "${prefix}/.dependencies" && load_dependencies "$_"
                fi

		local output=$("${modulecmd}" 'bash' ${opts} 'load' \
                                              "${current_modulefile}" 2> "${tmpfile}")

                # we do not want to print the error message we got from
                # modulecmd, they are a bit ugly
                # :FIXME: Not sure whether this is now correct!
                # The idea is to supress the error messages from the Tcl
                # modulecmd, but not the output to stderr coded in a
                # modulefile.

		local error=$( < "${tmpfile}")
		if [[ "${error}" =~ ":ERROR:" ]]; then
                        local s=${error%%$'\n'*}
                        local error_txt='failed'
                        if [[ "$s" =~ ' conflicts ' ]]; then
                                error_txt="conflicts with already loaded module(s): ${s#*module(s) }"
                        fi
			std::die 3 "%s %s: %s -- %s" \
                                 "${CMD}" "${subcommand}" \
                                 "${error_txt}" \
                                 "${m}"
                fi
                if [[ "${Shell}" == "sh" ]]; then
			# for sh-like shells just echo 
                        echo "${output}"
                else
			# re-run with right shell
                        "${modulecmd}" "${Shell}" ${opts} 'load' \
                                       "${current_modulefile}"
                fi
		eval "${output}"
		if [[ -n "${error}" ]]; then
                       	echo "${error}" 1>&2
		fi
		local msg=''
		if [[ ${verbosity_lvl} != silent ]] && \
			   [[ ${rel_stage} != stable ]]; then
			msg=$(printf "%s %s: %s -- %s" \
				     "${CMD}" 'load' \
				     "${rel_stage} module has been loaded" \
				     "${m}")
		        std::info "%s" "${msg}"
		fi
		msg=$(printf "%s: %s %s %s" \
			     'load' \
			     "modulefile=${current_modulefile}" \
			     "rel-stage=${rel_stage}" \
			     "user=${USER}")
		${logger} -t Pmodules "${msg}"
	done
	# fix LOADEDMODULES
	LOADEDMODULES="${_LMFILES_}"
        local dir
	while read dir; do
		# if the first or last character of MODULEPATH is ':',
		# we read an empty string.
		[[ -z ${dir} ]] && continue
		[[ "${dir: -1}" == "/" ]] || dir+="/"
		LOADEDMODULES="${LOADEDMODULES//${dir}}"
	done <<< "${MODULEPATH//:/$'\n'}"
        g_env_must_be_saved='yes'
	export_env 'LOADEDMODULES'
}

##############################################################################
#
# unload
#
Subcommands[rm]='unload'
Subcommands[unload]='unload'
Options[unload]='-o \?H -l help'
Help[unload]="
USAGE:
        module rm      modulefile...
        module unload  modulefile...
                Remove modulefile(s) from the shell environment. Removing
		a 'group-head' will also unload all modules belonging to
		this group.
"

subcommand_unload() {
        local -r subcommand='unload'
	# :FIXME: add dependency tests: don't unload if module is required
        #         be another module.
        #         For the time being the modules requiring this module will
        #         be unloaded too.
	local args=()
	while (( $# > 0 )); do
		case $1 in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
	fi
	
	# The module() function uses PMODULES_HOME to call modulecmd.
	# If a Pmodules module is unloaded this evnvironment variable
	# will be unset. In consequence the module() function would
	# fail. Instead of comparing the name of the module to unload
	# with 'Pmodules', we save the value and set it at the end of
	# the loop again, if it has been unset.
	local saved_home="${PMODULES_HOME}"
	
        local arg
	for arg in "${args[@]}"; do
	        local output=$("${modulecmd}" "${Shell}" 'unload' "${arg}")
                eval "$(echo "${output}"|${sed} -e 's/;unalias [^;]*//g')"
		case ${Shell} in
			sh | bash | zsh )
				echo "${output}"
				;;
			* )
				"${modulecmd}" "${Shell}" 'unload' "${arg}"
                		;;
		esac
	done
	if [[ -z ${PMODULES_HOME} ]]; then
		PMODULES_HOME=${saved_home}
		export_env 'PMODULES_HOME'
	fi
	g_env_must_be_saved='yes'
} # subcommand_unload

##############################################################################
#
# swap <module> [<module>]
#
Subcommands[switch]='swap'
Subcommands[swap]='swap'
Options[swap]='-o \?H -l help'
Help[swap]="
USAGE:
        module switch  [modulefile1] modulefile2
        module swap    [modulefile1] modulefile2
		Switch loaded modulefile1 with modulefile2. If modulefile1
		is not specified, then it is assumed to be the currently
		loaded module with the same base name as modulefile2.
"

subcommand_swap() {
        local -r subcommand='swap'
	local args=()
	while (( $# > 0 )); do
		case $1 in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
        elif (( ${#args[@]} > 2 )); then
                std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "too many arguments"
	fi
        if (( ${#args[@]} == 1 )); then
                local -r module_to_load=${args[0]}
                local -r module_to_unload=${module_to_load%/*}
        else
                local -r module_to_unload=${args[0]}
                local -r module_to_load=${args[1]}
        fi
	subcommand_unload "${module_to_unload}"
        subcommand_load   "${module_to_load}"
}

##############################################################################
#
# show <module>
#
Subcommands[display]='show'
Subcommands[show]='show'
Options[show]='-o \?H -l help'
Help[show]='
USAGE:
        module display modulefile...
        module show    modulefile...
		Display information about one or more modulefiles.  The
		display sub-command will list the full path of the
		modulefile(s) and all (or most) of the environment changes
		the modulefile(s) will make if loaded.  It will not display
		any environment changes found within conditional statements.
'

subcommand_show() {
        local -r subcommand='show'
	local args=()
	while (( $# > 0 )); do
		case $1 in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
        if (( ${#args[@]} == 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "missing argument"
	fi

        local arg
	for arg in "${args[@]}"; do
	        "${modulecmd}" "${Shell}" "${subcommand}" "${arg}"
	done
}

###############################################################################
# Find all modules in a given modulepath matching a specific string.
# The search can be restricted to certain release stages.
#
# Args:
#	$1	upvar for results
#	$2	search pattern
#	$3	release stages
#	$4...	module directories
#
# return list like
#	modulename_1 rel_stage_1 modulefile_1 ...
#
get_available_modules() {
        local var="$1"
	local -r module="$2"
	local -r used_rel_stages="${3:-${UsedReleaseStages}}"
	shift 3 # in the for loop below we use $@ to loop over the directories
	local -a mods=()
	local    rel_stage

        local -A dict
	local -A modulenames
	local dir
        for dir in "$@"; do
                test -d "${dir}" || continue
	        {
		        cd "${dir}"
			# there might be no mapping for ${dir}!
			# - after loading the parent of a hierarchical group
			# - if we do a search
			# - if we create a new hierarchical group
			local ol=''
			local group=''
			find_overlay ol group "${dir}"

                        # if no modules are installed in ${dir}, '*' expands to
                        # the empty string! Using '*' in the find command below
                        # would cause problems with some non-GNU find
                        # implementations.
                        local entries=$(echo *)
			[[ -n ${entries} ]] || continue
			local mod=''	# module_name/module_version
                        while read mod; do
				local add='no'
				if [[ -n "${ol}" ]]; then
					# module is in an overlay
					#
					# add to list of available modules, if
					# - first time found by name only
					# - in same overlay as first found
					# - new version and not hidden by overlay
					local name="${mod%/*}"
					local key="${dir##/${PMODULES_MODULEFILES_DIR}}/${name}"
					if [[ -z "${modulenames[${name}]}" ]]; then
						if [[ "${OverlayInfo[${ol}:type]}" == "${ol_hiding}" ]]; then
							modulenames[${name}]="${ol}"
						else
							modulenames[${name}]='0'
						fi
						add='yes'
					elif [[ "${modulenames[${name}]}" == "${ol}" ]]; then
						add='yes'
					elif [[ "${modulenames[${name}]}" == '0'  ]] \
						     && [[ -z ${dict[${mod}]} ]]; then
						add='yes'
					fi
				else
					# module is NOT in an overlay
					add='yes'
				fi
				[[ "${add}" == 'no' ]] && continue
			        get_release_stage \
					rel_stage \
					"${dir}" \
					"${mod}"
				[[ :${used_rel_stages}: =~ :${rel_stage}: ]] || continue

			        mods+=( "${mod}" ${rel_stage} "${dir}/${mod}" "${ol}" )
                                dict[${mod}]=1
		        done < <(${find} -L ${entries} \
					 \( -type f -o -type l \) \
					 -not -name ".*" \
                                         -ipath "${module}*" \
					 | ${sort} --version-sort)
	        }
        done
	std::upvar ${var} "${mods[@]}"
} # get_available_modules()

#
# find module(file) to load. Input arguments are
#       $1      upvar: return module file
#       $2      upvar: return module release stage
#       $3      module to load
#       $4      a modulepath (usually MODULEPATH)
#
# The module name can be
#       name
#       name/version
#       name/version_flag
#
# Return
#       return code 0 and modulefile in first argument
#       return code 1 and modulefile in first argument
#               if module
#       return code 2
#               if no modulefile could be found
#       
find_module() {
	local "$1"
	local "$2"
	local -r module="$3"
	local -a dirs=("${@:4}")

        for dir in "${dirs[@]}"; do
                test -d "${dir}" || continue
                local -i col=$((${#dir} + 2 ))
                local -a modules
                if [[ ${module} == */* ]]; then
                        # a version number has been specified. But we still might
                        # have the same module/version with different use flags.
                        # The different release stages we ignore in this case.
                        modules=( $(${find} -L "${dir}" -type f -not -name ".*" \
                                       -ipath "${dir}/${module}*" \
                                          | cut -b${col}-) )
                        for mod in "${modules[@]}"; do
                                #
                                # loop over all used flags. If a module with
                                # a used flag is available load this module.
				local rel_stage
		                for flag in "${UseFlags[@]/#/_}" ""; do
                                        [[ ${mod} == ${module}${flag} ]] || continue
                                        std::upvar $1 "${dir}/${mod}"
                                        get_release_stage \
						rel_stage \
                                                "${dir}" \
						"${mod}"
                                        std::upvar $2 "${rel_stage}"
                                        return 0
                                done
                        done
                else
                        # no version has been specified. This makes it more
                        # difficult. We have to load the newest version taking
                        # the used release stages and flags into account.

			# get list of reverse sorted version numbers
                        (( col += ${#module} + 1 ))
                        modules=( $(${find} -L "${dir}" -type f -not -name ".*" \
                                         -ipath "${dir}/${module}/*"  \
                                            | cut -b${col}- \
                                            | sort -rV ) )
			# prepend module name
                        modules=( "${modules[@]/#/${module}/}" )

			# now modules contains a reverse sorted list of
			# available modules in the form name/version
                        for mod in "${modules[@]}"; do
                                #
                                # loop over all used flags. If a module with
                                # a used flag is available load this module.
                                local rel_stage=''
		                for flag in "${UseFlags[@]/#/_}" ""; do
                                        [[ ${mod} == ${module}/*${flag} ]] || continue
                                        std::upvar $1 "${dir}/${mod}"
                                        get_release_stage \
						rel_stage \
                                                "${dir}" \
						"${mod}"
                                        std::upvar $2 "${rel_stage}"
                                        [[ :${rel_stage}: =~ :${UsedReleaseStages}: ]] && \
                                                return 0
                                done
                        done
                fi
        done
        return 1
} # find_module()

##############################################################################
#
# avail [-hlt] [<module-pattern>...]
#
Subcommands[avail]='avail'
Options[avail]='-l help -o \?Hahlmtg: -l all -l all-release-stages -l group: '
Options[avail]+='-l human -l long -l machine -l terse'
Help[avail]="
USAGE:
        module avail [switches] string
		List all available modulefiles in the current MODULEPATH. If
		an argument is given, then each directory in the MODULEPATH
		is searched for modulefiles whose pathname match the argument.

		This command does *not* display all installed modules on the
		system. Only *loadable* modules are listed.  The list of
		available modules may change either by loading other modules,
		e.g. a compiler, or with the sub-command 'use'.

SWITCHES:
        -a|--all||--all-release-stages
		List all available modules independend of the release stage.

	-t|--terse
		Output in short format.

	-l|--long
		Output in long format.

        -g|--group=<GROUP>
                Output modules available in <GROUP>

	-h|--human
		Output in human readable format.

	-m|--machine
		Output in machine readable format
"

subcommand_avail() {
        local -r subcommand='avail'
	# use this variable in the output functions
	local -a mods=()
	local dir=''

	# get number of columns of terminal
	cols=$(tput cols)

	#......................................................................
	output_header() {
		local caption="$1"
		let i=($cols-${#caption})/2-2
		printf -- "%0.s-" $(seq 1 $i) 1>&2
		printf -- " %s " "${caption}" 1>&2
		printf -- "%0.s-" $(seq 1 $i) 1>&2
		printf -- "\n" 1>&2
	}

	#......................................................................
	terse_output() {
		output_header "$1"
		local -i i=0
		for (( i=0; i<${#mods[@]}; i+=4 )); do
			local mod=${mods[i]}
			local rel_stage=${mods[i+1]}
			case ${rel_stage} in
				stable )
					out=''
					;;
				* )
					out="${rel_stage}"
					;;
			esac
			printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2
		done
		std::info ""
	}

	#......................................................................
	machine_output() {
		for (( i=0; i<${#mods[@]}; i+=4 )); do
			printf "%-20s\t%s\n" "${mods[i]}" "${mods[i+1]}" 1>&2
		done
	}

	#......................................................................
	# :FIXME: for the time being, this is the same as terse_output!
	long_output() {
		output_header "$1"
		for (( i=0; i<${#mods[@]}; i+=4 )); do
			local mod=${mods[i]}
			local rel_stage=${mods[i+1]}
			case ${rel_stage} in
				stable )
					out=''
					;;
				* )
					out=${rel_stage}
					;;
			esac
			printf "%-20s\t%s\n" "${mod}" "${out}" 1>&2
		done
		std::info ""
	}

	#......................................................................
	human_readable_output() {
		output_header "$1"

		local -a available_modules=()
		local mod=''
		local -i max_length=1
		for ((i=0; i<${#mods[@]}; i+=4)); do
			if [[ ${verbosity_lvl} == 'verbose' ]]; then
			        local  rel_stage=${mods[i+1]}
			        case ${rel_stage} in
					stable )
						mod="${mods[i]}"
						;;
					* )
						mod="${mods[i]}(${rel_stage:0:1})"
						;;
				esac
			else
				mod=${mods[i]}
			fi
			local -i n=${#mod}
			(( n > max_length )) && (( max_length=n ))
			available_modules+=("${mod}")
		done
		IFS=$'\n' available_modules=($(sort --version-sort <<<"${available_modules[*]}"))
		unset IFS
		local -i span=$(( max_length / 16 + 1 ))	# compute column size
		local -i colsize=$(( span * 16 ))		# as multiple of 16
		local -i column=$cols				# force a line-break
		for mod in "${available_modules[@]}"; do
			local -i len=${#mod}
			if (( column+len >= cols )); then
			        printf -- "\n" 1>&2
				column=0
			fi
			if (( column+colsize < cols )); then
			        fmt="%-${colsize}s"
			else
				fmt="%-s"
			fi
			printf "${fmt}" "${mod}" 1>&2
			column+=colsize
		done
		printf -- "\n\n" 1>&2
	}

	#......................................................................
	local pattern=()
	local output_function='human_readable_output'
	local opt_use_rel_stages="${UsedReleaseStages}"
	local -A opt_groups=()
	local val=''
	while (($# > 0)); do
		case $1 in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
			-a | --all | --all-release-stages )
				opt_use_rel_stages="${ReleaseStages}"
				;;
			-h | --human )
				output_function='human_readable_output'
				;;
			-l | --long )
				output_function='long_output'
				;;
			-t | --terse )
				output_function='terse_output'
				;;
			-m | --machine )
				output_function='machine_output'
				;;
			-g | --group | --group=* )
				if [[ $1 == --group=* ]]; then
					val="${1#--*=}"
				else
					val="$2"
					shift
				fi
				opt_groups[${val}]=1
				;;
			-- )
				shift 1
				pattern+=( "$@" )
				break
				;;
			* )
				pattern+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#pattern[@]} == 0 )); then
	        pattern+=( '' )
	fi
	IFS=':'
	local -a modulepath=(${MODULEPATH})
	unset IFS
	local dir
        local group=''
        local ol=''
        for dir in "${modulepath[@]}"; do
                group='other'
                find_overlay ol group "${dir}"
		if [[ ! -v modulepath_${group} ]]; then
			typeset -a modulepath_${group}
		fi
		typeset -n path=modulepath_${group}
		path+=("${dir}")
        done
        local p
	for string in  "${pattern[@]}"; do
                for group in ${UsedGroups//:/ } other; do
			if (( ${#opt_groups[@]} > 0 )) && [[ ! -v opt_groups[${group}] ]]; then
				continue
			fi
			[[ -v modulepath_${group} ]] || continue
			typeset -n path=modulepath_${group}
			get_available_modules \
				mods \
				"${string}*" \
                                "${opt_use_rel_stages}" \
				"${path[@]}"

			[[ ${#mods[@]} == 0 ]] && continue
			${output_function} "${group}"
		done
	done
} # subcommand_avail()

##############################################################################
#
# use [-a|--append|-p|--prepend] [directory|group|release_stage...]
#
Subcommands[use]='use'
Options[use]='-l help -o \?Hap -l append -l prepend'
Help[use]="
USAGE:
        module use [-a|--append|-p|--prepend] [directory|group|release_stage|...]
		Without arguments this sub-command displays information about
		the module search path, used groups and release stages. You can
		use this sub-command to get a list of available groups and
		releases stages.

		With a directory as argument, this directory will either be
		prepended or appended to the module search path. The default
		is to prepend the directory.

		With a group as argument, the modules in this group will
		be made available.

		With a release as argument, this modules with this release
		will be made available.

SWITCHES:
        -a | --append -p | --prepend )
                Append/prepend agrument to module search path or list of to be
                searched releases.
"

subcommand_use() {
        local -r subcommand='use'
	IFS=':'
	local -a modulepath=(${MODULEPATH})
	unset IFS
        local add2path_func='std::append_path'

	#......................................................................
        group_is_used() {
	        [[ :${UsedGroups}: =~ :$1: ]]
        }

	#......................................................................
	print_info() {
		print_ol_info(){
			local only_used="$1"
			local ol=''
			for ol in "${Overlays[@]}"; do
				[[ ${OverlayInfo[${ol}:used]} == ${only_used} ]] || continue
				local inst_root="${OverlayInfo[${ol}:inst_root]}"
				local mod_root="${OverlayInfo[${ol}:mod_root]}"
				local txt="\t${ol}"
				if [[ ${inst_root} == ${mod_root} ]]; then
					txt+="\n\t\t${inst_root}"
				else
					txt+="\n\t\t${inst_root} (install root)"
					txt+="\n\t\t${mod_root} (modulefiles root)"
				fi
				case "${OverlayInfo[${ol}:type]}" in
					"${ol_hiding}" )
						txt+='\n\t\t(hiding modules with same name)'
						;;
					"${ol_replacing}" )
						txt+='\n\t\t(replacing groups)'
						;;
				esac
				std::info "${txt}"
			done
		}

		local f
		local r
		std::info "Used groups:"
		for f in ${UsedGroups//:/ }; do
			std::info "\t${f}"
		done
		std::info ''
		std::info "Unused groups:"
		local _group
		for _group in "${!GroupDepths[@]}"; do
			local -i depth=${GroupDepths[${_group}]}
			if ! group_is_used "${_group}" && (( depth == 0 )); then
				std::info "\t${_group}"
			fi
		done

		std::info "\nUsed releases stages:"
		for r in ${UsedReleaseStages//:/ }; do
			std::info "\t${r}"
		done
		std::info "\nUnused release stages:"
		for r in ${ReleaseStages//:/ }; do
			[[ ! ":${UsedReleaseStages}:" =~ :$r: ]] && std::info "\t${r}"
		done

		std::info "\nUsed flags:"
		for flag in "${UsedFlags[@]}"; do
			std::info "\t${flag}"
		done

		std::info ''
                std::info "Used overlays:"
		print_ol_info 'yes'

		std::info ''
		std::info "Unused overlays:"
		print_ol_info 'no'

		std::info ''
		std::info "Additonal directories in MODULEPATH:"
		let n=0
                local group
		for (( i=0; i<${#modulepath[@]}; i++)); do
			if ! find_overlay ol group "${modulepath[i]}"; then
			        std::info "\t${modulepath[i]}"
			        let n+=1
			fi
		done
		if (( n == 0 )); then
		        std::info "\tnone"
		fi
		std::info ""
	} # print_info

	#......................................................................
	use () {
                use_overlay() {
			local ol_name="$1"
			
			if [[ -n "${LOADEDMODULES}" ]] && \
				   [[ "${LOADEDMODULES}" != Pmodules/+([.0-9rc]) ]]; then
				std::die 3 "%s %s: %s %s" \
					   "${CMD}" "${subcommand}" \
					   "overlay cannot be added since some" \
					   "modules are already loaded!"
			fi

			if [[ ${OverlayInfo[${ol_name}:used]} == 'yes' ]]; then
				std::die 3 "%s %s: %s -- %s" \
					 "${CMD}" "${subcommand}" \
                                         "overlay already in use" \
                                         "${ol_name}"
				return 0
			fi
			
			if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]]; then
				# if this overlay replaces groups, we have
				# to remove the modules made available by
				# other overlays in these groups
				for group in ${UsedGroups//:/ }; do
					# is this group in the to be added overlay?
                                        local dir="${OverlayInfo[${ol_name}:mod_root]}/"
                                        dir+="${group}/${PMODULES_MODULEFILES_DIR}"
                                        [[ -d "${dir}" ]] || continue  # no

					dir="/${group}/${PMODULES_MODULEFILES_DIR}"
					local -a dirs=()
					for ol in "${UsedOverlays[@]}"; do
						dirs+=( ${OverlayInfo[${ol}:mod_root]}${dir} )
					done
					std::remove_path MODULEPATH "${dirs[@]}"
				done
			fi
			scan_groups "${ol_name}"
                        for group in ${UsedGroups//:/ }; do
                                local dir="${OverlayInfo[${ol_name}:mod_root]}/"
                                dir+="${group}/${PMODULES_MODULEFILES_DIR}"
                                if [[ -d "${dir}" ]]; then
                                        std::prepend_path MODULEPATH "${dir}"
                                fi
                        done

                        UsedOverlays=( "${ol_name}" "${UsedOverlays[@]}" )
			OverlayInfo[${ol_name}:used]='yes'
                        export_env UsedOverlays
                        g_env_must_be_saved='yes'
	                scan_groups "${UsedOverlays[@]}"
                }

		#..............................................................
                use_group() {
			if (( ${GroupDepths[${arg}]} > 0 )); then
				# argument is a hierarchical group in our root
				std::die 3 "%s %s: %s -- %s" \
					 "${CMD}" "${subcommand}" \
					 "illegal group" \
					 "${arg}"
			fi
			std::append_path UsedGroups "$1"
                        local ol_name
                        for ol_name in "${UsedOverlays[@]}"; do
                                local dir="${OverlayInfo[${ol_name}:mod_root]}/$1/${PMODULES_MODULEFILES_DIR}"
                                [[ -d "${dir}" ]] || continue

                                std::prepend_path MODULEPATH "${dir}"
				[[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]] && break
                        done
                }
                
		#..............................................................
		local arg=$1

		if is_release_stage "${arg}"; then
                        # argument is release stage
			std::append_path UsedReleaseStages "${arg}"
                        return $?
                fi
		if [[ "${arg}" =~ "flag=" ]]; then
                        # argument is flag
			UsedFlags+=( "${arg/flag=}" )
                        return $?
                fi
		if [[ -v OverlayInfo[${arg}:type] ]]; then
			use_overlay "${arg}"
			return $?
		fi
		if [[ -d ${arg} ]]; then
			local dir=$(cd "${arg}" && pwd -L)
			${add2path_func} MODULEPATH "${dir}"
			return $?
		fi
                if [[ ! -v GroupDepths[${arg}] ]]; then
                        # this scan is required if a new group has been
                        # create inside an used overlay
			scan_groups "${UsedOverlays[@]}"
			g_env_must_be_saved='yes'
                fi
                if [[ -n ${GroupDepths[${arg}]} ]]; then
			use_group "${arg}"
                        return $?
                fi

		std::die 3 "%s %s: %s -- %s" \
                         "${CMD}" "${subcommand}" \
                         "invalid keyword, group, overlay or directory" \
                         "${arg}"
	} # use ()

	#......................................................................
	local -a args=()
	while (( $# > 0)); do
		case "$1" in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
		        -a | --append )
                                add2path_func='std::append_path'
			        ;;
		        -p | --prepend )
                                add2path_func='std::prepend_path'
			        ;;
		        -- )
				shift 1
				args+=( "$@" )
				break
			        ;;
		        * )
			        args+=( "$1" )
			        ;;
		esac
		shift
	done

	if (( ${#args[@]} == 0 )); then
	        print_info
                return
        fi
        for arg in "${args[@]}"; do
		use "${arg}"
        done
        g_env_must_be_saved='yes'
	export_env 'MODULEPATH'
}

##############################################################################
#
# unuse directory|group|release_stage|...
#
Subcommands[unuse]='unuse'
Options[unuse]='-o \?H -l help'
Help[unuse]='
unuse directory|group|release...
		Remove the given modulefiles directory, group, release stage,
                flag from the search path.
'

subcommand_unuse() {
        local -r subcommand='unuse'
	IFS=':'
	local -a modulepath=(${MODULEPATH})
	unset IFS

	#......................................................................
        unuse() {
		#..............................................................
                unuse_overlay() {
			local ol_name="$1"

			if [[ -n "${LOADEDMODULES}" ]] && \
				   [[ "${LOADEDMODULES}" != Pmodules/+([.0-9rc]) ]]; then
				std::die 3 "%s %s: %s %s" \
					   "${CMD}" "${subcommand}" \
					   "overlay cannot be removed since" \
					   "some modules are still loaded!"
			fi

                        [[ "${OverlayInfo[ol_name]:mod_root}" == "${PMODULES_HOME%%/Tools*}" ]] && \
                                std::die 3 "%s %s: %s -- %s" \
                                         "${CMD}" "${subcommand}" \
                                         "cannot remove base overlay" \
                                         "${ol_name}"
			[[ ${OverlayInfo[${ol_name}:used]} !=  'yes' ]] && \
                                std::die 3 "%s %s: %s -- %s" \
                                         "${CMD}" "${subcommand}" \
                                         "not an used overlay" \
                                         "${ol_name}"
			# make sure first index is '0' (it should, but you never know)
			UsedOverlays=( "${UsedOverlays[@]}" )
			[[ "${ol_name}" != "${UsedOverlays[0]}" ]] && \
				std::die 3 "%s %s: %s %s -- %s" \
					 "${CMD}" "${subcommand}" \
					 "overlay cannot be removed since" \
					 "it not on top of the stack" \
					 "${ol_name}"

			if [[ "${OverlayInfo[${ol_name}:type]}" == "${ol_replacing}" ]]; then
				# if this overlay hides groups, we have to re-add
				# the modules made available by other overlays
				local mod_root=${OverlayInfo[${ol_name}:mod_root]}
				for group in ${UsedGroups//:/ }; do
					# is this group in the to be removed overlay?
                                        local dir="${mod_root}/${group}/${PMODULES_MODULEFILES_DIR}"
                                        [[ -d "${dir}" ]] || continue  # no
					
					dir="/${group}/${PMODULES_MODULEFILES_DIR}"
					std::prepend_path MODULEPATH "${UsedOverlays[@]/%/${dir}}"
				done
			fi

                        OverlayInfo[${ol_name}:used]='no'
			UsedOverlays=( "${UsedOverlays[@]:1}")
                        g_env_must_be_saved='yes'
                        export_env UsedOverlays
                        local dir
                        for dir in "${modulepath[@]}"; do
                                [[ "${dir}" =~ "${OverlayInfo[${ol_name}:mod_root]}" ]] && \
                                        std::remove_path MODULEPATH "${dir}"
                        done
                }

		#..............................................................
                unuse_group() {
                        if (( ${GroupDepths[${arg}]} > 0 )); then
				# argument is a hierarchical group in our root
				std::die 3 "%s %s: %s -- %s" \
					 "${CMD}" "${subcommand}" \
					 "illegal group" \
					 "${arg}"
			fi

                        local var="PMODULES_LOADED_${arg^^}"
                        if [[ -n "${!var}" ]]; then
                                std::die 3 "%s %s: %s -- %s" \
                                         "${CMD}" "${subcommand}" \
                                         "cannot remove group due to loaded modules" \
                                         "${arg}"
                        fi
                        std::remove_path UsedGroups "${arg}"
                        local overlay
                        for overlay in "${UsedOverlays[@]}"; do
                                local dir="${overlay}/${arg}/${PMODULES_MODULEFILES_DIR}"
			        std::remove_path MODULEPATH "${dir}"
                        done
                }

		#..............................................................
                local arg=$1

                if is_release_stage "${arg}"; then
                        # argument is release stage
			std::remove_path UsedReleaseStages "${arg}"
                        return
                fi
		if [[ "${arg}" =~ "flag=" ]]; then
                        # argument is flag
			local flag="${arg/flag=}"
			local i
			for i in ${!UsedFlags[@]}; do
				[[ ${UsedFlags[i]} == ${flag} ]] && unset UsedFlags[i]
			done
                        return
                fi
		if [[ -v OverlayInfo[${arg}:type] ]]; then
			unuse_overlay "${arg}"
			return 0
		fi
		if [[ -d ${arg} ]]; then
			local dir=$(cd "${arg} && pwd -L")
			std::remove_path MODULEPATH "${dir}"
		fi
                if [[ -n ${GroupDepths[${arg}]} ]]; then
                        unuse_group "${arg}"
                        return
                fi

		std::die 3 "%s %s: %s -- %s" \
                         "${CMD}" "${subcommand}" \
                         "invalid keyword, group, overlay or directory" \
                         "${arg}"
        } # unuse()

	#......................................................................
	local -a args=()
	while (( $# > 0)); do
		case "$1" in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
		        -- )
				shift 1
				args+=( "$@" )
				break
			        ;;
		        * )
			        args+=( "$1" )
			        ;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         'missing argument'
	fi
        for arg in "${args[@]}"; do
                unuse "${args[@]}"
        done
        g_env_must_be_saved='yes'
        export_env 'MODULEPATH'
}

##############################################################################
#
# update
#
# :FIXME:
# either compile Modules with --enable-beginenv or remove the
# sub-command
#
Subcommands[update]='update'
Options[update]='-o \?H -l help'
Help[update]='
USAGE:
        module update
                Attempt  to  reload  all  loaded  modulefiles.
'

subcommand_update() {
        subcommand_generic0 'update' "$@"
}

##############################################################################
#
# refresh
#
Subcommands[refresh]='refresh'
Options[refresh]='-o \?H -l help'
Help[refresh]='
USAGE:
        module refresh
                Force a refresh of all non-persistent components of currently
		loaded modules.  This should be used on derived shells where
		aliases need to be reinitialized but the environment variables
		have already been set by the currently loaded modules.
'

subcommand_refresh() {
        subcommand_generic0 'refresh' "$@"
}

#
# Helper functions, used during initialization and for purging all modules.
#
# Arguments:
#	none
#
init_modulepath() {
	declare -gx MODULEPATH=''
	local group
	local ol
	for ol in "${UsedOverlays[@]}"; do
		for group in ${UsedGroups//:/ }; do
			local dir="${OverlayInfo[${ol}:mod_root]}/${group}/${PMODULES_MODULEFILES_DIR}"
			if [[ -d "${dir}" ]]; then
				std::prepend_path MODULEPATH "${dir}"
			fi
		done
	done
}

pmodules_init() {
	init_used_groups() {
		declare -gx UsedGroups=''
		local group
		for group in ${DefaultGroups//:/ }; do
			std::append_path UsedGroups "${group}"
		done
		g_env_must_be_saved='yes'
	}
	init_used_releases() {
		declare -g  UsedReleaseStages=''
		for r in ${DefaultReleaseStages//:/ }; do
			std::append_path UsedReleaseStages "${r}"
		done
		g_env_must_be_saved='yes'
	}
	init_overlay_vars() {
		declare -ag UsedOverlays=( 'base' )
		OverlayInfo['base:used']='yes'
	}

	init_manpath() {
		std::replace_path MANPATH "${PMODULES_HOME%/*}/.*"

		if [[ -r /etc/man.config ]]; then
			declare _manconf='/etc/man.config'
		elif [[ -r /etc/man.conf ]]; then
			declare _manconf='/etc/man.conf'
		fi
		if [[ -n ${_manconf} ]]; then
			while read name value rest; do
				std::append_path MANPATH "${value}"
			done < <(grep "^MANPATH\s" "${_manconf}")
			unset _manconf
		else
			std::append_path  MANPATH "${PMODULES_HOME}/share/man"
			std::append_path  MANPATH "/usr/share/man"
		fi
	}

	pm::read_config

	declare -gx LOADEDMODULES=''
	declare -gx _LMFILES_=''
	declare -Ag GroupDepths=()
	declare -ag UsedFlags=()
	declare -g  Version="${PMODULES_VERSION}"

        init_used_groups
        init_used_releases
	init_overlay_vars
        init_modulepath
        init_manpath
	scan_groups "${UsedOverlays[@]}"
	save_env
        export_env \
                LOADEDMODULES \
                _LMFILES_ \
                MODULEPATH \
                PATH \
                MANPATH
}

##############################################################################
#
# purge
#
Subcommands[purge]='purge'
Options[purge]='-o \?H -l help'
Help[purge]='
USAGE:
        module purge
		Unload all loaded modulefiles.
'

subcommand_purge() {
	#
	# unload all loaded modules
	#
	# Note:
	#     If a Pmodule module is loaded, it will *not* be
	#     unloaded! 
	#
        local -r subcommand='purge'
	local -a args=()
	while (( $# > 0)); do
		case "$1" in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
		        -- )
				shift 1
				args+=( "$@" )
				break
			        ;;
		        * )
			        args+=( "$1" )
			        ;;
		esac
		shift
	done
        if (( ${#args[@]} > 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "no arguments allowed"
        fi

	# is a Pmodule module loaded?
	# if yes, save name in variable 'pmodule'
	# We also have to save PMODULES_HOME since it will be
	# unset while sourcing the shell's init script.
	IFS=':'
	local -a lmfiles=($_LMFILES_)
	unset IFS
	for f in "${lmfiles[@]}"; do
		if [[ $f == */${PMODULES_MODULEFILES_DIR}/Pmodules/* ]]; then
			local pm_home="${PMODULES_HOME}"
			local pmodule="${f##*/${PMODULES_MODULEFILES_DIR}/}"
			break;
		fi
	done

	# run module purge
	# since we might have to reload a Pmodules module, we cannot
	# just run 'modulecmd ${Shell} purge'
	local output=$("${modulecmd}" 'bash' 'purge' 2> "${tmpfile}")

	local error=$( < "${tmpfile}")
	if [[ "${error}" =~ ":ERROR:" ]]; then
                local s=${error%%$'\n'*}
                local error_txt='failed'
		std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "${error_txt}"
        fi
        if [[ "${Shell}" == "sh" ]]; then
		# for sh-like shells just echo
		eval "$(echo "${output}"|${sed} -e 's/;unalias [^;]*//g')"
        else
		# re-run with right shell
                "${modulecmd}" "${Shell}" 'purge'
        fi
	eval "$(echo "${output}"|sed -e 's/;unalias [^;]*//g')"
	if [[ -n "${error}" ]]; then
                echo "${error}" 1>&2
	fi

	if [[ -v pmodule ]]; then
		# reload a previously loaded Pmodule module
		# stderr is redirected to /dev/null, otherwise
		# we may get output like
		# 'unstable module has been loaded'
		PMODULES_HOME="${pm_home}"
		export_env PMODULES_HOME
		subcommand_load "${pmodule}" 2> /dev/null
	fi
        init_modulepath
	
	export_env MODULEPATH
}

##############################################################################
#
# list [-hlt]
#
Subcommands[list]='list'
Options[list]='-l help -o \?Hhlt -l human -l long -l terse'
Help[list]='
USAGE:
        module list
		List loaded modules.
'

subcommand_list() {
        local -r subcommand='list'
	local opts=()
        local args=()
	while (( $# > 0 )); do
		case $1 in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
			-h | --human )
				opts+=( '-h' )
				;;
			-l | --long )
				opts+=( '-l' )
				;;
			-t | --terse )
				opts+=( '-t' )
				;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
                                args+=( "$1" )
				;;
		esac
		shift
	done
        if (( ${#args[@]} > 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "no arguments allowed"
        fi
	"${modulecmd}" "${Shell}" list "${opts[@]}"
}

##############################################################################
#
# clear
#
Subcommands[clear]='clear'
Options[clear]='-o \?H -l help'
Help[clear]='
USAGE:
        module clear
		Force the Modules package to believe that no modules are
		currently loaded.
'

subcommand_clear() {
        local -r subcommand='clear'
        local -a args=()
	while (( $# > 0 )); do
		case $1 in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
                                args+=( "$1" )
				;;
		esac
		shift
	done
        if (( ${#args[@]} > 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "no arguments allowed"
        fi
	pmodules_init
}

##############################################################################
#
# search [switches] [STRING...]
#
Subcommands[search]='search'
Subcommands[find]='search'
Options[search]='-o a\?H -l help -l no-header -l print-modulefiles '
Options[search]+='-l release-stage: -l with: -l all-release-stages -l src: -l print-csv '
Options[search]+='-l verbose '
Options[search]+='-l all-deps -l wrap '
Options[search]+='-l glob '
Options[search]+='-l newest '
Options[search]+='-l group:'
Help[search]='
USAGE:
        module find|search [switches] STRING...
		Search installed modules. If an argument is given, search
		for modules whose name match the argument.

SWITCHES:
        -a|--all-release-stages
		Search within all releases.

	--all-deps
                Show all dependecies

        --glob
                Interpret STRING as shell pattern.

        --no-header
		Suppress output of a header.

        --release-stage=RELEASE_STAGE
		Search for modules within this release stage. You can specify
                this switch multiple times.  Without this switch, the release
                stages in use will be searched.

        --verbose
                vebose output

        --with=STRING
		Search for modules compiled with modules matching string. The
		command

		module search --with=gcc/4.8.3

		lists all modules in the hierarchy compiled with gcc 4.8.3.

        --wrap
                wrap output
'

subcommand_search() {
	local -r subcommand='search'
	local modules=()
	local groups=()
	local with_modules='//'
	local -ir cols=$(tput cols)	# get number of columns of terminal
	local -i max_len_modulename=0
	local src_prefix=()
	local opt_print_header='yes'
	local opt_print_modulefiles='no'
	local opt_print_csv='no'
        local opt_print_verbose='no'
	local opt_use_releases=':'
	local opt_all_deps='no'
	local opt_wrap='no'
	local opt_glob='no'
	local opt_newest='no'

	#.....................................................................
	#
	# output result of search
	# Args:
	#   $1: tmp file
	#
	# variables used from enclosing function:
	#   opt_print_header
	#   opt_print_modulefiles
	#   with_modules
	#
	print_result() {
                local func_print_header=''
                local func_print_line=''
                local fmt=''
                
                print_default() {
			fmt="%-${max_len_modulename}s %-10s %-12s %-12s %-s"
                        if [[ ${opt_print_header} == 'yes' ]]; then
                                func_print_header='print_header_default'
                        else
                                func_print_header='print_header_none'
                        fi
                        func_print_line='print_line_default'
                }

                print_header_default() {
		        std::info ''
		        std::info "${fmt}" "Module" "Rel.stage" "Group" "Overlay" "Requires"
		        std::info '-%.0s' $(seq 1 ${cols})
	        }

                print_line_default() {
			write_line() {
				local str="$1"
				if (( ${#str} >= cols )); then
					str="${str:0:$((cols-1))}>"
				fi
				std::info "${str}"
			}
			if [[ "${opt_wrap}" == 'no' ]]; then
				local deps="${@:6}"
				local str=$(printf "${fmt}"  "$1" "$2" "$3" "$5" "${deps[@]}")
				write_line "${str}"
			else
				local deps=( "${@:6}" )
				local str=$(printf "${fmt}"  "$1" "$2" "$3" "$5" "${deps[0]}")
				for (( i = 1; i < ${#deps[@]}; i++ )); do
					if (( ${#str} + ${#deps[i]} + 1 <= cols )); then
						str+=" ${deps[i]}"
					else
						write_line "${str}"
						str=$(printf "${fmt}"  "" "" "" "> ${deps[i]}")
					fi
				done
				write_line "${str}"
			fi
                }

                print_verbose() {
                        fmt="%-${max_len_modulename}s %-12s %-14s %-s"
                        func_print_header='print_header_verbose'
                        func_print_line='print_line_verbose'
                }
                
                print_header_verbose() {
		        :
	        }

                print_line_verbose() {
			local deps="${@:6}"
			[[ -z ${deps} ]] && deps="(none)"
			std::info "$1:"
			std::info "  release stage: $2"
			std::info "  group:         $3"
                        std::info "  overlay:       $5"
			std::info "  modulefile:    $4"
                        std::info "  dependencies:  ${deps}"
                 }

                # print full modulefile names only
                print_modulefiles() {
			fmt=''
			func_print_header='print_header_none'
                        func_print_line='print_line_modulefile'
                }

                print_header_none() {
                        :
                }

                print_line_modulefile() {
			if (( $# >= 4 )) && [[ -n $4 ]]; then
				std::info "$1 $4"
			fi
                }

                print_line_csv() {
                        :
                }
                
                print_csv() {
                        fmt=''
                        func_print_header='print_header_none'
                        func_print_line='print_line_csv'
                }

                if [[ "${opt_print_modulefiles}" == 'yes' ]]; then
                        print_modulefiles
                elif [[ "${opt_print_csv}" == 'yes' ]]; then
                        print_csv
                elif [[ "${opt_print_verbose}" == 'yes' ]]; then
                        print_verbose
                else
                        print_default
                fi

		local _script=''
		if [[ ${opt_newest} == 'yes' ]]; then
			_script='{} END{print}'
		fi
                ${func_print_header}
                while read -a toks; do
                        ${func_print_line} "${toks[@]}"
                done < <("${sort}" --version-sort -k 1,1 -k 6,6 -k 7,7 "${tmpfile}" | \
                                 ${awk} "${with_modules} ${_script}")
        }

	#.....................................................................
	#
	# search modules
	# Args:
	#   $1: module name pattern
	#
	# Variables used from enclosing function
	#   :FIXME:
	#
	search () {
		local module="$1"
		local group="$2"
		# write results to a temporary file for later processing

		# loop over all directories which can be added to
		# MODULEPATH inside current group
		local depth=${GroupDepths[${group}]}
		local s=''
		if (( depth > 0 )); then
			s=$(printf '/*%.0s' $(seq 1 ${depth}))
		fi
		local modulepath=( ${src_prefix[@]/%//${group}/modulefiles$s} )
		
		# get and print all available modules in $mpath
		# with respect to the requested release stage
		# tmpfile: module/version rel_stage group dependencies...
		local mods
		get_available_modules \
			mods \
			"${module}" \
			"${opt_use_rel_stages}" \
			"${modulepath[@]}"
		local i=0
		for (( i=0; i<${#mods[@]}; i+=4 )); do
			local name=${mods[i]}
			local rel_stage=${mods[i+1]}
			local modulefile=${mods[i+2]}
			local ol=${mods[i+3]}
			
			if (( ${#name} > max_len_modulename)); then
				max_len_modulename=${#name}
			fi
			
			if [[ "${opt_print_verbose}" == 'yes' ]] || \
				   [[ "${opt_all_deps}" == 'yes' ]]; then
				local prefix=''
				get_module_prefix prefix "${modulefile}"
				local dependencies_file="${prefix}/.dependencies"
				if [[ -n ${prefix} ]] && [[ -r "${dependencies_file}" ]]; then
					deps=($(< "${dependencies_file}"))
				else
					deps=()
				fi
			else
				# get dependencies encoded in directory name
				local deps=()
				local -i j
				IFS='/'   # note: IFS is used to concat in the for loop!
				local toks=( ${modulefile} )
				for ((j = -depth-2; j < -2; j += 2)); do
					deps+=( "${toks[*]: $j:2}" );
				done
				unset IFS
			fi
			
			echo ${name} ${rel_stage} ${group} ${modulefile} \
			     ${ol} \
                             ${deps[@]} >> "${tmpfile}"
		done
	}

	while (( $# > 0 )); do
		case $1 in
                        -\? | -H | --help )
                                print_help "${subcommand}"
                                ;;
			--all-deps )
				opt_all_deps='yes'
				;;
			--no-header )
				opt_print_header='no'
				;;
			--print-modulefiles )
				opt_print_modulefiles='yes'
				opt_print_header='no'
				;;
			--print-csv )
				opt_print_csv='yes'
				opt_print_header='no'
				;;
			--release-stage | --release-stage=* )
                                if [[ "$1" == "--release" ]]; then
                                        local arg=$2
                                        shift
                                else
                                        local arg=${1/--release=}
                                fi
				is_release_stage "${arg}" || \
				        std::die 1 "%s %s: %s -- %s" \
                                                 "${CMD}" 'search' \
                                                 "illegal release stage" \
                                                 "${arg}"
				opt_use_rel_stages+="${arg}:"
				;;
			--with | --with=* )
                                if [[ "$1" == --with ]]; then
                                        local arg=$2
                                        shift
                                else
                                        local arg=${1/--with=}
                                fi
				if [[ -z ${arg} ]] || [[ "${arg}" =~ "-*" ]]; then
				        std::die 1 "%s %s: %s -- %s" \
                                                 "${CMD}" 'search' \
                                                 "illegal value for --with option" \
                                                 "${arg}"
				fi
                                arg=${arg//:/ }
                                arg=${arg//,/ }
                                for module in ${arg}; do
				        with_modules+=" && / ${module//\//\\/}/"
                                done
				;;
			-a | --all-releases-stages )
				opt_use_rel_stages+="${ReleaseStages}"
				;;
			--src | --src=*)
                                if [[ "$1" == --src ]]; then
                                        local dir="$2"
                                        shift
                                else
                                        local dir="${1/--src=}"
                                fi
				if [[ ! -e "${dir}" ]]; then
					std::die 1 "%s %s: %s -- %s" \
						 "${CMD}" 'search' \
                                                 "illegal value for --src option" \
                                                 "${dir} does not exist"
				fi
				if [[ ! -d "${dir}" ]]; then
					std::die 1 "%s %s: %s -- %s" \
						 "${CMD}" 'search' \
                                                 "illegal value for --src option" \
                                                 "${dir} is not a directory"
				fi
				src_prefix+=( $(std::get_abspath "${src_prefix}") )
				;;
                        -v | --verbose )
                                opt_print_verbose='yes'
                                ;;
			--wrap )
				opt_wrap='yes'
				;;
			--glob )
				opt_glob='yes'
				;;
			--newest )
				opt_newest='yes'
				;;
			--group | --group=* )
				if [[ $1 == *=* ]]; then
					groups+=( ${1/--*=} )
				else
					groups+=( $2 )
					shift
				fi
				;;
					
			-- )
				shift 1
				modules+=( "$@" )
				break
				;;
			* )
				modules+=( "$1" )
				;;
		esac
		shift
	done
	if [[ -z "${src_prefix}" ]]; then
		local ol=''
		for ol in "${UsedOverlays[@]}"; do
			src_prefix+=( "${OverlayInfo[${ol}:mod_root]}" )
		done
	fi

	if [[ "${opt_use_rel_stages}" == ":" ]]; then
                opt_use_rel_stages=":${UsedReleaseStages}:"
	fi

	if [[ ${#modules[@]} == 0 ]]; then
		modules+=( '' )
	fi

        # :FIXME: do we need this?
	if (( ${#GroupDepths[@]} == 0 )) || \
                   [[ ${src_prefix} != ${PMODULES_HOME%%/Tools*} ]]; then
		scan_groups "${src_prefix}"
		g_env_must_be_saved='yes'
	fi

	local module
	if (( ${#groups[@]} == 0 )); then
		groups=( "${!GroupDepths[@]}" )
	fi	
	for module in "${modules[@]}"; do
		if [[ ${opt_glob} != 'yes' ]]; then
			module+="*"
		fi
		local group
		for group in "${groups[@]}"; do
			search "${module}" "${group}"
		done
		print_result
		echo -n > ${tmpfile}
	done
}

##############################################################################
#
# help [module|sub-command]
#
Subcommands[help]='help'
Options[help]='-o hHV\? -l version -l help'
Help[help]='
USAGE:
        module [ switches ] [ subcommand ] [subcommand-args ]

SWITCHES:
        -h|-H|-?|--help         this usage info
        -V|--version            modules version & configuration options
        --debug                 enable debug output

SUBCOMMANDS:
        + add|load [switches]   modulefile [modulefile ...]
        + rm|unload             modulefile [modulefile ...]
        + switch|swap           [modulefile1] modulefile2
        + display|show          modulefile [modulefile ...]
        + avail [switches]      [modulefile [modulefile ...]]
        + search [switches]     [args]
        + use [switches]        [dir|group|release ...]
        + unuse                 dir|group|release [dir|group|release ...]
        + refresh
        + purge
        + list [switches]
        + clear
        + help                  [modulefile|subcommand]
        + whatis                [modulefile [modulefile ...]]
        + apropos|keyword       string
        + initadd               modulefile [modulefile ...]
        + initprepend           modulefile [modulefile ...]
        + initrm                modulefile [modulefile ...]
        + initswitch            modulefile1 modulefile2
        + initlist
        + initclear
'

subcommand_help() {
        local -r subcommand='help'
	local -a args=()
	while (( $# > 0 )); do
		case $1 in
			-\? | -h | -H | --help )
				print_help "${subcommand}"
				;;
			-V | --version )
				print_help 'version'
				;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
	        print_help 'help'
        fi
        local arg
        for arg in "${args[@]}"; do
        	if [[ -n "${Help[${arg}]}" ]] ; then
                	print_help "${arg}"
                else
		        # :FIXME: print help of newest *available* module
		        # (respecting UsedReleaseStages)
                        "${modulecmd}" "${Shell}" "${subcommand}" "${arg}"
	        fi
        done
}

##############################################################################
#
# whatis
#
Subcommands[whatis]='whatis'
Options[whatis]='-o \?Ha -l help -l all'
Help[whatis]='
USAGE:
        module whatis [modulefile...]
                Display the information set up by the module-whatis commands
		inside the specified modulefile(s). If no modulefile is
		specified, all 'whatis' lines will be shown.
'

subcommand_whatis() {
	local options=()
	local args=()
	while (( $# > 0 )); do
		case $1 in
			-\? | --help )
				print_help "${subcommand}"
				;;
			-a | --all )
				options+=( '-a' )
				;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done

	local group=''
	for group in "${!GroupDepths[@]}"; do
		local mod_name=''
		local file_name=''
		while read mod_name file_name; do
			[[ -n ${file_name} ]] || continue
			local whatis=$("${modulecmd}" bash \
						      whatis \
						      "${file_name}" \
						      2>&1 1>/dev/null)
			printf "%-25s: %s\n" "${mod_name}" "${whatis/*:}" 1>&2
		done < <(set +x; subcommand_search \
			--group "${group}" \
			--print-modulefiles \
			--newest \
			"${options[@]}" \
			"${args[@]}" 2>&1)
	done
}

##############################################################################
#
# apropos
#
Subcommands[apropos]='apropos'
Subcommands[keyword]='apropos'
Options[apropos]='-o \?Ha -l help -l all'
Help[apropos]='
USAGE:
        module apropos string
        module keyword string	Seeks through the 'whatis' informations of
                all modulefiles for the specified string.  All module-whatis
                informations matching the string will be displayed.
'

subcommand_apropos() {
	local options=()
	local args=()
	while (( $# > 0 )); do
		case $1 in
			-\? | --help )
				print_help "${subcommand}"
				;;
			-a | --all )
				options+=( '-a' )
				;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} == 0 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "no search string specified"
	elif (( ${#args[@]} > 1 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "more then one search string specified"
	fi
	local arg="${args[0]}"
	local group=''
	for group in "${!GroupDepths[@]}"; do
		local mod_name=''
		local file_name=''
		while read mod_name file_name; do
			[[ -n ${file_name} ]] || continue
			local whatis=$("${modulecmd}" bash \
						      whatis \
						      "${file_name}" \
						      2>&1 1>/dev/null)
			if [[ ${whatis,,} =~ ${arg,,} ]]; then
				printf "%-25s: %s\n" "${mod_name}" "${whatis/*:}" 1>&2
			fi
		done < <(set +x; subcommand_search \
			--group "${group}" \
			--print-modulefiles \
			"${options[@]}" 2>&1)
	done
}

##############################################################################
#
# initadd module...
#
Subcommands[initadd]='initadd'
Options[initadd]='-o \?H -l help'
Help[initadd]="
USAGE:
        module initadd	modulefile...
		Add modulefile(s) to the shell's initialization file in the
		user's home directory.  The startup files checked (in order)
		are:

                    csh -   .modules, .cshrc(.ext), .csh_variables, and
                            .login(.ext)
                    tcsh -  .modules, .tcshrc, .cshrc(.ext), .csh_variables,
                            and .login(.ext)
                    (k)sh - .modules, .profile(.ext), and .kshenv(.ext)
                    bash -  .modules, .bash_profile, .bash_login,
                            .profile(.ext) and .bashrc(.ext)
                    zsh -   .modules, .zcshrc(.ext), .zshenv(.ext), and
                            .zlogin(.ext)

                If a 'module load' line is found in any of these files, the
                modulefile(s) is(are) appended to any existing list of
                modulefiles.  The 'module load' line must be located in at
                least one of the files listed above for any of the 'init'
                sub-commands to work properly.  If the 'module load' line
                line is found in multiple shell initialization files, all
		of the lines are changed.
"

subcommand_initadd() {
        subcommand_generic1plus 'initadd' "$@"
}

##############################################################################
#
# initprepend module...
#
Subcommands[initprepend]='initprepend'
Options[initprepend]='-o \?H -l help'
Help[initprepend]="
USAGE:
        module initprepend modulefile...
		Does the same as initadd but prepends the given modules to
		the beginning of the list.
"

subcommand_initprepend() {
        subcommand_generic1plus 'initprepend' "$@"
}

##############################################################################
#
# initrm module...
#
Subcommands[initrm]='initrm'
Options[initrm]='-o \?H -l help'
Help[initrm]="
USAGE:
        module initrm modulefile...
		Remove modulefile(s) from the shell's initialization files.
"

subcommand_initrm() {
        subcommand_generic1plus 'initrm' "$@"
}

##############################################################################
#
# initswitch module1 module2
#
Subcommands[initswitch]='initswitch'
Options[initswitch]='-o \?H -l help'
Help[initswitch]="
USAGE:
        module initswitch modulefile1 modulefile2
		Switch modulefile1 with modulefile2 in the shell's
                initialization files.
"

subcommand_initswitch() {
        subcommand='initswitch'
	local args=()
	while (( $# > 0 )); do
		case $1 in
			-\? | --help )
				print_help "${subcommand}"
				;;
			-- )
				shift 1
				args+=( "$@" )
				break
				;;
			* )
				args+=( "$1" )
				;;
		esac
		shift
	done
	if (( ${#args[@]} != 2 )); then
	        std::die 3 "%s %s: %s" \
                         "${CMD}" "${subcommand}" \
                         "two arguments required not less not more"
	fi
	"${modulecmd}" "${Shell}" "${subcommand}" "${args[@]}"
}

##############################################################################
#
# initlist
#
Subcommands[initlist]='initlist'
Options[initlist]='-o \?H -l help'
Help[initlist]="
USAGE:
        module initlist
		List all of the modulefiles loaded from the shell's
                initialization file.
"

subcommand_initlist() {
	subcommand_generic0 'initlist' "$@"
}

##############################################################################
#
# initclear
#
Subcommands[initclear]='initclear'
Options[initclear]='-o \?H -l help'
Help[initclear]="
USAGE:
	module initclear
		Clear all of the modulefiles from the shell's
                initialization files.
"

subcommand_initclear() {
	subcommand_generic0 'initclear' "$@"
}

##############################################################################
#
# main
#

# parse arguments
#

# first argument must be a shell!
case "$1" in
	sh | bash | zsh )
		declare Shell="sh"
		;;
	csh | tcsh )
		declare Shell='csh'
		;;
	* )
		std::die 1 "${CMD}: unsupported shell -- $1"
		;;
esac
shift

# parse agruments till and including the sub-command
declare -a opts=()
while (( $# > 0 )); do
	case $1 in
		-\? | -H | --help | -help )
			print_help 'help'
			;;
		-V | --version )
			print_help 'version'
			;;
		--debug )
			set -x
			;;
		'' )
			;;
		-* )
			opts+=( "$1" )
			;;
		* )
			subcommand="$1"
			shift
			break
			;;
	esac
	shift
done

if [[ -z "${subcommand}" ]]; then
	std::die 1 "${CMD}: no sub-command specified."
fi

if [[ -z "${Subcommands[${subcommand}]}" ]]; then
	std::die 1 "${CMD}: unknown sub-command -- ${subcommand}"
fi

# restore variables from last call
unset Version
if [[ -n ${PMODULES_ENV} ]]; then
	eval "$("${base64}" -d <<< "${PMODULES_ENV}" 2>/dev/null)"
fi
# Version should now be defined again, if not:
# - PMODULES_ENV was not set
# - Version was not defined the last time the status was saved.
#   This is true for older Pmodules versions.

# We (re-)initialise the Pmodules system, if
# - PMODULES_ENV was not set/is empty
# - A new Pmodules version has been loaded
if [[ -z ${Version} ]] || [[ ${Version} != ${PMODULES_VERSION} ]]; then
	declare _tmp_loaded_modules_="${LOADEDMODULES}"
	declare _tmp_lmfiles_="${_LMFILES_}"

	pmodules_init

	LOADEDMODULES="${_tmp_loaded_modules_}"
	_LMFILES_="${_tmp_lmfiles_}"
        export_env \
                LOADEDMODULES \
                _LMFILES_
fi

# we need to handle help text and options for sub-cmd aliases
subcommand=${Subcommands[${subcommand}]}

# We need a tmp-file in the following sub-commands. It will be removed
# in the exit function if exists.
case ${subcommand} in
	load|purge|search|swap|whatis|apropos )
		declare -r tmpfile=$( ${mktemp} /tmp/Pmodules.XXXXXX ) \
			|| std::die 1 "Oops: unable to create tmp file!"
		;;
	* )
		declare -r tmpfile=''
		;;
esac

# parse arguments of the sub-command and call it
set -- $("${getopt}" --unquoted --name="${CMD}" ${Options[${subcommand}]} -- "${opts[@]}" "$@" ) \
	|| print_help "${subcommand}"
subcommand_${Subcommands[$subcommand]} $@

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