#!/bin/sh
# @package      hubzero-use
# @file         environ
# @author       Rick Kennell <kennell@purdue.edu>
# @copyright    Copyright (c) 2006-2014 HUBzero Foundation, LLC.
# @license      http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3
#
# Copyright (c) 2006-2014 HUBzero Foundation, LLC.
#
# This file is part of: The HUBzero(R) Platform for Scientific Collaboration
#
# The HUBzero(R) Platform for Scientific Collaboration (HUBzero) is free
# software: you can redistribute it and/or modify it under the terms of
# the GNU Lesser General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# HUBzero 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# HUBzero is a registered trademark of HUBzero Foundation, LLC.
#

# ----------------------------------------------------------------------------
# This is the main script that is invoked by the "use" and "unuse"
# command aliases.  It generates output that modifies the shell
# environment.  Its output is evaluated by the shell.
#
#echo Arguments are $* >&2
#set -x

##############################################################################
# The first argument is the type of shell to print commands for.
# It's either 'sh' or not 'sh' (csh for instance).
##############################################################################
shell=$1
shift

##############################################################################
# The second argument is the operation to perform.
# Either 'use' or 'unuse'.
##############################################################################
cmd=$1
shift

##############################################################################
# Form the list of environment configuration directories.
##############################################################################
dirs=""
if [ `expr index "$ENVIRON_CONFIG_DIRS" ":"` -gt 0 ]
then
	IFS=":"
fi

for dir in ${ENVIRON_CONFIG_DIRS}
do
	[ -d ${dir} ] && dirs="${dirs} ${dir}"
done

unset IFS

##############################################################################
# This function sets (or replaces) all of the other public functions
# with null versions.
##############################################################################
clear_functions() {
  help() { :; }
  desc() { :; }
  note() { :; }
  setenv() { :; }
  shellset() { :; }
  alias() { :; }
  prepend() { :; }
  conflict() { :; }
  command() { :; }
  require() { :; }
  is_registered() { :; }
  register() { :; }
  use() { :; }
  tags() { :; }
}

##############################################################################
# Set all of the public functions to null.
##############################################################################
clear_functions

##############################################################################
# Print a formatted message in arg 1.
##############################################################################
do_print_format() {
  echo "$1" | fmt -64 | awk '{ print "        " $0}' >&2
  echo "/bin/true;"
  exit 0
}

##############################################################################
# Prepend to $1 the contents of $2.
# First check that $2 doesn't already exist in $1
##############################################################################
do_prepend() {
  val="$(printenv $1)"
  # This only works in newer Bash...
  #[[ "${val}" =~ "^(|.*:)${2}(:.*|)$" ]] && return
  echo "${val}" | egrep -q "^(|.*:)${2}(:.*|)$" && return
  if [ "x${val}x" = "xx" ]; then
    do_setenv $1 "$2"
  else
    do_setenv $1 "$2:$val"
  fi
}

##############################################################################
# The opposite of "prepend" is actually "remove."
# If we've removed everything from $1, unset it.
##############################################################################
do_unprepend() {
  val="$(printenv $1)"
  val="$(printenv $1 | sed -e 's|^'"$2"'$||' -e 's|:'"$2"':|:|g' -e 's|:'"$2"'$||g' -e 's|^'"$2"':||g')"
  do_setenv $1 "$val"
  if [ "x${val}x" = "xx" ] || [ "${val}" = ":" ]; then
    do_unsetenv $1
  fi
}

##############################################################################
# Question for the user.  Should we replace one environment with another?
##############################################################################
should_replace() {
  echo -n "Do you want to replace the settings for $1? (y/n) " >&2
  read resp
  echo >&2 # Just in case stdin is /dev/null
  if [ "${resp}" = "Y" ] || [ "${resp}" = "y" ]; then
    return 0
  else
    return 1
  fi
}

##############################################################################
# Question for the user.  Should we record the environ choice in ~/.environ?
##############################################################################
should_persist() {
  echo -n "Do you want to make '${cmd} ${env}' persistent? (y/n) " >&2
  read resp
  echo >&2 # Just in case stdin is /dev/null
  if [ "${resp}" = "Y" ] || [ "${resp}" = "y" ]; then
    args="-p ${args}"
    should_persist() { return 0; }	# replace this function
  else
    args="-e ${args}"
    should_persist() { return 1; }	# replace this function
    register() { :; }
  fi
}

##############################################################################
# Make sure that the .environ file exists.
##############################################################################
ensure_environ_exists() {
  umask 077
  if [ ! -e ${HOME}/.environ ] ; then
    echo "# This file is modified by the 'use' command." > ${HOME}/.environ
  fi
}

##############################################################################
# Print the commands to set a single environment variable.
# Also set the envvar in the current shell so we can see it here.
##############################################################################
do_setenv() {
  if [ "x$(printenv $1)x" = "x${2}x" ]; then
    return 0;
  fi
  eval export $1="'$2'"
  if [ "${shell}" = "csh" ]; then
    echo "setenv $1 '"$2"';"
  else
    echo "$1='"$2"';export $1;"
  fi
}

##############################################################################
# Print the commands to unset a single environment variable.
# Also unset the envvar in the current shell so we don't see it here.
##############################################################################
do_unsetenv() {
  if [ "$(eval echo \$${1-__unset__})" = "__unset__" ]; then
    return 0;
  fi
  unset $1
  if [ "$shell" = "csh" ]; then
    echo "unsetenv $1;"
  else
    echo "unset $1;"
  fi
}

##############################################################################
# Print the commands to set a single shell variable.
##############################################################################
do_shellset() {
  if [ "$shell" = "csh" ]; then
    echo "set ${1}='"$2"';"
  else
    echo "$1='"$2"';"
  fi
}

##############################################################################
# Print the commands to unset a single shell variable.
##############################################################################
do_shellunset() {
  echo "unset $1;"
}

##############################################################################
# Print the commands to set an alias.
##############################################################################
do_alias() {
  if [ "$shell" = "csh" ]; then
    echo "alias ${1} '"$2"';"
  else
    echo "alias $1='"$2"';"
  fi
}

##############################################################################
# Print the commands to unset an alias.
##############################################################################
do_unalias() {
  echo "unalias $1;"
}

##############################################################################
# Print an arbitrary command to be executed in the user's shell.
##############################################################################
do_command() {
  echo "$1;"
}

##############################################################################
# Opposite of do_command.
##############################################################################
do_nocommand() {
  :
}

##############################################################################
# Check for and resolve conflicts for the new environment.
##############################################################################
do_conflict() {
  val=$(eval echo \${${1}-_not_})
  if [ "$val" = "_not_" ]; then
    do_setenv ${1} ${env}
    return
  fi
  if [ "$val" = "$env" ]; then
    #echo "Environment for $1 is already set up." >&2
    echo "/bin/true;"
    return
  fi
  should_replace $val || exit 0
  $0 $shell unuse ${args} ${val}
  eval `$0 sh unuse ${args} ${val}`
  do_setenv ${1} ${env}
}

##############################################################################
# Find anything the the .environ that conflicts with the current selection.
##############################################################################
file_conflict() {
  should_persist || return
  vars=$(egrep '^[[:space:]]*conflict' ${env_path} | sed -e 's|^[[:space:]]*conflict[[:space:]]*||' -e 's|[[:space:]]*#.*$||')
  for dir in $dirs; do
    for var in $vars; do
      egrep -l '^[[:space:]]*conflict[[:space:]]*'${var} ${dir}/* | sed 's|^.*/||'
    done
  done
}

##############################################################################
# Return 0 if the selection is either in .environ or doesn't need to be.
# Return 1 (false) if otherwise.
##############################################################################
check_registered() {
  should_persist || return 0
  ensure_environ_exists
  if /bin/egrep -q '^'${env}'[[:space:]]*(#.*)?$' ${HOME}/.environ ; then
    return 0
  else
    return 1
  fi
}

##############################################################################
# Return 0 if the selection is neither in .environ nor needs to be.
# Return 1 (false) if otherwise.
##############################################################################
check_not_registered() {
  should_persist && return 1
  ensure_environ_exists
  if /bin/egrep -q '^'${env}'[[:space:]]*(#.*)?$' ${HOME}/.environ ; then
    return 0
  else
    return 1
  fi
}

##############################################################################
# Add an entry to the .environ file if required.
##############################################################################
do_register() {
  should_persist || return
  check_registered || echo "${env}" >> ${HOME}/.environ
}

##############################################################################
# Remove an entry from the .environ file if required.
##############################################################################
do_unregister() {
  should_persist || return
  check_not_registered && return
  egrep -v '^'${env}'[[:space:]]*(#.*)?$' ${HOME}/.environ > ${HOME}/.environ.new
  mv ${HOME}/.environ ${HOME}/.environ.bak
  mv ${HOME}/.environ.new ${HOME}/.environ
}

##############################################################################
# Print a syntax summary.
##############################################################################
print_syntax() {
  echo "
Syntax: $cmd [-h] [-e|-p] [-k|-r] [-x] <environment>
   -h: help (specify an environment to find out more about it)
   -e: Environment only.  Act on the current shell.  Do not preserve.
   -p: Preserve selection across sessions without prompting.
   -k: Keep an already selected version.
   -r: Replace an already selected version without prompting.
   -x: Print no errors (and take no action) if the environment doesn't exist.
" >&2
}


##############################################################################
# Implement each of the public functions using one of the private functions.
##############################################################################
if [ "$cmd" = "use" ]
then
  alias() { do_alias "$1" "$2"; }
  setenv() { do_setenv "$1" "$2"; }
  shellset() { do_shellset "$1" "$2"; }
  prepend() { do_prepend "$1" "$2"; }
  command() { do_command "$1"; }
  conflict() { do_conflict "$1"; }
  is_registered() { check_registered; }
  register() { do_register; }
  use() { str=$(/etc/environ $shell use $*); eval "${str}"; echo ${str}; }
else
  alias() { do_unalias "$1" "$2"; }
  setenv() { do_unsetenv "$1"; }
  shellset() { do_shellunset "$1" "$2"; }
  prepend() { do_unprepend "$1" "$2"; }
  command() { do_nocommand "$1"; }
  conflict() { do_unsetenv $1; }
  is_registered() { check_not_registered; }
  register() { do_unregister; }
  use() { str=$(/etc/environ $shell unuse $*); eval "${str}"; echo ${str}; }
fi

##############################################################################
# Set up the argument list.
##############################################################################
args=""
rflag=0
kflag=0
pflag=0
eflag=0
onlyifexist=0
while [ $# -gt 0 ]
do
  case $1 in
    -h|-help|--help) shift;
        clear_functions
        help() { do_print_format "$1"; }
        ;;
    -r) args="${args} $1"; shift; rflag=1;;
    -k) args="${args} $1"; shift; kflag=1;;
    -p) args="${args} $1"; shift; pflag=1;;
    -e) args="${args} $1"; shift; eflag=1;;
    -x) args="${args} $1"; shift; onlyifexist=1;;
    *) break;;
  esac
done


##############################################################################
# Check for flag conflicts and set up functions.
##############################################################################
if [ ${rflag} -eq 1 ] && [ ${kflag} -eq 1 ]
then
  echo "You may not use -r and -k together." >&2
  print_syntax
  exit 1
fi

if [ ${pflag} -eq 1 ] && [ ${eflag} -eq 1 ]
then
  echo "You may not use -p and -e together." >&2
  print_syntax
  exit 1
fi

if [ ${rflag} -eq 1 ]; then
  should_replace() { return 0; }
fi

if [ ${kflag} -eq 1 ]; then
  should_replace() { return 1; }
fi

if [ ${pflag} -eq 1 ]; then
  should_persist() { return 0; }
fi

if [ ${eflag} -eq 1 ]; then
  should_persist() { return 1; }
fi

##############################################################################
# Find the named environment file and source it.  Exit if found.
##############################################################################
for dir in ${dirs}
do
  if [ "x${1}x" != "xx" ] && [ -e "${dir}/$1" ]
  then
    env=$1
    env_path=${dir}/${env}
    is_registered
    . ${env_path}
    register
    exit 0
  fi
done

##############################################################################
# Environment not found.  Clear functions and issue help.
##############################################################################

if [ ${onlyifexist} -eq 1 ]; then
  echo "/bin/true;"
  exit 0
fi

clear_functions

print_syntax

if [ "x${1}x" != "xx" ]; then
  echo "No environment exists for '$1'." >&2
fi

echo "The following environments are available:" >&2

##############################################################################
# Print a brief description of the environment.
##############################################################################
desc() {
  /usr/bin/printf "%-13s %s %s\n" $env: "$inuse" "$1"
}

##############################################################################
# Traverse all the directories and print a brief description for each one.
# Print a mark to indicate the environments that are recorded in .environ.
##############################################################################
for dir in ${dirs}
do
  if [ -d "${dir}" ]; then
    for env in $(/bin/ls ${dir})
    do
      inuse=' '
      if [ -e "${dir}/${env}" ] && [ -r "${dir}/${env}" ]; then
        /bin/egrep -q "^${env}([[:space:]].*|)$" ${HOME}/.environ 2>/dev/null && inuse='*'
        env_path=${dir}/${env}
        . ${env_path}
      fi
    done
  fi
done | sort >&2
echo >&2
echo "             '*' (These environments are automatically used for new sessions.)" >&2
echo >&2
echo "/bin/true;"
exit 1

