#!/bin/bash

#---------------------------------------------------------------------
## @param string to explode
## @param delimiter
## @param name of array to put it in
##
## Turns a string into an array. Each element of the array
## is separated in the string by the delimiter.
##
## Note: The array you want the fields put into must be
## declared before you call this function.
## <pre>
## EXAMPLE
##
## my_array=()
## explode "a_string_to_explode" "_" "my_array"
## echo my_array[*]
##
## Produces "a" "string" "to" "explode".
## </pre>
#---------------------------------------------------------------------
function explode()
{ # $1==string to explode, $2==delimiter, $3==name of array to put it in

  [[ "$3" ]] || return 1
  local l=$1
  local i=0
  while [[ $l ]]; do
    local result=${l%%$2*}
    l=${l#"$result"}
    l=${l#$2}
    eval "$3[$i]=\"\$result\""
    ((i++))
  done
  # this adds an empty array element at the end if the line ended in $2
  local lc=${1//\*$2/true}
  if [ "$lc" = "true" ]; then
    eval "$3[$i]=\"\""
  fi
}


#---------------------------------------------------------------------
## @Type API
## @param sed command
## @param file
##
## First argument is a sed command.  Second argument is a file.
## sedit performs the sed command on the file, modifiying the
## original file.  For example,
## <br>sedit "s/foo/bar/g" /tmp/somefile <br>
## will replace all occurances of foo with bar in /tmp/somefile.
## This function is often used in spells to make changes to source
## files before compiling.  See the sed man page for more information.
##
#---------------------------------------------------------------------
function real_sedit()  {
  sed -i "$1" "$2"
}

#---------------------------------------------------------------------
## @Type API
## @param string to check
## @return 0 if true
## @return 1 if false
##
## Argument is a string to check if the string contains all digits or
## not
#--------------------------------------------------------------------
function isdigit() {
  echo $1 | grep '[[:digit:]]+' >> /dev/null 2>&1
}

#--------------------------------------------------------------------
## @Type API
## @param string to check
## @return 0 if true
## @return 1 if false
##
## Argument is a string to check if it contains all chars a-zA-Z
#--------------------------------------------------------------------
function isalpha() {
  echo $1 | grep '[[:alpha:]]+' >> /dev/null 2>&1
}

#---------------------------------------------------------------------
## @Type API
## @param question
## @param default answer
##
## @return 0 on yes
## @return 1 on no
##
## Asks the user a yes/no question.  First argument is the question to
## ask, second argument is the default answer.  If a timeout occurs
## before the question is answered, the given default answer is
## applied. Pressing spacebar or enter applies the default answer
## immediatelly without waiting for timeout.
## Returns true or false based on the answer given.
##
#---------------------------------------------------------------------
function real_query()  {
  debug "libmisc" "Running query() with the following arguments: '$1' and '$2'"
  local _response

  while true; do
    _response=""

    if [[ -z $SILENT ]]; then
      echo -e -n "${QUERY_COLOR}$1 [$2] ${DEFAULT_COLOR}"

      read -t $PROMPT_DELAY -n 1 _response
      echo
    fi

    _response=${_response:=$2}
    case  $_response in
      n|N) return 1 ;;
      y|Y) return 0 ;;
    esac
  done
}

#---------------------------------------------------------------------
## @param return_var
## @param question
## @param default answer
##
## @return 0 user supplied answer
## @return 1 default answer is used
##
## Asks user for string, with default answer and timeout (like query)
##
#---------------------------------------------------------------------
function real_query_string () {

    debug  "libmisc" "Running question() with the following arguments: '$1' and '$2'"

    local RESPONSE=""
    local RETURN=0
    local ANSWER_first ANSWER_rest

    local DEFAULT=""
    [ -n "$3" ] && DEFAULT=" [$3] "

    if [ -z "$SILENT" ]; then
        echo -e -n "${QUERY_COLOR}$2${DEFAULT}${DEFAULT_COLOR}"
        read -t $PROMPT_DELAY -n 1 ANSWER_first
        if [[ $ANSWER_first ]] ; then
          read -e ANSWER_rest
        fi
        RESPONSE="${ANSWER_first}${ANSWER_rest}"
        echo
    fi

    [ -z "$RESPONSE" ] && RETURN=1 && RESPONSE="$3"

    eval $1=\"\${RESPONSE}\"
    return $RETURN
}



#---------------------------------------------------------------------
## @Type API
## @param message to echo
## @Stdout message
## echo's the given arguments if SILENT is not set.
#---------------------------------------------------------------------
function real_message()    {
  if  [  !  -n  "$SILENT"  ];  then
    if  [  "$1"  ==  "-n"  ];  then
      shift  1
      echo  -n  -e  "$*"
    else
      echo  -e  "$*"
    fi
  fi

}

#---------------------------------------------------------------------
## @Type Private
## @param error message to echo
## @Stdout message
## @Stderr message
## echo's the given arguments if SILENT is not set.
#---------------------------------------------------------------------
function error_message() {
  real_message "$@" 1>&2
}

#---------------------------------------------------------------------
## @param type
## @param message
##
## Enters a debug message if the type of debug message != 'no'
##
## The 'type' is usually the name of the file the debug statement
## comes from. i.e. DEBUG_liblock, DEBUG_libsorcery, DEBUG_cast etc.
##
#---------------------------------------------------------------------
function debug()  {

  [[ $DEBUG ]] || return 0

  local debugVar="DEBUG_${1}"
  local i
  if [[ ${!debugVar} != "no" ]] ; then
    echo -n "$1($$): " >>$DEBUG
    shift
    for i in "$@" ; do
      echo -n " \"$i\"" >>$DEBUG
    done
    echo >>$DEBUG
  fi

  true
}

#---------------------------------------------------------------------
## @Stdout progress spinner
## Displays progress spinner like the one in fsck.
##
#---------------------------------------------------------------------
function progress_spinner()  {
  let  PROGRESS_SPINNER=$PROGRESS_SPINNER+1
  if ((  PROGRESS_SPINNER > ${#PROGRESS_SPINNER_CHARS}-1  )); then
    PROGRESS_SPINNER=0;
  fi
  echo -en "\b${PROGRESS_SPINNER_CHARS:$PROGRESS_SPINNER:1}"
}


#---------------------------------------------------------------------
## @param opt "dot"
## @param count
## @param total
## @param opt length
## <pre>
## Displays progress bar in the form of [---->   ] 100%
## Or just a dot in the dot format
##</pre>
#---------------------------------------------------------------------
function progress_bar()  {
debug "libmisc" "progress_bar - $*"
  local percent i len num_dash

  if [[ $1 == -dot ]] ; then
    len=0
    shift 1
  else
    len=${3:-${COLUMNS:-70}}
  fi

  # Can't make a bar because there's no total, or the length isn't
  #  long enough, or if there is no length
  if [[ $# -lt 2 ]] || [[ $len -lt 8 ]]; then
    message -n "."
    return 0
  fi

  if [[ $1 -lt 1 ]] || [[ $2 -lt 1 ]]; then return 1; fi

  percent=$((100*$1/$2))
  percent=$(printf "%.0f" $percent)

  if [[ $LAST_PERCENT == $percent ]] ; then
    progress_spinner
    return 0
  fi

  LAST_PERCENT=$percent


  dash_len=$(($len-8))
  num_dash=$(( $dash_len*$1/$2 ))

  #Format: [--->  ] 100%

  #opening of the bar
  BAR_STRING="["

  #The '=' signs
  for (( i=0 ; i<$num_dash ; i++ )) ; do
    BAR_STRING="${BAR_STRING}="
  done

  #Now a pretty indicator
  [ $num_dash -lt $((dash_len-1)) ] && BAR_STRING="${BAR_STRING}>"

  #Fill the rest of the bar up with blanks
  for (( i++ ; i<$dash_len-1 ; i++ )) ; do
    BAR_STRING="${BAR_STRING} "
  done

  #Put on the closing for the bar and the percent
  BAR_STRING="${BAR_STRING}] ${percent}%"

  #Clear the rest of the space
  for (( i+=3+${#percent} ; i<$len ; i++ )) ; do
    BAR_STRING="${BAR_STRING} "
  done

  clear_line
  message -n "$BAR_STRING"
  progress_spinner

 return 0


}
#---------------------------------------------------------------------
## @Stdout Clear line
## Clears the current line and puts the cursor at the begining of it.
##
#---------------------------------------------------------------------
function clear_line()  {

  echo -en '\r\033[2K'
  return 0

}


#---------------------------------------------------------------------
##
## @Globals SOUND
## Plays a given soundfile if sounds are on and the file exists in
## the chosen theme.
##
#---------------------------------------------------------------------
function sound()  {

  case  $SOUND  in
    on)  SOUND_FILE=$SOUND_DIRECTORY/$SOUND_THEME/$1
         if  [  -e  $SOUND_FILE  ];  then
           (  cd  /  ;  play  $SOUND_FILE  2>/dev/null  & )
           debug "libmisc" "Playing $SOUND_FILE"
         else
           debug "libmisc" "Error playing $SOUND_FILE: no such file"
         fi
    ;;
  esac

}


#---------------------------------------------------------------------
## @param filename
##
## Runs the editor given by EDITOR on the file given in the first
## argument.  If EDITOR is not set, "nano -w" is used.
##
#---------------------------------------------------------------------
function edit_file()  {

  ${EDITOR:-nano -w} $1

}


#---------------------------------------------------------------------
## @param string
## @param (optional) upvar
## @Stdout escaped string
##
## Adds escape sequences to strings for special characters.
## Does NOT escape the string ".*".
## Used for putting escape sequences in regexp strings that should
## be treated literaly.
##
#---------------------------------------------------------------------
function esc_str()
{
  if [[ $1 == ".*" ]]; then
    if [[ -z $2 ]]; then
      echo "$1"
    else
      upvar $2 "$1"
    fi
  else
    local escaped="${1//./\.}"
    escaped="${escaped//\*/\*}"
    if [[ -z $2 ]]; then
      echo "$escaped"
    else
      upvar $2 "$escaped"
    fi
  fi
}

#---------------------------------------------------------------------
## @param string
## @param upvar
##
## esc_str with a minor modification used for escaping provider
## queries, where only the provider name needs to be escaped.
## Does NOT escape strings matching "\.\*(.*".
##
#---------------------------------------------------------------------
function esc_provider_str()
{
  local _escaped
  # the pattern is intentionally not quoted, so glob matching occurs
  if [[ $1 == .\*\(* ]]; then
    esc_str "${1:2}" _escaped
    _escaped=".*$_escaped"
  else
    _escaped="${1//./\.}"
    _escaped="${_escaped//\*/\*}"
  fi
  upvar $2 "$_escaped"
}

#---------------------------------------------------------------------
## Properly quotes and backquotes parameters so they can be passed
## through su at the start of many sorcery commands
##
## Expected usage is:
##
##  PARAMS=$(consolidate_params "$@")
##  su -c "$0 $PARAMS" root
#---------------------------------------------------------------------
function consolidate_params() {
  local param
  for param in "$@"; do
    # add and remove a dummy space, so echo parameters can be used as values
    echo -n "" $param | sed 's/ //; s/ /\\ /g'
    echo -n " "
  done
}

#---------------------------------------------------------------------
## @Stdin directories.
## @Stdout directories' basenames
## Takes newline separated list of directories
## and outputs their base names.
##
#---------------------------------------------------------------------
function get_basenames() {
  local directory
  while read directory; do
    smgl_basename "$directory"
  done
}


#---------------------------------------------------------------------
## @Stdin directories.
## @Stdout directories' basenames
## Takes newline separated list of pathnames
## and outputs their directory names.
##
#---------------------------------------------------------------------
function get_dirnames() {
  local path_name
  while read path_name; do
    smgl_dirname "$path_name"
  done
}

#---------------------------------------------------------------------
## @param directory to check
## @Stderr error if the input is not a directory, but something else
## @return 0 on success
## @return >0 on failure
##
## Checks whether the passed directory exists and if not, tries to
## create it, first checking if it already exists as something else.
#---------------------------------------------------------------------
function ensure_dir() {
  if ! [[ -d $1 ]]; then
    if [[ -e $1 ]]; then
      message "${PROBLEM_COLOR}$1 exists, but is not a directory!" \
        "Please correct this and retry.${DEFAULT_COLOR}"
      return 1
    else
      mkdir -p "$1"
    fi
  fi
}

#---------------------------------------------------------------------
##  @param The function to call on each itteration and it's arguments
##  @param The separator string. If there is no $3, only the first char counts
##  @param The string to itterate over. (optional)
##  @Stdin is used when $3 isn't given.
##
##  $3 is optional, when it isn't used,
##  stdin is used and only the first letter in $2 is used. Note, using stdin
##  can have odd side effects when your function uses read and stdin itself.
##
## Special vars:
##  BREAK: Use this to break out of the loop prematurely, also causes a return of 1.
##
## If the function returns the value of the last return
## Notes:
##  In stdin mode, if the string does not terminate with the delimiter, the last
##  token will be ignored.
#----------------------------------------------------------------------
function iterate()
{ # $1=callback+args, $2=separator, $3=(opt)string
#  debug "libmisc" "iterate - $@"

  [[ $# -lt 2 ]] && return 2

  local oldIFS="$IFS"
  local token
  local func="$1"
  local returnValue=0
  if [[ $# -gt 2 ]] ; then
    IFS="$2"
    shift 2
    for token in $* ; do
      IFS="$oldIFS"

      eval "$func \"\$token\""
      [[ $BREAK ]] && break

    done
    IFS="$oldIFS"
  else
    while read -r -d "$2" token ; do
      eval "$func \"\$token\""
      [[ $BREAK ]] && break
    done
#   debug "leftover token: $token"
  fi

  returnValue=$?
  [[ $BREAK ]] && debug "libmisc" "iterate - I was BREAKed."
  unset BREAK
  return $returnValue
}

#---------------------------------------------------------------------
## @param return_var (must not be i, foo, temp, number, returnvar, stuff, msgstr or hashname)
## @param default choice
## @param elements, ..
##
## gives the user some nice select list and puts the selected
## item in return_var
##
#---------------------------------------------------------------------
function select_list()
{
    local i
    local foo temp number
    local returnvar=$1
    local _default=$2
    local stuff=()
    local _default_num=0

    shift 2
    hash_unset select_list_hash
    # see note in select_provider
    stuff=(0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)

    let i=0
    for foo in "$@"; do
        message "\t$DEFAULT_COLOR(${stuff[$i]})  $SPELL_COLOR$foo$DEFAULT_COLOR"
        hash_put select_list_hash "${stuff[$i]}" "$foo"
        [[ "$foo" ]] && [[ "$foo" == "$_default" ]] && _default_num=${stuff[$i]}
        let i++
    done

    local msgstr="\n${QUERY_COLOR}Which one do you want? [$_default_num]$DEFAULT_COLOR "
    select_list_sub "$returnvar" select_list_hash "$msgstr" "$_default_num"
    hash_unset select_list_hash
}

#---------------------------------------------------------------------
## Common code for select_list and select_provider
## The user should have already printed out the menu, this handles
## getting a valid answer from the user.
## @param name of return value
## @param name of hash table mapping answers to results
## @param message to print out for the query
## @param default answer
#---------------------------------------------------------------------
function select_list_sub() {
  local returnvar=$1
  local hashname=$2
  local msgstr=$3
  local _default=$4

  local _result _answer

  while [[ ! $_result ]] ; do
    message -n "$msgstr"
    read   -t  $PROMPT_DELAY  -n  1  _answer
    [[ "$_answer" ]] || _answer=$_default
    hash_get_ref $hashname $_answer _result
  done
  echo
  eval $returnvar=\"\$_result\"
}

#---------------------------------------------------------------------
## @param title
##
## sets the terminal title if TERM=xterm|rxvt or the window title if
## TERM=screen
##
#---------------------------------------------------------------------
function set_term_title()
{
    if [[ $SET_TERM_TITLE != on ]]; then return; fi
    case $TERM in
        xterm*|rxvt*)  echo -ne "\e]0;$@\007"
                       ;;
              screen)  echo -ne "\ek$@\e\\"
                       ;;
                   *) ;;
    esac
}


#---------------------------------------------------------------------
## @param return_var
## @param elements, ...
##
## Removes from the list string(s). Strings are kept to be unique and
## are separated by spaces
##
#---------------------------------------------------------------------
function real_list_remove () {
    local var=$1
    shift

    local i

    for i in $@; do
        eval "$var=\`echo \" \$$var \" | sed  \
               -e \"s/[[:space:]]\\+/ /g\"   \
               -e \"s/ $i / /g\"             \
               -e \"s/^ *//\"                \
               -e \"s/ *$//\"\`"
    done
}


#---------------------------------------------------------------------
## @param return_var
## @param elements, ...
##
## Puts in the list string(s). Strings are kept to be unique and are
## separated by spaces
##
#---------------------------------------------------------------------
function real_list_add () {
  local var=$1
  shift
  local i
  for i in $@; do
    local found=0
    function list_add_sub() {
      if [[ $i == $1 ]]; then
        found=1
      fi
    }
    iterate list_add_sub " " "$(eval "echo \$$var")"
    if [ $found -eq 0 ]; then
      eval "$var=\`echo \"\$$var $i\"\`"
      eval "$var=\"\${$var/# /}\""
    fi
  done
}


#---------------------------------------------------------------------
## @param string
## @param elements, ...
##
## return 0 at least one element is in list
## return 1 none of supplied elements is not in list
##
## Finds if at least one of the given elements is in the string. They
## can be delimited by spaces, tabs or newlines. The search elements
## must not contain any of these or they won't match. The matching is
## exact, regular expressions and globbing patterns are not supported.
##
## Warning, this function takes a real string, not a variable name as
## other list_* functions. Use the ${var[*]} form when passing arrays
##
#---------------------------------------------------------------------
function real_list_find () {
  local item token
  [[ "$1" ]] || return 1
  local input=$1
  shift

  for item in $input
  do
    for token in "$@"
    do
      [[ "$item" == "$token" ]] && return 0
    done
  done
  return 1
}

#---------------------------------------------------------------------
## @param spell
## @param variable to read
## @param variable name
##
## finds the persistent var for spell and sets variable name to the value
## of the variable to read
##
#---------------------------------------------------------------------
function real_persistent_read() {
  local exfile pfile tb_dir
  local varin=$2
  local varout=$3
  tablet_find_spell_dir $1 tb_dir  || return 2
  tablet_get_spell_file $tb_dir EXPORTS exfile || return 4
  tablet_get_persistent_config $tb_dir pfile || return 3
  if grep -q "^${varin}$" $exfile
  then
    eval "$3="'$( . "$pfile" && echo "${!varin}" )'
  else
    return 1
  fi &&
  return 0
}

#---------------------------------------------------------------------
## @param variables, ...
##
## Adds variable names to the list of persistent variables
##
#---------------------------------------------------------------------
function real_persistent_add () {
  if [[ $PROTECT_SORCERY ]] ; then
    for each in "$@" ; do
      if is_sorcery_var $each; then
        # complain, but not if we're persistent loading, bug 9416
        if ! [[ $PERSISTENT_LOADING ]] ; then
          complain_sorcery_var $each
        fi
      else
        list_add PERSISTENT_VARIABLES $each
      fi
    done
  else
    list_add PERSISTENT_VARIABLES "$@"
  fi
}


#---------------------------------------------------------------------
## @param variables, ...
##
## Removes variable names from the list of persistent variables
##
#---------------------------------------------------------------------
function real_persistent_remove () {
    list_remove PERSISTENT_VARIABLES "$@"
}


#---------------------------------------------------------------------
## Loads persistent variables stored in file "$SPELL_CONFIG"
##
#---------------------------------------------------------------------
function real_persistent_load () {
    local PERSISTENT_LOADING=yes
    local PERSISTENT_FILE=${1:-$SPELL_CONFIG.p}
    lock_file "$PERSISTENT_FILE"
    if [[ -f $PERSISTENT_FILE ]]; then
        . "$PERSISTENT_FILE"
        local line
        persistent_add $(while read line; do echo ${line%%=*}; done < "$PERSISTENT_FILE")
    fi
    unlock_file "$PERSISTENT_FILE"
}


#---------------------------------------------------------------------
## Saves variables marked as persistent to file "$SPELL_CONFIG". The
## File is completely overwritten. Also unsets all persistent
## variables
##
#---------------------------------------------------------------------
function real_persistent_save () {
    local VAR
    local TMP
    local file=$SPELL_CONFIG.p
    local tfile
    lock_start_transaction "$file" tfile
    > "$tfile"
    for VAR in $PERSISTENT_VARIABLES; do
        config_get_option $VAR TMP
        echo "$VAR=\"$TMP\"" >> "$tfile"
        unset "$VAR"
    done
    lock_commit_transaction "$file"
    unset PERSISTENT_VARIABLES
}


#---------------------------------------------------------------------
## Unsets all persistent variables. Mainly usable as replacement of
## persistent_save for functions which can be called by nonroot users
## ( for example from 'gaze what' )
##
#---------------------------------------------------------------------
function real_persistent_clear () {
    local VAR
    for VAR in $PERSISTENT_VARIABLES; do
        unset "$VAR"
    done
    unset PERSISTENT_VARIABLES
}



#---------------------------------------------------------------------
## @param config file variable
## @param return variable (optional)
##
## @return 1 option is not present in the config
## @return 0 option is present in the config (even if the option is empty string "") and it's set.
##
## Retrieves setting from $SPELL_CONFIG file and optionally sets user
## supplied variable. Function is here to make possible changes to
## config system easy, since all other functions are using this
## function and are not working with variables directly
##
#---------------------------------------------------------------------
function config_get_option () {
  if [[ $PROTECT_SORCERY ]] ; then
    if is_sorcery_var $1; then
      complain_sorcery_var $1
      return 1
    fi
  fi

  # afrayedknot hates variable leakage
  if real_list_find $1 $PERSISTENT_VARIABLES; then
    # variable is known and valid
    [[ -n $2 ]] && eval "$2=\"\$$1\""
    return 0
  else
    # variable is not known and is invalid
    eval "unset \"$1\""
    return 1
  fi
}


#---------------------------------------------------------------------
## @param Name of variable
## @param return variable
##
## @return 0 if the variable could be found, 1 if no old persistent file exists.
##
## Get value of a persistent variable from the previous cast when
## there is a -r. The persistent data moves to a seperate directory
## when cast -r is run, this makes the new persistent config clean
## but allows us to provide the user defaults from the last cast.
#---------------------------------------------------------------------
function config_get_last_option() {
  if [[ $PROTECT_SORCERY ]] ; then
    if is_sorcery_var $1; then
      complain_sorcery_var $1
      return 1
    fi
  fi

  local pfile
  if test -f $ABANDONED_PERSIST/$SPELL.p; then
    pfile=$ABANDONED_PERSIST/$SPELL.p
  # leave this out for now until theres a way to disable it
  # having it enabled will make it difficult to get back to the true defaults
  #else
  #  local tb_dir
  #  tablet_find_spell_dir $SPELL tb_dir &&
  #  tablet_get_persistent_config $tb_dir pfile || return 1
  fi

  local foo
  if [[ $pfile ]] ; then
    foo=$(
      persistent_load $pfile
      if real_list_find $1 $PERSISTENT_VARIABLES; then
        eval "echo \"\$$1\""
      fi
    )
  else
    return 1
  fi

  eval "$2=\"\$foo\""
  return 0
}


#---------------------------------------------------------------------
## @param config file variable
## @param value
##
## Stores string to given variable and makes the variable persistent
## Function is here to make possible changes to config system easy,
## since all other functions are using this function and are not
## working with variables directly
##
#---------------------------------------------------------------------
function config_set_option () {
    persistent_add $1
    eval "$1=\"$2\""
}


#---------------------------------------------------------------------
## @param config file variable
## @param question
## @param default answer
##
## @return 0 in all cases
##
## Asks user for string, with default answer and timeout (like query)
## Return variable is also marked as persistent
##
#---------------------------------------------------------------------
function real_config_query () {
    local ANSWER
    if config_get_option "$1" ANSWER; then
        # option allready ANSWERed in config
        message "[[ ${QUERY_COLOR}$2${DEFAULT_COLOR} -> ${QUERY_COLOR}$ANSWER${DEFAULT_COLOR} ]]"

        # already have a reasonable value...
        [ "$ANSWER" == "y" ] || [ "$ANSWER" == "n" ] && return 0
    fi

    local default
    config_get_last_option "$1" default
    [[ $default ]] || default="$3"

    if query "$2" "$default"; then
        config_set_option "$1" y
    else
        config_set_option "$1" n
    fi
    return 0
}

#---------------------------------------------------------------------
## @param config file variable
## @param question
## @param default answer [y|n]
## @param option_yes - can't be empty string
## @param option_no - can't be empty string
##
## @return 0 in all cases
##
## Asks user for string, with default answer and timeout (like query)
## The string is added to the variable
## If you want to use empty string, place there dummy string and remove
## it later by list_remove function. Also for one config variable, all
## option_yes and option_no have to be different.
##
## Return variable is also marked as persistent
#---------------------------------------------------------------------
function real_config_query_option () {
    local ANSWER key

    # If the option exists
    # If the option contains option_yes or option_no
    if config_get_option "$1" ANSWER && list_find "$ANSWER" $4 $5; then
        # Then option allready ANSWERed in config

        # Find out if the option was 'y' or 'n'
        list_find "$ANSWER" $4 && key=y
        list_find "$ANSWER" $5 && key=n

        if [[ "$key" ]]; then
            message "[[ ${QUERY_COLOR}$2${DEFAULT_COLOR} -> ${QUERY_COLOR}$key${DEFAULT_COLOR} ]]"
            return 0
        fi
    fi

    local last_answer default=$3
    config_get_last_option "$1" last_answer
    # Find out if the option was 'y' or 'n'
    list_find "$last_answer" $4 && default=y
    list_find "$last_answer" $5 && default=n

    if query "$2" "$default"; then
        list_add ANSWER $4
    else
        list_add ANSWER $5
    fi

    config_set_option "$1" "$ANSWER"
    return 0
}


#---------------------------------------------------------------------
## @param config file variable, return variable
## @param question
## @param default answer
##
## @return 0 in all cases
##
## Asks user for string, with default answer and timeout (like query)
## Return variable is also marked as persistent
##
#---------------------------------------------------------------------
function real_config_query_string () {
    local ANSWER

    if config_get_option "$1" ANSWER; then
        # option already answered in config
        message "[[ ${QUERY_COLOR}$2${DEFAULT_COLOR} -> '${QUERY_COLOR}$ANSWER${DEFAULT_COLOR}' ]]"
    else
        local default
        config_get_last_option "$1" default
        [[ $default ]] || default="$3"
        query_string ANSWER "$2" "$default"
        config_set_option "$1" "$ANSWER"
    fi
    return 0
}


#---------------------------------------------------------------------
## @param config file variable, return variable
## @param question
## @param elements, ...
##
## @return 0 in all cases
##
## Asks user for string, with numbered possibilities listed
## Return variable is also marked as persistent
##
#---------------------------------------------------------------------
function real_config_query_list () {
  local ANSWER
  local VARIABLE="$1"
  local QUESTION="$2"
  shift
  shift

  if config_get_option "$VARIABLE" ANSWER; then
    # option already ANSWERed in config
    (
        for foo in "$@"; do
            [ "$foo" == "$ANSWER" ] && exit 0
        done
        echo "!!!! WARNING !!!!"
        echo "!!!! stored option '$ANSWER' in config is not in list of provided options !!!!"
        echo "!!!! WARNING !!!!"
    )
    message "[[ ${QUERY_COLOR}${QUESTION}${DEFAULT_COLOR} -> '${QUERY_COLOR}$ANSWER${DEFAULT_COLOR}' ]]"
  else
    # if there was an answer before, find it
    local default default_num=0 foo
    config_get_last_option "$VARIABLE" default

    # we have to ask the user
    message "$QUESTION"
    select_list ANSWER "$default" "$@"
    config_set_option "$VARIABLE" "$ANSWER"
  fi

  eval $VARIABLE=\"$ANSWER\"
  return 0
}

#---------------------------------------------------------------------
## Output a list of source numbers associated with the current spell.
## This is the number portion of SOURCE[[:digit:]], eg '', "2", "3", etc.
## A prefix may be given and it will be prepended to each result value.
#---------------------------------------------------------------------
function real_get_source_nums() {
  local foo
  compgen -v SOURCE |
  grep '^SOURCE[[:digit:]]*$'|sort -u|
  while read foo; do echo "${1}${foo/SOURCE/}"; done
}

function get_spell_files() {
  for src in $(real_get_source_nums SOURCE); do
    echo ${!src}
  done
}


#---------------------------------------------------------------------
## misc_is_function <function name>
## @param function name
## @return 0 if argument is a valid function
## @return 1 otherwise
## Returns true if input argument is the name of an
## existing function, false otherwise.
##
#---------------------------------------------------------------------
function misc_is_function()  {
  local  FUNCTION_NAME=$1
  [[ "$(type -t $FUNCTION_NAME)" == "function" ]]
}


#---------------------------------------------------------------------
## Remove files listed and any directories which become empty after
## subsequent file removal.
##
## @param Filename to read as input, a pipe may not be used.
## @param Base directory to remove directories up to (usually $INSTALL_ROOT)
#---------------------------------------------------------------------
function remove_files_and_dirs() {
    cat $1 | while read file; do
      if [[ ! -d $file ]]; then
        rm -f $file
      else
        # also try to remove empty dirs, as
        # the leaves are not taken care of by the next block #15804
        rmdir $file &>/dev/null
      fi
    done

    # remove possibly empty directories, rmdir WILL have error output
    # because some directories wont be empty for one of many reasons,
    # this is OKAY
    cat $1|get_dirnames|sort -u|while read dir; do
      until [[ $dir == ${2:-/} ]] ; do
        rmdir $dir &>/dev/null || break
        smgl_dirname "$dir" dir
      done
    done
}

#---------------------------------------------------------------------
## Safely creates $TMP_DIR and exports the variable so we can use it
## even in subprocesses called through make.
## @param name of the script needing the tmp dir
#---------------------------------------------------------------------
function mk_tmp_dirs() {
  debug "$FUNCNAME" "Making tmp dirs for $$"
  local SCRIPT_NAME=$1 STATS tmp
  SCRIPT_NAME=${SCRIPT_NAME:-misc}

  local BASE_DIR=${2:-/tmp/sorcery}
  local SCRIPT_DIR=$BASE_DIR/$SCRIPT_NAME
  local FULL_DIR=$SCRIPT_DIR/$$



  tmp=$(ls -lnd $BASE_DIR 2>/dev/null|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:0:0" ]] ; then
    test -d $BASE_DIR &&
    message "$BASE_DIR has unknown permissions and ownership, replacing it" >&2
    rm -rf $BASE_DIR
    install -d -o root -g root -m 755 "$BASE_DIR" &&
    chmod a-s "$BASE_DIR" # work around installer bug where /tmp is suid/sgid
  fi || mk_tmp_dir_error $BASE_DIR

  # double check
  tmp=$(ls -lnd $BASE_DIR 2>/dev/null|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:0:0" ]] ; then
    mk_tmp_dir_error $BASE_DIR
  fi


  tmp=$(ls -lnd $SCRIPT_DIR 2>/dev/null|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:0:0" ]] ; then
    rm -rf $SCRIPT_DIR
    install -d -o root -g root -m 755 "$SCRIPT_DIR"
  fi || mk_tmp_dir_error $SCRIPT_DIR

  # double check
  tmp=$(ls -lnd $SCRIPT_DIR|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:0:0" ]] ; then
    mk_tmp_dir_error $SCRIPT_DIR
  fi


  if [[ -e $FULL_DIR ]]; then
    message "Looks like you had an old $SCRIPT_NAME on PID $$. Cleaning it out..." >&2
    rm -rf $FULL_DIR
  fi

  install -d -o root -g root -m 755 "$FULL_DIR" || mk_tmp_dir_error $FULL_DIR
  tmp=$(ls -lnd $SCRIPT_DIR|awk '{printf "%s:%s:%s\n" ,$1,$3,$4; }')
  if [[ "$tmp" != "drwxr-xr-x:0:0" ]] ; then
    mk_tmp_dir_error $FULL_DIR
  fi

  # in order for TMP_DIR to make it through make and into pass_three/four
  # we must export
  export TMP_DIR=$FULL_DIR
}

# emergency exit function if we fail to make a TMP_DIR
function mk_tmp_dir_error() {
    message "Failed to make temporary dir, this" \
            "might be due to a security attack,\nplease" \
            "check the permissions of $1. Bailing out..." 2>/dev/null
    exit 1
}

#---------------------------------------------------------------------
##
## 'which' is not in basesystem, heres our own simple version of it
## it should be just as fast as the real one.
##
## @param executable to look for
## @param variable to return path in (pass by reference)
##
## Marches through $PATH looking for the executable specified
##
#---------------------------------------------------------------------
function smgl_which() {
  local target=$1 location
  local BREAK
  function smgl_which_sub() {
    if test -f "$1/$target" -a -x "$1/$target" ; then
      echo "$1/$target"
      BREAK=yes
    fi
  }
  if [ -z "$1" -o -z "$2" ]; then
    echo "smgl_which: Not enough arguments"
    return 1
  fi
  location=$(iterate smgl_which_sub : "$PATH")
  if [[ "$location" ]]  ; then
    eval "$2=$location"
  else
    echo "which: no $target in ($PATH)"
    return 1
  fi
}

#---------------------------------------------------------------------
##
## Finds the make command and complains if its missing (shouldnt ever
## happen)
##
## @param variable to return path in (pass by reference)
##
#---------------------------------------------------------------------
function find_make() {
  local ___REAL_MAKE
  smgl_which make ___REAL_MAKE
  rc=$?
  if [[ $rc != 0 ]] ; then
    message "Cannot find make command!!"
    message "Bailing out"
    return $rc
  fi
  eval "$1=$___REAL_MAKE"
}


#---------------------------------------------------------------------
## @param tmp_dir, should begin with /tmp/sorcery
#---------------------------------------------------------------------
function cleanup_tmp_dir() {
  local TMP_DIR=$1
  if  [  ${TMP_DIR:0:13} ==  "/tmp/sorcery/"  ];  then
    rm -rf $TMP_DIR
  else
    message "Cowardly refusing to remove $TMP_DIR, this may be a bug," \
            "please alert the sorcery team."
  fi
}

#---------------------------------------------------------------------
## @param Variable to check if its a known sorcery variable
## @return Success if its a sorcery variable, false if not
##
## This function will probably need to be updated as sorcery variables
## come in and out of flux, the variables listed are roughly defined as
## things defined at the point in which a spell file is run, and when it
## is not run in a seperate subshell, note that the build phase of cast
## is seperate from the frontend, so a spell could technically modify
## something like "SPELLS" and not have a major problem. However the
## philosophy is to not discriminate about what sorcery variables
## are technically okay to use in what files and ones that arent,
## instead we'll treat all usages of sorcery variables equally.  The rules
## are of course subject to change without warning and its just easier
## to be consistent, also theres a good chance that if a variable is safe
## in some circumstances and not others, and one uses it where its safe,
## someone will forget and start using it in places that arent safe...
#---------------------------------------------------------------------
function is_sorcery_var() {
  local vars="TOP_LEVEL DISPLAY PATH TMP_DIR SAFE_CAST \
  FAILED_LIST SUCCESS_LIST SPELL SPELLS spells MAKEFILE DEPS_ONLY \
  CAST_PASS download_log IW_LOG OPTS SOLO QUIET INSTALL_QUEUE \
  OVERRIDE_CFLAGS OVERRIDE_CXXFLAGS OVERRIDE_LDFLAGS NO_OPTIMIZATION_FLAGS \
  DOT_PROGRESS VOYEUR_OVERRIDE RECONFIGURE RECAST_DOWN COMPILE RECAST_UP \
  FORCE_DOWNLOAD SOURCE_CACHE SILENT FIX DEBUG SEPARATE \
  CAST_HASH BACK_CAST_HASH CANNOT_CAST_HASH uncommitted_hash NEW_DEPENDS \
  spell_depends DEPENDS_CONFIG UP_DEPENDS SPELL_CONFIG GRIMOIRE_DIR \
  SCRIPT_DIRECTORY SECTION_DIRECTORY GRIMOIRE SPELL_DIRECTORY SECTION \
  BUILD_API VOYEUR_STDOUT VOYEUR_STDERR C_LOG C_FIFO INSTALL_ROOT HOST \
  BUILD INST_LOG MD5_LOG INSTALLWATCHFILE INSTW_LOGFILE CAST_EXIT_STATUS"

  # the \\< \\> matches word boundaries
  real_list_find "$vars" "$1"
}


#---------------------------------------------------------------------
## @param Variable name used in complaint
## @stdout Complain vehemently that a variable name is used by sorcery and
## @stdout the user should file a bug because a spell is using the variable
#---------------------------------------------------------------------
function complain_sorcery_var() {
  message "${PROBLEM_COLOR}WARNING: ATTEMPTING TO USE" \
          "${DEFAULT_COLOR}${SPELL_COLOR}\"$1\"" \
           "${DEFAULT_COLOR}${PROBLEM_COLOR}AS A SPELL VARIABLE."
  message "THIS VARIABLE IS A USED BY SORCERY, THIS IS PROBABLY" \
          "A SPELL BUG\nAND SORCERY MAY BEHAVE IN UNDEFINED WAYS IF" \
          "YOU CONTINUE."
  message "\n\nPLEASE REPORT A BUG IMMEDIATELY!!${DEFAULT_COLOR}\n\n"
}

#-------------------------------------------------------------------
## @param (optional) Architecture to use
## Sets the SPECFILE glocal variable and the SMGL_COMPAT_ARCHS global array
## SPECFILE contains the compiler and other arch specifications
## SMGL_COMPAT_ARCHS is an array that holds architectures which are compatible
## with the desired architecture. The desired architecture is determined as
## follows:
## <pre>
## 1) If function is gien an argument, the argument is used, or
## 2) If cross-install is on, the TARGET architecture is used, or
## 3) The local ARCHITECTURE is used
## </pre>
## The least specific arch is in SMGL_COMPAT_ARCHS[0],
## SMGL_COMPAT_ARCHS[1] is more specific, et cetera. For example:
## desired architecture="athlon-xp" might result in:
## SPECFILE=/usr/share/archspecs/ia32/amd/athlon-xp
## SMGL_COMPAT_ARCHS=("ia32" "amd" "athlon-xp")
##
## ARCHITECTURE is also modified to be an array, the reverse of
## SMGL_COMPAT_ARCHS. The result is an array from most specific arch to least
## specific. $ARCHITECTURE does not change meaning since $A == ${A[0]}.
#-------------------------------------------------------------------
function set_architecture() {

  $STD_DEBUG
  local specdir
  local i j
  unset SPECFILE

  # If given an argument, treat as the architecture to use
  local arch=${1}

  # If no arch is specified, see if this is a cross-install, if so, set arch to
  # the target arch
  if  [[ ! $arch ]] &&
      [[ $CROSS_INSTALL == on ]] &&
      [[ $TARGET ]]
  then
    debug "libmisc" "set_architecture: using cross-install's target arch"
    arch=${arch:-$TARGET}
  fi

  # If no arch given and this isn't a cross-install, then default to the ARCHITECTURE var
  if ! [[ $arch ]] ; then
    arch=$ARCHITECTURE
  fi

  # Find the specfile to use
  local find_compat=0
  find --version|grep -q 'version 4\.1\(\.\|$\)' && find_compat=1
  for specdir in ${ARCH_SPECS[@]} ; do
    if [[ $find_compat == 1 ]] ; then
      # older find still exists run in slower compatibility mode
      SPECFILE=$(find ${specdir} -perm -400 -type f -name "$arch" -print 2>/dev/null)
    else
      # -L so symlinks work, -type f so it won't match dirs, -quit so it
      # stops searching when it finds the answer
      SPECFILE=$(find -L ${specdir} -perm -400 -type f -name "$arch" -print -quit 2>/dev/null)
    fi
    [ $SPECFILE ] && break
  done
  if [[ ! $SPECFILE ]] ; then
    message "${PROBLEM_COLOR}Cannot find arch spec for $arch!"
    message "Reverting to null!"
    message "Please run sorcery afterwards and pick another architecture!$DEFAULT_COLOR"
    echo
    sleep 2
    SPECFILE=$(find -L ${specdir} -perm -400 -type f -name "null" -print -quit 2>/dev/null)
  fi
  debug "libmisc" "set_architecture: SPECFILE=$SPECFILE"

  # turn the path into an array, but remove $specdir from the start first
  unset SMGL_COMPAT_ARCHS
  explode "${SPECFILE#$specdir/}" '/' SMGL_COMPAT_ARCHS

    unset ARCHITECTURE
    # Reverse the array so that the most specific arch is first
    j=0
    for(( i=${#SMGL_COMPAT_ARCHS[@]}-1; i>=0; i--)) ; do
      ARCHITECTURE[j++]=${SMGL_COMPAT_ARCHS[i]}
    done

    source "$SPECFILE"
}

#---------------------------------------------------------------------
## @param Function body
## @param Function name
## @param (optional) Function name
## @param ...
## Creates functions with identical bodies. It is useful if you need
## to override a bunch of functions which have already been defined.
#---------------------------------------------------------------------
function define_functions() {
  local funcName
  local funcContent="$1"
  shift
  for funcName in $* ; do
    debug "libmisc" "define_functions - redefining $funcName"
    eval "function $funcName () { \
      $funcContent \
    }"
  done
}

#---------------------------------------------------------------------
## @param target
## @param value
## Set variable by name, useful for setting variables that were passed
## by reference.
## Example:
## <pre>
## function uber() {
##   local x
##  unter x 5
##  echo $x
## }
## function unter() {
##   local var=$1 y=$2
##   read -p "enter value (default $2)" y
##   upvar $var $y
## }
## </pre>
#---------------------------------------------------------------------
function upvar() {
  # This makes eval see things as name=$2,
  # which makes leading/trailing whitespace and special chars magically work.
  eval "$1=\$2"

}

#---------------------------------------------------------------------
## Like uniq, but doesnt require a sorted list, implemented in awk.
#---------------------------------------------------------------------
function awkuniq() {
  awk '{ if ( seen[$0] != 1) { seen[$0]=1;print $0;}}'
}

#---------------------------------------------------------------------
## Dirname written in bash, optionally uses an upvar (making it forkless).
#---------------------------------------------------------------------
function smgl_dirname() {
  local __dir=() __dirname i __last_dir

  # Pull of any trailing /'s, there could be more than/one///
  __dirname=$1
  while [[ $__dirname != "$__last_dir" ]]; do
    __last_dir=$__dirname
    __dirname=${__dirname%/}
  done
  if [[ -z $__dirname ]]; then
    __dirname=/
  fi

  explode "$__dirname" / __dir
  [[ ${__dirname:0:1} == / ]] && unset __dirname || __dirname="."
  for (( i=0; i < ${#__dir[@]}-1; i++ )); do
    __dirname="$__dirname/${__dir[$i]}"
  done

  # Pull of any trailing /'s
  while [[ $__dirname != "$__last_dir" ]]; do
    __last_dir=$__dirname
    __dirname=${__dirname%/}
  done
  if [[ -z $__dirname ]]; then
    __dirname=/
  else
    if [[ ${__dirname:0:2} == // ]]; then
      __dirname="${__dirname:1}"
    elif [[ ${__dirname:0:2} == ./ ]]; then
      # usually not strictly necessary, but let's be conformant with dirname
      __dirname="${__dirname:2}"
    fi
  fi

  if [[ "$2" ]] ; then
    upvar "$2" "$__dirname"
  else
    echo "$__dirname"
  fi
}

#---------------------------------------------------------------------
## basename written in bash, optionally uses upvar (making it forkless).
## Does not have ability to remove file extension like the real basename.
#---------------------------------------------------------------------
function smgl_basename() {
  local __base=$1
  local __last_base

  # trim off leading /'s (foo/bar//// -> foo/bar)
  while [[ "$__base" != "$__last_base" ]] ; do
    __last_base=$__base
    __base=${__base%/}
  done

  # if there are no more /'s then this does nothing which is what we want
  __base=${__base##*/}

  [[ -z $__base ]] && __base=/

  if [[ "$2" ]] ; then
    upvar "$2" "$__base"
  else
    echo "$__base"
  fi
}

#--------------------------------------------------------------------
## @param new path to add
## @param old path
## @Stdout new path
##
## TODO: remove grimoire functions libgcc gcc_prepend_path and use
## this one
#--------------------------------------------------------------------
function envar_prepend_path()
{
  if test -z $2
  then
    echo $1
  else
    echo $1:$2
  fi
}

#---------------------------------------------------------------------
## Appends the input to the notice log, prepending the spell name as a
## title, appending a newline and teeing the input back unchanged.
#---------------------------------------------------------------------
function append_to_notice_log() {
  local notice_log=$TMP_DIR/notice_log
  local tmp_log=$TMP_DIR/tmp_notice_log

  tee $tmp_log
  if [[ -s $tmp_log ]]; then
    # use echo -e instead of message if we ever bring SILENT back
    echo -e "$SPELL_COLOR$SPELL:$DEFAULT_COLOR" >> $notice_log
    cat $tmp_log >> $notice_log
    echo >> $notice_log
  fi
  rm -f $tmp_log
}

#---------------------------------------------------------------------
## Appends the failure reason (input) to a log for use in displaying
## the final FAILED_LIST and the activity log
##
## @param reason
#---------------------------------------------------------------------
function log_failure_reason() {
  [[ -z $CAST_BACKUPDIR ]] && return 1

  # delve has a different TMP_DIR, so we can't use it
  local failure_reason_log=$CAST_BACKUPDIR/failure_reason_log
  local frl
  local spell=${2:-$SPELL}

  grep -sq "^$spell ($1)$" $failure_reason_log && return 0

  lock_start_transaction "$failure_reason_log" frl
  echo "$spell ($1)" >> "$frl"
  lock_commit_transaction "$failure_reason_log"
}

#---------------------------------------------------------------------
## Checks if there is an entry for the spell in the failure_reason_log
## If there is one, set the upvar to the reason
##
## @param upvar
#---------------------------------------------------------------------
function get_failure_reason() {
  local failure_reason_log=$CAST_BACKUPDIR/failure_reason_log
  local _upvar=$1
  local _reason

  # if a spell has more than one entry, the first will be used
  lock_file $failure_reason_log
  _reason=$(grep -s -m1 "^$SPELL " $failure_reason_log | sed 's,^[^(]*(\([^)]*\)).*$,\1,')
  unlock_file $failure_reason_log

  upvar $_upvar "$_reason"
}

#---------------------------------------------------------------------
## Checks if the file is compressed and displays it
##
## @param file
## @param use dialog (optional)
## @param message to print on error (optional)
#---------------------------------------------------------------------
function show_file() {
  local file=$1
  local dialog=${2:-yes}
  local msg=${3:-"File not found."}
  if [[ -f $file && -s $file ]]; then
    case "$(file -b "$file")" in
      *text) if [[ $dialog == "yes" ]]; then
                eval $DIALOG '--textbox  $1  0  0'
              else
                cat "$file" | $PAGER
              fi ;;
      bzip2*) bzcat "$file" | $PAGER ;;
      gzip*) gzip -cd "$file" | $PAGER ;;
      xz*|XZ*|LZMA*) xz -cd "$file" | $PAGER ;;
      7-zip*) 7z -d "$file" | $PAGER ;;
          *) message "Unknown file type."
              return 1 ;;
    esac
  else
    if [[ $dialog == yes ]]; then
      eval $DIALOG '--msgbox  "'$msg'"  0  0'
    else
      message "$msg" 1>&2
    fi
    return 1
  fi
}

# Standard debug line:
# file "function@line" "all" "args"
STD_DEBUG='eval local _stddbg_file=${BASH_SOURCE[0]} ;
  _stddbg_file=${_stddbg_file##*/};
  debug "${_stddbg_file}" "${FUNCNAME[0]}@$LINENO" "$@"'


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