#!/bin/bash

set -e

###############################################
###
### check for environment variables
###

if test -z $MAKE; then
  MAKE=make
fi

###############################################
###
### read lib
###

canonicalname(){
    if test $# -ne 1; then
        echo Usage: canonicalname path >&2
        return 1
    fi
    file="`eval echo $1`" # expand ~
    if test ! -e "$file"; then
        echo $file: file not found >&2
        return 1
    fi
    # if this is a symlink, then follow the symlink
    if test -L "$file"; then
        fdir="`dirname \"$file\"`"
        flink="`readlink \"$file\"`"
        if test -e "$flink"; then
            # these are absolute links, or links in the CWD
            canonicalname "$flink"
        else
            canonicalname "$fdir/$flink"
        fi
    else
        # if this is a file, then remember the filename and
        # canonicalize the directory name
        if test -f "$file"; then
            fdir="`dirname \"$file\"`"
            fname="`basename \"$file\"`"
            fdir="`canonicalname \"$fdir\"`"
            echo "$fdir/$fname"
        fi
        # if this is a directory, then create an absolute 
        # directory name and we are done
        if test -d "$file"; then
            (cd "$file"; pwd)
        fi
    fi
}

canonicalpath(){
  if test $# -ne 1; then
     echo Usage: canonicalpath path >&2
     return 1
  fi
  dirname `canonicalname "$1"`
}

checkdebug () {
  while test $# -gt 0; do
    if test x$1 = x--debug; then
      echo yes
      return
    fi
    shift
  done
  echo no
}

DEBUG=`checkdebug $@`
if test "x$DEBUG" = "xyes"; then
  set -x
  set -v
fi

export PREFIX_DIR="`canonicalpath $0`/.."
export RESUME_FILE="$HOME/.dune.resume"

# Read the modules find part
. "$PREFIX_DIR/lib/dunemodules.lib"

# create PKG_CONFIG_PATH for installed dune modules
export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:`canonicalpath $0`/../lib/pkgconfig"

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

onbuildfailure() {
  echo "Terminating $(basename $0) due to previous errors!" >&2
  exit 1
}

#
# for each module load the $CONTROL script part and run $command
#
# parameters:
# $1 list of modules
# $2-$* commands + parameters to execute
#
build_modules() {
  local modules=$1
  shift
  while test $# -gt 0; do
    # get command
    command=$1
    shift

    # only load other parameters
    load_opts NONE
    # get command options
    CMD_FLAGS=
    while test $# -gt 0 && test "$1" != ":"; do
      COMMAND=$(echo $command | tr '[:lower:]' '[:upper:]')
      # setup paramter list
      CMD_FLAGS="$CMD_FLAGS \"$1\""
      shift
    done
    if test -z "$CMD_FLAGS"; then
      load_opts $command
    else
      # disable usage of opts file
      if test "x$DUNE_OPTS_FILE" != "x"; then
        echo "WARNING: commandline parameters will overwrite setting in opts file \"$DUNE_OPTS_FILE\""
      fi 
    fi

    # skip command delimiter
    if test "$1" = ":"; then shift; fi

    # actually run the commands (we already know that these are valid commands)
    local runcommand=run_$command
    for module in $modules ; do
      echo $module
    done > $RESUME_FILE
    for module in $modules; do
      local path=$(eval "echo \$PATH_${module}")
      eval echo "--- calling $command for \$NAME_${module} ---"
	  trap onbuildfailure EXIT
      if ! (
        set -e
        cd "$path"
        export module
        eval_control $runcommand $path/$CONTROL
      ); then eval echo "--- Failed to build \$NAME_${module} ---"; exit 1; fi
	  trap onfailure EXIT

      modules_togo=`cat $RESUME_FILE`
      for module_togo in $modules_togo ; do
        if test x$module_togo != x$module ; then
          echo $module_togo
        fi
      done > $RESUME_FILE
      eval echo "--- \$NAME_${module} done ---"
    done
  done
}

#
# load command options from an opts file
# the name of the opts file is stored in the global variable $DUNE_OPTS_FILE
#
# parameters:
# $1 command
#
load_opts() {
  local command=$1
  local COMMAND=$(echo $command | tr '[:lower:]' '[:upper:]')
  CMD_FLAGS="$(eval echo \$${COMMAND}_FLAGS)"
  local CMD_FLAGS_FROM_FILE=""
  BUILDDIR=$DUNE_BUILDDIR
  if test "x$DUNE_OPTS_FILE" != "x"; then
    BUILDDIR="$(eval BUILDDIR=""; . $DUNE_OPTS_FILE; eval echo \$BUILDDIR)"
    CMD_FLAGS_FROM_FILE="$(eval ${COMMAND}_FLAGS=""; . $DUNE_OPTS_FILE; eval echo \$${COMMAND}_FLAGS)"
  fi
  if test -n "$CMD_FLAGS_FROM_FILE"; then
    echo "----- using default flags \$${COMMAND}_FLAGS from $DUNE_OPTS_FILE -----"
    CMD_FLAGS=$CMD_FLAGS_FROM_FILE
  elif test -n "$CMD_FLAGS"; then
    echo "----- using default flags \$${COMMAND}_FLAGS from environment -----"
  fi
}

###############################################
###
### Commands
###

# check all parameter
check_commands() {
  while test $# -gt 0; do
    # get command
    command=$1
    shift
    # skip command options
    while test $# -gt 0 && test "$1" != ":"; do
      shift
    done
    # skip command delimiter
    if test "$1" = ":"; then shift; fi
    # test the commands
    if ! is_command $command; then
      usage
      echo "ERROR: unknown command \"$command\""  >&2
      exit 1
    fi
  done
}

# check wheteher the parameter is valid command or not
is_command() {
eval '
case "$1" in
  '`echo $COMMANDS | sed -e 's/ / | /g'`')
    return 0
    ;;
  *)
    return 1
    ;;
esac'
}

# list of all dunecontrol commands
COMMANDS="update autogen configure make all exec status svn"

# help string for the commands
update_HELP="updated all modules from the repository"
autogen_HELP="run the autogen.sh script for each module"
configure_HELP="run configure for each module"
make_HELP="run make for each module"
all_HELP="\trun 'autogen', 'configure' and 'make' command for each module"
exec_HELP="execute an arbitrary command in each module directory"
status_HELP="show vc status for all modules"
svn_HELP="\trun svn command for each svn managed module"

#
# setup command proxies
# call will be forwarded to run_default_$command
#

for command in $COMMANDS; do
  eval "run_$command () { run_default_$command; }"
done

#
# default implementations for commands... 
# these can be overwritten in the $CONTROL files
#

run_default_exec () { bash -c "eval $CMD_FLAGS"; }

run_default_status () {
  local verbose=0
  local update=""
  for i in $CMD_FLAGS; do
    if eval test "x$i" = "x-v"; then verbose=1; fi
    if eval test "x$i" = "x-vv"; then verbose=2; fi
    if eval test "x$i" = "x-u"; then update="-u"; fi
  done
  # is out output connected to a tty?
  if test -t 1; then
    blue="\e[1m\e[34m"
    green="\e[1m\e[32m"
    red="\e[1m\e[31m"
    reset="\e[0m\e[0m"
  fi

  if test $verbose -eq 1; then
    svn status $update | grep -E "^M|^A|^D|^C|^U"
  elif test $verbose -eq 2; then
    svn status $update
  fi

  name="$(eval echo \$NAME_$module)"
  changed=$(svn status | grep -E "^M|^A|^D" | wc -l)
  collisions=$(svn status | grep -E "^C"| wc -l)
  pending=$(svn status $update | grep -E "^...... \* " | wc -l)

  color=$green
  text="no changes"
  if [ $changed -eq 0 ]; then
    true
  elif [ $changed -eq 1 ]; then
    color=$blue;
    text="1 change"
  else
    color=$blue;
    text="$changed changes"
  fi
  if [ $pending -eq 0 ]; then
    true
  elif [ $pending -eq 1 ]; then
    color=$blue;
    text="$text, 1 update pending"
  else
    color=$blue;
    text="$text, $pending updates pending"
  fi
  if [ $collisions -eq 0 ]; then
    true
  elif [ $collisions -eq 1 ]; then
    color=$red
    text="$text, 1 collision"
  else
    color=$red
    text="$text, $count collisions"
  fi
  echo -e "$color[$text]$reset $name"
}

run_default_update () {
  DUNELINK=0
  if test -L dune; then
    rm dune
    DUNELINK=1
  fi
  if test -d .svn; then
    svn update
  elif test -d CVS; then
    cvs update -dP
  else
    echo "WARNING: $module is not under a known version control system."
    echo "         We support svn and cvs."
  fi
  if test "$DUNELINK" != 0 && ! test -d dune; then
    echo "WARNING: $module is using the deprecated dune symlink"
    ln -s . dune
  fi
}

run_default_autogen () {
  PARAMS="$CMD_FLAGS"
  local M4_PATH=""
  if test -f configure.ac && \
     ( test -d .svn || test -d .git || test -d CVS || test -f stamp-vc ); then
    for m in $FOUND_MODULES; do
      path=$(eval "echo \$PATH_$m")
      MODULE_PATHS="$MODULE_PATHS$path "
    done
    if test -f autogen.sh; then
      eval echo "WARNING: \$NAME_$module contains obsolete autogen.sh," \
          >&2
      echo "         dune-autogen is used instead." >&2
    fi
    eval "$PREFIX_DIR/bin/dune-autogen" "$MODULE_PATHS" "$PARAMS" || exit 1
  else
    echo Skipping dune-autogen
  fi
}

run_default_configure () {
  PARAMS="$CMD_FLAGS"
  if test -x configure; then
    ACLOCAL_FLAGS="-I ."
    MY_MODULES=
    # get dependencies & suggestions
    sort_modules $module
    for m in $MODULES; do
      if test x$module = x$m; then continue; fi # skip myself
      path=$(eval "echo \$PATH_$m")
      name=$(eval "echo \$NAME_$m")
      if test -d "$path/m4"; then
          ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I $path/m4"
      fi
      if test -d "$path/share/aclocal"; then
          ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I $path/share/aclocal"
      fi
      if test -d "$path/$BUILDDIR"; then
        PARAMS="$PARAMS \"--with-$name=$path/$BUILDDIR\""
      else
        PARAMS="$PARAMS \"--with-$name=$path\""
      fi
    done
    if test "x$HAVE_duneweb" == "xyes"; then
      PARAMS="$PARAMS \"--with-duneweb=$PATH_duneweb\""
    fi
    PARAMS="$PARAMS ACLOCAL_AMFLAGS=\"$ACLOCAL_FLAGS\""
    echo ./configure "$PARAMS"
    # create build directory of requested
    if test -n "$BUILDDIR"; then
      test -d "$BUILDDIR" || mkdir "$BUILDDIR"
      SRCDIR="$PWD"
      cd "$BUILDDIR"
      eval "$SRCDIR/configure" "$PARAMS" || exit 1
    else
      eval ./configure "$PARAMS" || exit 1
    fi
  else
    if test -f configure.in || test -f configure.ac; then
      echo "ERROR: configure.[in|ac] found, but configure missing." >&2
      echo "Did you forget to run autoconf?" >&2
      echo "Perhaps you didn't update your project to" >&2
      echo "the latest buildsystem changes (FS#382)." >&2
      echo "If your project is under version control, please make sure" >&2
      echo "you have a file stamp-vc in you top_srcdir." >&2
      exit 1
    fi
  fi
}

run_default_make () {
  test ! -d "$BUILDDIR" || cd "$BUILDDIR"
  PARAMS="$CMD_FLAGS"
  echo make "$PARAMS"
  eval $MAKE "$PARAMS"
}

run_default_all () {
  for cmd in autogen configure make; do
    eval echo "--- calling $cmd for \$NAME_${module} ---"
    load_opts $cmd
    run_$cmd
  done
}

run_default_svn () {
  if test -d .svn; then
    PARAMS="$CMD_FLAGS"
    eval svn "$PARAMS"
  fi
}

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

onfailure() {
  echo "Execution of $(basename $0) terminated due to errors!" >&2
  exit 1
}

usage () {
  (
    echo "Usage: $(basename $0) [OPTIONS] COMMANDS [COMMAND-OPTIONS]"
    echo ""
    echo "  Execute COMMANDS for all Dune modules found. All entries in the"
    echo "  DUNE_CONTROL_PATH variable are scanned recursively for Dune modules."
    echo "  If DUNE_CONTROL_PATH is empty, the current directory is scanned."
    echo "  Dependencies are controlled by the $CONTROL files."
    echo ""
    echo "OPTIONS:"
    echo "  -h, --help         show this help"
    echo "      --debug        enable debug output of this script"
    echo "      --module=mod   only apply the actions on module mod"
    echo "                     and all modules it depends on"
    echo "      --only=mod     only apply the actions on module mod"
    echo "                     and not the modules it depends on"
    echo "      --current      only apply the actions on the current module,"
    echo "                     the one whose source tree we are standing in"
    echo "      --resume       resume a previous run (only consider the modules"
    echo "                     not built successfully on the previous run)"
    echo "      --opts=FILE    load default options from FILE"
    echo "                     (see dune-common/doc/example.opts)"
    echo "      --builddir=NAME make out-of-source builds in a subdir NAME."
    echo "                     This directory is create inside each module."
    echo "      --[COMMAND]-opts=opts   set options for COMMAND"
    echo "                     (this is mainly useful for the all COMMAND)"
    echo "COMMANDS:"
    echo "  Colon-separated list of commands. Available commands are:"
    printf "  \`help'\tguess what :-)\n"
    printf "  \`print'\tprint the list of modules sorted after their dependencies\n"
    for i in $COMMANDS; do
      printf "  \`$i'\t$(eval echo \$${i}_HELP)\n"
    done
    printf "  \`export'\trun eval \`dunecontrol export\` to save the list of\n"
    printf "  \t\tdune.module files to the DUNE_CONTROL_PATH variable\n"
    echo
  )  >&2
}

# create the module list
create_module_list() {
  find_modules_in_path
  if test "x$SEARCH_MODULES" != "x"; then
    sort_modules $SEARCH_MODULES
  else
    sort_modules $MODULES
  fi
  if test "x$ONLY" != x; then
    export MODULES="$ONLY"
  fi
}

# print the module list
print_module_list() {
  DELIM=$1
  shift
  while test -n "$2"; do
    echo -n "$(eval echo \$NAME_$1)$DELIM"
    shift
  done
  echo -n "$(eval echo \$NAME_$1)"
}

trap onfailure EXIT

# clear variables
export SEARCH_MODULES=""
export MODULES=""
export ONLY=""

# parse commandline parameters
while test $# -gt 0; do
    # get option
    command=$1
    option=$1

    # get args
    set +e
    # stolen from configure...
    # when no option is set, this returns an error code
    arg=`expr "x$option" : 'x[^=]*=\(.*\)'`
    set -e

    # switch
    case "$option" in
    --opts=*)
      if test "x$arg" = "x"; then
        usage
        echo "ERROR: Parameter for --opts is missing"  >&2
        echo  >&2
        exit 1;
      fi
      DUNE_OPTS_FILE=`canonicalname $arg`
      if ! test -r "$DUNE_OPTS_FILE"; then
        usage
        echo "ERROR: could not read opts file \"$DUNE_OPTS_FILE\""  >&2
        echo  >&2
        exit 1;
      fi
    ;;
    --*-opts=*)
      optcmd=`expr "x$option=" : 'x--\([^-]*\)-opts=.*'`
      if is_command $optcmd; then
        COMMAND=`echo $optcmd | tr '[:lower:]' '[:upper:]'`
        export ${COMMAND}_FLAGS="$arg"
      else
        usage
        echo "ERROR: unknown option \"$option\""  >&2
        exit 1
      fi
    ;;
    -h|--help) 
      command=help
      break
    ;;
    -p|--print) 
      command=print
      break
    ;;
    --module=*)
      if test "x$arg" = "x"; then
        usage
        echo "ERROR: Parameter for --module is missing"  >&2
        echo  >&2
        exit 1;
      fi
      for a in `echo $arg | tr ',' ' '`; do
        export NAME_`fix_variable_name $a`="$a"
        fix_and_assign MODULE "$a"
        export SEARCH_MODULES="$SEARCH_MODULES $MODULE"
      done
    ;;
    --only=*)
      if test "x$arg" = "x"; then
        usage
        echo "ERROR: Parameter for --only is missing"  >&2
        echo  >&2
        exit 1;
      fi
      for a in `echo $arg | tr ',' ' '`; do
        export NAME_`fix_variable_name $a`="$a"
        fix_and_assign MODULE "$a"
        export SEARCH_MODULES="$SEARCH_MODULES $MODULE"
        export ONLY="$ONLY $MODULE"
      done
    ;;
    --builddir=*)
      export DUNE_BUILDDIR=$arg
    ;;
    --no-builddir)
      export DUNE_BUILDDIR=""
    ;;
    --skipversioncheck)
      export SKIPVERSIONCHECK=yes
    ;;
    --current)
      while ! test -f $CONTROL; do
        cd ..
        if test "$OLDPWD" = "$PWD"; then
          echo "You are not inside the source tree of a DUNE module." >&2
          exit -1
        fi
      done;
      parse_control $PWD/$CONTROL
      fix_and_assign MODULE "$module"
      export SEARCH_MODULES="$SEARCH_MODULES $MODULE"
      export ONLY="$ONLY $MODULE"
      export HAVE_${module}=
    ;;
    --resume)
      if test -s $RESUME_FILE ; then
        RESUME="`cat $RESUME_FILE`"
        for a in $RESUME ; do
          export NAME_`fix_variable_name $a`="$a"
          fix_and_assign MODULE "$a"
          export SEARCH_MODULES="$SEARCH_MODULES $MODULE"
          export ONLY="$ONLY $MODULE"
        done
      else
        echo "Error: No previous run to resume. ('$RESUME_FILE' is empty or does not exist)"
        exit 1
      fi
    ;;
    --debug) true ;; # ignore this option, it is handled right at the beginning
    --*)
      usage
      echo "ERROR: Unknown option \`$option'"  >&2
      echo  >&2
      exit 1
      ;;
    *)
      break
    ;;
    esac

    shift
done

# we assume there should be a command...
if test "x$command" = "x"; then
  usage
  exit 1
fi

case "$command" in
  print)
    create_module_list
    eval "print_module_list ' ' $MODULES"
    echo >&2
    ;;
  export)
    create_module_list
    DUNE_CONTROL_PATH=""
    for mod in $MODULES; do
      if test x != x$DUNE_CONTROL_PATH; then
        export DUNE_CONTROL_PATH="$DUNE_CONTROL_PATH:$(eval echo \$PATH_$mod/dune.module)"
      else
        export DUNE_CONTROL_PATH="$(eval echo \$PATH_$mod/dune.module)"
      fi
    done
    echo export DUNE_CONTROL_PATH=$DUNE_CONTROL_PATH
    ;;
  m4create)
    find_modules_in_path
    mainmod=`echo $SEARCH_MODULES`
    fname="dependencies.m4"
    name=`eval echo \\${NAME_$mainmod}`
    version=`eval echo \\${VERS_$mainmod}`
    maintainer=`eval echo \\${MAIN_$mainmod}`
    # get dependencies
    eval deps=\$DEPS_$module
    #initially remove leading space
    deps=`echo "$deps" | sed 's/^ *//'`
    while test -n "$deps"; do
      #the end of the name is marked either by space, opening paren
      #or comma
      depname="${deps%%[ (,]*}"
      #remove the name and adjacent whitespace
      deps=`echo "$deps" | sed 's/^[^ (,]* *//'`
      #check whether there is a dependency version
      case "$deps" in
      '('*) deps="${deps#(}"
            depver="${deps%%)*}"
            deps="${deps#*)}"
            ;;
      *)    depver=
            ;;
      esac
      #remove any leading whitespace or commas for te next iteration
      deps=`echo "$deps" | sed 's/^[, ]*//'`

      requires="$requires $depname $depver "
    done
    # ensure a version number
    if test "x$version" = "x"; then version="0.0"; fi
    echo "writing $fname"
    echo "    for $name $version $maintainer"
    echo "        requires $requires"
      AC_MACRO_DIR="."
      test ! -d m4 || AC_MACRO_DIR=m4
    cat > $fname <<EOF
# dependencies.m4 generated by dunecontrol

m4_define([DUNE_AC_INIT],[
  AC_INIT([$name], [$version], [$maintainer])
  AM_INIT_AUTOMAKE([foreign 1.5 tar-pax])
  AC_SUBST([DUNE_MOD_VERSION], [$version])
  AC_SUBST([DUNE_MOD_NAME], [$name])
  AC_SUBST([DUNE_MAINTAINER_NAME], [$maintainer])
  DUNE_PARSE_MODULE_VERSION([$name], [$version])
  REQUIRES="$requires"
  AC_SUBST(REQUIRES, [$REQUIRES])
  AC_CONFIG_MACRO_DIR([$AC_MACRO_DIR])
])

AC_DEFUN([DUNE_CHECK_MOD_DEPENDENCIES], [
EOF
    ### initialize AM_CONDITIONAL for suggestions that were not found
    for name in $(eval echo \$SUGS_$mainmod); do
      mod=$(fix_variable_name $name)
      MOD=`echo $mod | tr [:lower:] [:upper:]`
      if test "x$(eval echo \$HAVE_$mod)" = "x"; then
        cat >> $fname <<EOF
  ### add a conditional check for $name,
  # just in case the module is not available at autogen time
  AM_CONDITIONAL([HAVE_${MOD}], false)
EOF
      fi
    done
    ### DEPENDENCIES
    if test "x$SEARCH_MODULES" != "x"; then
      MODULES=$SEARCH_MODULES
    fi
    sort_dependencies $MODULES
    for mod in $MODULES; do
      name=`eval echo \\$NAME_$mod`
      MOD=`echo $mod | tr [:lower:] [:upper:]`
      cat >> $fname <<EOF
  ### check dependency $name
  # invoke checks required by this module
  AC_REQUIRE([${MOD}_CHECKS])
  # invoke check for this module
  AC_REQUIRE([${MOD}_CHECK_MODULE])
  if test x\$with_$mod = xno; then
    AC_MSG_ERROR([could not find required module _dune_name])
  fi
EOF
    done
    ### 
    sort_suggestions $mainmod
    for mod in $MODULES; do
      name=`eval echo \\$NAME_$mod`
      MOD=`echo $mod | tr [:lower:] [:upper:]`
      cat >> $fname <<EOF
  ### check suggestion $name
  # invoke checks required by this module
  AC_REQUIRE([${MOD}_CHECKS])
  # invoke check for this module
  AC_REQUIRE([${MOD}_CHECK_MODULE])
  if test x\$with_$mod = xno; then
    AC_MSG_WARN([could not find suggested module _dune_name])
  fi
EOF
    done
    ###
    # only test for the module if we really define our own checks
    if test -d m4; then
      mod=$mainmod
      name=`eval echo \\$NAME_$mod`
      MOD=`echo $mod | tr [:lower:] [:upper:]`
      cat >> $fname <<EOF
  ### invoke checks for $name
  AC_REQUIRE([${MOD}_CHECKS])
EOF
    fi
    cat >> $fname <<EOF
])
EOF
  ;;
  unexport)
    echo export DUNE_CONTROL_PATH=""
  ;;
  help)
    usage
  ;;
  *)
    if test "$1" = "update"; then export SKIPVERSIONCHECK=yes; fi
    check_commands "$@"
    create_module_list
    NAMES=""
    BUILDMODULES=""
    for mod in $MODULES; do
      if test "$(eval echo \$INST_$mod)" != "yes"; then
        NAMES="$NAMES$(eval echo \$NAME_$mod) "
        BUILDMODULES="$BUILDMODULES$mod "
      fi
    done
    echo "--- going to build $NAMES ---"
      build_modules "$BUILDMODULES" "$@"
    echo "--- done ---"
  ;;
esac

trap - EXIT