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

if [[ "$DEBUGLEVEL" == "" ]]; then export DEBUGLEVEL=0; fi;

# DEBUGLEVEL has to >=1 for log to be written then if logfile is not declared, default location for logfile is set
if (( DEBUGLEVEL <= 0 )); then
	export AUTOPACKAGE_DEBUG_LOGFILE="/dev/null"
elif [[ "$AUTOPACKAGE_DEBUG_LOGFILE" == "" ]]; then
	if [[ "$executed_from_directory" != "" ]]; then
		export AUTOPACKAGE_DEBUG_LOGFILE="$executed_from_directory/autopackage.log"
	else
		export AUTOPACKAGE_DEBUG_LOGFILE="$PWD/autopackage.log"
	fi
	touch "$AUTOPACKAGE_DEBUG_LOGFILE" 2>/dev/null
	chmod a+rw "$AUTOPACKAGE_DEBUG_LOGFILE" 2>/dev/null
	if [ -w "$AUTOPACKAGE_DEBUG_LOGFILE" ]; then
		echo "`date --iso-8601=seconds` $0 $@" > "$AUTOPACKAGE_DEBUG_LOGFILE"
		echo "##################################################" >> "$AUTOPACKAGE_DEBUG_LOGFILE"
	else
	        export AUTOPACKAGE_DEBUG_LOGFILE="/dev/null"
	fi
fi

# if first time through then set up session file
if [[ "$_AUTOPACKAGE_SESSION" == "" ]]; then
	marker=`date +%Y%m%d%H%M%S`
	rm -f "$TMP"/autopackage.session* 2>/dev/null
	export _AUTOPACKAGE_SESSION="$TMP/autopackage.session.$marker"
	touch "$_AUTOPACKAGE_SESSION" 2>/dev/null
	chmod a+rw "$_AUTOPACKAGE_SESSION" 2>/dev/null

	if [ -w "$_AUTOPACKAGE_SESSION" ]; then
		echo "# `date --iso-8601=seconds` $0 $@" > "$_AUTOPACKAGE_SESSION"
		echo "##################################################" >> "$_AUTOPACKAGE_SESSION"
	else
	        export _AUTOPACKAGE_SESSION=/dev/null
	fi
fi

##
# _systemInfo <NAME> <MESSAGE1> <MESSAGE2>
# NAME:    An identifier for the information
# MESSAGE1: Simple text message
# MESSAGE2: Multiple line message
#
# It is a template for the logging framework to print information to stdout and logged to file.
# Function is called from package file.
#
# Example:
#   locateCommand sed "--version"
#   _systemInfo "$lc_command" "$lc_location" "$lc_output"
#
#   _systemInfo "glibc" "/lib/libc.so.6" "`/lib/libc.so.6`"
#
function _systemInfo() {
	echo "$1: $2"
	echo "$1: $2" >> "$AUTOPACKAGE_DEBUG_LOGFILE"
	[ "$3" ] && echo "--------------------"
	[ "$3" ] && echo "--------------------" >> "$AUTOPACKAGE_DEBUG_LOGFILE"
	[ "$3" ] && echo "$3"
	[ "$3" ] && echo "$3" >> "$AUTOPACKAGE_DEBUG_LOGFILE"
	echo "##################################################"
	echo "##################################################" >> "$AUTOPACKAGE_DEBUG_LOGFILE"
	return 0
}

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

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

##
# err <MESSAGE>
# MESSAGE: The message you wish to be printed to the terminal and logged to file.
#
# err will print out the current function and the given message in red.
# It is part of the logging framework and will always give output to stdout.
function _err() {
    if [[ "$_err_suppress_level" != "" ]] && (( _err_suppress_level > 0 )); then (( _err_suppress_level-- )); return 0; fi
    [ -e /dev/tty ] && red >/dev/tty
    [ -e /dev/tty ] && echo -n 'err:' >/dev/tty
    echo -n 'err:' >> "$AUTOPACKAGE_DEBUG_LOGFILE"
    [ -e /dev/tty ] && [[ "$1" != "-f" ]] && echo -n `echo "$1" | sed s/^-f//`'()' >/dev/tty
    [[ "$1" != "-f" ]] && echo -n `echo "$1" | sed s/^-f//`'()' >> "$AUTOPACKAGE_DEBUG_LOGFILE"
    [ -e /dev/tty ] && normal >/dev/tty
    shift; [ -e /dev/tty ] && echo " $@" >/dev/tty;
    echo "$@" >> "$AUTOPACKAGE_DEBUG_LOGFILE"
}

##
# fixme <MESSAGE>
# MESSAGE: The message you wish to be printed to the terminal and logged to file.
#
# This function is used by the autopackage developers to remind us of things we need to fix.
# It is part of the logging framework and will only give stdout when DEBUGLEVEL is >= 2.
function _fixme() {
    [ -e /dev/tty ] && cyan >/dev/tty
    [ -e /dev/tty ] && echo -n 'fixme:' >/dev/tty
    echo -n 'fixme:' >> "$AUTOPACKAGE_DEBUG_LOGFILE"
    [ -e /dev/tty ] && [[ "$1" != "-f" ]] && echo -n `echo "$1" | sed s/^-f//`'()' >/dev/tty
    [[ "$1" != "-f" ]] && echo -n `echo "$1" | sed s/^-f//`'()' >> "$AUTOPACKAGE_DEBUG_LOGFILE"
    [ -e /dev/tty ] && normal >/dev/tty
    shift; [ -e /dev/tty ] && echo " $@" >/dev/tty;
    echo "$@" >> "$AUTOPACKAGE_DEBUG_LOGFILE"
}

##
# suppressNextErr
#
# Increments a suppression count. For each time you call this function, an err() log message will be suppressed.
# For instance, if you call it twice, the next two calls to err() will be ignored.
# Use it in unit tests where you know a given situation will give a certain number of errors and don't want them to appear.
function suppressNextErr() {
    (( _err_suppress_level++ ));
    export _err_suppress_level;
}

shopt -s expand_aliases
alias trace='(( DEBUGLEVEL < 3 )) && true || _trace -f$FUNCNAME'
alias warn='(( DEBUGLEVEL < 2 )) && true || _warn -f$FUNCNAME'
alias err='_err -f$FUNCNAME'
alias fixme='_fixme -f$FUNCNAME'

#### logging framework END ####

if [[ "$AUTOPACKAGE_NOCOLORS" != "1" ]]; then
    # 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"; }
    function cyan()      { echo -en "\033[1;36m"; }
    function white()     { echo -en "\033[1;37m"; }

    # Background font colors
    function bgblack()   { echo -en "\033[1;49m"; }
    function bgblue()    { echo -en "\033[1;44m"; }
    function bgcyan()    { echo -en "\033[1;46m"; }
else
    # Font styles
    function normal()    { return; }
    function bold()      { return; }
    function underline() { return; }
    function blink()     { return; }

    # Foreground font colors with bold
    function black()     { return; }
    function red()       { return; }
    function green()     { return; }
    function yellow()    { return; }
    function blue()      { return; }
    function magenta()   { return; }
    function cyan()      { return; }
    function white()     { return; }

    # Background font colors
    function bgblack()   { return; }
    function bgblue()    { return; }
    function bgcyan()    { return; }
fi

##
# 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";
}

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

##
# countFileLines <FILENAME>
# FILE: a text file path
# Outputs: The number of lines in FILE
#
# Calculate how many lines FILE has.
#
# See also: countStringLines()
function countFileLines() {
    cat "$1" | wc -l | sed 's/^ *//g'
}



##
# getSection <FILENAME> <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 -n +$line1 "$input" | grep -n "^\[.*\]" - | sed 's/:.*//' - `
    trace line2=$line2
    if [[ $line2 == "" ]]; then
        # last section
        tail -n +$line1 "$input"
        return $?;
    else
        line2=`echo "$line2" | head -n 1 -` # first line
        ((line2 = line1 - 1 + line2))
        ((length = line2 - line1))
        tail -n +$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" || true
    [ "$match" ] && match_line=`getSection "$filename" "$section" | grep -e $match` || true
    #                                                                      ^^^^^^^^ don't quote this - why not though?

    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. If not specified, - is used
# 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() {
    local from=$1
    local key=$2
    if [[ "$2" == "" ]]; then
	from="-"
	key=$1
    fi
    grepBeginsWith "$1" "$2: " | sed 's/^[^:]*://' | sed 's/^ *//'
}


##
# getSectionKey <FILENAME> <SECTION> <KEY>
# FILENAME: INI-type file from which to read [SECTION] text
# SECTION: name of section in the FILE, excluding brackets.
# KEY: piece of data to find in SECTION text.
# Output: if KEY exists in SECTION, then the data is printed in a localized language value or defaults to english value.
# Returns: 0 if KEY exists in SECTION text.
# Returns: 1 if KEY does not exist in SECTION text.
#
# Function will print the KEY value from
# a FILENAME from within its SECTION text.
# If KEY is localized, then the localized
# value will be printed.
#
# Example:
# skeleton_file=`_locateSkeleton "$root_name"`
# displayname=`getSectionKey "$skeleton_file" "Meta" "DisplayName"`

function getSectionKey() {

	local filename="$1"
	local section="$2"
	local key="$3"
	local meta
	local text

	if [[ $1 != "" ]] && [[ "$2" == "" ]] && [[ "$3" == "" ]]; then
		err "getSectionKey must be given a filename: $1, section: $2, and key: $3."
		return 1;
	fi


	# Grab section text from filename and strip the extraneous comments
	meta=`getSection "$filename" "$section"`
	meta=`stripComments "$meta"`

	# Evaluate what grep matches the KEY from the SECTION text. There should
	# be a base KEY that matches in every case which is the english default.
	# If there is a KEY that matches for any of the languages listed from
	# the function 'getLanguages' it is printed as function output. If no KEY
	# evaluates to any language then the default english is printed.

	# Convert l10n section keys to variables
	trace processing $section section keys for $key
	meta=`echo "$meta" | grep "^$key"`
	if [[ "$meta" != "" ]]; then
		meta=`echo "$meta" | sed 's/\[/\_/g'`
		meta=`echo "$meta" | sed 's/\]: /\=\"/g'`
		meta=`echo "$meta" | sed 's/: /\=\"/g'`
		meta=`echo "$meta" | sed 's/^\(.*\)$/\1\"/'`
		eval "$meta"
	fi

	for language in `getLanguages`; do

		a="STATE_LANGUAGE"
		b="${key}_${language}"
		text=`echo "${!b}"`
		if [[ "$text" == "" ]]; then
			#a="STATE"
			b="${key}"
			text=`echo "${!b}"`
		fi

	done

	if [ "$text" ]; then
		echo "$text"
		return 0
	else
		err The $key was not found in the file: $filename .
		return 1
	fi

}


##
# escapeValue <VALUE>
# VALUE: A string.
# Outputs: The escaped value.
#
# Escape VALUE for sed (useful for strings that contain / or .).
#
# Example:
# value=`escapeValue "usr/local"`
# echo "usr/bin/foo" | sed 's/usr/$value/g'
function escapeValue() {
    echo "$1" | sed -e 's/\//\\\//g; s/\./\\./g; s/\*/\\*/g; s/\[/\\[/g; s/\]/\\]/g' -
    #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, for use in bash.
#
# Example:
# # We want a file that's literally 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 ))

    if [[ "$a" == "" ]] || [[ "$b" == "" ]]; then
	err "markers not found"
	return 1
    fi

    top=`echo "$2" | head -n $a`
    bottom=`echo "$2" | tail -n +$b`

    echo "$top"
    echo
    echo "$3"
    echo "$bottom"
    return 0
}


##
# substituteCode <SOURCE-FILE> <START-MARKER> <END-MARKER> <TEMPLATE> <TEMPLATE-MARKER>
# SOURCE-FILE: file which contains the START-MARKER and END-MARKER.
# START-MARKER: string which locates the line number to start gathering code.
# END-MARKER: string which locates the line number to end gathering code.
# TEMPLATE: string which contains the TEMPLATE-MARKER.
# TEMPLATE-MARKER: string which locates the line number to place the gathering code.
# Outputs: The substituted code weaved into the TEMPLATE.
#
# Substitutes gathered code into a template. This is useful such that only one copy of a function
# is actually within the codebase, while it is copied into other locations at package build time.
#
# Example:
# output-file=`cat output-file`
# substituteCode "useful-stuff" "START SOMEFUNCTION" "END SOMEFUNCTION" "$output-file" "%InsertSomeFunctionHere%"` >output-file
function substituteCode() {
	local find_file="$1"
	local find_start_marker="$2"
	local find_end_marker="$3"
	local template_file="$4"
	local template_marker="$5"
	local find_start_line
	local find_end_line
	local find_total_line
	local find_text
	local template

	if [[ ${#@} != 5 ]]; then
		err substituteCode function needs five parameters of data.
		exit 1
	fi

	trace called with markers $find_start_marker:$find_end_marker, source file $find_file, template_marker=$template_marker

	# determine location line numbers to start, end, and total length
	# markers have to be unique to the source file and template file
	find_start_line=`cat "$find_file" | grep -n "$find_start_marker" | sed 's/:.*//'`
	find_end_line=`cat "$find_file" | grep -n "$find_end_marker" | sed 's/:.*//'`
	find_total_line=`cat "$find_file" | wc -l`

	if [[ "$find_start_line" == "" ]] || [[ "$find_end_line" == "" ]]; then
	    err could not locate start/end markers in $find_file
	    return 1
	fi
			
	# leave in the start and end markers
	(( a = find_total_line - ( find_start_line - 0 ) ));
	(( b = find_end_line - ( find_start_line + 1 ) ));

	# grab the text
	find_text=`cat "$find_file" | tail -n $a | head -n $b`
	[ -e "$template_file" ] && template=`cat $template_file`
	# weave the code into the template
	template=`joinLines "$template_marker" "$4" "$find_text"`
	echo "$template"
	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
## START YESNO
## Function is substituted from apkg-bashlib.
function yesNo() {
	export yesno_suppress=1
	local retval
	while [ 0 ]; do
		read -s -n1 -p "$1" || return 3
		in=`echo "$REPLY" | tr A-Z a-z`;
		echo "$in"
		if [[ "$in" == "n" ]]; then
			return 1
		elif [[ "$in" == "y" ]] || [[ "$in" == "" ]]; then
			return 0
		else
			echo "$intl_YESNO"
		fi
	done
}
## END YESNO


##
# compareVersions <REQUIRED> <CURRENT>
# REQUIRED: Required version.
# CURRENT: Current version.
# Returns: 0 if CURRENT equals or is bigger than REQUIRED, 1 not.
#
# 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 [ `expr index "${x[$index]}" '0'` -eq "1" ] || [ "${x[$index]}" == "" ]; then
			if [ `expr index "${y[$index]}" '[abcdefghijklmnopqrstuvwxyz]'` -eq "1" ]; then
				return 1
			fi
		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" '0'` -eq "1" ] || [ "$a" == "" ]; then
		if [ `expr index "$b" '[abcdefghijklmnopqrstuvwxyz]'` -eq "1" ]; then
			return 1
		fi
		# example: REQUIRED 2.4 > CURRENT 2.4.24-pre2 because the third decimal group
		# is undefined from REQUIRED, set a equal to 0 so that expr can eval the decimal group
		if [ "$a" == "" ]; then
			let "a=0"
		fi
	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" == "" ]]; then return 1; fi
			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" == "" ]]; then return 1; fi
			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 is substituted from apkg-bashlib.
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


# Usage: message <MESSAGE>
## START MESSAGE
## Function is substituted from apkg-bashlib.
function message() {
	if ! tty -s; then
		# there is no tty
		if [[ "$DISPLAY" == "" ]] || ! xdpyinfo &>/dev/null; then     # check for X
			# we're not in X, so just echo (we might be in a headless script)
			echo "$1"
		else
			# we ARE in X, and there is no TTY; started from a gui file manager
			if command -v zenity &>/dev/null; then
				zenity --info "--text=$1"
			elif command -v gdialog &>/dev/null; then
				gdialog --msgbox "$1" 100 100
			elif command -v kdialog &>/dev/null; then
				kdialog --msgbox "$1"
			elif command -v xmessage &>/dev/null; then
				xmessage "$1"
			else
				local DIALOG=
				DIALOG=`command -v dialog 2>/dev/null` || DIALOG=`command -v gdialog 2>/dev/null` || DIALOG=`command -v whiptail 2>/dev/null`
				if [[ "$DIALOG" != "" ]]; then
					# No graphical dialog; fallback to terminal showing a dialog
					launchInTerminal --title "Message" $DIALOG --msgbox "$1" 17 65
				else
					local msg=`escapeFilename "$1"`
					launchInTerminal --title "Message" bash -c "echo $msg; read"
				fi
			fi
		fi
	else
		# else there is a tty, ie we're in an xterm or the standard console
		# so just use echo
		echo "$1"
	fi
}
## END MESSAGE


##
# 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; check for write access
        [ -w "$1" ]
        return $?
    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> [--no-recurse]
# DIRECTORY: A directory name.
# --no-recurse: Do not recursively look for empty directories.
# 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,
# unless --no-recurse is passed.
function dirIsEmpty() {
	if [ -x "$autopackage_prefix/libexec/autopackage/dirisempty" ]; then
		"$autopackage_prefix/libexec/autopackage/dirisempty" "$@"
	# for use by scripts in main directory like unsetup
	elif [[ "$autopackage_libexecdir" && -x "$autopackage_libexecdir/dirisempty" ]]; then
		"$autopackage_libexecdir/dirisempty" "$@"
	else
		err dirisempty executable not found
		return 1
	fi
}


##
# 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() {
    trace called with $1, $2
    local file="$1"
    local 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
}


##
# addLine <FILENAME> <STRING>
# FILENAME: A filename.
# STRING: The line to add.
#
# Add STRING to FILENAME as a new newline. If the file doesn't already
# end with a newline, it will be added.
function addLine() {
    trace called with $1, $2
    local file="$1"
    local line="$2"

    if ! endsWithNewline "$file"; then
        echo >> "$file"
    fi
    echo "$line" >> "$file"
}


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() {
    local end=`tail -c 1 "$1"`
    test "$end" == "`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]
# OPTION -o: print COMMAND's output to stdout
# OPTION -l: print COMMAND's absolute filename to stdout
# OPTION -r: run COMMAND even if no parameters are given
# OPTION --: 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 if file located, then executed with parameters and exit code > 0;<br /> 0 if file is located, or if file is located, then executed with parameters and exit code = 0
#
# Finds the location of an object with the combined efforts of 'which' then 'whereis'
# If PARAMETERS are given, or if -o or -r is given, then it will run COMMAND with PARAMETERS (if any).
# <pre>
# 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
#     $lc_ret      - return value of COMMAND if COMMAND is run
# </pre>
# Example:
#     locateCommand -o kde-config --prefix
#         ---> $lc_array    = "/usr/bin/kde-config"
#         ---> $lc_location = "/usr/bin/kde-config"
#         ---> $lc_output   = "/usr"
#         ---> $lc_ret      = 0
#         ---> 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
#         ---> $lc_ret      = 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"

## START LOCATECOMMAND
## Function is substituted from apkg-funclib.
function locateCommand() {
	unset lc_array
	unset lc_location
	unset lc_output
	unset lc_command
	unset lc_ret
	local ret_location
	local ret_output
	local ret_run
	local temp

	# process option flags
	if [[ "$1" = "-o" ]]; then
		ret_output=1
		shift
	elif [[ "$1" = "-l" ]]; then
		ret_location=1
		shift
	elif [[ "$1" = "-r" ]]; then
		ret_run=1
		shift
	elif [[ "$1" = "--" ]]; then
		shift
	fi
	lc_command="$1"

	temp=`which "$lc_command" 2>/dev/null | sed "s|~|$HOME|"`
	if [ "$temp" ]; then
		lc_array="$temp"
		lc_location="$lc_array"

		trace which:lc_location=$lc_location
		if [[ $ret_run = 1 || $ret_output = 1 || "$2" != "" ]]; then
			shift
			lc_output=`"$lc_location" "$@"`
			lc_ret=$?
		fi

		[ "$ret_output" ] && echo "$lc_output"
		[ "$ret_location" ] && echo "$lc_location"
		return 0
	fi

	temp=`whereis "$lc_command" 2>&1 | sed 's/.*: *//'`
	if [ "$temp" ]; then
		local -a x
		x=( $temp )
		lc_array="${temp[@]}"

		index=0
		while [[ "$index" -lt "${#x[@]}" ]]
		do
			# We're only looking for executable ojects
			if [[ -x "${x[$index]}" ]]; then
				lc_location="${x[$index]}"

				if [[ "$2" != "" || $ret_output = 1 || $ret_run = 1 ]]; then
					shift
					lc_output=`"$lc_location" "$@"`
					lc_ret=$?

					trace whereis:lc_location=$lc_location
					[[ "$lc_ret" != "0" ]] && return 1
				fi
				[ "$ret_output" ] && echo "$lc_output"
				[ "$ret_location" ] && echo "$lc_location"
				return 0
			fi
			let "index = $index + 1"
		done
		return 1
	else
		unset lc_location
		return 1
	fi
}
## END LOCATECOMMAND

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

function outn() {
    s="$1"; shift;
    printf "$s" "$@"
    (( "$DEBUGLEVEL" > "0" )) && printf "$s" "$@" >> "$AUTOPACKAGE_DEBUG_LOGFILE"
    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;
    local r;
    for (( i=${start};i<=( ${#array[*]} - 1);i++ )) ; do
	dirpath="${dirpath}${array[${i}]}/"
	if [ ! -d "$dirpath" ] ; then
	    if ! l=$( mkdir "$dirpath" 2>&1 ); then
		IFS="$oIFS"
		err failed "$dirpath": $l
		return 1
	    fi
	    echo "$dirpath"
	else
	    warn directory $dirpath already exists
	fi
    done
    IFS="$oIFS"
    return 0
}


##
# 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'`

    # find the first actually existing directory
    while [[ "$diskspace_location" != "." ]]; do
        if [ -d "$diskspace_location" ]; then
                break
        fi
        diskspace_location=`dirname "$diskspace_location"`
    done

    trace needed=$diskspace_needed, location=$diskspace_location

    "$autopackage_prefix/libexec/autopackage/freespace" "$diskspace_location" $diskspace_needed
    return $?
}


##
# _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
# }
### START PUSHOPTE
### Function is substituted from apkg-bashlib.
function pushOptE() {
    local last=${#__opt_stack_e[@]}
    if [[ $- == *e* ]]; then
	__opt_stack_e[$last]="1"
    else	
	__opt_stack_e[$last]="0"
    fi	
}
### END PUSHOPTE


##
# popOptE
#
# Pops the stack for the shell option e. See pushOptE for more details
### START POPOPTE
### Function is substituted from apkg-bashlib.
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]
}
### END POPOPTE


##
# 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() {
    local v
    local lastarg=$#
    lastarg=${!lastarg}
    for v in $@; do
	local minor=`getMinor $v`
	local major=`getMajor $v`
	local sequence
	local c
	
	if [[ "$minor" == "" ]]; then minor="0"; fi
	
	# seq is crap. some versions insist we give it a delta, and then it bitches if we use 0 -1 0
	if [[ "$minor" == "0" ]]; then
	    echo -n "${major}.0 "
	else
            local oIFS="$IFS"; IFS=' '
	    for c in `seq -s' ' $minor -1 0`; do
		echo -n "${major}.$c"
		# don't output a space on the last item
		if (( c != 0 )) || [[ "$v" != "$lastarg" ]]; then echo -n " "; fi
	    done
            IFS="$oIFS"
	fi
	
    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 -n "$V "
    	    done
    	) | sort | uniq
    else
    	(
    	    for V in `echo "$@"`; do
    	    	echo -n "$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
}




##
# 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_FAIL"; normal;
    out "$intl_ASSERTION_NOT_REACHED";
    if [[ $ASSSERTION_TOPLEVEL_SHELL_PID != "" ]]; then
	kill $ASSSERTION_TOPLEVEL_SHELL_PID;
    else
	exit 1;
    fi
}


##
# replaceStr <FROM> <TO>
# FROM: A string.
# TO: A string.
# Outputs: The result.
#
# Reads input from stdin, replaces all occurances of FROM to TO, and output the result.
# Use this function instead of sed s/$from/$to/g, because it can handle special characters
# (/, \, $, etc.) correctly.
function replaceStr
{
	local from=`escapeValue "$1"`
	local to=`escapeValue "$2"`
	sed "s/$from/$to/g"
}

function _legacyWarn {
    red; outn "WARN: "; normal; out "Please rebuild this package ($1)";
}

##
# chewArgument
#
# process an argument value of the form --key=value, --key value, -k value and spits out the value on stdout
#
# Example:
# chewArgument --prefix=/usr/local  # => /usr/local
# chewArgument --key value          # => value
# chewArgument "--three four"       # => four (you can quote the input ok)
function chewArgument() {
    if [[ "$#" == "1" ]]; then set -- $@; fi
    if echo "$1" | grep '=' >/dev/null; then
	echo "$1" | sed 's/.*=//'
    else
	echo "$2"
    fi
}


### START STARTDOWNLOAD
### Function is substituted from apkg-bashlib.
function _downloadStart() {
	if ! which "$2" > /dev/null 2> /dev/null; then
		return 1
	fi

	URL="$1"
	echo -n "Downloading $1... "
	shift
	$@ "$URL" > "`basename $URL`"
	STATUS=$?

	if test "$STATUS" = "0"; then
		echo "done!"
		return 0
	else
		echo "failed!"
		return $STATUS
	fi
}
### END STARTDOWNLOAD


### START DOWNLOADFUNCTION
### Function is substituted from apkg-bashlib.
function download() {
	echo
	echo "Downloading $1..."
	echo
	if which proz > /dev/null 2> /dev/null; then
		if proz $PROZILLA_FLAGS -r "$1"; then
			echo "Download completed."
		else
			echo "Download failed."
			echo "Please check your Internet connection, or try again later."
			_finishWindow
			exit 1
		fi
	elif which wget > /dev/null 2> /dev/null; then
		if wget --cache=off $WGET_FLAGS -O $( basename "$1" ) -c "$1"; then
			echo "Download completed."
		else
			echo "Download failed."
			echo "Please check your Internet connection, or try again later."
			_finishWindow
			exit 1
		fi
	else
		(_downloadStart "$1" lynx -source ||
		_downloadStart "$1" links -source ||
		_downloadStart "$1" w3m -dump_source) &&
		return 0

		echo "Error: no download program found! Please install one of these programs:"
		echo "proz (Prozilla), wget, links, lynx, w3m"
		_finishWindow
		exit 1
	fi

	return 0
}
### END DOWNLOADFUNCTION


##
# getFile [OPTIONS] <FILENAME>
# FILENAME: file to retrieve information from.
# --perms: permission block of file.
# --links: links to file.
# --uid: user id of file.
# --gid: groud id of file.
# --mtime: last modified time in seconds of file.
# --name: name of file.
# --md5: generated MD5 checksum of file.
# --baseline: text to be used in baseline file.
# --no-whitespace: output to not contain whitespace
# --no-newline: output to not contain a newline
#
# Returns any information about a valid file.
#
# See also: getFileMD5().
#
# Example:
#   getFile "/lib/libfoo.so.1.1.2"
#
function getFile() {

    local output
    local line
    local file
    local count count_mtime
    local b i j
    local no_whitespace no_newline

    # grab last argument - the filename to match against
    declare -a args
    args=("$@")
    count=${#args[@]}
    b="${count}"
    file=`echo "${!b}"`

    # check for existance and output from command
    if [ -f "$file" ]; then
        # can not use --time-style because of older ls (fileutils) 4.1 - use --full-time and translate with date command
        #line=`/bin/ls -l --numeric-uid-gid --block-size=1 --time-style=+%s "$file"`
        line=`LC_ALL=C /bin/ls -l --numeric-uid-gid --block-size=1 --full-time "$file"`
        if [[ "$line" == "" ]]; then
            return 1
        fi
    else
        warn "Filename $file does not exist."
        return 1
    fi

    # decrement count and use it to parse over arguments
    (( count-- ))
    
    # if only one argument then set no_whitespace and no_newline
    [[ "$count" == "1" ]] && no_whitespace=1; no_newline=1;
    
    i=0
    while (( $i < count )); do

        unset temp
        case "$1" in

            "--perms" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $1 }'`
                        ;;

            "--links" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $2 }'`
                        ;;

            "--uid" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $3 }'`
                        ;;

            "--gid" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $4 }'`
                        ;;

            "--size" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $5 }'`
                        ;;

            "--mtime" )
                        elements=(`echo $line`)
                        count_mtime=("${#elements[@]}")
                        (( count_mtime-- ))
                        # convert possible --full-time `2004-02-25 17:55:03.000000000 -0800'
                        # to readable `2004-02-25 17:55:03 -0800' for conversion
                        for ((j=5; j<$count_mtime; j++)); do
                            if [ `expr index "${elements[$j]}" '.'` -gt "0" ]; then
                                temp=`echo -n "$temp"; echo "${elements[$j]} " | awk 'BEGIN { FS="." } { print $1 " " }'`
                            else
                                temp=`echo -n "$temp"; echo "${elements[$j]} "`
                            fi
                        done
                        temp=`date --date="$temp" +%s`
                        output=`echo -n "$output"; echo "$temp"`
                        ;;

            "--name" )
                        output=`echo -n "$output"; echo "$file"`
                        ;;

            "--md5" )
                        temp=`getFileMD5 "$file"`
                        output=`echo -n "$output"; echo "$temp"`
                        ;;

            "--baseline" )
                        #output=`getFile --md5 --perms --uid --gid --mtime --name --size "$file"`
                        output=`getFile --md5 --name "$file"`
                        unset no_whitespace
                        unset no_newline
                        ;;

            "--no-whitespace" )
                        no_whitespace=1
                        ;;

            "--no-newline" )
                        no_newline=1
                        ;;

            "*" )
                        ;;

        esac

        (( i++ ))
        if [[ "$output" != "" ]]; then
            if [ "$no_whitespace" ]; then
                output=`echo "$output"`
            else
                output=`echo "$output  "`
            fi
        fi
        shift

    done

    # output if output is available - otherwise fail
    if [[ "$output" != "" ]]; then
        if [ "$no_newline" ]; then
            echo -n "$output"
        else
            echo "$output"
        fi
        return 0
    fi
    return 1

}


##
# getBaseline [OPTIONS] <MATCH>
# MATCH: string to match and retrieve information from.
# --perms: permission block of file.
# --links: links to file.
# --uid: user id of file.
# --gid: groud id of file.
# --mtime: last modified time in seconds of file.
# --name: name of file.
# --md5: generated MD5 checksum of file.
# --no-whitespace: output to not contain whitespace
# --no-newline: output to not contain a newline
#
# Returns any information from the baseline file that matches the defined string.
# The MATCH string must match an entire piece of data, therefore a string that
# is a filename must be absolute.
#
# See also: getFile().
#
# Example:
#   getBaseline "/lib/libfoo.so.1.1.2"
#
function getBaseline() {

    local output
    local line
    local match
    local temp
    local b
    local count

    # grab last argument - the string to match against
    declare -a args
    args=("$@")
    count=${#args[@]}
    b="${count}"
    match=`echo "${!b}"`

    # check for existance and output from command
    if [ -e "$autopackage_db_location/$ROOTNAME/baseline" ]; then
        line=`grep "\ $match\ " "$autopackage_db_location/$ROOTNAME/baseline"`
        if [[ "$line" == "" ]]; then
            return 1
        fi
    else
        err "Baseline file $autopackage_db_location/$ROOTNAME/baseline does not exist."
        return 1
    fi

    # decrement count and use it to parse over arguments
    (( count-- ))
    
    # if only one argument then set no_whitespace and no_newline
    [[ "$count" == "1" ]] && no_whitespace=1; no_newline=1;
    
    i=0
    while (( $i < count )); do

        case "$1" in

            "--perms" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $2 }'`
                        ;;

            "--uid" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $3 }'`
                        ;;

            "--gid" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $4 }'`
                        ;;

            "--size" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $7 }'`
                        ;;

            "--mtime" )
                        output=`echo -n "$output"; echo "$temp" | awk '{ print $5 }'`
                        ;;

            "--name" )
                        output=`echo -n "$output"; echo "$line" | awk '{ print $2 }'`
                        ;;

            "--md5" )
                        output=`echo -n "$output"; echo "$line" | cut -b-32`
                        ;;

            "--no-whitespace" )
                        no_whitespace=1
                        ;;

            "--no-newline" )
                        no_newline=1
                        ;;

            "*" )
                        ;;

        esac

        (( i++ ))
        if [[ "$output" != "" ]]; then
            if [ "$no_whitespace" ]; then
                output=`echo "$output"`
            else
                output=`echo "$output  "`
            fi
        fi
        shift

    done

    # output if output is available - otherwise fail
    if [[ "$output" != "" ]]; then
        if [ "$no_newline" ]; then
            echo -n "$output"
        else
            echo "$output"
        fi
        return 0
    fi
    return 1

}


##
# getFileMD5 <FILENAME>
# FILENAME: file to generate MD5 checksum.
#
# Generate a MD5 checksum for the given file.
#
# See also: getFile(), getBaseline().
#
# Example:
#   getFileMD5 "/lib/libfoo.so.1.1.2"
#
function getFileMD5() {

    if [ -e "$1" ]; then
        "$apkg_md5sum_command" "$1" >/dev/null
        local r="$?"
        if [[ "$r" == "0" ]]; then
           local output=`"$apkg_md5sum_command" "$1" | cut -b-32`
           echo "$output"
           return 0
        else
           return "$r"
        fi
    else
        return 1
    fi

}



##
# launchInTerminal [--title TITLE] <COMMAND> [ARGUMENTS]
# --title: A title for the terminal.
# COMMAND: A command.
# ARGUMENTS: Arguments to pass to COMMAND.
# Returns: 0 on success, 1 on failure.
#
# Launch COMMAND in an X terminal emulator, selected according to
# currently running desktop environment. Known terminal emulators
# include Konsole, gnome-terminal (both v1 and v2), and xterm.

## START LAUNCHINTERMINAL
## Function is substituted from apkg-funclib.
function launchInTerminal() {
	# Don't grep for grep...
	local ps=`ps auxw | grep -v grep`
	local title="$1 - Terminal"
	if [[ "$1" = "--title" ]]; then
		title=$2
		shift
		shift
	fi

	function _gnomeTerminal()
	{
		# Autodetect whether we're dealing with GNOME Terminal 1
		LC_ALL=C LC_MESSAGES=C LANG=C locateCommand gnome-terminal --version
		if echo "$lc_output" | grep -Fq 'Gnome Terminal 1.'; then
			gnome-terminal "$@"
			return 0
		else
			gnome-terminal --disable-factory --working-directory="`pwd`" "$@"
			return 0
		fi
	}

	if echo "$ps" | grep -q ' kicker'; then
		# KDE
		locateCommand konsole -T "$title" --notoolbar -e "$@" ||
		_gnomeTerminal -t "$title" -x "$@" ||
		locateCommand xterm -T "$title" -e "$@" ||
		return 1
	elif echo "$ps" | grep -q gnome-panel; then
		# Gnome 2
		_gnomeTerminal -t "$title" -x "$@" ||
		locateCommand konsole -T "$title" --notoolbar -e "$@" ||
		locateCommand xterm -T "$title" -e "$@" ||
 		return 1
	elif echo "$ps" | grep -q panel; then
		# GNOME 1
		_gnomeTerminal -t "$title" -x "$@" ||
		locateCommand konsole -T "$title" --notoolbar -e "$@" ||
		locateCommand xterm -T "$title" -e "$@" ||
		return 1
	else
		# Neutral
		locateCommand xterm -T "$title" -e "$@" ||
		_gnomeTerminal -t "$title" -x "$@" ||
		locateCommand konsole -T "$title" --notoolbar -e "$@" ||
		return 1
	fi

	return 0
}
## END LAUNCHINTERMINAL

##
# isInList [-F?] <SEARCH-ITEM> <LIST>
# -F: specify the list separator, defaults to :
# SEARCH-ITEM: which item to look for
# LIST: string containing the items separated by the field separator
#
# isInList returns 0 if the given SEARCH-ITEM is one of the members of
# LIST. LIST is a string with members separated by either : or the specified
# separator character.
#
# Example:
# isInList a a:b:c -> returns 0
# isInList -F/ x a/b/c -> returns 1
function isInList() {
    local oIFS=$IFS
    
    if [[ ${1:0:2} == "-F" ]]; then
	IFS=${1:2:3}
	shift
    else
	IFS=:
    fi
    
    if [[ ${#@} != 2 ]]; then
	err not enough arguments given
	IFS=$oIFS
	return 1
    fi
    
    for i in $2; do
	if [[ "$i" == "$1" ]]; then
	    IFS="$oIFS";
	    return 0;
	fi
    done
    
    IFS=$oIFS
    return 1
}


### THIS FILE MUST END IN A NEWLINE

