#!/bin/bash

#.............................................................................
# get absolute path of script
declare	    mydir=$(dirname "$0")
declare -r  mydir=$(cd ${mydir} && pwd -P)
declare -r  prog=$(basename "$0")

# The libs are found via PATH
PATH="/usr/bin:/bin:/usr/sbin:/sbin:${mydir}:${mydir}/../lib:${mydir}/../config"
source libstd.bash    || { echo "Oops: library '$_' cannot be loaded!" 1>&2; exit 3; }

#.............................................................................
# constants
declare -r  PMODULES_BUILD_CONFIG='modbuild.conf'

##############################################################################
#
usage() {
	std::error "
USAGE:
        $0 [options..] [build_script] version

MANDATORY ARGUMENTS:

version
        Variant of module to build.

SELECT VARIANT TO BUILD:

--system
        Specify the system for selecting a variants. Defaults to the
	output of 'uname -s'.

--with=P/V
        Select variant to compile. Use multiple '--with' arguments
	to make the selected variant unique.

BUILD-STEPS OPTIONS:

--prep
	Prepare sources: unpack sources and apply patches only.

--configure
	Prepare and configure sources.

--compile
	Prepare, configure and compile everything.

--install
	Prepare, configure and compile everything. Finally run install
	step. Do not cleanup build and source directory.

--all
	Run throu all steps including cleanup.

--update-modulefiles
	Only install the modulefile and set the release.

MISCELLANEOUS OPTIONS:

-? | -h | --help
        Print usage

-v | --verbose )
        Verbose output

-j N | --jobs=N
        Run N parallel make jobs

-f | --force-rebuild
        Force rebuild of module.

--dry-run
	Dry run.

--disable-cleanup-build
--enable-cleanup-build
	Cleanup files in the build directory. Default is to remove.
      	all files in the build-directory.

--disable-cleanup-src
--enable-cleanup-src
	Cleanup files in the source directory. Default is to
	remove all files in the source directory.

--disable-cleanup
--enable-cleanup
	Cleanup all files in temporyry directory. Default is to
	remove all files created during building.

--distdir
	Directory wwhere to store and lookup downloaded files.

--tmpdir
        Directory used for building a module.

"
	exit 1
}

##############################################################################
#
parse_args() {
	while (( $# > 0 )); do
		case $1 in
		-j )
			JOBS=$2
			shift
			;;
		--jobs=[0-9]* )
			JOBS=${1/--jobs=}
			;;
		-v | --verbose )
			trap 'echo "$BASH_COMMAND"' DEBUG
			;;
		--debug )
			set -x
			;;
		-f | --force-rebuild )
			force_rebuild='yes'
			;;
		-\? | -h | --help )
			usage
			;;
		--dry-run )
			dry_run='yes'
			;;
		--disable-cleanup )
			enable_cleanup_build='no'
			enable_cleanup_src='no'
			;;
		--enable-cleanup-build )
			enable_cleanup_build='yes'
			;;
		--disable-cleanup-build )
			enable_cleanup_build='no'
			;;
		--enable-cleanup-src )
			enable_cleanup_src='yes'
			;;
		--disable-cleanup-src )
			enable_cleanup_src='no'
			;;
		--distdir )
			PMODULES_DISTFILESDIR=$2
			shift
			;;
		--distdir=* )
			PMODULES_DISTFILESDIR=${1/--distdir=}
			;;
		--tmpdir )
			TEMP_DIR=$2
			shift
			;;
		--tmpdir=* )
			TEMP_DIR=${1/--tmpdir=}
			;;
		--system )
			SYSTEM=".$2"
			shift
			;;
		--system=* )
			SYSTEM=".${1/*=}"
			;;
		--with )
			with_modules+=( "$2" )
			shift
			;;
		--with=*/* )
			m="${1/--with=}"
			with_modules+=( ${m} )
			;;
		--prep | --configure | --compile | --install | --all )
			build_target=${1:2}
			;;
		--bootstrap )
			bootstrap='yes'
			;;
		--update-modulefiles )
			opt_update_modulefiles='yes'
			;;
		-* )
			std::die 1 "${prog}: invalid option -- '$1'"
			;;
		[0-9]* )
			[[ -z "$V" ]] || std::die 1 "${prog}: version already set -- '$1'"
			V=$1
			;;
		'')	
			:
			;;
		* )
			[[ -z "${BUILD_SCRIPT}" ]] || \
				std::die 1 "${prog}: build script already set to '${BUILD_SCRIPT}' -- '$1'"
		        BUILD_SCRIPT=$(std::get_abspath "$1")
			test -r ${BUILD_SCRIPT} || \
				std::die 1 "${prog}: build script does not exist" \
						    "or is not readable -- '$_'"
			BUILDBLOCK_DIR=$(dirname "${BUILD_SCRIPT}")
			;;
		esac
		shift
	done	
	[[ -n ${BUILD_SCRIPT} ]] || std::die 1 "${prog}: No build-block specified!"
	[[ -n ${V} ]]            || std::die 1 "${prog} Module version not specified!"
}

#......................................................................
#
# compute full module name and installation prefix
#
# The following variables are expected to be set:
#	GROUP	    module group
#	P		    module name
#	V		    module version
#       variables defining the hierarchical environment like
#	COMPILER and COMPILER_VERSION
#
# The following variables are set in this function
#	ModuleName
#	PREFIX
#
set_full_module_name_and_prefix() {
	join_by() {
		local IFS="$1"
		shift
		echo "$*"
	}

	[[ -n ${GROUP} ]] || std::die 1 "${P}/${V}: group not set."
	
	# build module name
	# :FIXME: this should be read from a configuration file
	local module_name=()
	case ${GROUP} in
	Compiler )
		module_name+=( "${COMPILER}/${COMPILER_VERSION}" )
		module_name+=( "${P}/${V}" )
		;;
	MPI )
		module_name+=( "${COMPILER}/${COMPILER_VERSION}" )
		module_name+=( "${MPI}/${MPI_VERSION}" )
		module_name+=( "${P}/${V}" )
		;;
	HDF5 )
		module_name+=( "${COMPILER}/${COMPILER_VERSION}" )
		module_name+=( "${MPI}/${MPI_VERSION}" )
		module_name+=( "${HDF5}/${HDF5_VERSION}" )
		module_name+=( "${P}/${V}" )
		;;
	OPAL )
		module_name+=( "${COMPILER}/${COMPILER_VERSION}" )
		module_name+=( "${MPI}/${MPI_VERSION}" )
		module_name+=( "${OPAL}/${OPAL_VERSION}" )
		module_name+=( "${P}/${V}" )
		;;
	HDF5_serial )
		module_name+=( "${COMPILER}/${COMPILER_VERSION}" )
		module_name+=( "hdf5_serial/${HDF5_SERIAL_VERSION}" )
		module_name+=( "${P}/${V}" )
		;;
	* )
		module_name+=("${P}/${V}" )
		;;
	esac

	# set full module name
	ModuleName=$( join_by '/' "${module_name[@]}" )
	# set PREFIX of module
	PREFIX="${PMODULES_ROOT}/${GROUP}/"
	for ((i=${#module_name[@]}-1; i >= 0; i--)); do
		PREFIX+="${module_name[i]}/"
	done
}

#......................................................................
#
# $1:	absolute path to build script
# $2:	version 
initialize_module_vars() {
	local -r script_name="$1"
	local    v="$2"

	# split path of build script into components
	local -a fname
	IFS=/ read -r -a fname <<< "${script_name:1}"

	# the second last element defines the module name!
	P=${fname[${#fname[@]}-2]}

	# use third last element as group if dir with this name exists
	# in PMODULES_ROOT
	GROUP=${fname[${#fname[@]}-3]}
	if [[ -d "${PMODULES_ROOT}/${GROUP}" ]]; then
		# Note:
		# The module group might be overwritten in the build script.
		set_full_module_name_and_prefix
	else
		GROUP=''
	fi

	# module is unstable by default
	ModuleRelease='unstable'

	V_MAJOR=''		# first number in version string
	V_MINOR=''		# second number in version string (or empty)
	V_PATCHLVL=''		# third number in version string (or empty)
	V_RELEASE=''		# module release (or empty)
	USE_FLAGS=''		# architectures (or empty)

	local tmp=''
	
	if [[ "$v" =~ "_" ]]; then
		tmp="${v#*_}"
		USE_FLAGS=":${tmp//_/:}:"
		v="${v%%_*}"
	fi
	V_PKG="${v%%-*}"	# version without the release number
	V_RELEASE="${v#*-}"	# release number 

	case "${V_PKG}" in
		*.*.* )
			V_MAJOR="${V_PKG%%.*}"
			tmp="${V_PKG%%.*#*.}"
			V_MINOR="${tmp%%.*}"
			V_PATCHLVL="${tmp#*.}"
			;;
		*.* )
			V_MAJOR="${V_PKG%.*}"
			V_MINOR="${V_PKG#*.}"
			;;
		* )
			V_MAJOR="${V_PKG}"
			;;
	esac
}

#......................................................................
# setup environment for bootstrapping
#
setup_env_for_bootstrapping() {
	ModuleName="Pmodules/${PMODULES_VERSION}"
	# set PREFIX of module
	GROUP='Tools'
	PREFIX="${PMODULES_ROOT}/${GROUP}/${ModuleName}"

	C_INCLUDE_PATH="${PREFIX}/include"
	CPLUS_INCLUDE_PATH="${PREFIX}/include"
	CPP_INCLUDE_PATH="${PREFIX}/include"
	LIBRARY_PATH="${PREFIX}/lib"
	LD_LIBRARY_PATH="${PREFIX}/lib"
	DYLD_LIBRARY_PATH="${PREFIX}/lib"

	PATH+=":${PREFIX}/bin"
	PATH+=":${PREFIX}/sbin"
}

#......................................................................
#
# Search for variants file to use
#
# Arguments:
#   none
#
# Used global variables:
#   SYSTEM
#   BUILDBLOCK_DIR
#   variants_file [out]
#
search_variants_file() {
	local -a eligible_variants_files=()
	eligible_variants_files+=( "${V%.*.*}/variants.${SYSTEM}" )
	eligible_variants_files+=( "${V%.*.*}/variants" )
	eligible_variants_files+=( "${V%.*}/variants.${SYSTEM}" )
	eligible_variants_files+=( "${V%.*}/variants" )
	eligible_variants_files+=( "${V}/variants.${SYSTEM}" )
	eligible_variants_files+=( "${V}/variants" )
	eligible_variants_files+=( "files/variants.${SYSTEM}" )
	eligible_variants_files+=( "files/variants" )

	for variants_file in "${eligible_variants_files[@]}"; do
		if [[ -e "${BUILDBLOCK_DIR}/${variants_file}" ]]; then
			variants_file="${BUILDBLOCK_DIR}/${variants_file}"
		    	return 0
	    	fi
	done
	variants_file=''
	return 1
}

#.............................................................................
# defaults for arguments/options

# number of parallel make jobs
declare -i  JOBS=3

declare force_rebuild='no'
declare dry_run='no'
declare enable_cleanup_build='yes'
declare enable_cleanup_src='no'
declare build_target='all'
declare bootstrap='no'
declare variants_file=''
declare SYSTEM="$(uname -s)"
declare opt_update_modulefiles='no'

# array collecting all modules specified on the command line via '--with=module'
declare -a with_modules=()

# save arguments, we might need them later again for building dependencies
declare -r ARGS="$@"

#.............................................................................
# main
parse_args "$@"
source libpbuild.bash || std::die 3 "Oops: library '$_' cannot be loaded!"
declare -r BUILD_SCRIPT
declare -r BUILDBLOCK_DIR

initialize_module_vars "${BUILD_SCRIPT}" "$V"

# source Pmodule environment configuration
test -d ":${BUILDBLOCK_DIR}/../../${PMODULES_CONFIG_DIR}" && PATH+=":$_"
test -d "${PMODULES_ROOT}/${PMODULES_CONFIG_DIR}" && PATH+=":$_"
source "${PMODULES_BUILD_CONFIG}" || std::die 3 "${prog}: Cannot source build configuration file."

: ${TEMP_DIR:="${PMODULES_TMPDIR:-/var/tmp/${USER}}"}
declare -rx  TEMP_DIR
: ${PMODULES_DISTFILESDIR:="${BUILD_BASEDIR}/Downloads"}
declare -xr  PMODULES_DISTFILESDIR
mkdir -p "${PMODULES_DISTFILESDIR}"

SRC_DIR="${TEMP_DIR}/$P-$V/src"
BUILD_DIR="${TEMP_DIR}/$P-$V/build"

if [[ ${bootstrap} == no ]]; then
	search_variants_file || \
		std::die 2 "${prog}: No usable variants file found!"

        # initialize module environment
	MODULECMD="${PMODULES_HOME}/bin/modulecmd"
	[[ -x ${MODULECMD} ]] || \
			std::die 2 "${prog}: no such file or executable -- '${MODULECMD}'"

	eval $( "${MODULECMD}" bash purge )
	eval $( "${MODULECMD}" bash use unstable )
	eval $( "${MODULECMD}" bash use deprecated )

	# :FIXME: this is a hack!!!
	eval $( "${MODULECMD}" bash use Libraries )
else
	setup_env_for_bootstrapping
fi

#
# run build
#
source "${BUILD_SCRIPT}"

pbuild::make_all

std::info "${P}/${V}: Done ..."

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