#!/bin/bash
#---------------------------------------------------------------------
##
##=head1 SYNOPSIS
##
## Functions for dealing with dependencies as a non-cyclic directed
## graph. Since that's such a mouthful, it will simply be referred to
## as a tree, even though it's not.
##
##=head1 DESCRIPTION
##
## A spell is represented as a node containing some pieces of data
## seperated by colons.
## :spellname:casting_flag:is_a_target_flag:
##
##=head1 COPYRIGHT
##
## Copyright (C) 2002 The Source Mage Team <http://www.sourcemage.org>
##
##=head1 CONTRIBUTORS
## Chris Brien <christopher_brien@hotmail.com>
## Paul Mahon <pmahon@sourcemage.org)
##
##=head1 FUNCTIONS
#---------------------------------------------------------------------

# Surprise env vars:
# COMPILE: set in cast. It means that the main spells should be recompiled
# RECONFIGURE: set in cast. I means that the info in the state depends should 
#  be disregarded and replaced.
# LAST_SPELL: set in here. It is for the recursive dependency checking. It is
#  set to the parent spell.
# PRETEND_NOT_INSTALLED: set here. It is a list of spells that are to be 
#  recompiled, so they should not be treated as installed.
# CAST_HASH: The name of the hast table to put spells and dependencies
#  that are to be cast (only used in this lib)
# DO_NOT_CAST_HASH: The name of the hash table to put spells and dependencies
#  that are not to be cast, but may still be needed (only used in this lib)
# CANNOT_CAST: The name of the hash table to put spells that cannot be cast
#  and the reason why. Usualy because they are exiled or don't exist
#  (only used in this lib)

function run_prepare() 
{

  SCRIPT_DIRECTORY=`codex_find_spell_by_name $SPELL`
  debug "cast" "run_prepare() - SCRIPT_DIRECTORY = $SCRIPT_DIRECTORY"
 
  if  [  -n  "$RECONFIGURE"  ];  then
    rm  -f  $DEPENDS_CONFIG/$SPELL
    remove_depends_status  $SPELL
  fi

  message  "${CHECK_COLOR}Preparing environment for"  \
               "${SPELL_COLOR}${SPELL}"                   \
               "${DEFAULT_COLOR}"
  prepare_spell_config #from cast

  if  [  -x  $SCRIPT_DIRECTORY/PREPARE  ];  then
    persistent_load
    . $SCRIPT_DIRECTORY/PREPARE
    persistent_save
  fi
  
  # ask the questions about xinetd/initd script installation
  use_xinetd
  use_initd
}


function run_depends() 
{ 
 
  if  [  -x  $SCRIPT_DIRECTORY/DEPENDS  ];  then

    message  "${CHECK_COLOR}Checking dependencies for"  \
               "${SPELL_COLOR}${SPELL}"                   \
               "${DEFAULT_COLOR}"

      persistent_load
      .  $SCRIPT_DIRECTORY/DEPENDS
      persistent_save

    fi
}


function run_configure() 
{

  SCRIPT_DIRECTORY=`codex_find_spell_by_name $SPELL`
  debug "cast" "run_configure() - SCRIPT_DIRECTORY = $SCRIPT_DIRECTORY"
 
  if  [  -x  $SCRIPT_DIRECTORY/CONFIGURE  ];  then
    persistent_load
    . $SCRIPT_DIRECTORY/CONFIGURE
    persistent_save
    true  # otherwise if CONFIGURE returns `false' DEPENDS is not executed
  fi
}


function run_spell_config() 
{

  SPELL_CONFIG=$DEPENDS_CONFIG/$SPELL
  debug "libdepends" "run_spell_config() - DEPENDS_CONFIG=$DEPENDS_CONFIG SPELL=$SPELL, SPELL_CONFIG=$SPELL_CONFIG DEPENDS_STATUS=$DEPENDS_STATUS" 
    
  if  [  -x  $SPELL_CONFIG  ];  then
    debug "libdepends" "run_spell_config() - found $SPELL_CONFIG"
	.  $SPELL_CONFIG
  fi
}


function show_depends()  
{
  debug "libdepends" "show_depends() - $@"
  local DEP_SPELL=`esc_str $1`
  local DEPTH=$2

  if  !  echo  "$DONE"  |  grep  -q  "$DEP_SPELL";  then
    DONE="$DONE  $1"

    function ld99()  {

     [[ $MAX_DEPTH ]] && [[ $DEPTH -ge $MAX_DEPTH ]] && return 1
 
      SPELL=`echo  $1 |  cut  -d :  -f1`
      STATUS=`echo  $1  |  cut  -d :  -f3`

      if    [  "$STATUS"  ==  "on"  ]; then
        echo          $1 
        show_depends  $SPELL $(( DEPTH + 1 ))
      fi
      
    }
    
    iterate "ld99" $'\n' `grep  ":$DEP_SPELL:"  $DEPENDS_STATUS`

  fi
}


function satisfy_depends()
{

  debug "libdepends" "Running satisfy_depends() on $SPELL"  
  local DEP
 
  unset  OPTS

  # some spells apply changes to OPTS directly
  run_spell_config

  local LINE=''
  search_depends_status "$SPELL" | while read LINE; do

    debug "libdepends" "satisfy_depends() - $LINE"

    # line format is:
    #  spell:dependency(provider):status:something:on-opts:off=opts
    DEP=()
    explode "$LINE" ":" "DEP"
    DEP[1]=${DEP[1]%(*}   # Strip possible provider name

    # If dependency name is "" then don't bother with the rest of this loop.
    if [[ ! ${DEP[1]} ]] ; then
      continue
    fi

    # First, see if the spell exists.
    DEP_SPELL_LOCATION=`codex_find_spell_by_name ${DEP[1]}`
    if  [  -z  "$DEP_SPELL_LOCATION"  ];  then
      message  "${PROBLEM_COLOR}Unable to find previously configured"  \
               "dependency ${SPELL_COLOR}'${DEP[1]}'${PROBLEM_COLOR}"
      message  "in grimoire.  Removing spell from cached depends."     \
               "${DEFAULT_COLOR}"
      remove_depends_status  "$SPELL"  "${DEP[1]}"
    else

      #Second, am I trying to cast --fix or is this just a normal cast?
      if  [[  $FIX  ]] ; then
      
	#Third, if it's a fix, and the dependency is on, cast --fix it
        if  [[ ${DEP[2]} == on ]] 
	then  debug "libdepends" "cast  $FIX  ${DEP[1]}" && cast  $FIX  ${DEP[1]}
	else debug "libdepends" "Fix=$FIX, and DEP[2]=${DEP[2]}"
        fi

      #(second) It's a normal cast, and this may need to be cast
      elif  [[  ${DEP[2]}  ==  on  ]]; then
        #
        # Spell must be installed or help up to this point (this is a guard fence)
        #
        if  !  spell_installed  ${DEP[1]}  &&
            !  spell_held       ${DEP[1]}
        then
          message "One of the dependencies of ${DEP[0]} failed to cast."
          message "You should try to cast ${DEP[1]} manualy."
          message "Failed to cast ${DEP[1]}. I will have trouble casting ${DEP[0]}."
          return 1          
        fi
        #
        # End of guard fence
        #
      fi
    fi

  done

  #
  # TODO: Do something about exiled spells so that they've got `off'
  #       in DEPENDS_STATUS file, or something else do improve this
  #
  local OPTS_DEPENDS=$(get_depends_options "$SPELL")
  OPTS="$OPTS $OPTS_DEPENDS"
  debug 'libdepends' "satisfy_depends() - final OPTS=$OPTS"
}

#-----------------------------------------------------------------------
# calling this will accomplish several things:
# 1) most importantly it finds the closure of all spells that need to
#    be cast
# 2) it builds a hash table mapping spells to their depends, possibly by
#    asking the user for input
# 3) it updates DEPENDS_STATUS, arguably it shouldn't be doing this.
#
# What happens: take all the spells we've been asked to resolve
# for each one of them run its details file
# the details file will call back to depends/optional_depends
# at this point we determine/find/query for depends info
# update the hash table, update DEPENDS_STATUS, and append to the
# list of spells to resolve
#-----------------------------------------------------------------------
function compute_uninstalled_depends() 
{ 

  # $1=table to place spells in
  # $2=table to put problem spells in, $* = root spells

  debug "libdepends" "compute_depends of $*"
  local CAST_HASH="$1"
  local CANNOT_CAST_HASH="$2"
  shift 2
  BASE_SPELLS=( $@ )
  
  local i
  
  # All specified spells are assumed to be not installed, or else -c and -r 
  # would have to be specified all the time.
  PRETEND_NOT_INSTALLED=" ${BASE_SPELLS[@]} "
  
  SPELLS=()
  let i=0;
  for spell in "$@" ; do
    SPELLS[$i]="$spell"
    let i++
  done
  
  for (( i=0 ; i<${#SPELLS[*]} ; i++ )) ; do
    if ! private_run_depends ${SPELLS[$i]} ; then
		private_remove_dependees ${SPELLS[i]}
		hash_unset "libdepends" "${SPELLS[i]}"
		SPELLS[i]=""
	fi
  done

  SPELLS="${BASE_SPELLS[*]}"
}

#---------------------------------------------------------------------
## @param  spell or provider name
## @param  addition to OPTS
## @param  description
## Delegates provider and spell cases to different worker functions.
#---------------------------------------------------------------------
function real_depends()
{
  if ! codex_does_spell_exist $1 &> /dev/null; then
    work_depends_provider "$@"
  else
    work_depends_spell "$@"
  fi

}


function real_requires() 
{ 
	#deprecated, martin 20030906
    depends "$@"
}

#---------------------------------------------------------------------
## @param  spell or provider name
## @param  addition to OPTS if enabled
## @param  addition to OPTS if disabled
## @param  description
## Delegates provider and spell cases to different worker functions.
#---------------------------------------------------------------------
function real_optional_depends()
{

  if ! codex_does_spell_exist $1 &> /dev/null; then
    work_optional_depends_provider "$@"
  else
    work_optional_depends_spell "$@" 
  fi

}


#---------------------------------------------------------------------
## @param  name of return variable
## @param  provider name
## @param  reason text
## @param  optional switch (set to "optional" to enable)
## Gives the user some nice select list and puts the selected spell in
## return_var.
#---------------------------------------------------------------------
function select_provider()
{
    local INSTALLED=false
    local returnvar=$1
    local provname=$2
    local reasontext=$3

    if [[ $4 == "optional" ]] ; then
      local optional=true
    fi

    if [[ $optional == true ]] ; then
      message -n "$SPELL_COLOR$SPELL$DEFAULT_COLOR optionally needs some "
      message "$SPELL_COLOR$2$DEFAULT_COLOR. ($reasontext)"
    else
      message -n "$SPELL_COLOR$SPELL$DEFAULT_COLOR needs some "
      message "$SPELL_COLOR$provname$DEFAULT_COLOR as a requirement."
    fi

    local INST_PROV_LIST temp spell spellversion
    local CANDIDATES=$( find_providers $provname )

    for spell in $CANDIDATES; do
        if spell_installed $spell || spell_held $spell; then
            [[ $INSTALLED == false ]] && INST_PROV="$spell"
            INSTALLED=true
        fi

        if ! spell_exiled $spell; then
            # setup for displaying spellversion in listing.
            spellversion=$(
                . $( codex_find_spell_by_name $spell )/DETAILS > /dev/null
                echo $VERSION
            )
            temp="$temp ${spell}:${spellversion}"
        fi
    done

    if [[ $INSTALLED == true ]]; then
        message -n "$SPELL_COLOR$provname$DEFAULT_COLOR is already provided " 
        message   "by $SPELL_COLOR$INST_PROV$DEFAULT_COLOR"
        if query "Do you want to use that?" "y"; then
            eval ''$returnvar'='$INST_PROV''
            return 0
        fi 
    fi

    if [[ ! $temp ]]; then
        echo "Cannot satisfy requirements for $provname"
        return 1
    fi

    local selected

    if [[ $optional == true ]]; then
        select_list "selected" "[none]" $temp
    else
        select_list "selected" $temp
    fi

    # Before returning selected item which is in form <spell>:<version>,
    # need to clean off the ':<version>'.
    eval ''$returnvar'='${selected%%:*}''

}

function work_depends_provider()
{

  debug "libdepends" "$FUNCNAME - $@"

  local status=()
  
  # See if there are preferences already.
  # Example: rxvt-unicode:xfree86(X11-LIBS):on:required::
  explode "$(search_depends_status "$SPELL" ".*($1)")" ":" "status"

  local provider=${status[1]%(*}    # Name of spell which provides $1
  local provider_installed="yes"

  # If we used to depend on one spell but it no longer is installed then the
  # whole procedure with the asking and whatnot should happen again.
  if [[ $provider ]] && ( ! spell_installed $provider && ! spell_held $provider ) ; then
    provider_installed=""

    # Remove the bad entry.
    remove_depends_status $SPELL ${status[1]}
  fi

  if [[ $RECONFIGURE || ! $provider || ! $provider_installed ]] ; then
    select_provider "provider" "$1"
  fi

  private_common_depends "$provider($1)" "on" "required" "$2" "$3"

}

function work_depends_spell()
{

  debug "libdepends" "$FUNCNAME - $@"

  if spell_exiled $1 ; then
  	hash_put $CANNOT_CAST_HASH "$1" "Exiled"
    message "${SPELL_COLOR}$1${DEFAULT_COLOR} has been exiled!"
    return 1
  fi

  private_common_depends "$1" "on" "required" "$2" "$3"

}

#---------------------------------------------------------------------
## @param  provider name
## @param  addition to OPTS if enabled
## @param  addition to OPTS if disabled
## @param  description
## Handles optional dependency on a provider.
#---------------------------------------------------------------------
function work_optional_depends_provider()
{

  debug "libdepends" "$FUNCNAME - $@"

  local install="off"
  local status=()
  
  # See if there are preferences already.
  # Example when enabled:
  #  GD:xfree86(X11-LIBS):on:optional:-options=JPG,FT,XPM:-options=JPG,FT
  # Example when disabled:
  #  GD:(X11-LIBS):off:optional:-options=JPG,FT,XPM:-options=JPG,FT
  explode "$(search_depends_status "$SPELL" ".*($1)")" ":" "status"

  local provider=${status[1]%(*}    # Name of spell which provides $1
  local provider_installed="yes"

  # If we used to depend on one spell but it no longer is installed then the
  # whole procedure with the asking and whatnot should happen again.
  if [[ ${status[2]} != off ]] && [[ $provider ]] \
        && ( ! spell_installed $provider && ! spell_held $provider ) ; then
    provider_installed=""

    # Remove the old entry.
    remove_depends_status $SPELL ${status[1]}
  fi

  if [[ $RECONFIGURE || ! ${status[2]} || ! $provider_installed ]] ; then

    select_provider "provider" "$1" "$4" "optional"

    if [[ $provider == "[none]" ]] ; then
      provider=""       # User selected [none]. Disable dependency
    elif ! spell_installed $provider && ! spell_held $provider ; then
      if query "Do you want to cast ${SPELL_COLOR}$provider${DEFAULT_COLOR}?" n
      then install="on"
      else provider=""
      fi
    else
      install="on"
    fi
  elif [[ ${status[2]} == on ]] ; then
    install="on"
  fi

  if [[ $provider == "" ]] ; then
    install=off
  fi

  private_common_depends "$provider($1)" "$install" "optional" "$2" "$3"

}

#---------------------------------------------------------------------
## @param  spell name
## @param  addition to OPTS if enabled
## @param  addition to OPTS if disabled
## @param  description
## Handles optional dependency on a spell.
#---------------------------------------------------------------------
function work_optional_depends_spell()
{

  debug "libdepends" "$FUNCNAME - $@"
  
  if spell_exiled $1 ; then 
    message "${SPELL_COLOR}$1${DEFAULT_COLOR} has been exiled!"
    hash_put $CANNOT_CAST_HASH "$1" "Exiled"
    return 1
  fi
  
  local install="off"
  local status=()
  
  #See if there are preferences already
  #example: icewm:imlib:off:optional:--with-imlib:--with-xpm
  explode "$(search_depends_status "$SPELL" "$1")" ":" "status" 

  if [[ $RECONFIGURE || ! ${status[2]} ]] ; then

    message -n "${SPELL_COLOR}$1${DEFAULT_COLOR} is an optional "
    message "dependency for ${SPELL_COLOR}$SPELL${DEFAULT_COLOR} ($4)"

    if spell_installed $1 || spell_held $1 ; then
      query "Do you want to use ${SPELL_COLOR}$1${DEFAULT_COLOR}?" "y" && 
        install="on"
    else
      query "Do you want to cast ${SPELL_COLOR}$1${DEFAULT_COLOR}?" "n" && 
        install="on"
    fi
  elif [[ ${status[2]} == on ]] ; then
    install="on"
  fi

  private_common_depends "$1" "$install" "optional" "$2" "$3"
}


#---------------------------------------------------------------------
##=item requires <"provides" specification>
##
## Given a category of spell, returns true if a spell is installed
## that provides that category of spell.  For example,
## C<requires email-client> returns true if evolution is installed.
##
#---------------------------------------------------------------------
#requires()  { #deprecated, martin 20030906 
#
#  local CANDIDATES SATISFIED CANDIDATE 
#  local temp found
#
#  CANDIDATES=$(  find_providers  $1  )
#
#  for   CANDIDATE  in       $CANDIDATES;  do
#	if spell_installed $CANDIDATE	|| spell_held $CANDIDATE ; then
#	   message "${SPELL_COLOR}${SPELL}${QUERY_COLOR} requires a" \
#  	   	"${MESSAGE_COLOR}$1${DEFAULT_COLOR}."
#	   message "You already have ${SPELL_COLOR}${CANDIDATE}${DEFAULT_COLOR} providing that. "\
#	    "Using ${SPELL_COLOR}${CANDIDATE}${DEFAULT_COLOR}."
#	   CANDIDATES="$CANDIDATE"
#	   SATISFIED="true"
#	   break
#	 else
#        temp="$temp"$'\n'"$CANDIDATE"
#	fi	 
#  done
#  if ! [[ $SATISFIED ]] ; then
#    message  "${SPELL_COLOR}${SPELL}${QUERY_COLOR} requires a"  \
#             "${MESSAGE_COLOR}$1${DEFAULT_COLOR}"   \
#             "which can be provided by:"
#    message  "${FILE_COLOR}$CANDIDATES${DEFAULT_COLOR}"
#
#    SATISFIED="false"
#    for  CANDIDATE  in  $CANDIDATES;  do
#      if    query "Cast ${SPELL_COLOR}$CANDIDATE${DEFAULT_COLOR}? " "y"  && 
#            depends  $CANDIDATE
#      then  SATISFIED="true";  break
#      else
#            message "Didn't cast ${SPELL_COLOR}$CANDIDATE${DEFAULT_COLOR}."
#      fi
#    done
#  fi
#  
#  if [[ $SATISFIED ]] ; then
#    private_common_depends "$CANDIDATE" "on" "required" "" ""
#  else
#    return 1
#  fi
#  
#}


function private_common_depends()
{
  debug "libdepends" "$FUNCNAME - $@"
  add_depends "$SPELL" "$@"
  
  if [[ $2 == on ]] ; then
    # ${1%(*} = spell name (strips potential provider name)
    local spell_name=${1%(*}
    NEW_DEPENDS=( "${NEW_DEPENDS[@]}" $spell_name )
  fi
  
  return 0
}


function private_remove_installed_spells()
{

  for ((i=0 ; i< ${#SPELLS[*]} ; i++)) ; do
    spell="${SPELLS[$i]}"
    if private_should_cast $spell ; then
      message "${SPELL_COLOR}$spell${DEFAULT_COLOR} is already installed. Skipping."
	  SPELLS[$i]=" "
    fi
  done
  
  SPELLS=( ${SPELLS[*]} ) #Compact the array
}


function private_run_depends()
{
  debug "libdepends" "$FUNCNAME - $*"
  SPELL="$*"
  NEW_DEPENDS=()
  
  #We only need to run the stuff if we are going to be casting.
  #It only needs to be added to the casting hash table if we are really casting it
  if private_should_cast $SPELL ; then
	  run_prepare     	  && 
	  run_details     	  && 
	  run_configure   	  && 
	  run_depends $1  	  && 
	  private_add_depends ||	{ debug "libdepends" "$FUNCNAME: false inside if." ; return 1; }
  fi
  return 0
}


function private_add_depends()
{
  debug "libdepends" "$FUNCNAME: SPELL=$SPELL, NEW_DEPENDS=${NEW_DEPENDS[*]}"
  local i j
  hash_put "$CAST_HASH" "$SPELL" "${NEW_DEPENDS[*]}"
  
  for (( i=0 ; i<${#SPELLS[*]} ; i++ )) ; do
    for (( j=0 ; j<${#NEW_DEPENDS[*]} ; j++ )) ; do
      [[ ${SPELLS[$i]} == ${NEW_DEPENDS[$j]} ]] && NEW_DEPENDS[$j]=" "
    done
  done
  SPELLS=( "${SPELLS[@]}" ${NEW_DEPENDS[*]} )
}


function private_list_dependees()
{

  local i
  for (( i=0 ; "${SPELLS[$i]}"!="$SPELL" ; i++ )) ; do
    is_member "$1" $(hash_get "$CAST_HASH" "${SPELLS[$i]}") && echo $i
  done
}

function private_remove_dependees()
{

	local spell i
	echo "Removing dependees of $1"

	# spell is being removed from cast list, add to FAILED_LIST
	echo "$1" >> $FAILED_LIST

	for spell in $(private_list_dependees $1) ; do
		hash_unset "$CAST_HASH" "${SPELLS[spell]}"
		SPELLS[spell]=""
		for ((i=0;i<${#BASE_SPELLS[*]}; i++)) ; do
			[[ $spell == ${BASE_SPELLS[i]} ]] && BASE_SPELLS[i]=""
		done
	done
	SPELLS=( ${SPELLS[*]} )
	BASE_SPELLS=( ${BASE_SPELLS[*]} )
}

function private_should_cast()
{
  codex_does_spell_exist $1				&&
  {
    echo "$PRETEND_NOT_INSTALLED" | grep -q " $1 "	||
    ! { spell_installed $1 || spell_held $1; }
  }							&&
  ! spell_exiled $1 
}

#-----------------------------------------------------------------------
# Create a map of spells to their dependent spells.
# Then for all installed or held spells, output a libhash command to
# join the spell name and dependency info.
# Then evaluate the output, thus filling a libhash with dependency info.
#-----------------------------------------------------------------------
function compute_installed_depends() {
  #$1==hash table to fill
  local hash=$1

  # From here forward $1 and $2 are only used to refer to awk variables

  # sub(/(.*)/, "", $2) removes a provider name
  eval $(awk -F : 'BEGIN {
    while (getline < ARGV[1] ) {
      if( $3=="on") {
        sub(/\(.*\)/, "", $2);
        depmap[$1]=depmap[$1]" "$2" "
      }
    }
    while (getline < ARGV[2] ) {
      if( $3=="installed" || $3=="held") {
        printf("hash_put $hash %s \" %s \";\n",$1,depmap[$1]);
      }
    }
  }' $DEPENDS_STATUS $SPELL_STATUS )
}
#---------------------------------------------------------------------
##=back
##
##=head1 LICENSE
##
## This software 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 2 of the License, or
## (at your option) any later version.
##
## This software 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 software; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
##
#---------------------------------------------------------------------
