#!/bin/bash
#---------------------------------------------------------------------
## Handles storage of state information, incluing depends and package
## files. Also handles looking information up about what is installed
## and what depends on what.
##
## @Copyright Copyright (C) 2002 The Source Mage Team <http://www.sourcemage.org>
#---------------------------------------------------------------------

#---------------------------------------------------------------------
##
## Adds a dependency to the a depends database file.  Returns 1 is the 
## 3rd or 4th fields are not valid.
##
## @param  Depends status file
## @param  Name of the "parent" spell
## @param  Name of the spell the parent spell depends on
## @param  "on" or "off" depending on whether the user installed the dependency
## @param  Type of dependency (required or optional)
## @param  option to pass to C&lt;configure&gt; if the dependency is installed
## @param  option to pass to C&lt;configure&gt; if the dependency is not installed
##
## @Example add_depends kdelibs alsa-driver on optional --with-alsa --without-alsa
##
#---------------------------------------------------------------------
function add_depends()
{ # $1=depends file $2=spell, $3=depends, $4=on/off, $5=optional/required, $6=on arg, $7=off arg
	debug "libstate" "add_depends() - $*"

	#already here for some reason
	search_depends_status_exact "$@" >/dev/null  && return 0
	
        #probably not needed with commit on succeed
	#remove stale depends info
	search_depends_status "$@" >/dev/null && remove_depends_status "$@"

        local depends_status=$1
        shift
	
	#ensure the info is valid (perhaps add check that spells exist?)
	if ( [[ $3 != on ]]       && [[ $3 != off ]]       )   ||
           ( [[ $4 != required ]] && [[ $4 != optional ]]  )
	then
	  return 1 
	fi
	
	echo "$1:$2:$3:$4:$5:$6" >> $depends_status
	
	return 0	

}

#---------------------------------------------------------------------
## Sets up the uncommitted depends file. If the name isn't
## found in the hash table create it. If the file already exists
## move it to the abandoned depends directory and start with a new one.
## The uncommitted_hash hash table should be hash_export'ed to
## make it through the call to make.
##
## @param spell
## @param variable name to put the filename name in (pass by reference)
##
#---------------------------------------------------------------------
function get_uncommitted_depends_file() {
    local SPELL=$1
    local temp_spell_depends=$(hash_get "uncommitted_hash" $SPELL)
    if ! [[ $temp_spell_depends ]] ; then
      temp_spell_depends="$UNCOMMITTED_DEPENDS/$SPELL"
      hash_put "uncommitted_hash" "$SPELL" "$temp_spell_depends"
    fi
    eval "$2=\"$temp_spell_depends\""
    if [ -e $temp_spell_depends ]; then
      mkdir -p "$ABANDONED_DEPENDS"
      mv "$temp_spell_depends" "$ABANDONED_DEPENDS"
    fi
    mkdir -p "$UNCOMMITTED_DEPENDS"
    touch "$temp_spell_depends"
}

#---------------------------------------------------------------------
##
## In case you want to search in all the fields of the $1
##
## Arguments can be regexp
##
## Prints out the matching line(s)
## @Stdout the matching line(s)
## @param depends file
## @param spell
## @param depends
## @param on/off
## @param optional/required
## @param on arg
## @param off arg
##
#---------------------------------------------------------------------
function search_depends_status_exact()
{ # $1=depends file $2=spell, $3=depends, $4=on/off, $5=optional/required, $6=on arg, $7=off arg

        local depends_status=$1
        shift

	local a1 a2 a3 a4 a5 a6
	a1=`esc_str "$1"` ; a2=`esc_str "$2"` ; a3=`esc_str "$3"` ; a4=`esc_str "$4"`; a5=`esc_str "$5"`; a6=`esc_str "$6"`
        debug "libstate" "search_depends_status_exact: [$a1:$a2:$a3:$a4:$a5:$a6]"
	grep "^$a1:$a2:$a3:$a4:$a5:$a6$" $depends_status

}


#---------------------------------------------------------------------
## @param depends file
## @param spell
## @param depends (optional)
## @Stdout mathing lines
## In case you want to search by spell or dependency in $1
##
## Arguments can be regexp
##
## Prints out the matching line(s)
##
#---------------------------------------------------------------------
function search_depends_status()
{ # $1=depends file $2=spell, $3=(opt)depends

  local depends_status=$1
  shift

  [ $# -eq 1 ] && grep "^`esc_str "$1"`:" $depends_status
  [ $# -gt 1 ] && grep "^`esc_str "$1"`:`esc_str "$2"`:" $depends_status

}



#---------------------------------------------------------------------
## 
## @param depends file
## @param spell
## @param dependency spell
## @Stdout list of options
##
## Returns a list of options for ./configure from $1
## If the second argument is given, returns options for the
## matching pair of spell:dependency
## Primarily aimed at generating $OPTS contents
##
## Prints out one line of output
#---------------------------------------------------------------------
function get_depends_options()
{ # $1=depends_status $2=spell [$3]=dependency spell

  local depends_status=$1
  shift

  [ -z "$1" ] && {
    message "${PROBLEM_COLOR}${1:-<null>} is not a spell name${DEFAULT_COLOR}"
    return
  }

  local START="$(esc_str $1):"
  [ -n "$2" ] && START="${START}$(esc_str $2):"
  debug "libstate" "get_depends_options() - START=$START"
  awk -F ':' "/^$START/ { if (\$3 == \"on\") OPTS = OPTS \" \" \$5; else OPTS = OPTS \" \" \$6; } END { print OPTS; }" $depends_status
}


#---------------------------------------------------------------------
##
## Arguments can be regexp, and all but the spell are optional
##
## @param depends file
## @param spell
## @param depends
## @param on/off
## @param optional/required
## @param on arg
## @param off arg
##
#---------------------------------------------------------------------
function remove_depends_status()
{ # $1=depends file $2=spell, $3=(OPT)depends, $4=(OPT)on/off, $5=(OPT)optional/required, $6=(OPT)on arg, $7=(OPT)off arg

        local depends_status=$1
        shift

	local a1 a2 a3 a4 a5 a6
        # why do we esc_str the args? spells shouldnt have regexps in their name
        # and "on" "off" "optional" and "required" are the only valid
        # values for a3 and a4, also, with the exception of dispel depends
        # this isnt ever called with more than a1 as a parameter...
	#a1=`esc_str $1` ; a2=`esc_str $2` ; a3=`esc_str $3` ; a4=`esc_str $4`;
	a1=$1 ; a2=$2 ; a3=$3 ; a4=$4;
        a5=`esc_str $5`; a6=`esc_str $6`
	
	a2=${a2:-.*}
	a3=${a3:-.*}
	a4=${a4:-.*}
	a5=${a5:-.*}
	a6=${a6:-.*}

	[[ "$a5" == "[none]" ]] && a5="\\[none\\]"

	sedit "/^$a1:$a2:$a3:$a4:$a5:$a6$/D" $depends_status

}

#---------------------------------------------------------------------
# Default depends and default provider api
# default depends examples:
#
# default xfree86 provider to ImageMagick is xfree86
# ImageMagick:X11-LIBS:xfree86
#
# default answer to optional_depends graphviz for ImageMagick is "no"
# ImageMagick:graphviz:off
#
#
# default provider examples:
# use xorg as X11-LIBS even if its optional
# xorg:X11-LIBS:on
# 
# use xfree86 as X11-LIBS unless the [none] option exists (and use that instead)
# xfree86:X11-LIBS:off
#---------------------------------------------------------------------

#---------------------------------------------------------------------
##
## add default depends entry, if $3 is a spell $4 must be on/off
## if $3 is a PROVIDER $4 must be a spell
##
## @param depends file
## @param spell
## @param depends/PROVIDER
## @param on/off
##
#---------------------------------------------------------------------
function add_default_depends() {
        debug "libstate" "add_default_depends() - $*"

        #already here
        search_default_depends "$@" >/dev/null  && return 0

        local file=$1
        shift
        # ensure the info is valid... (this is redundant if calling
        # from sorcery, but that may not always be the case...
        if [[ $3 != on ]] && [[ $3 != off ]] ; then
          return 1
        fi

        { [[ ! $1 ]] && [[ ! $2 ]] ; } && return 1

        if [[ $1 ]] && ! codex_does_spell_exist $1 &>/dev/null; then
          return 1
        fi

        if [[ $2 ]] && ! codex_does_spell_exist $2; then
          return 1
        fi

        tfile=`lock_start_transaction $file`
        remove_default_depends $tfile $1 $2
        echo "$1:$2:$3" >> $tfile
        lock_commit_transaction $file
       
       return 0

}

#---------------------------------------------------------------------
##
## Arguments can be regexp, $2, $3 and $4 are optional
## Caller must lock the file
##
## @param depends file
## @param spell
## @param depends/PROVIDER
## @param on/off/spell
##
#---------------------------------------------------------------------
function remove_default_depends() {

        local file=$1
        shift
        test -e $file || return

	local a1 a2 a3
	a1=`esc_str $1` ; a2=`esc_str $2` ; a3=`esc_str $3`
	
	a3=${a3:-.*}

	sedit "/^$a1:$a2:$a3$/D" $file
}

#---------------------------------------------------------------------
##
## Arguments can be regexp, $2 $3 and $4 are optional
##
## @param depends file
## @param spell
## @param depends/PROVIDER
## @param on/off/spell
##
#---------------------------------------------------------------------
function search_default_depends() {

        local default_depends=$1
        shift
        [[ -s $default_depends ]] || return

	local a1 a2 a3
	a1=`esc_str "$1"` ; a2=`esc_str "$2"` ; a3=`esc_str "$3"`
	a3=${a3:-.*}

        debug "libstate" "search_default_depends: [$a1:$a2:$a3]"
	grep "^$a1:$a2:$a3$" $default_depends
}


#---------------------------------------------------------------------
##
## Arguments can be regexp, $3 and $4 are optional
##
## @param depends file
## @param spell
## @param PROVIDER
## @param on/off
##
#---------------------------------------------------------------------
function add_default_provider() {
	debug "libstate" "add_default_depends() - $*"

	#already here
	search_default_provider "$@" >/dev/null  && return 0

        local file=$1
        shift

	#ensure the info is valid...
        { [[ ! $1 ]] && [[ ! $2 ]] ; } && return 1
        { [[ $3 != on ]] && [[ $3 != off ]] ; } && return 1
        if [[ $1 ]] && ! codex_does_spell_exist $1 &>/dev/null ; then
          return 1
        fi
        if [[ $2 ]] && ! codex_does_service_exist $2 &>/dev/null ; then
          return 1
        fi

        tfile=`lock_start_transaction $file`
        remove_default_provider $tfile "" $2
	echo "$1:$2:$3" >> $tfile
        lock_commit_transaction $file
	
	return 0	
}

# these are the same, as their countparts but are here for completeness
# and incase they do someday diverge
function remove_default_provider() {
        remove_default_depends "$@"
}

function search_default_provider() {
        search_default_depends "$@"
}

#---------------------------------------------------------------------
##
## Adds an entry to the SPELL_STATUS file
##
## Arguments may either be SPELL, ACTION, VERSIONS, or just
## ACTION if there exists SPELL and VERSIONS variables already
## set.
## @param spell OR action
## @param (if spell) action
## @param (if spell) version
##
#---------------------------------------------------------------------
function add_spell_status()
{ #$1=spell, $2=action, $3=version, OR $1=action.

  local spell action version date
  if [ $# -eq 1 ] ; then
    spell="$SPELL"
    action="$1"
    version="$VERSION"
  else
    spell=$1
    action=$2
    version=$3
  fi
  [[ $spell ]] && [[ $action ]] && [[ $version ]] || return 1
  date=`date +%Y%m%d`
  tSPELL_STATUS=`lock_start_transaction $SPELL_STATUS`
  echo "$spell:$date:$action:$version" >> $tSPELL_STATUS
  lock_commit_transaction $SPELL_STATUS

}

#---------------------------------------------------------------------
## @param spell name
##
## Given a spell, status (installed, held, etc), and version, add_spell
## adds the spell to the /var/state/sorcery/packages file
##
#---------------------------------------------------------------------
function add_spell()  {
  remove_spell  $1
  add_spell_status $1 $2 $3
}


#---------------------------------------------------------------------
##
## @param spell ( [A-Za-z0-9_-] )
## @param version ( may not contain space,
##                  '.' and '*' have special meaning)
## @Stdout last matching line ( only one even if there are more )
##
## Searches the SPELL_STATUS file for a spell and optionaly
## a version.
##
## Prints out the matching spell's status
## Returns 1 only for critical problems ( missing SPELL_STAUS file ) 
##         0 for all other cases ( even when spell is not found )
##
#---------------------------------------------------------------------
function query_spell_status()
{ #$1=spell, $2=(OPT)version

	local version
	version=`esc_str ${2:-".*"}`

	tac $SPELL_STATUS | grep -m 1 "^$1:.*:.*:$version$" | \
		cut -d ":" -f 3
}

#---------------------------------------------------------------------
## @Type API
## @param spell name
##
## @return 0 if the given spell's status is "installed"
#---------------------------------------------------------------------
function real_spell_installed() {
  [  "$(  query_spell_status  $1  )"  ==  "installed"  ];
}


#---------------------------------------------------------------------
## @param spell name
##
## @return 0 if the given spell's status is "held"
##
#---------------------------------------------------------------------
function real_spell_held()      {
  [  "$(  query_spell_status  $1  )"  ==  "held"       ];
}

#---------------------------------------------------------------------
## @Type API
## @param spell name
##
## @return 0 if the given spell's status is "installed" or "held"
#---------------------------------------------------------------------
function real_spell_ok() {
  local res="$(  query_spell_status  $1  )"
  [  "$res"  ==  "installed"  ] || [  "$res"  ==  "held" ]
}


#---------------------------------------------------------------------
## @Type API
## @param Provider name
##
## @return 0 if any provider of $1 is installed
#---------------------------------------------------------------------
function real_provider_ok() {
  for spell in $(search_depends_status_exact $DEPENDS_STATUS '.*' ".*($1)" \
                 'on' '.*'  '.*' |cut -f2 -d:|cut -f1 -d\(|sort|uniq); do
    spell_ok $spell && return 0
  done
  return 1
}

#---------------------------------------------------------------------
## @Type API
## @param Spell name
## @param Provider name
## @param If empty get the uncommited spell info, if anything else get
## info from the committed ($DEPENDS_STATUS) database. If the uncommited
## db doesnt exist (maybe we're not casting) use DEPENDS_STATUS
##
## @return 0 if a provider could be found 1 if not
## @stdout the provider name(s)
#---------------------------------------------------------------------
function real_get_spell_provider() {
  local dep_status
  if [[ $3 ]] ; then
    dep_status=$DEPENDS_STATUS
  else
    dep_status=$(hash_get uncommitted_hash $1)
    [[ $dep_status ]] || dep_status=$DEPENDS_STATUS
  fi
  search_depends_status_exact $dep_status "$1" ".*($2)" \
                 'on' '.*'  '.*' '.*' |cut -f2 -d:|cut -f1 -d\(|sort|uniq
}


#---------------------------------------------------------------------
## @param spell name
##
##  @return 0 if the given spell's status is "exiled"
##
#---------------------------------------------------------------------
function real_spell_exiled()    {
  [  "$(  query_spell_status  $1  )"  ==  "exiled"     ];
}


#---------------------------------------------------------------------
## @param spell to remove status of
## Removes all specified offending entries in SPELL_STATUS
##
#---------------------------------------------------------------------
function remove_spell_status()
{ #$1=spell to remove status of

  tSPELL_STATUS=`lock_start_transaction $SPELL_STATUS`
  grep  -v  "^$1:" $SPELL_STATUS  >  $tSPELL_STATUS
  lock_commit_transaction $SPELL_STATUS
    
}

#---------------------------------------------------------------------
## @param spell name
##
## Removes the given spell from the /var/state/sorcery/packages file.
## if C<EXILE> is set, the spell is changed to "exiled" in the file.
##
#---------------------------------------------------------------------
function remove_spell()  {
  debug  "libsorcery" "Running remove_spell() on $1"
  remove_spell_status $1
  if  [  -n  "$EXILE"  ];  then
    add_spell_status $1 "exiled" "0.0"
  fi
}


#---------------------------------------------------------------------
## @Stdout All spell stati
## Just here to round out the SPELL_STATUS functions so that
## SPELL_STATUS doesn't have to mentioned in libsorcery
##
#---------------------------------------------------------------------
function all_spell_status()
{

  cat $SPELL_STATUS

}


#---------------------------------------------------------------------
## @param spells
## sets &lt;spells&gt;'s status to held
##
#---------------------------------------------------------------------
function set_held () {
    local t_status=$(lock_start_transaction $SPELL_STATUS)
    cp $SPELL_STATUS $t_status
    for to_hold in $@; do
        sedit 's/\(^'$(esc_str $to_hold)':[^:]*:\)installed/\1held/' $t_status
    done
    lock_commit_transaction $SPELL_STATUS
}


#---------------------------------------------------------------------
## @param spells
## sets &lt;spells&gt;'s status to exiled
##
#---------------------------------------------------------------------
function set_exiled () {
    local TO_DISPELL=""
    local TO_EXILE
    local MY_STATUS

    for TO_EXILE in $@; do
        MY_STATUS=$(query_spell_status "$TO_EXILE")
        case "$MY_STATUS" in
            "" )
                add_spell_status $TO_EXILE exiled 0.0
                message "Spell '$TO_EXILE' exiled."
                ;;
            "exiled" )
                message "${MESSAGE_COLOR}Spell ${DEFAULT_COLOR}'$TO_EXILE'" \
                    "${MESSAGE_COLOR}is allready exiled.${DEFAULT_COLOR}"
                ;;
            "installed" )
                local ask="'$TO_EXILE' is installed."
                ask="${ask} Do you want to dispel and exile it ?"
                query "$ask" &&
                TO_DISPELL="$TO_DISPELL $TO_EXILE"
                ;;
            * )
                message "${PROBLEM_COLOR}Can't exile spell" \
                    "${DEFAULT_COLOR}'$TO_EXILE'${PROBLEM_COLOR}" \
                    "which has status '$MY_STATUS'${DEFAULT_COLOR}"
                ;;
        esac
    done
    if [ -n "$TO_DISPELL" ]; then
        dispel -e $TO_DISPELL
    fi
}


#---------------------------------------------------------------------
## @param spells
## resets <spells>'s 'exiled' status
##
#---------------------------------------------------------------------
function set_unexiled () {
    local MY_STATUS
    local TO_UNEXILE
    for TO_UNEXILE in $@; do
        MY_STATUS=$(query_spell_status "$TO_UNEXILE")
        if [ x"$MY_STATUS" == x"exiled" ]; then
            remove_spell $TO_UNEXILE
        else
            message "${MESSAGE_COLOR}Can't unexile spell${DEFAULT_COLOR}" \
                "'$TO_UNEXILE'${MESSAGE_COLOR}, it's not exiled${DEFAULT_COLOR}"
        fi
    done
}


#---------------------------------------------------------------------
## @param spells
## sets <spells>'s status to installed
##
#---------------------------------------------------------------------
function set_unheld () {
    local t_status=$(lock_start_transaction $SPELL_STATUS)
    cp $SPELL_STATUS $t_status
    for to_hold in $@; do
        sedit 's/\(^'$(esc_str $to_hold)':[^:]*:\)held/\1installed/' $t_status
    done
    lock_commit_transaction $SPELL_STATUS
}


#---------------------------------------------------------------------
## @param status
## @Stdout spells
## returns all spells that are in that status
##
#---------------------------------------------------------------------
function get_all_spells_with_status () {
    lock_file $SPELL_STATUS
    gawk -F: 'BEGIN { status="'$1'" }
              ($3 == status) { print $1 }' $SPELL_STATUS
    unlock_file $SPELL_STATUS
}


#---------------------------------------------------------------------
## @param variable
## @param value
## @param command (optional)
##
## Modifies (or adds) an entry in the local/config.
## If "command" is the third argument, a space will separate the 
## variable and value rather than the equals sign.
##
#---------------------------------------------------------------------
function modify_local_config() {
    debug "libstate" "modify_local_config() - $*"
    modify_config $LOCAL_CONFIG "$@"
}


#---------------------------------------------------------------------
## @param Filename
## @param variable
## @param value
## @param command (optional)
##
## Modifies (or adds) an entry in the local/config.
## If "command" is the third argument, a space will separate the 
## variable and value rather than the equals sign.
##
#---------------------------------------------------------------------
function modify_config()  {

  debug "libstate" "modify_config() - $*"
  local FILE=$1
  shift;
  
  if ! [[ $1 ]]; then
    debug "libstate" "modify_config() - Warning: No name given for config option."
    return 1
  fi
  
  local TEMP separator EQUALS_COL VARIABLE
  
  # what to use as separator?
  if [[ $3 == command ]] 
  then separator=" " 
  else separator="=" ; fi

  if ! test -f $FILE; then
    if ! test -d $(dirname $FILE); then
      if test -e $(dirname $FILE); then
        message "Trying to modify $FILE, but $(dirname $FILE) is not a directory"
        return 1
      fi
      mkdir -p $(dirname $FILE)
    fi &&
    touch $FILE || {
      message "Failed to create $FILE"
      return 1
    }
  fi

  # remove previous reference
  tFILE=`lock_start_transaction $FILE`
  grep  -v  "^[[:blank:]]*${1}${separator}" $FILE >  $tFILE

  # justifying...
  EQUALS_COL=$((20-${#1}))
  [ $EQUALS_COL -lt 0  ] && EQUALS_COL=0
  TEMP=""
  for (( ; EQUALS_COL>0 ; EQUALS_COL-- )) ; do
    TEMP="$TEMP "
  done
  
  debug "libstate" "modify_config() - entering new value $VARIABLE $seperator $@ into $tFILE"
  # put new value and justification in
  echo  "${TEMP}${1}${separator}\"$2\""  >>  $tFILE

  lock_commit_transaction $FILE
    
}

#---------------------------------------------------------------------
## @param Filename
## @param variable
## @param command (optional)
##
## Modifies (or adds) an entry in the local/config.
## If "command" is the third argument, a space will separate the 
## variable and value rather than the equals sign.
##
#---------------------------------------------------------------------
function remove_config()  {

  debug "libstate" "remove_config() - $*"
  local FILE=$1
  shift;
  
  if ! [[ $1 ]]; then
    debug "libstate" "remove_config() - Warning: No name given for config option."
    return 1
  fi
  
  local TEMP separator EQUALS_COL VARIABLE
  
  # what to use as separator?
  if [[ $2 == command ]] 
  then separator=" " 
  else separator="=" ; fi
  
  # remove previous reference
  tFILE=`lock_start_transaction $FILE`
  grep  -v  "^[[:blank:]]*${1}${separator}" $FILE >  $tFILE
  lock_commit_transaction $FILE
    
}

#---------------------------------------------------------------------
## @param grimoire-name
##
## returns true if the grimoire is set local, false otherwise.
#---------------------------------------------------------------------
function codex_is_local () {(
    local g_name=$1

    local grimoire=$(codex_find_grimoire $g_name)
    if ! [[ $grimoire ]]; then
        return 1
    fi

    . "$grimoire/GRIMOIRE"

    [[ $CODEX_IS_LOCAL == "yes" ]]
)}


#---------------------------------------------------------------------
##
## 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
##
#---------------------------------------------------------------------
# vim: filetype=sh:tabstop=4:shiftwidth=4:expandtab
