#!/usr/bin/env bash
#
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
	echo "BASH version >= 4 is required and must be available in PATH!" 1>&2
	exit 1
fi

declare TCL_VERSION='8.6'

declare -r OS=$(uname -s)

declare	BOOTSTRAP_DIR=$(dirname "$0")
source "${BOOTSTRAP_DIR}/Pmodules/libstd.bash" || { echo "Oops!" 1>&2; exit 42; }

declare -r BOOTSTRAP_DIR=$(std::get_abspath "${BOOTSTRAP_DIR}")
declare -r SRC_DIR="${BOOTSTRAP_DIR}/Pmodules"

# these variables need to be defined but must be initialized with an empty string
declare -x PMODULES_HOME=''
declare -x PMODULES_DISTFILESDIR=''
declare -x PMODULES_TMPDIR=''

declare -rx CONFIG_DIR='config'
declare -rx CONFIG_FILE='modbuild.conf'

# defaults
declare -rx DEFAULT_PMODULES_ROOT='/opt/psi'
declare -rx DEFAULT_DISTFILES_DIR="var/distfiles"
declare -rx DEFAULT_VERSIONS_CONFIG="${CONFIG_DIR}/versions.conf"
declare -rx DEFAULT_TMPDIR='var/tmp/${USER}'

# directory where the required tools will be installed (like bash, tclsh, etc)
declare -rx UTILBIN_DIR='libexec'

#-----------------------------------------------------------------------------
#
get_version() {
	local -r name="$1"
	echo $(awk "/^$1[[:blank:]]/ {print \$2}" "${DEFAULT_VERSIONS_CONFIG}")
}

declare -rx PMODULES_VERSION=$(get_version 'Pmodules')
declare -rx MODULES_VERSION=$(get_version 'modules')

#-----------------------------------------------------------------------------
#
usage() {
	local prog=$(basename $0)
	echo "
Usage: ${prog} help|configure|compile|install

This script must be used to
- Bootstrap/configure a new Pmodules environment,
- compile and install the required tools
- install a new Pmodules version

Run

    ${prog} help configure|compile|install

or

    ${prog} configure|compile|install --help

to get help for a specific sub-command.
"
	std::die 1 ""
}

#-----------------------------------------------------------------------------
#
pmodules::help() {
	if (( $# > 1 )); then
		usage
	fi
	case $1 in
		configure|compile|install )
			pmodules::help_$1
			;;
		* )
			echo -en "$1 - invalid sub-command!\n" 1>&2
			usage
			;;
	esac
}

#-----------------------------------------------------------------------------
#
read_config_file() {
	local var="$1"
	local fname="${!var}"
	if [[ ! -r "${fname}" ]]; then
		std::die 1 "Configuration file '${fname}' does not exist or is not readable!"
	fi

	source "${fname}" || std::die 1 "Cannot read configuration file '${fname}'"

	if [[ -z "${PMODULES_ROOT}" ]]; then
		std::die 1 "Error in configuration file '${fname}': PMODULE_ROOT not defined!"
	fi
	if [[ -z "${PMODULES_VERSION}" ]]; then
		std::die 1 "Error in configuration file '${fname}': PMODULE_VERSION not defined!"
	fi
	if [[ -z "${PMODULES_DISTFILESDIR}" ]]; then
		std::die 1 "Error in configuration file '${fname}': PMODULES_DISTFILESDIR not defined!"
	fi
	if [[ -z "${PMODULES_TMPDIR}" ]]; then
		std::die 1 "Error in configuration file '${fname}': PMODULE_TMPDIR not defined!"
	fi

	# we have to make these variables global, otherwise they are not set
	# in the calling function
	declare -g PMODULES_ROOT="${PMODULES_ROOT}"
	declare -g PMODULES_HOME="${PMODULES_HOME}"
	declare -g PMODULES_DISTFILESDIR="${PMODULES_DISTFILESDIR}"
	declare -g PMODULES_TMPDIR="${PMODULES_TMPDIR}"

	std::upvar "${var}" "${fname}"
}

#-----------------------------------------------------------------------------
#
pmodules::help_configure() {
	echo "
Usage: $(basename $0) configure [OPTION...]

Configure and setup a new Pmodules environment. You need permissions
to write to the installation root.

Options:
--prefix=DIR
	Root of the Pmodules environment installation. Everything will be
	installed in a directory hierarchy with 'DIR' as prefix.
       	The default is '${DEFAULT_PMODULES_ROOT}'.

--distfilesdir=DIR
	Directory where downloaded files are stored.
	The default is '${DEFAULT_PMODULES_DISTFILESDIR}' in the 
	Pmodules root directory.

--tmpdir=DIR
	Directory for temporary files.
	The default is '${DEFAULT_PMODULES_TMPDIR}'

--help
	Print this help text.

" 1>&2
	std::die 1 ""
}

#-----------------------------------------------------------------------------
#
pmodules::configure() {
	local prefix="${PMODULES_ROOT:-${DEFAULT_PMODULES_ROOT}}"
	local distfilesdir=''
	local tmpdir=''
	local config_file=''
	local opt_force='no'

	while (( $# > 0 )); do
		case "$1" in
			--prefix )
				prefix="$2"
				shift 1
				;;
			--prefix=* )
				prefix="${1#*=}"
				;;
			--distfilesdir )
				distfilesdir="$2"
				shift 1
				;;
			--distfilesdir=* )
				distfilesdir="${1#*=}"
				;;
			-f | --force )
				opt_force='yes'
				;;
			--tmpdir )
				tmpdir="$2"
				shift 1
				;;
			--tmpdir=* )
				tmpdir="${1#*=}"
				;;
			--help | -h | -\? )
				pmodules::help_configure
				;;
			-* )
				std::die 1 "$1: illegal option"
				;;
			* )
				std::die 1 "$1: illegal argument to sub-command 'configure'."
				;;
		esac
		shift 1
	done
	if [[ ! -d ${prefix} ]]; then
		echo "The root directory '${prefix}' does not exist, trying to create it..."
		if ! mkdir -p "${prefix}"; then
			std::die 1 "Creating the root directory failed!\nAborting..."
		fi
	fi
	if [[ ! -w ${prefix} ]]; then
		std::die 1 "The root directory '${prefix}' is not writable!\nAborting..."
	fi
	mkdir -p "${prefix}/${CONFIG_DIR}" || \
		std::die 1 "Aborting..."

	local config_file="${prefix}/${CONFIG_DIR}/${CONFIG_FILE}"
	if [[ "${opt_force}" == 'yes' ]]; then
		rm -f "${config_file}"
	fi

	if [[ -e "${config_file}" ]]; then
		std::die 1 "The Pmodules environment has already been configured!
Use the option --force to override.\nAborting..."
	fi

	[[ -z "${distfilesdir}" ]] && distfilesdir="${prefix}/${DEFAULT_DISTFILES_DIR}"
	[[ -z "${tmpdir}" ]] && tmpdir="${prefix}/${DEFAULT_TMPDIR}"

	sed_cmd="s:@PMODULES_ROOT@:${prefix}:g;"
	sed_cmd+="s:@PMODULES_DISTFILESDIR@:${distfilesdir}:g;"
	sed_cmd+="s:@PMODULES_TMPDIR@:${tmpdir}:g;"
        sed_cmd+="s:@PMODULES_VERSION@:${PMODULES_VERSION}:g"
        
	sed "${sed_cmd}" "${BOOTSTRAP_DIR}/${CONFIG_DIR}/${CONFIG_FILE}.in" \
		> "${config_file}" || \
		std::die 1 "Cannot create configuration file in Pmodules root\nAborting..."

	read_config_file 'config_file'
	install -d -m 0755 "${PMODULES_HOME}/bin"
	install -d -m 0755 "${PMODULES_HOME}/init"
	install -d -m 0755 "${PMODULES_HOME}/lib"
	install -d -m 0755 "${PMODULES_HOME}/libexec"
	echo "Configuration:"
	echo "  root of Pmodules environment: ${prefix}"
	echo "  Pmodule prefix:               ${PMODULES_HOME}"
	echo "  tmp directory:                ${tmpdir}"
	echo "  store for downloaded files:   ${distfilesdir}"
	echo "Done..."
}

#-----------------------------------------------------------------------------
#
pmodules::help_compile() {
	echo "
Usage: $(basename $0) compile [OPTION...]

Compile and install the required tools for a new Pmodules environment.
ou need the permissions to write to the installation root.

Options:
--prefix=DIR
	Root of the Pmodules environment installation. The root of the
       	installation must be either specified via this option or the
	environment variable PMODULES_ROOT. If this option is used and
	the PMODULES_ROOT is set, the directory specified with this 
	option will be used.

--debug
	Enable verbose/debug output.

--disable-cleanup )
	Do not cleanup the tmp directory after compilation and installation.

--force | -f
	Force compilation.

--help
	Print this help text.

" 1>&2
	std::die 1 ""
}

pmodules::compile() {
	build () {
		local -r name="$1"
		local -r version=$(get_version "${name}")
		shift

		"${BOOTSTRAP_DIR}/Pmodules/modbuild.in" \
			"--config=${config_file}" \
			"--enable-cleanup" \
			"--force-rebuild" \
			"--debug" \
			"--verbose" \
			"${BOOTSTRAP_DIR}/Tools/${name}/build" \
			"${build_opts[@]}" "$@" "${version}" || \
			std::die 3 "Compiling '${name}' failed!"
	}

	local prefix="${PMODULES_ROOT:-${DEFAULT_PMODULES_ROOT}}"
	local opt_force='no'
	local config_file=''

	while (( $# > 0 )); do
		case $1 in
			--prefix )
				prefix="$2"
				shift 1
				;;
			--prefix=* )
				prefix="${1#*=}"
				;;
			--disable-cleanup )
				build_opts+=( "$1" )
				;;
			--debug )
				build_opts+=( "$1" )
				;;
			-f | --force )
				opt_force='yes'
				;;
			--help | -h | -\? )
				pmodules::help_compile
				;;
			-* )
				std::die 1 "$1: illegal option"
				;;
			* )
				std::die 1 "$1: illegal argument for  sub-command 'compile'."
				;;
		esac
		shift 1
	done

	local config_file="${prefix}/${CONFIG_DIR}/${CONFIG_FILE}"
	read_config_file config_file
	install -d -m 0755 "${PMODULES_HOME}/bin"
	install -d -m 0755 "${PMODULES_HOME}/init"
	install -d -m 0755 "${PMODULES_HOME}/lib"
	install -d -m 0755 "${PMODULES_HOME}/libexec"

	echo "Configuration:"
	echo "  root of Pmodules environment: ${prefix}"
	echo "  Pmodule prefix:               ${PMODULES_HOME}"

	#if [[ ! -f "${PMODULES_HOME}/${UTILBIN_DIR}/base64" ]] || [[ ${opt_force} == 'yes' ]]; then
	#	build coreutils
	#fi

	if [[ "${OS}" == 'Darwin' ]]; then
		if [[ ! -f "${PMODULES_HOME}/${UTILBIN_DIR}/getopt" ]] || [[ ${opt_force} == 'yes' ]]; then
			build getopt
		fi

		if [[ ! -f "${PMODULES_HOME}/${UTILBIN_DIR}/find" ]] || [[ ${opt_force} == 'yes' ]]; then
			build findutils
		fi
	fi

	if [[ ! -f "${PMODULES_HOME}/${UTILBIN_DIR}/bash" ]] || [[ ${opt_force} == 'yes' ]]; then
		build bash
	fi

	if [[ ! -e "${PMODULES_HOME}/${UTILBIN_DIR}/tclsh" ]] || [[ ${opt_force} == 'yes' ]]; then
		build Tcl
	fi

	if [[ ! -e "${PMODULES_HOME}/lib/tcllib1.20" ]] || [[ ${opt_force} == 'yes' ]]; then
		build tcllib
	fi

	if [[ ! -e "${PMODULES_HOME}/libexec/modulecmd.bin" ]] || [[ ${opt_force} == 'yes' ]]; then
		build modules
	fi
	rm -rf "${PMODULES_HOME}/include"
	rm -rf "${PMODULES_HOME}/lib/"*.a
	rm -rf "${PMODULES_HOME}/lib/"*.la
	rm -rf "${PMODULES_HOME}/lib/bash"
	rm -rf "${PMODULES_HOME}/lib/pkginfo"
	rm -rf "${PMODULES_HOME}/man"
	rm -rf "${PMODULES_HOME}/share"

	echo "Done..."
}

#-----------------------------------------------------------------------------
#
pmodules::help_install() {
	echo "
Usage: $(basename $0) install [OPTION...]

Install a new Pmodules version.

Options:
--prefix=DIR
	Root of the Pmodules environment installation. The root of the
       	installation must be either specified via this option or the
	environment variable PMODULES_ROOT. If this option is used and
	the PMODULES_ROOT is set, the directory specified with this 
	option will be used.

--debug
	Enable verbose/debug output.

--disable-cleanup )
	Do not cleanup the tmp directory after compilation and installation.

--force | -f
	Force compilation.

--help
	Print this help text.

" 1>&2
	std::die 1 ""
}

pmodules::install() {
	local prefix="${PMODULES_ROOT:-${DEFAULT_PMODULES_ROOT}}"
	local config_file=''
	local opt_force='no'

	while (( $# > 0 )); do
		case $1 in
			--debug )
				set -x
				;;
			--prefix )
				prefix="$2"
				shift 1
				;;
			--prefix=* )
				prefix="${1#*=}"
				;;
			-f | --force )
				opt_force='yes'
				;;
			--help | -h | -\? )
				pmodules::help_install
				;;
			-* )
				std::die 1 "$1: illegal option"
				;;
			* )
				std::die 1 "$1: illegal argument to sub-command 'install'."
				;;
		esac
		shift 1
	done

	local config_file="${prefix}/${CONFIG_DIR}/${CONFIG_FILE}"
	read_config_file 'config_file'

	###
	#
	# begin installation
	#
	echo "Configuration:"
	echo "  root of Pmodules environment: ${PMODULES_ROOT}"
	echo "  Pmodule prefix:               ${PMODULES_HOME}"
	sed_cmd="s:@PMODULES_HOME@:${PMODULES_HOME}:g;"
	sed_cmd+="s:@PMODULES_VERSION@:${PMODULES_VERSION}:g;"
	sed_cmd+="s:@MODULES_VERSION@:${MODULES_VERSION}:g;"
	sed_cmd+="s:@PMODULES_DISTFILESDIR@:${PMODULES_DISTFILESDIR}:g;"
	sed_cmd+="s:@PMODULES_TMPDIR@:${PMODULES_TMPDIR}:g;"
	sed_cmd+="s:@TCLSHDIR@:${PMODULES_HOME}/${UTILBIN_DIR}:g;"
	sed_cmd+="s:@pager@::g;"
	sed_cmd+="s:@pageropts@::g;"
	sed_cmd+="s:@etcdir@:${PMODULES_ROOT}/${CONFIG_DIR}:g;"
	sed_cmd+="s:@VERSIONING@:#:g;"
	sed_cmd+="s:@prefix@:${PMODULES_HOME}:g;"
	sed_cmd+="s:@initdir@:${PMODULES_HOME}/init:g;"
	sed_cmd+="s:@MODULES_RELEASE@:${PMODULES_VERSION}:g;"
	sed_cmd+="s:@BASH@:${PMODULES_HOME}/${UTILBIN_DIR}/bash:g;"
	sed_cmd+="s:@MODULECMD@:${PMODULES_HOME}/${UTILBIN_DIR}/modulecmd.bash:g;"
	sed_cmd+="s:@MODMANAGE@:${PMODULES_HOME}/${UTILBIN_DIR}/modmanage.bash:g;"
	sed_cmd+="s:@TCL_VERSION@:${TCL_VERSION}:g;"
	
	sed "${sed_cmd}" "${SRC_DIR}/profile.bash.in"     > "${PMODULES_ROOT}/${CONFIG_DIR}/profile.bash-${PMODULES_VERSION}"
	sed "${sed_cmd}" "${SRC_DIR}/profile.csh.in"      > "${PMODULES_ROOT}/${CONFIG_DIR}/profile.csh-${PMODULES_VERSION}"
	sed "${sed_cmd}" "${SRC_DIR}/profile.zsh.in"      > "${PMODULES_ROOT}/${CONFIG_DIR}/profile.zsh-${PMODULES_VERSION}"
	chmod 0644 "${PMODULES_ROOT}/${CONFIG_DIR}"/*-${PMODULES_VERSION}

	test -e "${PMODULES_ROOT}/${CONFIG_DIR}/profile.bash" || \
		install -m 0644 "$_-${PMODULES_VERSION}" "$_"

	test -e "${PMODULES_ROOT}/${CONFIG_DIR}/profile.csh" || \
		install -m 0644 "$_-${PMODULES_VERSION}" "$_"

	test -e "${PMODULES_ROOT}/${CONFIG_DIR}/profile.zsh" || \
		install -m 0644 "$_-${PMODULES_VERSION}" "$_"

	sed "${sed_cmd}" "${SRC_DIR}/modulecmd.in"        > "${PMODULES_HOME}/bin/modulecmd"
	chmod 0755 "${PMODULES_HOME}/bin/modulecmd"
	sed "${sed_cmd}" "${SRC_DIR}/modulecmd.bash.in"   > "${PMODULES_HOME}/libexec/modulecmd.bash"
	chmod 0755 "${PMODULES_HOME}/libexec/modulecmd.bash"

	sed "${sed_cmd}" "${SRC_DIR}/libpmodules.bash.in"    > "${PMODULES_HOME}/lib/libpmodules.bash"
	chmod 0755 "${PMODULES_HOME}/lib/libpmodules.bash"

        sed "${sed_cmd}" "${SRC_DIR}/modbuild.in"         > "${PMODULES_HOME}/bin/modbuild"
	chmod 0755 "${PMODULES_HOME}/bin/modbuild"

	sed "${sed_cmd}" "${SRC_DIR}/modmanage.in"        > "${PMODULES_HOME}/bin/modmanage"
	chmod 0755 "${PMODULES_HOME}/bin/modmanage"
	sed "${sed_cmd}" "${SRC_DIR}/modmanage.bash.in"   > "${PMODULES_HOME}/libexec/modmanage.bash"
	chmod 0755 "${PMODULES_HOME}/libexec/modmanage.bash"

	test -e "${PMODULES_ROOT}/${CONFIG_DIR}/Pmodules.conf" || \
	    install -m 0644 "${SRC_DIR}/Pmodules.conf"	"${PMODULES_ROOT}/${CONFIG_DIR}"
	
	install -m 0644 "${SRC_DIR}/bash"		"${PMODULES_HOME}/init"
	install -m 0644 "${SRC_DIR}/bash_completion"	"${PMODULES_HOME}/init"
	install -m 0644 "${SRC_DIR}/csh"		"${PMODULES_HOME}/init"
	install -m 0644 "${SRC_DIR}/Pmodules.py"	"${PMODULES_HOME}/init"
	install -m 0644 "${SRC_DIR}/zsh"		"${PMODULES_HOME}/init"

	install -m 0644 "${SRC_DIR}/libpbuild.bash"	"${PMODULES_HOME}/lib"
	install -m 0644 "${SRC_DIR}/libpbuild_dyn.bash"	"${PMODULES_HOME}/lib"
	install -m 0644 "${SRC_DIR}/libstd.bash"	"${PMODULES_HOME}/lib"
	install -m 0755 -d				"${PMODULES_HOME}/lib/Pmodules"
	install -m 0644 "${SRC_DIR}/libmodules.tcl"	"${PMODULES_HOME}/lib/Pmodules"

	{
		PATH="${PMODULES_HOME}/${UTILBIN_DIR}:${PATH}"
		cd "${PMODULES_HOME}/lib/Pmodules"
		"${BOOTSTRAP_DIR}/mkindex.tcl"
	}

	install -m 0755 -d "${PMODULES_ROOT}/Tools/modulefiles/Pmodules"
	install -m 0644 "${SRC_DIR}/modulefile" "${PMODULES_ROOT}/Tools/modulefiles/Pmodules/${PMODULES_VERSION}"
	mkdir -p "${PMODULES_ROOT}/Libraries/modulefiles"
	
	echo "Done..."
}

#=============================================================================
#

declare -a build_opts=()
build_opts+=( '--bootstrap' )

declare subcmd=''
declare -a subcmd_args=()

while (( $# > 0 )); do
	case "$1" in
		--help | -h | -\? )
			usage
			;;
		--debug )
			set -x
			;;
		-* )
			std::die 1 "$1: illegal option"
			;;
		help | configure | compile | install )
			subcmd="$1"
			shift 1
			subcmd_args=( "$@" )
			shift $#
			;;
		* )
			std::die 1 "Invalid sub-command '$1'.\n\nUse 'build --help' to get help..."
			;;
	esac
	shift 1
done

[[ -n "${subcmd}" ]] || std::die 1 "Missing sub-command.\n\nUse 'build --help' to get help..."

pmodules::${subcmd} "${subcmd_args[@]}"

