# easybashlib - EasyBash Library...
#
# For more lib modules and improvement ideas see:
# http://dberkholz.com/2011/04/07/bash-shell-scripting-libraries/


# To learn how to include easybashlib in your scripts,
# have a look into easybashgui.
# It contains instructions on how to load easbashgui as a module,
# and is at the same time an example of including easybashlib.


# prevent repeated inclusion
if [ "${eb_incl_easybashlib__imported+defined}" = "defined" ]
then
	echo "easybashlib: ERR: easybashlib not sourced (already sourced)."
	return 0
fi
#
#echo -e "...easybashlib sourcing..."
#
#eb_incl_easybashlib__imported=1
eb_incl_easybashlib__imported=please_prevent_next_time

#
###################
# Debugging stuff #
###################
#


# Bash debugging:
: we_are_here
[ $(declare | grep "BASH_SOURCE=" | wc -c ) -eq 0 ] && declare BASH_SOURCE ; \
		[ "${BASH_SOURCE:-unset}" = "unset" ] && declare -r BASH_SOURCE="${eb_runtime_invoke_name}"
[ $(declare | grep "LINENO=" | wc -c ) -eq 0 ] && declare LINENO ; \
		[ "${LINENO:-unset}" = "unset" ] && declare -ir LINENO="0"
[ $(declare | grep "FUNCNAME=" | wc -c ) -eq 0 ] && declare FUNCNAME ; \
		[ "${FUNCNAME:-unset}" = "unset" ] && declare -r FUNCNAME="unknown_function"
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'

#set -o verbose -o xtrace
[ "${BASHPID:-unset}" = "unset" ] && declare -ir BASHPID="${PPID}"



#
##########################################à
#
if [ "${easybashgui_debug_mode}" = "YES" ]
	then
	# enable some sane default checks to make bash scripts more robust
	# http://www.davidpashley.com/articles/writing-robust-shell-scripts.html
	echo "easybashgui_debug_mode ON" ; echo
	set -o nounset -o errexit
	shopt -s extdebug
	#
elif [ "${easybashgui_debug_mode}" = "NO" ]
	then
	#
	#echo "easybashgui_debug_mode OFF"
	set +o nounset +o errexit
	shopt -u extdebug
	#
else
	#
	echo "easybashgui_debug_mode UNSET"
	:
	#
fi
##########################################à

# Note that a lot of code out there (e.g. easybashgui) gives errors if run with the checks enabled,
# and has thus a high potetial to do unexpected things on errors.
# The following function allows to executes such code without the checks (but you have been warned):
eb_skip_exit_on_err_or_unset() 
	{
	set +o nounset +o errexit
	shopt -u extdebug
	#echo "easybashgui_debug_mode OFF"
	"$@"
	exit_code=$?
	#
	##########################################
	#
	if [ "${easybashgui_debug_mode}" = "YES" ]
		then
		# enable some sane default checks to make bash scripts more robust
		# http://www.davidpashley.com/articles/writing-robust-shell-scripts.html
		echo "easybashgui_debug_mode ON" ; echo
		set -o nounset -o errexit
		shopt -s extdebug
		#
	elif [ "${easybashgui_debug_mode}" = "NO" ]
		then
		#
		#echo "easybashgui_debug_mode OFF"
		set +o nounset +o errexit
		shopt -u extdebug
		#
	else
		#
		echo "easybashgui_debug_mode UNSET"
		:
		#
	fi
	##########################################à
	#
	return $exit_code
	}


# Hint to write code that runns with the checks enabled:
# Catch error codes by using "if <command>; then..." statements,
# or "<command> || true" (if you really don't need to act on any error),
# and catch (dynamically set) unset variables like this: "${variablename:-unset}".



# Configuring eb_debug_action=1 will not enable debug output during initialization
# (before load_base_config has run), however executing "export eb_debug_action=1"
# prior to starting sucsynct will do it.
#eb_debug_action="1"



# commands preceeded by eb_debug_action should only execute with the debug-flag set or eb_debug_action=1
function eb_debug_action() {
	if    [ -s "${eb_state_dir:-}/debug-flag" ] \
	   || [ ! -e "${eb_state_dir:-}/debug-flag" -a "${eb_debug_action:-0}" = "1" ]
	then
		$@ || true   # || true, so the return code is always 0
	fi
}


function eb_debug() {
	if    [ -s "${eb_state_dir:-}/debug-flag" ] \
	   || [ ! -e "${eb_state_dir:-}/debug-flag" -a "${eb_debug_action:-0}" = "1" ]
	then
		return 0
	else
		return 1
	fi
}

function eb_debug_output()
{
eb_debug_action echo -e "${eb_process_scope:-$eb_runtime_invoke_name} (S$$-P$BASHPID): $@"
}


function eb_stop_debug()
{
	eb_debug_output "Disabling debug output."
	echo -n "" > "${eb_state_dir}/debug-flag"
}

function eb_start_debug()
{
	echo -n "set (non-empty) at runtime" > "${eb_state_dir}/debug-flag"
	eb_debug_output "Debug output enabled."
}



function eb_error_output()
{
	echo -e "${eb_process_scope:-$eb_runtime_invoke_name} (S$$-P$BASHPID): $@" 1>&2
}




#####################
# Process managment #
#####################


function eb_kill_process_tree_of_pid() {
# Grows a process tree that can kill an existing process tree beginning with
# the given PID as parent.
# (Spawning a killtree allows to avoid to kill 0 (the process group) that may even kill parent and sibling processes.)
# Basic idea on http://stackoverflow.com/questions/392022/best-way-to-kill-all-child-processes

# To support decending the process tree, the calling script must call this function if
# the script is executed as: $0 killtree ${this_descendant_pid} ${kill_signal}

	local target_pid=$1
	local kill_signal=${2-TERM}
	local this_descendant_pid
	local this_descendant_pname
    
	local descendant_pids=$(ps -o pid --no-headers --ppid ${target_pid})
    
    #echo "$BASHPID: descendant_pids=${descendant_pids:-}"
    # newline separated!
    
	# Kill the target process right away (may even be the calling process),
	# to prevent it from spawning new processes.
	#echo "$BASHPID: killing target ${target_pid}"
	kill -${kill_signal} ${target_pid} || true
	eb_debug_output "Signaled ${kill_signal} to ${target_pid}."
	
	
	# call a killtree for each child process (except self and other killtree processes)
	for this_descendant_pid in ${descendant_pids:-}
	do
		#echo "$BASHPID: -${this_descendant_pid}-"
		if [ -e /proc/${this_descendant_pid} ]
		then
			this_descendant_pname="$(</proc/${this_descendant_pid}/cmdline)"
		else
			this_descendant_pname="maybe killed by another killtree"
		fi
		#if    [ "${this_descendant_pid}" != "$BASHPID" ] && 
		if [[ "${this_descendant_pname}" != *killtree* ]]
		then
			#echo "$BASHPID: calling killtree ${this_descendant_pid}"
			eb_debug_output "Spawning ${kill_signal} killtree for ${this_descendant_pid}."
			$0 killtree ${this_descendant_pid} ${kill_signal}
			#eb_kill_process_tree_of_pid ${this_descendant_pid} ${kill_signal}
			
		else
			true 
			#echo "$BASHPID: not killing ${this_descendant_pid} ${this_descendant_pname}"
		fi
	done
	
}




# Define a mechanism for automatic clean up on exit, based upon the idea
# http://www.linuxjournal.com/content/use-bash-trap-statement-cleanup-temporary-files
#
#declare -a eb_on_exit_commands #UNUSEFUL since causes "unbound variable" because array is set ONLY if at least element exists...
#ebg_on_exit_commands=( "echo -en \"\nEBG v.${MODULE_VERSION} exiting... \" 1>&2" )
ebg_on_exit_commands=( ":" )
#echo "1) How many elements in array \${ebg_on_exit_commands[@]}: ${#ebg_on_exit_commands[@]} .........DEBUG" 1>&2

ebg_on_exit()
{
	# evaluate ebg_on_exit_commands() in reverse order of registration ...
	#
	#echo "3) How many elements in array \${ebg_on_exit_commands[@]}: ${#ebg_on_exit_commands[*]} .........DEBUG" 1>&2
	for (( i = $(( ${#ebg_on_exit_commands[@]} - 1 )) ; i > -1; i-- ))
		do
		#echo "ebg_on_exit $i: ${ebg_on_exit_commands[${i}]} .........DEBUG" 1>&2
		eb_debug_output "ebg_on_exit ${i}: ${ebg_on_exit_commands[${i}]}"
		eval "${ebg_on_exit_commands[${i}]}"
	done
	#
	#echo -e "Bye!" 1>&2
	#
	# Hint: Scripts that fork should start with "ebg_on_exit $0 killtree $BASHPID", to clean up child
	# processes. (Also remember to kill all disowned processes if appropriate.)
	#
	#echo "Exiting. .........DEBUG" 1>&2
	eb_debug_output "Exiting."
	#
	exit
}

ebg_add_on_exit()
{
	#
	#echo "2) How many elements in array \${ebg_on_exit_commands[@]}: ${#ebg_on_exit_commands[@]} .........DEBUG" 1>&2
	#
	local n=${#ebg_on_exit_commands[@]}
	#
	#echo "ebg_on_exit: ${ebg_on_exit_commands[$n]} .........DEBUG" 1>&2
	eb_debug_output "ebg_on_exit: ${ebg_on_exit_commands[${n}]}"
	#
	if [[ ${n} -eq 1 ]]
	then
		#echo "Setting ebg_on_exit trap. .........DEBUG" 1>&2
		eb_debug_output "Setting ebg_on_exit trap."
		trap ebg_on_exit EXIT
	fi
	#
	ebg_on_exit_commands[${n}]="$*"
	#
}






###############
# Directories #
###############


# determine eb_conf_dir
if [ "${USERPROFILE:-undefined}" = undefined ]
then
	if [ "${HOME:-undefined}" = undefined ]
	then
		eb_conf_basedir="c:/.config"
	else
		if [ "${XDG_DATA_HOME:-undefined}" = undefined ]
		then
			eb_conf_basedir="${HOME}/.config"
		else
			eb_conf_basedir="${XDG_DATA_HOME}"
		fi
	fi
else
	eb_conf_basedir="${USERPROFILE}/.config}"
fi
eb_conf_dir="${eb_conf_basedir}/${eb_runtime_invoke_name}"
eb_debug_output "eb_conf_dir is set to $eb_conf_dir"



# determine eb_log_dir
if [[ "$EUID" -ne 0 ]]
then
	if [ "${USERPROFILE:-undefined}" = undefined ]
	then
		if [ "${HOME:-undefined}" = undefined ]
		then
			eb_log_basedir="c:/var/log"
		else
			eb_log_basedir="${HOME}/.local/var/log"
		fi
	else
		eb_log_basedir="${USERPROFILE}/.local/var/log"
    fi
else
	eb_log_basedir=/var/log
fi
eb_log_dir="${eb_log_basedir}/${eb_runtime_invoke_name}"
eb_debug_output "eb_log_dir is set to $eb_log_dir"




# determine eb_persistence_dir
if [ "${USERPROFILE:-undefined}" = undefined ]
then
	if [ "${HOME:-undefined}" = undefined ]
	then
		eb_persistence_basedir="c:/var/log"
	else
		eb_persistence_basedir="${HOME}/.local/var"
	fi
else
	eb_persistence_basedir="${USERPROFILE}/.local/var"
fi
eb_persistence_dir="${eb_persistence_basedir}/${eb_runtime_invoke_name}"
eb_debug_output "eb_persistence_dir is set to $eb_persistence_dir"




# determine eb_state_dir
[ -e "/dev/shm" ] && export eb_state_basedir=/dev/shm || export eb_state_basedir=/tmp #This is for old *BSD systems without /dev/shm...

function eb_get_state_dir()
{
	local process_name="${1:-${eb_runtime_invoke_name}}"
	local -a files
	local candidate_file
	local file_count=0
	local old_IFS
	
	# enumerate all directories with the right name and permissions in $files array
	old_IFS=$IFS
	#
	find ${eb_state_basedir}/${process_name}-${USER}.* -maxdepth 0 -perm 0700 -user $USER -type d -print0 2>/dev/null \
	| while IFS= read -r -d '' candidate_file
			do
			files[file_count++]="$candidate_file"
		done 
	IFS=$old_IFS
	
	# if we found matching directories, use the first one
	if [ "${#files[@]}" -gt 0 ]
	then
		echo -n "${files[0]}"
	fi
}	


function eb_set_state_dir()
{
	# if emptied, we still need to identify the state dir (again)
	if [ "${eb_state_dir:-undefined}" = "undefined" ] || [ "${eb_state_dir}" = "" ]
	then
		
		# Because $eb_state_dir is a simple string, we are able to export it here to make it available
		# in subshells.
		export eb_state_dir
		eb_state_dir="$(eb_get_state_dir)"

		# if nothing was found, create new eb_state_dir
		if [ "${eb_state_dir}" = "" ]
		then
			if [ -e "/dev/shm" ] #This is for old *BSD systems without /dev/shm...
				then
				#eb_state_dir=$( mktemp --tmpdir=/dev/shm -d ${eb_runtime_invoke_name}-${USER}.XXXXXXX )
				eb_state_dir=$(cd /dev/shm ; mktemp -d ${eb_runtime_invoke_name}-${USER}.XXXXXXX ; cd - )
			else
				eb_state_dir=$(cd /tmp ; mktemp -d ${eb_runtime_invoke_name}-${USER}.XXXXXXX ; cd - )
			fi
			# TODO: call function that only removes eb_state_dir if quiting all (not if just closing admin-ui)
			#eb_add_on_exit	"rm -rf $eb_state_dir"
		fi
	fi
	eb_debug_output "eb_state_dir is set to $eb_state_dir"
}



if [ "${eb_state_dir:-undefined}" = "undefined" ]
then
	eb_set_state_dir
fi







##########
# Checks #
##########


function eb_gen_check_code()
# Generates code to check the tests given as parameters (all tests if none was specified).
# Usefull to run selections of tests in context as needed.
# The code lists testing results and exits with appropriate error code.
{

	# echoing the requested code to stdout

	# declaring vars
	# remote (busybox) may not support associative arrays
	echo "#generated checking code:"
	echo "declare error_code=0"
	echo "declare -a check_names"
	echo "declare -a check_commands"
	echo "declare -a check_reasons"

	# populating array
	if [ "$#" = "0" ]
	then
		for this_test in "${!eb_test_commands[@]}"
		do
			echo -e "check_names+=( \"${this_test}\" )"
			echo -e "check_commands+=( \"${eb_test_commands[$this_test]}\" )"
			echo -e "check_reasons+=( \"${eb_test_reasons[$this_test]}\" )"
		done
	else	
		for this_test in "$@"
		do
			echo -e "check_names+=( \"${this_test}\" )"
			echo -e "check_commands+=( \"${eb_test_commands[$this_test]}\" )"
			echo -e "check_reasons+=( \"${eb_test_reasons[$this_test]}\" )"
		done
	fi

	# giving context
	echo "echo -e \"Dependency checking results from host: \$HOSTNAME\""

	# loop over specified checks
	cat << "_____checking_loop____END"
for this_check in "${!check_names[@]}"
do
	echo -e "\nChecking for ${check_names[$this_check]}:"
	if eval ${check_commands[$this_check]} 2>&1
	then
		echo -e "\n\t Passed.\n"
	else
		echo -e "\n\t FAILED!  (${check_reasons[$this_check]})\n"
		error_code=1
	fi
done
_____checking_loop____END


	# if a check failed, exit with error, else exit cleanly
	echo "[ \"\$error_code\" = \"1\" ] && exit 1"
	echo "exit 0"
}



function eb_check_local()
{
	eb_gen_check_code "$@" | bash
	return $?
}





##############################################################
# Shared key-value paist in current state or persistent maps #
##############################################################
# In contrast to associative arrays that can not be exported,
# these maps can be shared between processes and instances.


# Example uses:
# eb_map put persistent "FavoriteSportOf" "Adam" "tennis"
# blocked_activity=$(eb_map get state "ActivityOf" "Adam")


eb_map() {
#parameters: action, storage_type, key, value, process_name

	[ "${#}" -lt 2 ] && exit 1
	local action="${1}"
	local storage_type="${2}"
	local mapname="${3}"
	local key="${4}"
	local value="${5}"
	local process_name="${6:-${eb_runtime_invoke_name}}"
	local mapdir
	
	if [ "${storage_type}" = "persistent" ]
		then
		mapdir="${eb_persistence_basedir}/${process_name}/eb_key-value-maps"
	else
		mapdir="$(eb_get_state_dir ${process_name})/eb_key-value-maps"
	fi
	
	if [ "${action}" = "put" ]
		then
		[ -d "${mapdir}/${mapname}" ] || mkdir -p "${mapdir}/${mapname}"
		echo $value >"${mapdir}/${mapname}/${key}"
	elif [ "${action}" = "get" ]
		then
		cat "${mapdir}/${mapname}/${key}"
	fi
}


return



