# -*-shell-script-*-

###
#
# This code is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright 2002 Mike Hearn (mike@theoretic.com)
# Copyright 2002,2003 Hongli Lai (h.lai@chello.nl)
#
###

#### logging framework ####

##
# trace <MESSAGE>
# MESSAGE: The debug message you wish to be printed to stderr
#
# trace will print out the line number in the file, the current function and the given message
# It is part of the debugging framework
# trace will only give output when DEBUGLEVEL is >= 3
function _trace() {
    echo -n ' * trace:' >/dev/tty
    [[ "$1" != "-f" ]] && echo -n `echo "$1" | sed s/^-f//`'()': >/dev/tty
    shift; echo "$@" >/dev/tty;
}

##
# warn <MESSAGE>
# message: The debug message you wish to be printed to stderr
#
# warn will print out the line number in the file, the current function and the given message
# It is part of the debugging framework
# warn will only give output when DEBUGLEVEL is >= 2
function _warn() {
    echo -n ' * warn:' >/dev/tty
    [[ "$1" != "-f" ]] && echo -n `echo "$1" | sed s/^-f//`'()': >/dev/tty
    shift; echo "$@" >/dev/tty;
}

##
# err <MESSAGE>
# message: The debug message you wish to be printed to stderr
#
# err will print out the line number in the file, the current function and the given message in red
# It is part of the debugging framework
# err will only give output when DEBUGLEVEL is >= 1
function _err() {
    red >/dev/tty;
    echo -n ' * err:' >/dev/tty
    [[ "$1" != "-f" ]] && echo -n `echo "$1" | sed s/^-f//`'()': >/dev/tty
    shift; echo "$@" >/dev/tty;
    normal >/dev/tty;
}

shopt -s expand_aliases
alias trace='(( DEBUGLEVEL >= 3 )) && _trace -f$FUNCNAME'
alias warn='(( DEBUGLEVEL >= 2 )) && _warn -f$FUNCNAME'
alias err='_err -f$FUNCNAME'



# Font styles
function normal()    { echo -en "\033[0m"; }
function bold()      { echo -en "\033[1m"; }
function underline() { echo -en "\033[4m"; }
function blink()     { echo -en "\033[6m"; }

# Foreground font colors with bold
function black()     { echo -en "\033[1;30m"; }
function red()       { echo -en "\033[1;31m"; }
function green()     { echo -en "\033[1;32m"; }
function yellow()    { echo -en "\033[1;33m"; }
function blue()      { echo -en "\033[1;34m"; }
function magenta()   { echo -en "\033[1;35m"; } # name from bash handbook
function lilac()     { echo -en "\033[1;35m"; } # deprecating for magenta
function cyan()      { echo -en "\033[1;36m"; }
function white()     { echo -en "\033[1;37m"; }

function bgblack()   { echo -en "\033[1;49m"; }
function bgblue()    { echo -en "\033[1;44m"; }
function bgcyan()    { echo -en "\033[1;46m"; }

##
# getLine <STRING> <N>
# STRING: A multi-lined string.
# N: A line number. The first line is 1.
# Outputs: The Nth line of STRING.
#
# Prints a certain line from STRING.
#
# Example:
# foo=`echo -e "hello\nworld"`
# getLine "$foo" 2  # Outputs "world"
function getLine() { 
    echo "$1" | sed -n "${2}p";
}


##
# getLineCount <STRING>
# STRING: a multi-lined string.
# Outputs: The number of lines in STRING.
#
# Calculate how many lines STRING has.
function getLineCount() {
    echo "$1" | wc -l | sed 's/^ *//g'
}


##
# getSection <INIFILE> <SECTION>
# INIFILE: The filename of a INI file.
# SECTION: The name of a section (excluding brackets).
#
# Extracts the contents of an INI file section.
function getSection() {
    section="$2"
    input="$1"
    trace getting section $section from $input
    line1=`grep -n "\[$section\]" $input | sed 's/:.*//' -`
    trace line1=$line1
    if [[ "$line1" == "" ]]; then return 1; fi;

    ((line1 += 1))
    line2=`tail +$line1 $input | grep -n "^\[.*\]" - | sed 's/:.*//' - `
    trace line2=$line2
    if [[ $line2 == "" ]]; then
        # last section
        tail +$line1 $input
        return $?;
    else
        line2=`echo "$line2" | head -n 1 -` # first line
        ((line2 = line1 - 1 + line2))
        ((length = line2 - line1))
        tail +$line1 $input | head -n $length -
        return $?
    fi
}


##
# removeSectionLine <INIFILE> <SECTION> <MATCH>
# INIFILE: The INI file to update.
# SECTION: The name of a section (excluding brackets).
# MATCH: The text to match against.
#
# Remove a text line to a named section of a INI file.
#
# Example:
# # hello.ini contains:
# # [Foo]
# # hello=1
# # world=2
# removeSectionLine "hello.ini" "Foo" "hello=1"
# # Now it contains:
# # [Foo]
# # world=2
function removeSectionLine() {
    local filename="$1"
    local section="$2"
    local match="$3"
    local temp

    # test to find match - if found then do not echo the $match_line text
    local match_line=`getSection "$filename" "$section" | grep -e $match`
    #                                            don't quote this ^^^^^^

        # gather file and do not echo the $match_line text
        while read;
        do
            line=`echo "$REPLY"`
            if [ `expr index "$line" '['` -eq 1 ]; then
                section_name=`echo "$line" | sed 's/.*\[//g; s/\].*//g'`
            fi
            if [[ ! "$line" = "$match_line" ]] || [[ ! "$section_name" = "$section" ]]; then
                if [ "$temp" ]; then
                    temp=`echo "$temp"; echo "$line"`
                else
                    temp=`echo "$line"`
                fi
            fi
        done <"$filename"
        echo "$temp" > "$filename"
}


##
# addSectionLine <FILENAME> <SECTION> <REPLACE> <MATCH>
# FILENAME: File to update.
# SECTION: Text section to modify or add text line.
# REPLACE: Text to be added or replaced against the matched text line.
# MATCH: Text to match against in the defined section text.
#
# Add or replace a text line to a named section of an INI-style file.
# Filename or section marker will be added if either is not present
# if NATCH is not given the REPLACE text will be added
function addSectionLine() {
    local filename="$1"
    local section="$2"
    local replace="$3"
    local match="$4"
    local match_line
    local temp

    trace filename=$filename, section=$section
    trace replace=$replace, match=$match

    # test to find match - if found then replace otherwise rewrite file and add text line
    [ ! -e "$1" ] && touch "$1"
    [ "$match" ] && match_line=`getSection "$filename" "$section" | grep -e $match`
    #                                                                      ^^^^^^^^ don't quote this
    if [ "$match_line" ]; then
        replace=`echo "$replace" | sed 's|\/|\\\/|g; s|\"|\\\"|g; s|\;|\\\;|g'`
        safeSed "$filename" "s|^${match_line}$|${replace}|"
    else
        local section_text=`getSection "$filename" "$section"`

        # does this section of text exist
        if [ "$section_text" ]; then

            # gather sections to add text line at the end of section
            while read;
            do
                line=`echo "$REPLY"`
                if [ `expr index "$line" '['` -eq 1 ]; then
                    section_name=`echo "$line" | sed 's/.*\[//g; s/\].*//g'`
                    # this order is to format the text output
                    if [ "$temp" ]; then
                        temp=`echo "$temp"; echo "[$section_name]"`
                    else
                        temp=`echo "[$section_name]"`
                    fi
                    section_text=`getSection "$filename" "$section_name"`
                    if [[ "$section" = "$section_name" ]]; then
                        section_text=`echo "$section_text"; echo "$replace"`
                    fi
                    if [[ "$section" = "Desktop Entry" ]]; then
                        section_text=`stripBlankLines "$section_text" | sort`
                        temp=`echo "$temp"; echo "$section_text"; echo " "`
                    else
                        section_text=`stripBlankLines "$section_text"`
                        temp=`echo "$temp"; echo "$section_text"; echo " "`
                    fi
                fi
            done <"$filename"
            echo "$temp" > "$filename"

        # add section marker and item text to file
        else

            echo "[$section]" >> "$filename"
            echo "$replace" >> "$filename"

        fi

    fi
    return 0
}


##
# getKey <INPUT> <KEY>
# INPUT: A string, or - indicating stdin.
# KEY: A string.
# Outputs: The KEY's value.
#
# getKey searches the input text for a line of the form
# "KEY: VALUE", and outputs VALUE.
#
# Example:
# cat <<EOF | getKey - name    # => keymaker
# name: keymaker
# age: 27
# occupation: making keys
# EOF
function getKey() {
	grepBeginsWith "$1" "$2: " | sed 's/^[^:]*://' | sed 's/^ *//'
}


# Parses strings of the form Key: Value into callbacks to the processKey() function
function parseKeys() {
    working="$1"
    parsing="yes"
    while [[ $parsing == "yes" ]]; do
        # read a line of working
        line=$( echo "$working" | head -n 1 | sed 's/^ \+//; s/ \+$//' )
	key=$( echo "$line" | sed 's/^\([^:]*\):.*$/\1/' )
        value=$( echo "$line" | sed 's/^[^:]*: //' )
	trace $key=$value
        # truncate working
        working=$( echo "$working" | sed '1d' )
        if [[ "$working" == "" ]]; then parsing="no"; fi
        if [[ "$key" == "" ]]; then warn "skipping line"; continue; fi
	if [[ "$value" == "" ]]; then warn "skipping line"; continue; fi;
        processKey "$key" "$value"
    done;
    return 0;
}


##
# escapeValue <VALUE>
# VALUE: A string.
# Outputs: The escaped value.
#
# Escape VALUE for sed (useful for strings with / or . in).
#
# Example:
# value=`escapeValue "$whatever"`
# echo "$something" | sed 's/hello/$value/g'
function escapeValue() {
    echo "$1" | sed -e 's/\//\\\//g' - | sed -e 's/\./\\./g' - 
}


##
# escapeFilename <FILENAME>
# FILENAME: A filename.
# Outputs: The escaped filename.
#
# Escape quotes and other special characters in a filename.
#
# Example:
# # We want a file called: "The $cript's World\".txt
# fn=`escapeFilename "\"The \$cript's World\\\".txt"`
# eval "touch $fn"      # touch touch \"The\ \$cript\'s\ World\\\".txt
function escapeFilename() {
	echo "$1" | sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\ /g; s/\$/\\\$/' | sed "s/'/\\\'/g"
}


##
# joinLines <JOIN-POINT> <STR1> <STR2>
# JOIN-POINT: A string that identifies the line where str2 will be inserted
# STR1: A multiline string that contains a line equal to JOIN-POINT
# STR2: The string to insert at that point. It doesn't have to be multi-line.
# Outputs: The result;
#
# Inserts str2 into str1 at the line which is equal to join-point. The result
# is sent to stdout.
# <p>
# The line containing the join point is removed first. You should remember to
# quote all the parameters, this will probably mess up: joinPoint foo $a $b,
# if a or b contain any spaces. Look at the example below to see how to call
# this function.
#
# Example:
# # (somefile is a text file which contains the line %UserScript%
# # somewhere in the middle)
# a=`cat somefile`
# b=`cat userscript`
# joinLines "%UserScript" "$a" "$b" >fullfile
# # (fullfile now contains the contents of userscript inserted at the
# # given point)
function joinLines() {
    l=`echo "$2" | grep -n "$1" | cut -d : -f 1`
    (( a = l - 1 ))
    (( b = l + 1 ))
    top=`echo "$2" | head -n $a`
    bottom=`echo "$2" | tail +$b`
    echo "$top"; echo "$3"; echo "$bottom"
    return 0;
}


##
# stripComments <STRING>
# STRING: A string.
# Outputs: STRING without comments.
#
# Removes all content after a # mark.
function stripComments() {
    working="$1"
    working=`echo "$1" | sed 's/#.*//g' -`
    echo "$working"
    return 0;
}


##
# stripBlankLines <STRING>
# STRING: A string.
# Outputs: STRING without blank lines.
#
# Removes all blank lines from STRING.
function stripBlankLines() {
    echo "$1" | awk 'NF > 0' -
}


# Take a $1 message, and return 0 for yes, or 1 for no
function yesNo() {
    while [ 0 ]; do
	read -s -n1 -p "$1";
	in=`echo $REPLY | tr A-Z a-z`; 
	echo $in
    	if [ "$in" = "y" ]; then return 0;
	elif [ "$in" = "n" ]; then return 1;
	else echo "$intl_YESNO"; fi
    done
}


##
# compareVersions <REQUIRED> <CURRENT>
# REQUIRED: Required version.
# CURRENT: Current version.
# Returns: 0 if passed, 1 if failed.
#
# This function compares 2 strings - infinite level of decimal groups.
# REQUIRED string for required version; CURRENT string for current version. 
# Returns 1 if REQUIRED is > CURRENT, else 0. [ 0 - PASS, 1 - FAIL ] 
#
# Parameter string format: "x.y.z", where y and z are optional. Wildcards can
# only be used for an entire decimal group like "1.6.x" or "2.x" NOT "2.5x" .
# Function looks ahead in the decimal groups with alphabetic and numeric
# identifers to match full numbers. For instance REQUIRED:2-RC10f and
# CURRENT:2-rc2d, it ends up comparing 10 to 2 and returns 1 [ FAIL ]
# instead of 1 to 2 returning 0 [ PASS ].
#
# Example:
#                     Required    Current          Return Value
#    compareVersions  "1"         "1.2"       --->  0 [ PASS ]
#    compareVersions  "1.2"       "1"         --->  1 [ FAIL ]
#    compareVersions  "1.1"       "1.2"       --->  0 [ PASS ]
#    compareVersions  "1.3"       "1.2"       --->  1 [ FAIL ]
#    compareVersions  "1.2"       "1.2"       --->  0 [ PASS ]
#    compareVersions  "1.2b"      "1.2b"      --->  0 [ PASS ]
#    compareVersions  "2.5-pre3"  "2.5"       --->  0 [ PASS ]
#    compareVersions  "2.5-pre3"  "2.5-pre2"  --->  1 [ FAIL ]
#    compareVersions  "2-RC10f"   "2-rc2d"    --->  1 [ FAIL ]
#    compareVersions  "3.1-RC3"   "3.1-rc12"  --->  0 [ PASS ]
#    compareVersions  "1.3"       "0.1.5"     --->  1 [ FAIL ]
#    compareVersions  "1.99.6"    "2"         --->  0 [ PASS ]
#    compareVersions  "1.6.x"     "1.6.7"     --->  0 [ PASS ]
#    compareVersions  "1.6.x"     "1.6"       --->  0 [ PASS ]
#    compareVersions  "1.6.x"     "1.5.7"     --->  1 [ FAIL ]
#    compareVersions  "1.x"       "1.5.7"     --->  0 [ PASS ]
function compareVersions() {
	local index=0
	# replace all '.' with whitespace to index for arrays and remove '-' '_' '/' '+'
	local x=${1//./ }; x=${x//-/}; x=${x//_/}; x=${x//\//}; x=${x//+/}
	local y=${2//./ }; y=${y//-/}; y=${y//_/}; y=${y//\//}; y=${y//+/}
	# create arrays from parameters
	x=(`echo $x | tr [:upper:] [:lower:]`)
	y=(`echo $y | tr [:upper:] [:lower:]`)
	# obtain maximum count of elements from either array
	let "cntx=${#x[@]}"
	let "cnty=${#y[@]}"
	if [ $cntx -gt $cnty ]; then
		max="$cntx"
	else
		max="$cnty"
	fi

	while [[ "$index" -lt "$max" ]]
	do
		# process each respective decimal group ...
		# if alphanumeric group then extend the compare to the sub else do string comparisons
		# special case: catches wildcard in REQUIRED decimal group and PASS
		if [ `expr length "${x[$index]}"` = 1 ] && [[ `expr index "${x[$index]}" 'x'` -eq "1" ]]; then return 0; fi
		# special case: with a blank REQUIRED group any alpha data in CURRENT group will FAIL
		# example: REQUIRED 1.2 < CURRENT 1.2.0b or REQUIRED 1.2 < CURRENT 1.2.b
		if [[ "${x[$index]}" = "" ]] && [ `expr index "${y[$index]}" '[abcdefghijklmnopqrstuvwxyz]'` -gt "0" ]; then return 1; fi
		if [ `expr index "${x[$index]}" '[abcdefghijklmnopqrstuvwxyz]'` -gt "0" ] || [ `expr index "${y[$index]}" '[abcdefghijklmnopqrstuvwxyz]'` -gt "0" ]; then
			if compareAlphaNumeric "${x[$index]}" "${y[$index]}";then 
				return 0
			elif [[ "$?" = "1" ]]; then
				return 1
			# passes all sub checks and returns to continue with next decimal groups
			elif [[ "$?" = "2" ]]; then
				break
			fi
		fi
		if [[ "${x[$index]}" < "${y[$index]}" ]]; then return 0; fi
		if [[ "${x[$index]}" > "${y[$index]}" ]]; then return 1; fi
		let "index = $index + 1"
	done
	return 0;
}


# compareAlphaNumeric <VERSION1> <VERSION2>
# Returns: 1 or 0.
#
# Compare two strings and return 1 if VERSION1 > VERSION2, otherwise 0.
# Otherwise, gathers digits forward to compare full numbers.
# Special case: point release is higher than alphabetic release.
function compareAlphaNumeric() {
	local a=$1
	local b=$2
	# example:
	# REQUIRED 2 > CURRENT 2b or REQUIRED 2.1 > CURRENT 2.1.b ( 0 > b )
	if [ `expr index "$a" '[abcdefghijklmnopqrstuvwxyz]'` -eq "0" ]; then
		return 1
	fi
	# get length of the longest string to index with over as $limit
	len1=`expr length "$a"`
	len2=`expr length "$b"`
	if [ $len1 -gt $len2 ]; then
		limit=$len1
	else
		limit=$len2
	fi
	counter=1
	while [ "$counter" -le $limit ]
	do
		# compare character by character indexing up the string
		char1=`expr substr $a $counter 1`
		# special case: point release is higher than alphabetic release
		# example: REQUIRED 2.5-pre3 < CURRENT 2.5 or REQUIRED alpha-char < CURRENT no char
		if [ `expr index "$char1" '[1234567890]'` = 0 ] && [ $counter -gt $len2 ]; then
			return 0
		else
			char2=`expr substr $b $counter 1`
		fi

		# look forward to find next index digit to complete the full number [x]
		counterchar=$counter
		temp=""
		tempchar="$char1"
		while [ `expr index "$tempchar" '[1234567890]'` -gt "0" ]
		do
			# grab next index to analyze
			tempchar=`expr substr $a $counterchar 1`
			# if next index is numeric add character to $temp
			if [ `expr index "$tempchar" '[1234567890]'` -gt "0" ]; then temp="${temp}${tempchar}"; fi
			# push out $temp to consistent variable $char1
			char1="$temp"
			let "counterchar+=1"
		done

		# look forward to find next index digit to complete the full number [y]
		counterchar=$counter
		temp=""
		tempchar="$char2"
		while [ `expr index "$tempchar" '[1234567890]'` -gt "0" ]
		do
			# grab next index to analyze
			tempchar=`expr substr $b $counterchar 1`
			# if next index is numeric add character to $temp
			if [ `expr index "$tempchar" '[1234567890]'` -gt "0" ]; then temp="${temp}${tempchar}"; fi
			# push out $temp to consistent variable $char2
			char2="$temp"
			let "counterchar+=1"
		done

		# if comparing numbers do an integer compare - otherwise do a string compare
		if [ `expr index "$char1" '[1234567890]'` -gt "0" ] || [ `expr index "$char2" '[1234567890]'` -gt "0" ]; then
			if [ "$char1" -gt "$char2" ]; then return 1; fi
			if [ "$char1" -lt "$char2" ]; then return 0; fi
			# numbers match then jump counter forward over the matched digits
			if [ "$char1" -eq "$char2" ]; then counter=$counter+$counterchar-3; fi
		else
			if [[ "$char1" < "$char2" ]]; then return 0; fi
	 		if [[ "$char1" > "$char2" ]]; then return 1; fi
		fi
		let "counter+=1"
	done
	# return to continue to process next decimal group
	return 2
}


# Compare simple version text - wildcard and recursive with arrays with full group comparisons
# Simple because it compares full decimal groups between the arrays not subcomponents
# Make it easy ... use only decimal version identifiers for this function ... some alpha combinations will work properly

## START COMPAREVERSIONS SIMPLE
function compareVersionsSimple() {
	local index=0
	local max
	# replace all '.' with whitespace to index for arrays and remove '-' '_' '/' '+'
	local x=${1//./ }; x=${x//-/}; x=${x//_/}; x=${x//\//}; x=${x//+/}
	local y=${2//./ }; y=${y//-/}; y=${y//_/}; y=${y//\//}; y=${y//+/}
	x=(`echo $x`)
	y=(`echo $y`)
	let "cntx=${#x[@]}"
	let "cnty=${#y[@]}"
	if [ $cntx -gt $cnty ]; then
		max="$cntx"
	else
		max="$cnty"
	fi

	while [[ "$index" -lt "$max" ]]
	do
		# special case: catches wildcard in decimal group and returns PASS
		if [ `expr length "${x[$index]}"` = 1 ] && [[ `expr index "${x[$index]}" 'x'` -eq "1" ]]; then return 0; fi
		if [[ "${x[$index]}" != "" ]] && [[ "${y[$index]}" = "" ]]; then return 1; fi
		if [[ "${x[$index]}" < "${y[$index]}" ]]; then return 0; fi
		if [[ "${x[$index]}" > "${y[$index]}" ]]; then return 1; fi
		let "index = $index + 1"
	done
	return 0;
}
## END COMPAREVERSIONS SIMPLE


##
# haveWriteAccess <DIRECTORY>
# DIRECTORY: The directory name.
# Returns: 0 if we have write access, 1 if we don't.
#
# Checks whether we have write access to DIRECTORY.
function haveWriteAccess()
{
    # Does $1 exist?  
    if [[ -d "$1" ]]; then
        # Yes; do we have acess(1) installed?
        if which access > /dev/null 2>&1; then
            access -rw "$1"
            return $?
        else
            # No; try to create a temp file in that dir.

            (( i = $$ / $RANDOM + ($RANDOM / 2) ))
            TEMPFILE="$1/apkg-$RANDOM-$$.$i"
            if touch "$TEMPFILE" 2> /dev/null; then
                # Success; remove temp file
                rm -f "$TEMPFILE"
                return 0
            else
                # Failure
                return 1
            fi
        fi
    else
        # No; try to create that directory.
        if mkdir -p "$1" 2> /dev/null; then
            # Success; we have write access. Now remove that dir.
            rmdir "$1"
            return 0
        else
            # Failure; no write access
            return 1
        fi
    fi
}


##
# dirIsEmpty <DIRECTORY>
# DIRECTORY: A directory name.
# Returns: 0 if DIRECTORY is empty, 1 if it's not.
#
# Check whether DIRECTORY is empty. If DIRECTORY only contains
# empty subdirectories then DIRECTORY will be considered empty.
function dirIsEmpty()
{
    if ! pushd "$1" &>/dev/null; then
        return 0
    fi

    file_array=( `ls -A1` )
    count=${#file_array[@]}
    i=0

    while [ "$i" -lt "$count" ]; do
        F="${file_array[i]}"
        if [[ -d "$F" ]]; then
            if ! dirIsEmpty "$F"; then
		popd &>/dev/null
                return 1
            fi
        else
	    popd &>/dev/null
            return 1
        fi
        (( i++ ))
    done

    popd &>/dev/null
    return 0
}


##
# removeLine <FILENAME> <STRING>
# FILENAME: A filename.
# STRING: The line to remove.
#
# Look for lines that equals STRING in a text file and removes them.
function removeLine() {
    file="$1"
    line="$2"

    res=`grep -n "$line" "$file" | sed -n 's/:.*//;1p'`
    while [[ $res != "" ]]; do
	safeSed "$file" "${res}d"
	res=`grep -n "$line" "$file" | sed -n 's/:.*//;1p'`
    done
}


function getAllButLastArg() {
    # copy everything but last argument to array args_array
    local -a args
    unset args_array

    args=("$@")
    count=${#@}
    (( count-- ))
    i=0

    while [[ $i -lt $count ]]; do
        args_array[$i]="${args[$i]}"
        (( i++ ))
    done
}


function getLastArg() {
    oIFS="$IFS"
    IFS=$'\n'
    args=($@)
    count=${#args[@]}
    (( count-- ))
    echo ${args[$count]}
    IFS="$oIFS"
}


##
# endsWithNewLine <FILENAME>
# FILENAME: A filename.
# Returns: 0 on true, 1 on false.
#
# Finds out if the last byte in file FILENAME is a newline (\n).
function endsWithNewline() {
    test "`tail -c 1 \"$1\"`" == "`echo`"
    return $?
}


# left pads a given string
# lpad width [strings]
function lpad() {
  w="$1"; shift
  l=`echo "$@" | wc -m`
  (( t = w - l ))
  c=0
  while (( c <= t )); do
    echo -n " "
    (( c++ ));
  done
  echo "$@"
}


##
# safeSed <FILENAME> <SCRIPTSTRING>
# FILENAME: The file to edit.
# SCRIPTSTRING: A string containing the script to execute.
#
# This function is like sed, but works on a file. It uses FD swaps internally
# so you can reliably edit a file without hitting asyncronity issues.
#
# Example:
# echo 'One Two Three' > foo.txt
# safeSed foo.txt 's/One/Two/g'
function safeSed() {
    ( exec 7<"$1"; rm "$1"; sed "$2" <&7 >"$1" );
    # use a subshell to prevent fd leaks
}


##
# locateCommand [options] command [parameters]
#
# Finds the location of an object with the combined efforts of 'which' then 'whereis'.
# If parameters exists then it will execute those parameters against the located executable command.
#
# Usage: locateCommand [options] command [parameters]
#
#     options    : optional
#                           :: -o print the executed command with parameters output text
#                           :: -l print the command location text
#                           :: -- no printed text (default)
#     command    : required :: executable file to search for
#     parameters : optional :: if found execute the command with these parameters
#
# Returns 1 if file not located, or
# Returns 1 if file located, then executed with parameters and exit code > 0, or
# Returns 0 for else case on an executable file [ 0 - PASS, 1 - FAIL ]
#
# Sets variables:
#     $lc_array    - array of filesystem objects matching the requested file
#     $lc_location - determined filesystem location of the executable file
#     $lc_output   - output data from the executable file using parameters
#
# Example Output:
#     locateCommand -o kde-config --prefix
#         ---> $lc_array    = "/usr/bin/kde-config"
#         ---> $lc_location = "/usr/bin/kde-config"
#         ---> $lc_output   = "/usr"
#         ---> returns 0 [ PASS ] and prints '/usr'
#         found with 'which' so use the 'which' output as $lc_location and
#         execute command with parameters for $lc_output - check passes and prints result
#
#     locateCommand panel
#         ---> $lc_array    = "panel: /usr/include/panel.h /usr/share/man/man3/panel.3x.bz2"
#         ---> $lc_location = NULL
#         ---> $lc_output   = NULL
#         ---> returns 1 [ FAIL ]
#         found with 'whereis' so loop the array for the first executable file and
#         in this case all objects fail
#
# Same result - different code
#
#         if locateCommand gnome-config --datadir; then
#             gnome1dir="$lc_output/gnome/apps"
#         fi
#
#         gnome1dir=`locateCommand -o gnome-config --datadir`"/gnome/apps"

function locateCommand() {
	unset lc_array
	unset lc_location
	unset lc_output
	local ret_location
	local ret_output
	
	# process option flags
	if [[ "$1" = "-o" ]]; then
		ret_output=1
		shift
	elif [[ "$1" = "-l" ]]; then
		ret_location=1
		shift
	elif [[ "$1" = "--" ]]; then
		shift
	fi
	if $( which "$1" >/dev/null 2>&1 ); then
		lc_array=$( which "$1" )
		lc_location="$lc_array"
		# if parameters exist then execute object with parameters
		if [[ "$2" != "" ]]; then
			lc_output="`$lc_location $2`"
			trace which:lc_location=$lc_location
			[[ "$?" != "0" ]] && return 1
			[ "$ret_output" ] && echo $lc_output
			[ "$ret_location" ] && echo $lc_location
			return 0
		else
			trace which:lc_location=$lc_location
			[ "$ret_output" ] && echo $lc_output
			[ "$ret_location" ] && echo $lc_location
			return 0
		fi
		unset lc_location
		return 1
	elif $( whereis "$1" > /dev/null 2>&1 ); then
		lc_array=$( whereis "$1" )
		x=(`echo "$lc_array"`)
		# skip zero index
		index=1
		while [[ "$index" -lt "${#x[@]}" ]]
		do
			# if parameters exist then the object needs to be executable
			if [[ "$2" != "" && -x "${x[$index]}" ]]; then
				lc_location="${x[$index]}"
				lc_output="`$lc_location $2`"
				trace whereis:lc_location=$lc_location
				[[ "$?" != "0" ]] && break
				[ "$ret_output" ] && echo $lc_output
				[ "$ret_location" ] && echo $lc_location
				return 0
			# final check for executable object
			elif [[ -x "${x[$index]}" ]]; then
				lc_location="${x[$index]}"
				trace whereis:lc_location=$lc_location
				[ "$ret_output" ] && echo $lc_output
				[ "$ret_location" ] && echo $lc_location
				return 0
			fi
			unset lc_location
		let "index = $index + 1"
		done
		return 1
	else
		unset lc_location
		return 1
	fi
}

# why do we need these functions? So we can easily use %s substitutions in internationalized strings
function out() {
    s="$1"; shift; 
    printf "$s\n" "$@"
    unset s
}

function outn() {
    s="$1"; shift; 
    printf "$s" "$@"
    unset s
}


# draws a solid blue line as a header, with the given text in yellow
function drawHeaderBlue() {
    bgblue; yellow;
    tput sc

    local spaces="";
    for i in $( seq 1 `tput cols` ); do
      spaces="$spaces "
    done
    echo -n "$spaces"

    tput rc
    outn "$@"
    bgblack; normal;
    echo
}

function drawHeaderCyan() {
    bgcyan; white;
    tput sc

    local spaces=""
    for i in $( seq 1 `tput cols` ); do
      spaces="$spaces "
    done
    echo -n "$spaces"

    tput rc
    outn "$@"
    bgblack; normal;
    echo
}

# _mkdirs <DIR>
#
# Create the directory given in argument one, creating any parents as needed
# Internal function only, package scripts should use mkdirs()
# Returns any error output in the error variable. 
#
# FIXME: why do we have this? why not simply use mkdir -pv?
#
# $1 = the directory path to be created
function _mkdirs() {
    local errstatus=0
    local dir="$1"
    local start
    local -a array

    # separate path into elements and place in array
    oIFS="$IFS"
    IFS="/"
    array=( $dir )

    # if the path started with a "/", we need to add it back on and
    # realize that these first element in our array is empty
    case $dir in
	/*) start=1; array[1]="/${array[1]}" ;;
         *) start=0 ;;
    esac

    # traverse through the array elements creating directories as needed
    local dirpath=""
    local i=0;
    for (( i=${start};i<=( ${#array[*]} - 1);i++ )) ; do
	dirpath="${dirpath}${array[${i}]}/"
	if [ ! -d "$dirpath" ] ; then
	    if ! mkdir "$dirpath" 2>&1; then
		IFS="$oIFS"
		warn failed "$dirpath"
		return 1
	    fi
    	    echo "$dirpath"
	else warn directory $dirpath already exists; fi
    done
    IFS="$oIFS"
    return 0
}


##
# prune <DIRECTORY>
# DIRECTORY: A directory.
#
# If DIRECTORY is empty, change to the parent directory and delete
# DIRECTORY. Repeats until it reaches a directory that is no longer
# empty.
# See also: removeDir().
#
# Example:
# mkdir -p a/deep/directory
# touch a/file
# prune a/deep/directory
# ls a/deep   # =>  "a/deep: no such file or directory"
function prune() {
    pushd "$1" >/dev/null
    while [[ `ls -1` = "" ]]; do
	local a=`basename "$(pwd)"`;
	echo "$a";
	cd ..;
	rmdir "$a";
    done
    popd >/dev/null
}

##
# checkDiskSpace <NEEDED> <LOCATION>
# NEEDED: The needed disk space in bytes.
# LOCATION: The directory to check for disk space.
# Returns: 0 if there is at least NEEDED byes of free disk space, or 1 if there isn't.
#
# Check for free diskspace in LOCATION.
#
# Example:
# # Check whether /tmp has at least 1392003 bytes of free disk space
# checkDiskSpace 1392003 "/tmp"
function checkDiskSpace() {
    local diskspace_needed="$1"
    local diskspace_location=`echo "$2" | sed 's|//|/|g'`

    while [[ "$diskspace_location" != "." ]]; do
        if [[ -d "$diskspace_location" ]]; then
                break
        fi
        diskspace_location=`dirname "$diskspace_location"`
    done

    diskspace_partition=`df --block-size=1 "$diskspace_location" | grep "^/" | awk '{print $1}'`
    diskspace_available=`df --block-size=1 "$diskspace_location" | grep "^/" | awk '{print $4}'`
    if [ "$diskspace_needed" -gt "$diskspace_available" ]; then
        return 1
    fi
    return 0
}


function _readlink() {
    local f=`LANG= file "$1"`
    if echo "$f" | grep "symbolic link" &>/dev/null; then
        echo "$f" | sed 's/.*symbolic link to //'
        return 0
    else
        return 1
    fi
}

##
# resolveLink SYMLINK
# SYMLINK: A symbolic link file.
# Outputs: The resolved filename.
#
# Recursively resolve symbolic links.
#
# Example:
# touch foo
# ln -s foo sym1
# ln -s sym1 sym2
# resolveLink sym2    # =>  foo
function resolveLink() {
	local resolved="$1"
	while [[ -L "$resolved" ]]; do
		local to=`_readlink "$resolved"`
		if echo "$to" | grep '^/' >/dev/null; then
			resolved="$to"
		else
			resolved=`dirname "$resolved"`
			resolved="$resolved/$to"
		fi
	done
	echo "$resolved"
}

declare -a __opt_stack_e
[[ $- == *e* ]] && __opt_stack_e[0]="1"
[[ $- != *e* ]] && __opt_stack_e[0]="0"

##
# pushOptE
#
# Pushes the shell option e onto a stack. The e option controls whether a script should exist on the first line
# that fails. If you write a function that might be called from a script with option set, and you don't want this
# to affect the functions operation, you can push the option using this function, then run `set +e` to clear the
# flag. When leaving the function, popping the stack returns the option to the state it was in before.
#
# Example:
# function foo() {
#   pushOptE; set +e;
#   (perform some code that may return a non-zero exit code)
#   popOptE
# }
function pushOptE() {
    local last=${#__opt_stack_e[@]}
    if [[ $- == *e* ]]; then
	__opt_stack_e[$last]="1"
    else	
	__opt_stack_e[$last]="0"
    fi	
}

##
# popOptE
#
# Pops the stack for the shell option e. See pushOptE for more details
function popOptE() {
    if [[ ${__opt_stack_e[${#__opt_stack_e[@]}-1]} == "1" ]]; then
	set -e
    else
	set +e
    fi
    unset __opt_stack_e[${#__opt_stack_e[@]}-1]
}

##
# getMajor <VERSION>
# VERSION: A version number.
# Outputs: The major version number.
#
# Extract the major version from a version number (anything before the first dot)
#
# Example:
# getMajor 2.3.4pre      # =>  2
function getMajor() {
    echo "$1" | awk -F. ' { print $1 } '
}

##
# getMinor <VERSION>
# VERSION: A version number.
# Outputs: The minor version number.
#
# Extract the minor version from a version number (anything between the first and second dots)
#
# Example:
# getMajor 2.3.4pre      # =>  3
function getMinor() {
    echo "$1" | awk -F. ' { print $2 } '
}

##
# versionFromRootName <ROOTNAME>
# ROOTNAME: A rootname
# Outputs: The root name's version number.
# Returns: 0 if there was a version in the root name, 1 if not.
#
# Extract the version number from a root name.
#
# Example:
# versionFromRootName "@foo.org/foobar:2.2"    # =>  2.2
function versionFromRootName() {
    local r=`echo "$1" | awk -F: '{print $2}'`
    if [[ "$r" == "" ]]; then
	return 1;
    else
	echo $r;
	return 0;
    fi
}


##
# justRootName <ROOTNAME>
# ROOTNAME: A rootname
# Outputs: The unique string part of the name, without any version information
#
# This function is useful to strip any version info from a root name, leaving
# you with just the part before the first colon. It outputs the result on stdout
#
# Example:
# justRootName "@foo.org/foo:2.2:1"      # => @foo.org/foo
function justRootName() {
    echo "$1" | sed 's/:.*//'
}


##
# matchVersionList <FIRST> <VERSION> [VERSION...]
# Returns: 0 on success, 1 on failure.
#
# Given a list of versions, will ensure that at least one of them is >= the
# first version given.
#
# Example:
# matchVersionList 2.4 1.8 2.0.3 2.5.3   # =>  0
function matchVersionList() {
    local needed_version="$1"; shift;
    for v in $@; do
	if compareVersions $needed_version $v; then
	    return 0;
	fi
    done
    return 1;
}

##
# countDownVersions <VERSION> [VERSION...]
#
# Given a version of the form A.B, will output a space delimited string
# consisting of A.B, A.(B-1), A.(B-2) and so on, until B is zero. You can
# use this to produce a list of supported interfaces from the highest interface
# version in the major set. If B is not specified, it will be assumed to be zero.
#
# The micro version, if present, is ignored - only the major and minor numbers
# are included in the output.
#
# Example:
# countDownVersions 6.2
# # -> 6.2 6.1 6.0
# countDownVersion 1.8.6
# # -> 1.8 1.7 1.6 1.5 1.4 1.3 1.2 1.1 1.0
function countDownVersions() {
    while [[ "$1" != "" ]]; do
	local minor=`getMinor $1`
	local major=`getMajor $1`
	if [[ "$minor" == "" ]]; then minor="0"; fi
	for c in `seq $minor 0`; do
	    echo -n "$major.$c "
	done
	shift
    done
    echo
}

##
# stripDupedItems [STRINGS]
# STRINGS: A single string containing items, seperated by whitespace.
#
# Given a list separated by spaces, like "a b c d c d b e" will eliminate the
# duplicated items and produce a list like "a b c d e" on stdout. If no
# parameters are supplied, input will be read from stdin.
#
# Example:
# stripDupedItems 6.2 6.1 6.0 6.1 6.0  # ==> 6.2 6.1 6.0
# countDownVersions `testForLib -v libfoo.so.3` | stripDupedItems
#   # ==> a list of all interface versions supported by installed libs, with
#   #     duplicates that could be caused by multiple versions of the
#   #     library being installed at once stripped.
function stripDupedItems() {
    if [[ $1 == "" ]]; then
    	(
    	    for V in `cat`; do
    	    	echo "$V"
    	    done
    	) | sort | uniq
    else
    	(
    	    for V in `echo "$@"`; do
    	    	echo "$V"
    	    done
    	) | sort | uniq
    fi
}


##
# grepBeginsWith <INPUT> <FIND>
# INPUT: A string to search in. If - is given, input will be read from stdin.
# FIND: The string to search for.
# Outputs: The found line(s).
# Returns: 0 if one or more lines are found, 1 if nothing's found.
#
# Find a line in INPUT that starts with FIND.
#
# Example:
# str=`cat << EOF
# hello=1
# world=2
# anotherworld=3
# EOF`
# grepBeginsWith "$str" "world="   # =>  "world=2"
function grepBeginsWith()
{
	local input=$1
	local find=$2
	if [[ "$input" = "-" ]]; then
		input=`cat`;
	fi

	local len=`echo -n "$find" | wc --chars`
	find=`echo -n "$find" | sed 's/"/\\\"/g'`
	echo "$input" | awk "BEGIN{i=0} {s=substr(\$0, 1, $len); if (s == \"$find\") {print \$0; i++}} END{if(i == 0) exit 1; else exit 0}"
}

function breakpoint() {
    xterm -e bash
}

##
# assertNotReached
#
# This function prints an error message, and terminates the current shell. It should be used
# when you know that a section of code should never be reached, and if it is then something
# went badly wrong.
#
# If the $ASSERTION_TOPLEVEL_SHELL_PID environment variable is defined, it will do a kill on
# that pid after printing the error. Otherwise, it calls exit 1
function assertNotReached() {
    out ""
    red; outn "$intl_ASSERTION_FAILURE"; normal;
    out "$intl_ASSERTION_NOT_REACHED";
    if [[ $ASSSERTION_TOPLEVEL_SHELL_PID != "" ]]; then
	kill $ASSSERTION_TOPLEVEL_SHELL_PID;
    else
	exit 1;
    fi
}

### THIS FILE MUST END IN A NEWLINE

