#!/bin/sh
################################################
#
# This is supplementary functions file
# it is not intended to be called directly
# see functions digest below
#
# Any global variable or function name defined here MUST start with "_"
# to avoid namespace clash (all user-defined names must NOT start with "_")
# Names started with two _'s are internals and should not be used outside

################################################
# Variables:

export _Me=`basename "$0"`			# script name
test -n "$_GMe" ||
export _GMe="$_Me"				# global script name (for set of scripts)
export _FMe=`realpath "$0"`			# script fullpath
export _MeDir=`dirname "$_FMe"`			# script directory
export _MePID="$$"				# PID of current shell
test -n "$_Home" ||
export _Home="$HOME"				# base directory
export _AMe=`echo "$_Me" | tr -cd "[:alnum:]"`	# script name alphanumeric part (for generating filenames)
export _RC="$_Home/.$_GMe"rc			# config file
export _RC_HOST="$_Home/.$_GMe"rc."`hostname`"	# host specific config file
export _Verb=0					# verbosity level
export _Options=""				# getopts options (generated!)
export _Usage=""				# short help string
export _Help=""					# long help string
export _TmpFiles=""				# temporary files
export _TmpDirs=""				# temporary directories
export _LC_COLLATE=C				# LC_COLLATE submissions
export __Version=0.5				# this functions file version
export _Version=0.0				# software version
__CR="
"
__Unsafe=""					# set this to non-empty to enable unsafe actions
						#(like non-/tmp/-directory deleting etc.)

# LC_COLLATE wrappers (e. g. for [a-z] regexps work properly etc.)
# sed grep
sed() { env LC_COLLATE="$_LC_COLLATE" sed "$@"; }
grep() { env LC_COLLATE="$_LC_COLLATE" grep "$@"; }

################################################
# Message displaying and error system

# Internal: display a message
#	Onw may redefine this (e.g. with XDialog) for another displaying method
__Message() { # message [title]
  { test -n "$2" && echo "$2: $1" || echo "$1"; } >&2
}

# Display an error to stderr
#	check _Verb if to display message
#	exit if errorlevel >0
#	do not display error type in "Q" (quiet) mode
_Error() { # errormsg [errorlevel|"q" [errorlevel]]
  local ER ret
  case "${2:-1}" in
    [1-9]*) ER="ERROR"; ret="${2:-1}" ;;
    [0Ii]*) ER="INFO"; ret=0 ;;
    [-Ww]*) ER="WARNING"; ret=-1 ;;
    [Qq]*) ER=""; ret="${3:--1}";;
    *) ER="ERROR"; ret=1 ;;
  esac
  if [ "$_Verb" -ge 1 ]	# verbose
  then
    __Message "$1" "$ER"
  elif [ "$_Verb" -ge 0 ]	# normal
  then
    test "$ret" -eq 0 || __Message "$1" "$ER" >&2
  else				# quiet
    test "$ret" -le 0 || __Message "$1" "$ER" >&2
  fi
  test $ret -le 0 || exit $ret
}

# Display a message to stderr
_Msg() { # [message]
  _Error "${*:-<STUB>}" q
}

################################################
# Temporary files and exit handler

# Remove all temporary files and directory
_clean_tmp() {
  local F
  test -z "$_TmpFiles" || echo "$_TmpFiles" | while read F
  do test -z "$F" || rm -f "$F"; done

  test -z "$_TmpDirs" || echo "$_TmpDirs" | while read F 
  do
    case "$F" in
      */tmp/?*) # it seems safe to delete this
      		rm -rf "$F";;
      "") ;;
      *) _Error "It seems unsafe to delete '$F' directory" w ;;
    esac
  done
  _TmpDirs=""; _TmpFiles=""
}

__cleanup_handler() { # [exit status]
  trap - EXIT
  _clean_tmp
  exit "$1"
}

# Perform actions on exit
_exit_handler() {
  __cleanup_handler "$?"
}

# Perform actions on deadly signal and exit 
_signal_handler() {
  __cleanup_handler 143	# SIGTERM+128
}

trap _exit_handler EXIT
trap _signal_handler HUP INT QUIT PIPE TERM

# create a temporary file and store its name to _TmpFiles
# second parameter: [-]d* -- directory, [-]p* -- pipe, other -- file
# assign filename to "variable"
_TmpFile() { # variable [type]  
  local _F _P _Z _N="file"
  case "$1" in
    *[!_a-zA-Z0-9]*) _Error "Cannot store valuse to '$1' variable, please user alphanumeric name" ;;
    "") _Error "no variable name provided to _TmpFile function" ;;
  esac
  case "$2" in
    *p|p*) _P=""; _N="pipe";;
    *d|d*) _P="-d"; _N="directory";;
  esac
  _F=`mktemp -t $_P $_AMe.$_N.XXXXXXXXXXXX` || _Error "Cannot create '$_F' temporary $_N"
  case "$2" in
    *p|p*) # XXX look for race conditions here
        _Z="`mktemp XXXXXXXXXXXX`"
    	mv "$_F" "$_Z"
	mkfifo "$_F"
	rm -f "$_Z"
	;;
  esac
  case "$_N" in 
    directory) _TmpDirs="$_TmpDirs
$_F" ;;
    *) _TmpFiles="$_TmpFiles
$_F" ;;
  esac
  eval "export $1='$_F'"
}

# create a temporary directory and store its' name to _TmpDirs
# assign filename to "variable"
_TmpDir() { # variable
  _TmpFile "$1" directory
}

################################################
# Help system
# TODO long options

# WARNING: this function strongly depends on "case" operator programming style:
#	key) # help string
# if help string contains capitalized word, it will be used as parameter name
# E. g.:
#	o) # redirect output to FILE
# will produce this help string:
#	-o FILE		redirect output to FILE	
# By default, help strings are searched
# from the first occurance of " getopts " string to the first " esac$" pattern
# so keep a commentary after additional "esac" if it emerges inside
#
# Generted varsiables:
# _Options -- option string in getopts format

# Store a help information
_MakeHelp() { # description [tail_text [additional_help [start_pattern end_pattern]]]
  local descr
  local selfname
  case "$1" in
    *--*) selfname="${1%% -- *}"; descr="${1##* -- }" ;;
    ?*) selfname="$_Me"; descr="$1" ;;
    "") _Error "Insufficient parameters in _MakeHelp" ;;
  esac
  local st="${4:-" getopts "}"
  local en="${5:-"[	 ]esac[ 	]*$"}"
  local sedfilter="/$st/,/$en/"
  local sedFpattern='^[ 	]*\([^)]*\))[ 	]*#[ 	]*\(.*\)'
  local sedUpattern='^[ 	]*\([^)]*\))[ 	]*#[ 	]*![ 	]*\(.*\)'
  local sedPpattern='^[ 	]*\([^)]*\))[ 	]*#[ 	]*\([^!]*[^!A-Z]\([A-Z]\{3,\}\).*\)'
  local genhelp="$(sed -n "$sedfilter{
  /$sedPpattern/s/$sedPpattern/\t-\1 <\3>\t\2/p
  /$sedUpattern/s/$sedUpattern/(TODO)\t-\1\t\t\2/p
  /$sedFpattern/s/$sedFpattern/\t-\1\t\t\2/p
  }" "$_FMe")"
  _Options=$(sed -n "$sedfilter{
  /$sedPpattern/s/$sedPpattern/\1:/p
  /$sedUpattern/n
  /$sedFpattern/s/$sedFpattern/\1/p
  }" "$_FMe" | tr -d '\n')
  _Usage="$selfname v$_Version -- $descr
Usage: $_Me [-$_Options]${2:+" "}$2"
  _Help="
$genhelp${3:+$__CR}$3"
}

################################################
# Configuration file editing

# Set "Variable=Value" inside Context of File or append it at the start of the Context
_CfgEqSet() { # File Variable Value [Context=0,$]
  local Context="${4:-1,\$}"
  N=$(sed -n "$Context{/^[ 	]*$2[ 	]*=/=}" "$1")
  # if Variable=Value exists
  test -n "$N" &&
  for n in $N
  do # Replace
    sed -i "$n""s/^\([ 	]*$2[ 	]*=[ 	]*\).*/\1$(_Quote "$3")/" "$1"
  done || { # Append
    N=$(sed -n "$Context{=;q}" "$1")
    test -n "$N" &&
    sed -i "$N""a\
    $2=$3
    " "$1" ||
    _Error "No '$Context' context found in $1" -1
  }
}

__CfGet() { # File Context Variable Div Value Tail
  test -z "$(sed -n "$2{=}" "$1")" &&
  _Error "No '$2' context found in $1" -1 || {
  local ret=$(sed -n "$2{/$3$4$5$6/{s/$3$4\($5\)$6/\1/p;q}}" "$1")
  echo -n $ret
  test -n "$ret"
  }
}

# Print Value from "Variable=Value" inside Context of File
_CfgEqGet() { # File Variable [Context=1,$]
  __CfGet "$1" "${3:-1,\$}" "^[ 	]*$2" "[ 	]*=[ 	]*" ".*" ""
}

# Print Value from "Variable Value" inside Context of File
_CfgBlGet() { # File Variable [Context=1,$]
  __CfGet "$1" "${3:-1,\$}" "^[ 	]*$2" "  *" ".*" ""
}

# Delete all "Variable=" entries inside Context of File
_CfgEqDel() { # File Variable [Context=1,$]
  local Context="${3:-1,\$}"
  test -z "$(sed -n "$Context{=}" "$1")" &&
  _Error "No '$Context' context found in $1" -1 ||
  sed -i "$Context{/^[ 	]*$2[ 	]*=/d}" "$1"
}

# Comment all "Variable=" entries inside Context of File
_CfgEqComm() { # File Variable [Context=1,$]
  local Context="${3:-1,\$}"
  test -z "$(sed -n "$Context{=}" "$1")" &&
  _Error "No '$Context' context found in $1" -1 ||
  sed -i "$Context{s/^\([ 	]*$2[ 	]*=.*\)/#\1/}" "$1"
}

# Uncomment all "#Variable=" entries inside Context of File
_CfgEqUnComm() { # File Variable [Context=1,$]
  local Context="${3:-1,\$}"
  test -z "$(sed -n "$Context{=}" "$1")" &&
  _Error "No '$Context' context found in $1" -1 ||
  sed -i "$Context{s/^[ 	]*#\([ 	]*$2[ 	]*=.*\)/\1/}" "$1"
}

################################################
# Parallel execution functions
 
# To run a number of tasks in parallel:
# first call _II_init <max_number_of_tasks>
# next call _II_run <task> [<parameters...>] for each task
# and last call _II_wait to wait for last task to be done

# Detect number of cores
_II_cores() { #
  N="`ls -d /proc/sys/kernel/sched_domain/cpu* 2>/dev/null | wc -l`" ||
  N="`sysctl -n kern.smp.cpus 2>/dev/null`"
  test -z "$N" && _Msg "Warning: cannot determine number of CPUs"
  echo "$(($N+0))"
}

# Initialize parallel environment for running maxproc tasks at once
_II_init() {	# [maxproc]
  _TmpFile __II_count		# number of processes
  _TmpFile __II_sync pipe	# pipe for end-of-proc catching
  __II_maxproc="${1:-$(_II_cores)}"	# parallel slots number
  __II_current=0		# tasks already executed
}

__II_done() { wc -l < $__II_count; }

__II_exit() {
  trap - EXIT
  echo $__II_err >> $__II_count
  echo $__II_err > $__II_sync
  exit $__II_err
}

___II_next() {
  o.par2sat.sh $__II_sync $__II_count "$@"
}

__II_next() {
  __II_err=0
  trap __II_exit EXIT
  "$@"
  __II_err="$?"
}

__II_pause() { { read __II_err < $__II_sync; } 2>/dev/null || :; }

# Wait for empty slot becomes available, then execute a task
_II_run() { # task [args ...]
  test $(($__II_current - $(__II_done) )) -lt $__II_maxproc || __II_pause
  (__II_next "$@") &
  __II_current=$(($__II_current + 1))
}

# Wait for all tasks to be done
_II_wait() {
  while test $(($__II_current - $(__II_done) )) -ne 0
  do __II_pause; done
}

################################################
# XTerm voodoo

export __XT=""		# XTerm voodoo text container
# Tackle XTerm to push response on string (e. g. ^[]50;?^G respond with fontname)
_XTResponse() { # string [tty]
  # XXX: using any tty instead of /dev/tty is not work
  local STTY DTTY="${2:-/dev/tty}"
  STTY=`stty -g < "$DTTY"`
  stty raw -echo < "$DTTY"
  echo -n "$1" > "$DTTY"
  test -n "$__XT" || _TmpFile __XT
  cat < "$DTTY" > "$__XT" 2>/dev/null &
  stty < "$DTTY" > /dev/null 2>&1  # pause
  kill $! > /dev/null 2>&1
  stty $STTY < "$DTTY"
  cat "$__XT"
}

################################################
# Misc functions

# TODO: "A_b_c" "D_e" -> "A b c" "D e" call

# print pseudo-random 0..Max (32767 if Max undefined)
_Random() { # [Max]
  echo $((`dd if=/dev/urandom count=2 bs=1 2>/dev/null | od -An -t u4`%${1:-32768}))
}

# # parse "ipcalc Parameters" and get Field value
# _IpCalc() { # Parameters Field
#   ipcalc $1 | sed -n "/$2/s/$2[ 	][ 	]*\([^ 	/]*\).*/\1/p"
# }
# 
# decode utf-8 output if not utf-8 locale is used
_DeU8() { # [locale]
  local l="${1:-$LANG}"
  case "$l" in
    *[uU][Tt][fF]*8*) cat ;;
    *) iconv -f utf-8 -t "${l##*.}" -r. ;;
  esac
}

# remove arg from argument_string
_DelArg() { # argument_string arg
  local a=" $1 "
  a="${a% $2 *} ${a#* $2 }"
  a="${a% }"; a="${a# }"
  echo "$a"
}

# translate string of format "3-5,12,15" to "3 4 5 12 15" and print it
_ExpandEnum() { # number_list [divider [diapazone_divider]]
  local div="${2:-,}"
  local dia="${3:--}"
  local d n
  for d in $(echo "$1" | tr "$div" " "); do
    case "$d" in
      *-*) for n in $(seq $(echo "$d" | tr "-" " ") ); do echo $n; done ;;
      *) echo "$d" ;;
    esac
  done
}

# quote Chars_regexp in Str (e. g. /home/dir -> \/home\/dir)
_Quote() { # Str [Chars_regexp="[/]"]
  echo "$1" | sed "s@\(${2:-[/]}\)@\\\\\1@g"
}

# use this to cat the stdin and echo args instead of running filter-like program
_CatEcho() { # [text]
  # TODO: prog name or something
  tty -s || cat
  echo " $@"
}

case "$_GMe" in
  functions|*.functions) # directly called
  echo "$_GMe v$__Version" >&2
  tty -s && PG=less || PG=cat
  sed -n '/^__/d
  /^_/p
  /^[ 	]*$/p
  /^#[^!]/p
  /^export _/s/^export \(_[^ =	,]*\)[^#]*#[ 	]*\(.*\)/\1\t\2/p
  ' "$_FMe" | $PG
  ;;
esac
