#!/bin/bash

#---------------------------------------------------------------------
## @param string to explode
## @param delimiter
## @param name of array to put it in
##
## @Description
## 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 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.  Returns true or false based on the answer given.
##
#---------------------------------------------------------------------
function real_query()  { (

  debug  "libmisc" "Running query() with the following arguments: '$1' and '$2'"
  
  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

) }


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

}

#---------------------------------------------------------------------
## @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}"
  if [[ ${!debugVar} != "no" ]] ; then
    echo -n "$1($$): " >>$DEBUG
    shift
    echo $* >>$DEBUG
  fi

  true
}

#---------------------------------------------------------------------
## @Stdout progress spinner
## Displays progress spinner like the one in fsck.
##
#---------------------------------------------------------------------
function progress_spinner()  {

  ((  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"
    len="${len:-$COLUMNS}"
  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
    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
## @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()
{

  local OUT
  if [[ $* == .* ]] ; then
    OUT="$1"
  else
    OUT=`echo "$@" | sed -e 's:\.:\\\.:' \
                         -e 's:\*:\\\*:'`
  fi
  echo "$OUT"

}

#---------------------------------------------------------------------
## @param Yes, there is a parameter.. 
## @Stdout wheee, even output.
## Dunno what that one does..
#---------------------------------------------------------------------
function parent_PIDs()
{
  
  local pid ppid
  
  pid="$1"
  if [[ $1 ]] ; then
    pid=$1
  else
    pid="$$"
    echo $$
  fi
  
  if [[ $pid == 0 ]] || ! [ -e /proc/$pid/status ] ; then return 0; fi
  
  ppid=`grep "^PPid:" /proc/$pid/status | cut -f 2`
  echo $pid
  parent_PIDs $ppid

}


#---------------------------------------------------------------------
## @Stdin directories.
## @Stdout directories' basenames
## Takes newline separated list of directories
## and outputs their base names.
##
#---------------------------------------------------------------------
function get_basenames() {
debug "DB" "basename - $*"
  local DIRECTORY=''
  while read DIRECTORY; do
    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
    dirname "$PATH_NAME"
  done
}

#---------------------------------------------------------------------
##  @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.
#3
## 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 3 ] && 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
    echo "leftover token: $token" >> $DEBUG
  fi
  
  returnValue=$?
  [[ $BREAK ]] && debug "libmisc" "iterate - I was BREAKed."
  unset BREAK
  return $returnValue
}

#---------------------------------------------------------------------
## not yet implemented
#---------------------------------------------------------------------
function cache_env() 
{

	debug "libmisc" "Storing environment for $1 in $2"
	echo "function ${1}_env()" >> $2
	echo "{" >> $2

	local exp_vars=`export | sed 's/declare -x //' | cut -d= -f1`
	local set_vars=`set | \
		awk 'BEGIN {funcs=0;} /^[^=]*[[:blank:]]/{funcs=1;} {if(funcs==0) print $0;}' | cut -d= -f1`
	local special_vars=`echo -e "BASH\nBASH_VERSINFO\nBASH_VERSION\nCOLUMNS\nDIRSTACK\nEUID
FUNCNAME\nGROUPS\nHISTFILE\nHISTFILESIZE\nHISTSIZE\nHOSTNAME\nHOSTTYPE\nIFS\nLINES\nMACHTYPE
MAILCHECK\nOPTERR\nOPTIND\nOSTYPE\nPATH1\nPATH2\nPATH3\nPATH4\nPIPESTATUS\nPPID\nPS2
PS4\nSHELLOPTS\nUID\n_"`
	local vars_to_cache=`{ echo "$exp_vars" ; echo "$special_vars" ; echo "$set_vars" ; echo "$set_vars" ; echo "$set_vars"; } | \
		sort | uniq -c | awk '$1=3{print $2;}' | egrep -v '^([[:punct:]]*|.*COLOR.*)$'`
	
	for i in $vars_to_cache ; do
		echo ${i}"='${!i}'" >> $2
	done
	
	echo "}" >> $2
	less $2
	echo "Check it."
	read
	
}
#---------------------------------------------------------------------
## not yet implemented
#---------------------------------------------------------------------
function restore_env()
{
	debug "libmisc" "Restoring env for $1 from $2"
	. $2
	${1}_env 2>/dev/null
	
}

#---------------------------------------------------------------------
## @param return_var 
## @param elements, ..
## 
## gives the user some nice select list and puts the selected
## item in return_var
##
#---------------------------------------------------------------------
function select_list()
{
    local i
    local number foo temp
    local returnvar=$1
    shift
    let i=0
    for foo in "$@"; do
        message "\t$DEFAULT_COLOR($i)  $SPELL_COLOR$foo$DEFAULT_COLOR"
        temp[$i]="$foo"
        let i++
    done

    message -n "\n${QUERY_COLOR}Which one do you want? [0]$DEFAULT_COLOR "
    read   -t  $PROMPT_DELAY  -n  1  number
    while ! [[ ${temp[$number]} ]]; do
        message -n "\n${QUERY_COLOR}Which one do you want? [0]$DEFAULT_COLOR "
        read number
    done

    echo

    eval $returnvar=\"${temp[$number]}\"
}

#---------------------------------------------------------------------
## @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\"             \
               -r -e \"s/^ (.+) $/\\1/\"\`"
    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
        eval "$var=\`echo \" \$$var \" | sed \
               -e \"s/[[:space:]]\\+/ /g\"   \
               -e \"s/ $i / /g\"             \
               -e \"s/^ //\"\`\"$i\""
    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 given elements is in the string. Warning,
## this function takes real string, not variable name as other list_*
## functions
##
#---------------------------------------------------------------------
function real_list_find () {
    local i
    local input="$1"
    shift

    for i in $@; do
        eval "echo \" $input \" | grep -qE -e \"[[:space:]]$i[[:space:]]\" && return 0"
    done
    return 1
}


#---------------------------------------------------------------------
## @param variables, ...
##
## Adds variable names to the list of persistent variables
##
#---------------------------------------------------------------------
function real_persistent_add () {
    list_add PERSISTENT_VARIABLES $@
}


#---------------------------------------------------------------------
## @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 () {
    if [ -f "$SPELL_CONFIG.p" ]; then
        . "$SPELL_CONFIG.p"
        persistent_add `awk 'BEGIN{FS="="}{print $1}' < "$SPELL_CONFIG.p"`
    fi
}


#---------------------------------------------------------------------
## 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
    > "$SPELL_CONFIG.p"
    for VAR in $PERSISTENT_VARIABLES; do
        config_get_option $VAR TMP
        echo "$VAR=\"$TMP\"" >> "$SPELL_CONFIG.p"
        unset "$VAR"
    done
    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 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 DEFAULT=""
    [ -n "$3" ] && DEFAULT=" [$3] "
    
    if [ -z "$SILENT" ]; then
        echo -e -n "${QUERY_COLOR}$2${DEFAULT}${DEFAULT_COLOR}"
        read -t $PROMPT_DELAY RESPONSE
    fi
  
    [ -z "$RESPONSE" ] && RETURN=1 && RESPONSE="$3"
    
    eval $1=\"${RESPONSE}\"
    return $RETURN
}


#---------------------------------------------------------------------
## @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 "")
##
## 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 {
#    local VAR=$2
#    local REPLY=`grep -E "^$1=" $SPELL_CONFIG | head -n 1 | sed "s/^[^=]*=//"`
#    [ -n "$VAR" ] && eval $VAR=\"$REPLY\"
#    [ -z "$REPLY" ] && return 1
#    return 0
#}
function config_get_option () {
    eval "
         [ -n \"$2\" ] && $2=\"\$$1\"

         # If variable has some value, return 0
         [ -n \"\$$1\" ] && return 0

         # Variable don't have value, and we don't have config file return 1
         test -e \"$SPELL_CONFIG.p\" || return 1

         # If the variable name is in config file, it means that we have this variable, and it's empty
         grep -qE \"^$1=\" \"$SPELL_CONFIG.p\" && return 0

         # We dont' have this variable
         return 1
     "
}


#---------------------------------------------------------------------
## @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 {
#    echo "$1=$2" >> $SPELL_CONFIG
#}
function config_set_option () {
    persistent_add $1
    eval "$1=\"$2\""
}


#---------------------------------------------------------------------
## @param config file variable
## @param question
## @param default answer
##
## @return 0 for positive answer (y)
## @return 1 for negative answer (n)
##
## 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 [ -z "$RECONFIGURE" ] && config_get_option "$1" ANSWER; then
        # option allready ANSWERed in config
        echo -e "[[ ${QUERY_COLOR}$2${DEFAULT} -> ${QUERY_COLOR}$ANSWER${DEFAULT} ]]"
  
        [ "$ANSWER" == "y" ] && return 0
        [ "$ANSWER" == "n" ] && return 1
        echo "Wrong value !!!"
        exit 1
    fi
  
    if query "$2" $3; then
        config_set_option "$1" y
        return 0
    else
        config_set_option "$1" n
        return 1
    fi
}

#---------------------------------------------------------------------
## @param config file variable
## @param question
## @param default answer [y|n]
## @param option_yes
## @param option_no
##
## @return 0 user supplied answer
## @return 1 default answer is used
##
## Asks user for string, with default answer and timeout (like query)
## The string is added to the variable
## Return variable is also marked as persistent
##
#---------------------------------------------------------------------
function real_config_query_option () {
    local ANSWER
  
    if [ -z "$RECONFIGURE" ] && config_get_option "$1" ANSWER && list_find "$ANSWER" $4 $5; then
        # option allready ANSWERed in config
        echo -e "[[ ${QUERY_COLOR}$2${DEFAULT} -> ${QUERY_COLOR}$ANSWER${DEFAULT} ]]"
        return 1
    fi
  
    if query "$2" $3; 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 user supplied answer
## @return 1 default answer is used
##
## 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
    local RET=0
    
    if [ -z "$RECONFIGURE" ] && config_get_option "$1" ANSWER; then
        # option allready answered in config
        echo -e "[[ ${QUERY_COLOR}$2${DEFAULT} -> '${QUERY_COLOR}$ANSWER${DEFAULT}' ]]"
    else
        query_string ANSWER "$2" "$3"
        RET=$?
        config_set_option "$1" "$ANSWER"
    fi
    
    return $RET
}


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

  if [ -z "$RECONFIGURE" ] && config_get_option "$VARIABLE" ANSWER; then
    # option allready 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 !!!!"
    )
    echo -e "[[ ${QUERY_COLOR}${QUESTION}${DEFAULT} -> '${QUERY_COLOR}$ANSWER${DEFAULT}' ]]"
  else
    # we have to ask user
    select_list ANSWER $@
    RET=$?
    config_set_option "$VARIABLE" "$ANSWER"
  fi

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

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