#!/usr/bin/env bash
#
# CONFIGURE, check requirements then set variables and create Makefiles
# Copyright (C) 2017 CEA/DAM
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

##########################################################################

progpath=$0
progdir=${progpath%/*}
prog=${progpath##*/}

# files to translate
targetlist="${progdir}/Makefile.inc ${progdir}/site.exp"

# argument list
arglist="TCLSH SPHINXBUILD PS BASENAME GREP RMDIR_IGN_NON_EMPTY VERSION \
baseprefix prefix bindir libexecdir etcdir initdir datarootdir mandir docdir \
modulefilesdir setmanpath setbinpath setdotmodulespath docinstall \
examplemodulefiles builddoc gitworktree usemanpath compatversion versioning \
pager pageropts modulepath loadedmodules quarantinevars"
compatarglist=

# flags to know if argument has been specified on command-line
defdotmodulespath=1
defcompatversion=1
defpageropts=1

# set argument default values
prefix=/usr/local/Modules
setmanpath=y
setbinpath=y
setdotmodulespath=n
docinstall=y
examplemodulefiles=y
compatversion=y
versioning=n
loadedmodules=
quarantinevars=
TCLSH=tclsh
SPHINXBUILD=sphinx-build
builddoc=y
gitworktree=y
usemanpath=y
PS=ps
BASENAME=basename
GREP=grep
RMDIR_IGN_NON_EMPTY=rmdir
VERSION=
pager=less
pageropts='-eFKRX'
# these args are initialized here but as they depend on other argument value
# they will get their default value later (after command-line parse)
bindir=
libexecdir=
etcdir=
initdir=
datarootdir=
mandir=
docdir=
modulefilesdir=
modulepath=

# git branch to use to build compat version
compatbranch='c-3.2'


# print message on stderr then exit
echo_error() {
   echo -e "ERROR: $1" >&2
   exit 1
}

# print message on stderr then exit
echo_warning() {
   echo -e "WARNING: $1"
}


# print usage message
echo_usage() {
   echo "Usage: $progpath [OPTION]...
Check requirements then set variables and create Makefiles.

Defaults for the options are specified in square brackets.

Configuration:
  -h, --help              display this help and exit

Installation directories:
  --prefix=PREFIX         install files in PREFIX [$prefix]

By default, \`make install' will install all the files in
\`$prefix/bin', \`$prefix/libexec', etc. You
can specify an installation prefix other than \`$prefix'
using \`--prefix', for instance \`--prefix=\$HOME'.

For better control, use the options below.

Fine tuning of the installation directories:
  --bindir=DIR            user executables [PREFIX/bin]
  --libexecdir=DIR        program executables [PREFIX/libexec]
  --etcdir=DIR            program configurations [PREFIX/etc]
  --initdir=DIR           environment initialization scripts [PREFIX/init]
  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
  --mandir=DIR            man documentation [DATAROOTDIR/man]
  --docdir=DIR            documentation root [DATAROOTDIR/doc]
  --modulefilesdir=DIR    system modulefiles [PREFIX/modulefiles]

Optional Features:
  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
  --enable-set-manpath    add mandir to MANPATH in init scripts [yes]
  --enable-set-binpath    add bindir to PATH in init scripts [yes]
  --enable-dotmodulespath configure modules path in .modulespath file rather
                          than modulerc file [no]
  --enable-doc-install    install documentation files in the documentation
                          directory defined with \'docdir' (no impact on
                          man pages installation) [yes]
  --enable-example-modulefiles
                          install in 'modulefilesdir' some modulefiles
                          provided as examples [yes]
  --enable-compat-version also build and install Modules compatibility (C)
                          version and enable switching capabilities between
                          the two versions [yes]
  --enable-versioning     append modules version to installation prefix and
                          deploy a \`versions' modulepath, shared between all
                          versioning enabled Modules installation, containing
                          modulefiles that enable to switch from one Modules
                          version to another [no]

Optional Packages:
  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
  --with-tclsh=BIN        name or full path of Tcl interpreter shell [tclsh]
  --with-pager=BIN        name or full path of default pager program to use to
                          paginate informational message output (can be super-
                          seeded at run-time by environment variable) [$pager]
  --with-pager-opts=OPTLIST
                          settings to apply to default pager program [$pageropts]
  --with-modulepath=PATHLIST
                          default modulepaths to set in default configuration
                          file to be enabled (each path in PATHLIST should
                          be separated by \`:') [PREFIX/modulefiles or
                          BASEPREFIX/\$MODULE_VERSION/modulefiles if versioning
                          installation mode enabled]
  --with-loadedmodules=MODLIST
                          default modulefiles to set in default configuration
                          file to be loaded (each modulefile name in MODLIST
                          should be separated by \`:') []
  --with-quarantine-vars='VARNAME[=VALUE] ...'
                          environment variables to put in quarantine when
                          running the module command to ensure it a sane
                          execution environment (each variable should be
                          separated by \` '). a value can eventually be set to
                          a quarantine variable instead of emptying it. []

Depending on the above configuration options the files are approximately
placed in the following directory structure:
  PREFIX/
    bin/
    etc/
    init/
    libexec/
    share/
      doc/
      man/
        man1/
        man4/
    modulefiles/"
}

# check requirement availability
check_requirement() {
   typeset req=$1
   typeset optmesg=$2
   typeset cmdenv=$3

   if [ $# -gt 3 ]; then
      shift 3
   else
      shift $#
   fi

   for cmd in $req "${@}"; do
      if [ -n "$cmd" ]; then
         echo -n "checking for ${cmd}... "
         if [ -n "$cmdenv" ]; then
            cmdsearch="$cmdenv command -v $cmd"
         else
            cmdsearch="command -v $cmd"
         fi
         reqpath=$(eval $cmdsearch)
         if [ -n "$reqpath" ]; then
            echo $reqpath
            break
         else
            echo "not found"
         fi
      fi
   done

   if [ -z "$reqpath" ]; then
      if [ -z "$optmesg" ]; then
         echo_error "$req could not be found"
      else
         echo_warning "$optmesg"
      fi
   fi
}

# parse optional argument to find if feature is enabled or disabled
get_feature_value() {
   typeset val="${1#*=}"

   if [ "${1//--enable/}" != "$1" ]; then
      if [ "$val" = 'no' ]; then
        echo 'n'
      else
        echo 'y'
      fi
   else
      echo 'n'
   fi
}

# parse optional argument to find package is set or disabled
get_package_value() {
   typeset val="${1#*=}"

   if [ "${1//--with-/}" != "$1" ]; then
      if [ "$val" = 'no' ]; then
        echo ''
      else
        echo "$val"
      fi
   else
      echo ''
   fi
}

# parse arguments
for arg in "$@"; do
   case "$arg" in
   --prefix=*)
      prefix="${arg#*=}" ;;
   --bindir=*)
      bindir="${arg#*=}" ;;
   --libexecdir=*)
      libexecdir="${arg#*=}" ;;
   --etcdir=*)
      etcdir="${arg#*=}" ;;
   --initdir=*)
      initdir="${arg#*=}" ;;
   --datarootdir=*)
      datarootdir="${arg#*=}" ;;
   --mandir=*)
      mandir="${arg#*=}" ;;
   --docdir=*)
      docdir="${arg#*=}" ;;
   --modulefilesdir=*)
      modulefilesdir="${arg#*=}" ;;
   --enable-set-manpath*|--disable-set-manpath)
      setmanpath=$(get_feature_value "$arg") ;;
   --enable-set-binpath*|--disable-set-binpath)
      setbinpath=$(get_feature_value "$arg") ;;
   --enable-dotmodulespath*|--disable-dotmodulespath)
      setdotmodulespath=$(get_feature_value "$arg")
      defdotmodulespath=0 ;;
   --enable-doc-install*|--disable-doc-install)
      docinstall=$(get_feature_value "$arg")
      defdocinstall=0 ;;
   --enable-example-modulefiles*|--disable-example-modulefiles)
      examplemodulefiles=$(get_feature_value "$arg")
      defexamplemodulefiles=0 ;;
   --enable-compat-version*|--disable-compat-version)
      compatversion=$(get_feature_value "$arg")
      defcompatversion=0 ;;
   --enable-versioning*|--disable-versioning)
      versioning=$(get_feature_value "$arg") ;;
   --with-tclsh=*|--without-tclsh)
      tclshbin=$(get_package_value "$arg") ;;
   --with-pager=*|--without-pager)
      pager=$(get_package_value "$arg") ;;
   --with-pager-opts=*|--without-pager-opts)
      pageropts=$(get_package_value "$arg")
      defpageropts=0 ;;
   --with-modulepath=*|--without-modulepath)
      modulepath=$(get_package_value "$arg") ;;
   --with-loadedmodules=*|--without-loadedmodules)
      loadedmodules=$(get_package_value "$arg") ;;
    --with-quarantine-vars=*|--without-quarantine-vars)
      quarantinevars=$(get_package_value "$arg") ;;
   --with-module-path=*)
      echo_warning "Option \`--with-module-path' ignored, use \`--modulepath' instead" ;;
   -h|--help)
      echo_usage
      exit 0
      ;;
   *)
      compatarglist+="$arg " ;;
   esac
done

# test requirements availability
check_requirement uname
kernelname=$(uname -s)

# Makefile for this project are GNU Makefile so we must ensure correct make
# command is available, on Linux/Darwin systems the 'make' bin refers to GNU
# Make whereas on other system it is not the default make flavor, so 'gmake'
# should be checked
if [ "$kernelname" = 'Linux' -o "$kernelname" = 'Darwin' ]; then
   make='make'
else
   make='gmake'
fi
check_requirement $make
check_requirement sed
# ensure a GNU grep is used on Solaris and Darwin
if [ "$kernelname" = 'SunOS' -o "$kernelname" = 'Darwin' ]; then
   grep='ggrep'
else
   grep='grep'
fi
check_requirement $grep
GREP=$reqpath
check_requirement cut
check_requirement runtest "Install \`dejagnu' if you want to run the \
testsuite"
check_requirement manpath 'Will rely on MANPATH to get enabled man directories'
[ -z "$reqpath" ] && usemanpath=n

# rmdir option may not be available, use || true in any case
if [ "$kernelname" = 'Linux' ]; then
    RMDIR_IGN_NON_EMPTY='rmdir --ignore-fail-on-non-empty'
else
    RMDIR_IGN_NON_EMPTY='rmdir'
fi

# if we install from git repository, must have sphinx to build doc and git to
# fetch current release number
if [ -d '.git' ]; then
   # pass even if sphinx not there but no doc will be built
   check_requirement sphinx-build "Install \`sphinx-build' if you want to \
build the documentation" '' 'sphinx-1.0-build'
   SPHINXBUILD=$reqpath
   [ -z "$reqpath" ] && builddoc=n
   check_requirement git
fi

# get tclsh location from standard PATHs or /usr/local/bin
# or validate location passed as argument
if [ -n "$tclshbin" ]; then
   check_requirement $tclshbin '' 'PATH=/usr/bin:/bin:/usr/local/bin'
else
   # also check version-specific names in case no generic tclsh binary exists
   check_requirement $TCLSH '' 'PATH=/usr/bin:/bin:/usr/local/bin' 'tclsh8.6' 'tclsh8.5' 'tclsh8.4'
fi
TCLSH=$reqpath

# get location of commands used 'fullpath' in init scripts
check_requirement 'ps' '' 'PATH=/usr/bin:/bin:/usr/local/bin'
PS=$reqpath
check_requirement 'basename' '' 'PATH=/usr/bin:/bin:/usr/local/bin'
BASENAME=$reqpath

# get pager program location from standard PATHs or /usr/local/bin
# or validate location passed as argument
if [ -n "$pager" ]; then
   check_requirement $pager '' 'PATH=/usr/bin:/bin:/usr/local/bin'
   pager=$reqpath
fi
# adapt pager program settings dependings of specified args
if [ $defpageropts -eq 1 -a "${pager##*/}" != 'less' ]; then
    pageropts=''
    echo_warning "As chosen pager is not \`less', default pager options are cleared"
fi

# fetch modules version from git repository tags
if [ -d '.git' ]; then
  gitcurtag=$(git describe --tags --abbrev=0)
  gitcurdesc=$(git describe --tags)
  gitcurbranch=$(git rev-parse --abbrev-ref HEAD)
  release=${gitcurtag#v}
  if [ "$gitcurtag" = "$gitcurdesc" ]; then
     build=''
  elif [ "$gitcurbranch" = 'master' ]; then
     build="+${gitcurdesc#${gitcurtag}-}"
  else
     build="+${gitcurbranch}${gitcurdesc#${gitcurtag}}"
  fi
# elsewhere fetch modules version from version.inc file
elif [ -r ${progdir}/version.inc ]; then
  release=$(grep '^MODULES_RELEASE ' ${progdir}/version.inc | cut -d ' ' -f 3)
  build=$(grep '^MODULES_BUILD ' ${progdir}/version.inc | cut -d ' ' -f 3)
fi
VERSION=$release$build

if [ -z "$VERSION" ]; then
   echo_error "Cannot fetch modules version"
fi

# adapt prefix if versioning enabled
baseprefix=$prefix
if [ "$versioning" = 'y' ]; then
   # append modules version to installation prefix
   prefix=$prefix/$VERSION
fi

# set argument default values for those depending of other argument
[ -z "$bindir" ] && bindir=$prefix/bin
[ -z "$libexecdir" ] && libexecdir=$prefix/libexec
[ -z "$etcdir" ] && etcdir=$prefix/etc
[ -z "$initdir" ] && initdir=$prefix/init
[ -z "$datarootdir" ] && datarootdir=$prefix/share
[ -z "$mandir" ] && mandir=$datarootdir/man
[ -z "$docdir" ] && docdir=$datarootdir/doc
[ -z "$modulefilesdir" ] && modulefilesdir=$prefix/modulefiles
# default modulepath based on MODULE_VERSION if versioning enabled
[ -z "$modulepath" -a "$versioning" = 'y' ] && modulepath=${modulefilesdir/#$prefix/$baseprefix\/\$MODULE_VERSION}
[ -z "$modulepath" ] && modulepath=$modulefilesdir

# check feature requirements are met
if [ "$setdotmodulespath" = 'y' -a -n "$loadedmodules" ]; then
   featmesg="As \`setdotmodulespath' is enabled beware that \`loadedmodules' may be ignored by init scripts"
   echo_warning "$featmesg"
fi
if [ -n "$loadedmodules" -a -z "$modulepath" ]; then
   featmesg="A value is set for \`loadedmodules' whereas \`modulepath' is not defined"
   echo_warning "$featmesg"
fi

# check compat version sources
if [ "$compatversion" = 'y' ]; then
   if [ ! -d 'compat' ]; then
      # extract sources from a git branch and prepare sources
      if [ -d '.git' ]; then
         check_requirement autoreconf
         # specific location to find autopoint on Darwin
         if [ "$kernelname" = 'Darwin' ]; then
            check_requirement autopoint '' 'PATH=/usr/bin:/bin:/usr/local/bin:/usr/local/opt/gettext/bin'
         else
            check_requirement autopoint
         fi
         check_requirement automake
         check_requirement aclocal

         echo "--- preparing compatibility version sources ------"
         repofull=1
         # retrieve compat version branch if not found locally
         git rev-parse --verify $compatbranch >/dev/null 2>/dev/null
         if [ $? -ne 0 ]; then
            curbranch=$(git rev-parse --abbrev-ref HEAD)
            curremote=$(git remote show | head -n 1)
            curnbcomm=$(git rev-list $curbranch | wc -l)
            if [ $curnbcomm -lt 350 ]; then
               repofull=0
            else
               git fetch $curremote $compatbranch || exit 1
               git branch $compatbranch $curremote/$compatbranch || exit 1
            fi
         fi

         # repository is truncated, need to standalone clone compat branch
         if [ $repofull -eq 0 ]; then
            curremoteurl=$(git config --get remote.$curremote.url)
            git clone --branch=$compatbranch $curremoteurl compat || exit 1
         else
            # install compat git branch in a directory
            git worktree list >/dev/null 2>/dev/null
            if [ $? -eq 0 ]; then
               git worktree add compat $compatbranch || exit 1
            else
               gitworktree=n
               # clone branch if no worktree command supported
               git clone --branch=$compatbranch .git compat || exit 1
            fi
         fi

         pushd compat
         autoreconf -f -i || exit 1
         popd
         echo "--------------------------------------------------"
      else
         echo_error "Cannot build compatibility version, \`compat' directory cannot be found"
      fi
   fi
fi

# display configuration set
# and build Makefile translation expression
translation_regexp=()
for arg in ${arglist}; do
   echo "$arg = ${!arg}"
   translation_regexp+=('-e' "s|@${arg}@|${!arg}|g")
done

# configure compat version sources
if [ "$compatversion" = 'y' ]; then
   echo "--- configuring compatibility version sources ----"
   pushd compat
   # unknown arguments for this configure script are passed down to the
   # compatibility version configure script (which raise error if arg unknown)
   # also pass arguments relative to installation paths to get compatibility
   # man pages set with them
   # keep versioning disabled for compat version to get expected paths defined
   ./configure --prefix=$prefix --bindir=$bindir --libexecdir=$libexecdir \
      --datarootdir=$datarootdir --mandir=$mandir --docdir=$docdir \
      --with-module-path=$modulepath --disable-versioning \
      $compatarglist || exit 1
   popd
   echo "--------------------------------------------------"
fi

# generate Makefiles
for target in ${targetlist}; do
   # escape any '$' in value for Makefile or testsuite interpretation
   if [ "${target/Makefile}" = "${target}" ]; then
      extra_regexp=()
   else
      extra_regexp=('-e' 's|\$|\$\$|g')
   fi
   echo "creating $target"
   sed "${translation_regexp[@]}" "${extra_regexp[@]}" ${target}.in >$target
done

exit 0
