#!/bin/bash
#---------------------------------------------------------------------
##
## @Synopsis Set of functions for dealing with a library of grimoires (spell books).
##
## A grimoire is a book containing one or more spells.  A codex
## is a collection of one or more grimoires.  There are functions
## for listing the available grimiores, listing spells in a grimoire,
## listing sections in a grimoire, etc.
##
## Note:  Each of the functions that returns a spell, section, or
## grimoire returns the full path.  Functions that explicitly return
## a spell I<name> or section I<name> do not return the full
## path.
##
## <br>grimoires<br>
##
## This section contains some notes on grimoires.
##
## <br>Grimoire Layout<br>
## The codex functions expect each grimoire to be a directory.
## Each directory entry in a grimoire directory is considered
## to be a section.  All directory entries in a section are
## considered to be a spell if they included an executable file
## named F<DETAILS>.
##
## <br>Multiple grimoires<br>
##
## Multiple grimoires are specified by setting entries in the
## I<GRIMOIRE_DIR> array.  For example, to set two additional
## grimoires, you would put something like the following in
## your local SMGL grimoire file (F</etc/sorcery/local/grimoire>).
## <pre>
##     GRIMOIRE_DIR[1]=/path/to/alternate/grimoire
##     GRIMOIRE_DIR[2]=/path/to/other/alternate/grimoire
## </pre>
## Grimoires are processed/searched in increasing order starting
## at index 0.  The SMGL configuration file provides the value for
## the default grimoire as I<GRIMOIRE_DIR[0]> or simply I<GRIMOIRE_DIR>.
##
## The following two lines show how to reorder the default
## grimoire so that it's not searched first (in this example
## it will be searched second).
## <pre>
##     GRIMOIRE_DIR[1]=$GRIMOIRE
##     GRIMOIRE_DIR[0]=/path/to/grimoire/to/search/first
## </pre>
## There is no limitation on the number of grimoires that can be
## specified.
##
## It is also possible to add and remove grimoires using the
## codex_add_grimoire and codex_remove_grimoire functions.
##
## @Copyright
##
## Copyright 2002 by the Source Mage Team
##
##
#---------------------------------------------------------------------

#####################GRIMOIRE FUNCTIONS###############################
#---------------------------------------------------------------------
## @param grimoire
## @param lookup (optional)
## @param variable to set (optional)
## @return 0 if grimoire can be canonicalized
##
## Outputs a grimoire in canonical form (full path)
## if lookup equal to "lookup" look up the grimoire name
## else build the grimoire name from $CODEX_ROOT/$grimoire
## CODEX_ROOT is the default grimoire location
## NOTE: one can specify partial paths
#---------------------------------------------------------------------
function codex_canonicalize_grimoire_name() {
  local grimoire=$1
  if [ "${grimoire:0:1}" == "/" ] ; then
    # already a full path
    if [[ -z $3 ]]; then
      echo $grimoire
    else
      eval "$3=\"\$grimoire\""
    fi
    return 0
  fi

  if [ "$2" == "lookup" ] ; then
    if [[ -z $3 ]]; then
      codex_find_grimoire $grimoire
    else
      local grim idx
      codex_find_grimoire $grimoire grim idx
      eval "$3=\"\$grim\""
    fi
  else
    if [[ -z $3 ]]; then
      echo "$CODEX_ROOT/$grimoire"
    else
      eval "$3=\"\$CODEX_ROOT/\$grimoire\""
    fi
  fi
  # do nothing and preserve return code
}

function codex_is_canonicalized() {
  if [ ${1:0:1} != "/" ] ; then
    message "$1 is not canonicalized!!"
    message "If you see this please contact the sorcery team"
    return 1
  fi
}

#---------------------------------------------------------------------
## @param grimoire name, or path to one
## @param variable name, is set to the matching grimoire (optional)
## @param variable name, is set to the matching grimoire's index (optional)
## @Stdout the grimoire's path
## @return 0 there is a grimoire
## @return 1 there is no grimoire
## NOTE: one can specify partial paths such as codex/stable
## <pre>
## example1:
## codex_find_grimoire test; echo "returned $?"
## /var/lib/sorcery/codex/test
## returned 0
## example2:
## codex_find_grimoire sorcery/codex/stable; echo "returned $?"
## /var/lib/sorcery/codex/stable
## returned 0
## </pre>
##
#---------------------------------------------------------------------
function codex_find_grimoire() {
  local cfg_lookup=$1
  local cfg_grimoire
  local use_refs=no
  if [ $# -eq 3 ] ; then
    use_refs=yes
    local grim_var=$2
    local position_var=$3
  fi

  # prepend a / so things like "able" dont match
  # /var/lib/sorcery/codex/stable
  [[ "${cfg_lookup:0:1}" == "/" ]] || cfg_lookup="/$cfg_lookup"

  local cfg_idx
  let cfg_idx=0
  for cfg_grimoire in $(codex_get_all_grimoires) $CODEX_ROOT/*; do
    [[ -d $cfg_grimoire ]] || continue
    # this will be empty if "$foo" matches the glob "*$bar"
    match=$(eval echo ${cfg_grimoire%%*$cfg_lookup})
    # if empty, echo and succeed
    if [ -z "$match" ] ; then
      if [ "$use_refs" == yes ] ; then
        eval "$position_var=\$cfg_idx"
        eval "$grim_var=\$cfg_grimoire"
      else
        echo $cfg_grimoire
      fi
      return 0
    fi
    let cfg_idx++
  done
  return 1
}

#---------------------------------------------------------------------
## @param grimoire
## @param [position]
## @param [overwrite]
##
## Adds the specified grimoire to the list of grimoires.  If no
## position is given, the grimoire is added to the end of the list.
## Position is 0 based.  Adding a grimoire to position 0 places it as
## the first grimoire in the list, and moves all other grimoires down
## one spot, unless [<overwrite>] is set to "overwrite".
##
## This function does not currently delete duplicate entries.
##
#---------------------------------------------------------------------
function codex_add_grimoire() {
  local  NEW_GRIMOIRE=$1
  local  POSITION=$2
  local  OVERWRITE=$3

  codex_is_canonicalized $NEW_GRIMOIRE || return 1

  local  GRIMOIRES=`codex_get_all_grimoires`

  local  CURRENT_GRIMOIRE
  local  GRIMOIRE_COUNT=0

  if [ -z "$POSITION"  ]; then
    # print everything, then print the new grimoire
    for  CURRENT_GRIMOIRE  in  $GRIMOIRES;  do
      GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$CURRENT_GRIMOIRE
      let GRIMOIRE_COUNT++
    done
    # decrement to overwrite
    if [ "$OVERWRITE" == "overwrite"  ]; then
      let GRIMOIRE_COUNT--
    fi
    GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$NEW_GRIMOIRE
    PAST_END=false
    # increment so the loop at end gets all the grimoires
    let GRIMOIRE_COUNT++
  else
    local  PAST_END=true
    if [ "$OVERWRITE" == "overwrite"  ] ; then
      # print everything, and overwrite at the right position
      for  CURRENT_GRIMOIRE  in  $GRIMOIRES;  do
        GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$CURRENT_GRIMOIRE
        if [  $POSITION -eq $GRIMOIRE_COUNT  ] ; then
          GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$NEW_GRIMOIRE
          PAST_END=false
        fi
        let GRIMOIRE_COUNT++
      done
    else
      for  CURRENT_GRIMOIRE  in  $GRIMOIRES;  do
        if [  $POSITION -eq $GRIMOIRE_COUNT  ] ; then
          GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$NEW_GRIMOIRE
          PAST_END=false
          let GRIMOIRE_COUNT++
          GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$CURRENT_GRIMOIRE
        else
          GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$CURRENT_GRIMOIRE
        fi
        let GRIMOIRE_COUNT++
      done
    fi
    # if the range is beyond the total number of grimoires add it now
    if [ "$PAST_END" == "true" ] ; then
      GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$NEW_GRIMOIRE
      let GRIMOIRE_COUNT++
    fi
  fi


  local i
  touch  $GRIMOIRE_LIST
  lock_start_transaction "$GRIMOIRE_LIST" tGRIMOIRE_LIST
  rm $tGRIMOIRE_LIST
  for ((i=0; i<$GRIMOIRE_COUNT;i++)); do
    echo GRIMOIRE_DIR[$i]=${GRIMOIRE_DIR[$i]} >> $tGRIMOIRE_LIST
  done
  lock_commit_transaction $GRIMOIRE_LIST

}


#---------------------------------------------------------------------
## @param grimoire
##
## Removes the specified grimoire from the list of grimoires.
##
#---------------------------------------------------------------------
function codex_remove_grimoire() {
  local  GRIMOIRE_TO_DELETE="$1"

  codex_is_canonicalized $GRIMOIRE_TO_DELETE || return 1

  local  GRIMOIRES=`codex_get_all_grimoires`

  lock_start_transaction "$GRIMOIRE_LIST" tGRIMOIRE_LIST
  touch  $GRIMOIRE_LIST
  cp     $GRIMOIRE_LIST  $GRIMOIRE_LIST_BACKUP
  rm  -f $tGRIMOIRE_LIST
  touch $tGRIMOIRE_LIST

  local  CURRENT_GRIMOIRE
  local  GRIMOIRE_COUNT=0
  for  CURRENT_GRIMOIRE  in  $GRIMOIRES ;  do
    if  [  "$CURRENT_GRIMOIRE"  !=  "$GRIMOIRE_TO_DELETE"  ];  then
      echo  GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$CURRENT_GRIMOIRE  >>  $tGRIMOIRE_LIST
    fi
    let GRIMOIRE_COUNT++
  done

  lock_commit_transaction $GRIMOIRE_LIST
  unset GRIMOIRE_DIR
  .  $GRIMOIRE_LIST
}

#---------------------------------------------------------------------
## Removes duplicate entries from the GRIMOIRE_LIST. This parses from
## 0 on up, and leaves only the first instance of a grimoire found.
## All others are removed.
## Then reloads the list
##
#---------------------------------------------------------------------
function codex_remove_duplicates() {

  local  GRIMOIRES=`codex_get_all_grimoires`
  local  CURRENT_GRIMOIRE=0
  local  GRIMOIRE_COUNT=0
  local  SEEN_GRIMOIRES=""
  local  ALREADY_SEEN=""


  touch  $GRIMOIRE_LIST
  lock_start_transaction "$GRIMOIRE_LIST" tGRIMOIRE_LIST
  rm  -f $tGRIMOIRE_LIST


  for  CURRENT_GRIMOIRE  in  $GRIMOIRES;  do
    ALREADY_SEEN=""
    real_list_find "$SEEN_GRIMOIRES" "$CURRENT_GRIMOIRE" && ALREADY_SEEN="yes"

    if [[  $ALREADY_SEEN  ]]; then
      SEEN_GRIMOIRES="$SEEN_GRIMOIRES $CURRENT_GRIMOIRE"
      echo "GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$CURRENT_GRIMOIRE" >> $tGRIMOIRE_LIST
      let GRIMOIRE_COUNT++
    fi

  done

  lock_commit_transaction $GRIMOIRE_LIST

  unset GRIMOIRE_DIR
  .  $GRIMOIRE_LIST

}

#---------------------------------------------------------------------
## @param grimoire name | grimoire dir ....
##
## Unsets the list of grimoires that existed before the call, then
## sets the lists of grimoires to be equal to the list of grimoires
## in the argument list. Grimoire names need not be canonicalized
##
#---------------------------------------------------------------------
function codex_set_grimoires() {

  local NEW_GRIMOIRE
  local GRIMOIRE_COUNT=0
  for NEW_GRIMOIRE in "$@";  do
    codex_canonicalize_grimoire_name $NEW_GRIMOIRE lookup NEW_GRIMOIRE &&
    test -d $NEW_GRIMOIRE ||
    { message "WARNING $NEW_GRIMOIRE does not exist!! skipping..."
      continue
    }
    NEW_GRIMOIRE_DIR[$GRIMOIRE_COUNT]=$NEW_GRIMOIRE
    let GRIMOIRE_COUNT++
  done
  unset GRIMOIRE_DIR
  let GRIMOIRE_COUNT--
  for ((; $GRIMOIRE_COUNT>=0;GRIMOIRE_COUNT--)) ; do
    GRIMOIRE_DIR[$GRIMOIRE_COUNT]=${NEW_GRIMOIRE_DIR[$GRIMOIRE_COUNT]}
  done
}


#---------------------------------------------------------------------
##
## @Stdout all grimoires in the codex.
##
#---------------------------------------------------------------------
function codex_get_all_grimoires() {
  debug "libcodex" "codex_get_all_grimoires()"
  echo "${GRIMOIRE_DIR[*]}" | tr '[:blank:]' '\n'
  return $?
}

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

  codex_find_grimoire $g_name grimoire idx
  if ! [[ $grimoire ]]; then
    return 1
  fi

  . "$grimoire/GRIMOIRE"

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

#####################SECTION FUNCTIONS###############################


#---------------------------------------------------------------------
## @param section
## @param variable to set (optional)
## @return 0 if section is found
## @return 1 if section is not found
##
## @Stdout full path to the section
## Given a valid section name, this function lists the full path to
## the section.  If an invalid section name is provided, nothing is
## listed.
##
#---------------------------------------------------------------------
function codex_find_section_by_name() {
  local SECTION_NAME="$1"
  local GRIMOIRE=''

  for GRIMOIRE in `codex_get_all_grimoires`; do
    if [ -d "$GRIMOIRE/$SECTION_NAME" ] ; then
      debug "libcodex" "codex_find_section_by_name() - found section $GRIMOIRE/$SECTION_NAME"
      if [[ -z $2 ]]; then
        echo "$GRIMOIRE/$SECTION_NAME"
      else
        eval "$2=\"\$GRIMOIRE/\$SECTION_NAME\""
      fi
      return 0
    fi
  done
  return 1
}


#---------------------------------------------------------------------
##
## @Stdout all section names from all grimoires.
##
#---------------------------------------------------------------------
function codex_get_all_section_names() {
  codex_get_all_sections | get_basenames
}


#---------------------------------------------------------------------
## @param grimoire-pathes (optional)
##
## @Stdout all sections from all grimoires or only from the specified grimoires.
##
#---------------------------------------------------------------------
function codex_get_all_sections() {
  if [[ $# -gt 0 ]]; then
    codex_get_sections $@
  else
    codex_get_sections `codex_get_all_grimoires`
  fi
}

#---------------------------------------------------------------------
## @param grimoire
##
## @Stdout Lists all section names in the specified grimoire.
## Relies on a wider-scope function <@function codex_get_sections>.
##
#---------------------------------------------------------------------
function codex_get_section_names() {
  codex_is_canonicalized $1 || return 1
  codex_get_sections "$1" | get_basenames
}

#---------------------------------------------------------------------
## @param canonicalized grimoire names
##
## @Stdout Lists all sections in the specified grimoire directories.
##
#---------------------------------------------------------------------
function codex_get_sections() {
  debug "libcodex" "codex_get_sections() - $@"
  local GRIMOIRE

  while [ $# -gt 0 ] ; do
    # sanity check
    codex_is_canonicalized $1 || return 1

    # ensure there is a cache
    codex_check_cache $1
    GRIMOIRE="$1"

    # comes in the format
    # spellname /path/to/section
    cut -d' ' -f2 "$GRIMOIRE/$SPELL_INDEX_FILE" | sort -u
    shift
  done
}


###########################SPELL FUNCTIONS############################

#---------------------------------------------------------------------
## @param full directory
##
## @return 0 if the specified directory is a spell directory.
## @return 1 otherwise
##
#---------------------------------------------------------------------
function codex_is_directory_a_spell() {
  [ -x "$1/DETAILS" ]
}


#---------------------------------------------------------------------
## @param spell
## @param [spell ...]
##
## @return 0 if all the specified spells exist
## @return 1 othterwise
##
#---------------------------------------------------------------------
function codex_does_spell_exist() {
  local i
  local retValue=0
  [[ "$#" -lt 1 ]] && return 1
  for i in "$@" ; do
    if [[ "$i" == "" ]] ; then
      message "${PROBLEM_COLOR}Empty string is not a spell!${DEFAULT_COLOR}"
      retValue=1
    elif ! [[ $(codex_find_spell_by_name "$i") ]] ; then
      message "${SPELL_COLOR}$i${DEFAULT_COLOR}${PROBLEM_COLOR} is not a spell!${DEFAULT_COLOR}"
      retValue=1
    fi
  done
  return $retValue
}



#---------------------------------------------------------------------
## @param spell name
## @Stdout spell name
## Given a valid spell name, this function lists the full path to the
## spell.  If an invalid spell name is provided, nothing is listed.
##
#---------------------------------------------------------------------
function codex_find_spell_by_name() {

  debug "libcodex" "codex_find_spell_by_name - $*"

  codex_cache_spell_lookup $1 `codex_get_all_grimoires`

}

#---------------------------------------------------------------------
## @param path/section
##
## @Stdout spells
## Lists full paths to spells in the specified section.
## Nothing is listed if the section doesn't include any spells.
##
#---------------------------------------------------------------------
function codex_get_spells_in_section() {
  debug "libcodex" "codex_get_spells_in_section - $*"
  codex_is_canonicalized $1 || return 1
  local section="$(smgl_basename $1)"
  local index="$(smgl_dirname $1)/$SPELL_INDEX_FILE"
  if ! test -r $index || ! test -s $index; then
    message "${PROBLEM_COLOR}${section:-<null>} is not a section directory!${DEFAULT_COLOR}"
      return 1
  fi
  grep "/$section$" < $index | awk '{ printf("%s/%s\n",$2,$1); }'

}


#---------------------------------------------------------------------
## @param path/section
##
## @Stdout spells
## Lists all spell names in the specified section.  Nothing is listed
## if the section doesn't include any spells.
##
#---------------------------------------------------------------------
function codex_get_spell_names() {
  codex_get_spells_in_section "$1" | get_basenames
}


#---------------------------------------------------------------------
## @param  grimoire-pathes (optional)
##
## @Stdout spells
## Lists all spells in all grimoires or only from the specified
## grimoires. Nothing is listed if no spells exist in any of grimoires.
##
##
## @NOTE This should be fixed so only the first of duplicate spells
## @NOTE are listed.
##
#---------------------------------------------------------------------
function codex_get_all_spells() {
  local section
  for section in `codex_get_all_sections $@`; do
    codex_get_spells_in_section $section
  done
}

#---------------------------------------------------------------------
## @param spell name
##
## @Stdout spell name
## Lists the section of the given spell name.  Nothing is listed if
## there are no spells with the given name.
##
#---------------------------------------------------------------------
function codex_get_spell_section() {
  codex_find_spell_by_name "$1" | get_dirnames
}


#---------------------------------------------------------------------
## @param spell name
##
## @Stdout section name
##
## Given a spell name, this function lists the section name.  If there
## are no spells with the given name, nothing is listed.
##
#---------------------------------------------------------------------
function codex_get_spell_section_name() {
  codex_find_spell_by_name "$1" | get_dirnames | get_basenames
}



#---------------------------------------------------------------------
##
## @Globals GRIMOIRE SECTION SECTION_DIRECTORY SPELL SPELL_DIRECTORY SCRIPT_DIRECTORY SPELL_DESCRIPTION VERSION SHORT UPDATED SOURCE WEB_SITE ENTERED MAINTAINER MD5 LICENSE
## Unets all these global variables.
##
#---------------------------------------------------------------------
function codex_clear_current_spell()  {
  unset GRIMOIRE SECTION SECTION_DIRECTORY SPELL  \
  SPELL_DIRECTORY SCRIPT_DIRECTORY SPELL_DESCRIPTION \
  VERSION SHORT UPDATED SOURCE WEB_SITE ENTERED MAINTAINER \
  MD5 LICENSE BUILD_API SECURITY_PATCH PATCHLEVEL
}

#---------------------------------------------------------------------
## @param spell directory
## @Globals All vars set in a spell
## Sets the GRIMOIRE, SECTION, SECTION_DIRECTORY, SPELL_DIRECTORY,
## SCRIPT_DIRECTORY global variables for the
## given spell directory.
##
## Assumes the directory passed in is a valid spell directory.
##
#---------------------------------------------------------------------
function codex_set_current_spell()  {

  debug "libcodex" "runing codex_set_current_spell"

  codex_clear_current_spell

  codex_get_spell_paths $1

  if  [ -f  $SPELL_CONFIG  ]; then
    .  $SPELL_CONFIG > /dev/null  2> /dev/null
  fi

  debug "libcodex" "looking around for API_VERSION"
  [[ -x $GRIMOIRE/API_VERSION ]] && . $GRIMOIRE/API_VERSION
  [[ -x $SECTION_DIRECTORY/API_VERSION ]] && . $SECTION_DIRECTORY/API_VERSION

  # load compatibility functions needed for any stage of cast
  load_libcompat

  debug "libcodex" "sourcing DETAILS"
  persistent_load
  .  $SPELL_DIRECTORY/DETAILS 1>/dev/null 2>&1
  persistent_clear

  STAGE_DIRECTORY=$BUILD_DIRECTORY/stage-$SPELL-$VERSION

  # set a default build api if there isn't one already
  # this isn't strictly necessary as other code should be able to handle the
  # lack of this variable, but I want to play it safe.
  [[ -z $BUILD_API ]] && BUILD_API=2
  true
}

#---------------------------------------------------------------------
## Load just the very basics for a spell, skip build api, libcompat
## and other debug stuff. Intended for use in tight loops.
#---------------------------------------------------------------------
function codex_set_current_spell_quick()  {
  codex_get_spell_paths $1

  test -f  "$SPELL_CONFIG" && .  "$SPELL_CONFIG" &> /dev/null
  persistent_load
  .  $1/DETAILS &> /dev/null
  persistent_clear
  true
}


#---------------------------------------------------------------------
## Setup the various spell paths associated with a spell without
## loading it.
#---------------------------------------------------------------------
function codex_get_spell_paths() {
  SPELL_DIRECTORY=$1
  SCRIPT_DIRECTORY=$1
  if test -e $SPELL_DIRECTORY/SCRIBBLED ; then
    local tmp
    smgl_dirname "$SPELL_DIRECTORY" tmp
    smgl_basename "$tmp" SECTION
    smgl_dirname "$tmp" tmp
    smgl_basename "$tmp" GRIMOIRE_NAME

    SECTION_DIRECTORY=${SPELL_DIRECTORY}/section
    GRIMOIRE=${SPELL_DIRECTORY}/grimoire
  else
    smgl_dirname "$SPELL_DIRECTORY" SECTION_DIRECTORY
    smgl_basename "$SECTION_DIRECTORY" SECTION
    smgl_dirname "$SECTION_DIRECTORY" GRIMOIRE
    smgl_basename "$GRIMOIRE" GRIMOIRE_NAME
  fi
  smgl_basename "$SPELL_DIRECTORY" SPELL
  SPELL_CONFIG="$DEPENDS_CONFIG/$SPELL"
  return 0
}

#---------------------------------------------------------------------
## @param spell name
## Sets the GRIMOIRE, SECTION, SECTION_DIRECTORY, SPELL_DIRECTORY,
## SCRIPT_DIRECTORY global variables for the given spell name.
##
## @return 1 if the given name is not a spell.
##
#---------------------------------------------------------------------
function codex_set_current_spell_by_name()  {
  debug "libcodex" "codex_set_current_spell_by_name -- $1"
  local SPELL_NAME=`codex_find_spell_by_name "$1"`
  debug "libcodex" $SPELL_NAME

  [  -n  "$SPELL_NAME"  ]  &&  codex_set_current_spell  $SPELL_NAME
}

#---------------------------------------------------------------------
## @param spell name
## Returns the features a spell is a provider off.
## Example, firefox: GECKO, GRAPHICAL-WEB-BROWSER, WEB-BROWSER,
##                   NS-PLUGIN-COMPATIBLE
##
#---------------------------------------------------------------------
function codex_find_spell_provides() {
  local spell="$1"
  local feature grimoire
  local features=$(
    for grimoire in $(codex_get_all_grimoires);  do
      gawk '/\/'$spell'$/ { print $1 }' \
        "$grimoire/$PROVIDE_INDEX_FILE"
    done | sort -u)

  # filter out providers we dont want, so far this is:
  # a) spells that do not provide something in the first grimoire they are
  # found in (see note in find_providers)
  local spell_path=$(codex_find_spell_by_name $spell)
  smgl_dirname $(smgl_dirname $spell_path) grimoire
  for feature in $features; do
    # ensure the first place we look for the spell is a provider
    grep -q "^$feature $spell_path$" $grimoire/$PROVIDE_INDEX_FILE &&
      echo $feature
  done
}

#---------------------------------------------------------------------
## @param category/service
## @Stdout spelllist
##
## First argument is a category of spell.  Returns a list of spells
## that match that category.  For example,
## C<find_providers email-client> returns evolution,althea,mutt,etc.
##
## This will list spells exactly once, exiled spells are not listed.
## This also takes into account grimoire ordering. A spell must provide
## the category in the first grimoire the spell is seen in (because that
## is the only one that will be cast) otherwise it is not listed.
#---------------------------------------------------------------------
function find_providers()  {
  local feature="$1"
  local provider GRIMOIRE spell_path
  local providers=$(
    for GRIMOIRE in $(codex_get_all_grimoires);  do
      gawk '/^'"$feature"'[[:blank:]]/ { print $2 }' \
        "$GRIMOIRE/$PROVIDE_INDEX_FILE"
    done | get_basenames|sort -u)

  # filter out providers we dont want, so far this is:
  # a) do not provide something in the first grimoire they are found in
  # b) are exiled.
  # ex: foo provides FOOBAR in the "stable" grimoire but not the "test"
  # grimoire and test is searched before stable in this case cast will
  # use the spell from test which does NOT provide FOOBAR, therefore
  # we should not list foo as a provider of FOOBAR
  for provider in $providers; do
    if ! spell_exiled $provider; then
      spell_path=$(codex_find_spell_by_name $provider)
      smgl_dirname "$(smgl_dirname $spell_path)" GRIMOIRE
      # ensure the first place we look for the spell is a provider
      grep -q "^$feature $spell_path$" $GRIMOIRE/$PROVIDE_INDEX_FILE &&
        echo $provider
    fi
  done
}


##############################CACHE FUNCTIONS#########################
#---------------------------------------------------------------------
## @param spell name
## @param grimoire-path
## @param [grimoire-path ...]
## @return 0 Spell found
## @return 1 Spell not found
## Searches the indicies of the specified grimories for a spell.
## This will return only the first match found.
##
#---------------------------------------------------------------------
function codex_cache_spell_lookup()  {
  local SECTION
  local spell="$1"
  shift
  while [ $# -gt 0 ] ; do
   debug "libcodex" "looking up $spell in ${1}'s cache"
    codex_check_cache $1
    SECTION=`grep -m 1 "^$spell " $1/$SPELL_INDEX_FILE | cut -d' ' -f2`
    [[ $SECTION ]] && echo "$SECTION/$spell" && return
    shift
  done
  return 1
}


#---------------------------------------------------------------------
## @param grimoire-path
## @Stdout error if cache doesn't exist after creation
## Checks that the cache exists. if it doesn't exist, make it.
## If it still doesn't exist, the barf an error
##
#---------------------------------------------------------------------
function codex_check_cache()  {
  codex_is_canonicalized $1 || return 1
  [[ -x $1 ]] || return 1

  if ! [ -f $1/$SPELL_INDEX_FILE ] || ! [ -f $1/$PROVIDE_INDEX_FILE ]; then
      codex_create_cache
  fi
  if ! [ -f $1/$SPELL_INDEX_FILE ] || ! [ -f $1/$PROVIDE_INDEX_FILE ]; then
    error_message "${PROBLEM_COLOR}Eeek, $1 is not a grimoire!${DEFAULT_COLOR}"
    exit 1
  fi
}

#---------------------------------------------------------------------
## @param [grimoire-path]
## Creates the cache/index for the specified grimoire, or all if none
## is asked for.
##
#---------------------------------------------------------------------
function codex_create_cache()  {

  debug "libcodex" "codex_create_cache - $*"
  local list="$*"
  [[ $list ]] || list="${GRIMOIRE_DIR[*]}"

  for i in $list ; do
    codex_find_in_grimoire $i "DETAILS" | \
    sed 's@\(.*\)/\([^/]*\)/DETAILS@\2 \1@' | \
    sort > $i/$SPELL_INDEX_FILE

    codex_list_provides $i > "$i/$PROVIDE_INDEX_FILE"
  done

}


#---------------------------------------------------------------------
## Create in-memory spell lookup table for named grimoires.
#---------------------------------------------------------------------
function codex_create_in_memory_cache() {
  local list
  if [[ $1 == -i ]] ; then
    list=$SPELL_STATUS
    shift
  fi
  local hash=$1
  shift
  eval $(
    for each in "$@"; do
      cat $each/$SPELL_INDEX_FILE
    done | awk -v i=$list -v hash=$hash '{
      if ( ! ($1 in map)) {
        map[$1]=$2;
      }
    } END {
      if (i) {
        FS=":";
        while (getline < i) {
          if( $3=="installed" || $3=="held") {
            printf("hash_put %s %s \"%s/%s\";\n",hash,$1,map[$1],$1);
          }
        }
      } else {
        for (spell in map) {
          printf("hash_put %s %s \"%s/%s\";\n",hash,spell,map[spell],spell);
        }
      }
    }'
  )

}

#---------------------------------------------------------------------
## Creae in-memory spell lookup table for all known grimoires.
#---------------------------------------------------------------------
function codex_create_in_memory_cache_all() {
  codex_create_in_memory_cache "$@" $(codex_get_all_grimoires)
}

###########################MISC FUNCTIONS#############################

#---------------------------------------------------------------------
## @param grimoire-path
## @param file-name
##
## @Stdout file names
## Prints every file matching file-name in grimoire
##
#---------------------------------------------------------------------
function codex_find_in_grimoire () {
    find "$1" -follow -maxdepth 3 -mindepth 3 -perm /700 -name "$2"
}

#---------------------------------------------------------------------
## @param grimoire-path
## @Stdout provides spell
## Lists all providers in grimoire in the form of "provides spell"
## for instance: <br>
## shell /home/martin/p4/grimoire/shell-term-fm/sash
##
#---------------------------------------------------------------------
function codex_list_provides () {
    grimoire=$1

    for file in $(codex_find_in_grimoire $grimoire "PROVIDES"); do
        smgl_dirname "$file" spell
        for provides in $(gawk '{if (/provides/) { print $2 }
                                 else { print $1 }}' $file); do
            echo "$provides $spell"
        done
    done | sort
}

#---------------------------------------------------------------------
## @param grimoire-path
## Creates keyword index
##
#---------------------------------------------------------------------
function codex_create_keyword_cache () { (
    grimoire=$1

    for file in $(codex_find_in_grimoire "$grimoire" "DETAILS"); do
        unset SPELL KEYWORDS
        codex_set_current_spell "${file%DETAILS}"
        echo "$SPELL $KEYWORDS"
    done | sort > $grimoire/$KEYWORD_INDEX_FILE
) }

#---------------------------------------------------------------------
## @param grimoire-path
## Creates the version index, which additionally includes the other
## queuing factors - patchlevels and UPDATED.
##
## Multiversioned spells will a dummy entry. Persistent
## variables are ignored.
#---------------------------------------------------------------------
function codex_create_version_cache () { (
  grimoire="$1"

  for file in $(codex_find_in_grimoire "$grimoire" DETAILS); do
    codex_get_spell_paths ${file%/DETAILS}
    source "$file" &> /dev/null
    if [[ $(grep -c "^[[:space:]]*VERSION=" "$file") == 1 ]]; then
      echo "$SPELL ${VERSION:-0} ${PATCHLEVEL:-0} ${SECURITY_PATCH:-0} ${UPDATED:-0}"
    else
      # multiversioned or unusually formatted spell - add a dummy entry
      echo "$SPELL multiversioned"
    fi
    unset SPELL VERSION PATCHLEVEL SECURITY_PATCH UPDATED
  done | sort > $grimoire/$VERSION_INDEX_FILE
) }

#---------------------------------------------------------------------
## @param servicename
## @return 0 if service exists
## @return 1 otherwise
## checks if servicename exists.
#---------------------------------------------------------------------
function codex_does_service_exist()
{
   local SERVICE="$@"
    for GRIMOIRE in $(codex_get_all_grimoires); do
        grep -qE '^'$SERVICE' ' "$GRIMOIRE/$PROVIDE_INDEX_FILE" && return 0
    done
}


#---------------------------------------------------------------------
## @param spell or section name
## @Globals CODEX_FOUND_SECTION CODEX_FOUND_SPELL
## @return 0 if the passed argument is a spell name or a section name.
## @return 1 otherwise
## CODEX_FOUND_SECTION is set if section was found
## CODEX_FOUND_SPELL   is set if a spell was found
##
#---------------------------------------------------------------------
function codex_find_spell_or_section_by_name()  {
    local SPELL_OR_SECTION="$1"

    CODEX_FOUND_SECTION=""
    CODEX_FOUND_SPELL=""

    codex_find_section_by_name $SPELL_OR_SECTION CODEX_FOUND_SECTION
    [  -n  "$CODEX_FOUND_SECTION"  ]  &&  return

    CODEX_FOUND_SPELL=`codex_find_spell_by_name $SPELL_OR_SECTION`
    [  -n  "$CODEX_FOUND_SPELL"  ]    &&  return

    return 1
}

#---------------------------------------------------------------------
## @param spell directory
## @Stdout spell description
## Echos the long description of the given spell.  Returns an
## empty string if the directory is not a valid spell.
##
#---------------------------------------------------------------------
function codex_get_spell_description()  {
  local width=$(stty size | cut -d' ' -f2)
  codex_is_directory_a_spell  "$1"  &&  .  "$1/DETAILS" | fmt -w $width -s
}

#---------------------------------------------------------------------
##
## @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
##
#---------------------------------------------------------------------
