#!/bin/bash

###########################################################
## Copyright (c) 2002-2024 Alexey Kuryakin daqgroup@mail.ru
###########################################################

###########################################################
## printc - print colorized messages by using escape codes.
###########################################################

function print_version(){
 local name="$(basename ${BASH_SOURCE[0]} .sh)";
 echo "$name version 1.1";
};

function print_help(){
 print_version;
 local name="$(basename ${BASH_SOURCE[0]} .sh)";
 echo "Copyright (c) 2019-2024 Alexey Kuryakin kouriakine@mail.ru";
 echo "Print console message(s) with different color(s).";
 echo "";
 echo "Brief:      PrintC command uses to print console message(s) with different colors.";
 echo "";
 echo "Syntax:     printc [options] [arguments]";
 echo "";
 echo "Options:    --               => end options";
 echo "            --version        => show version";
 echo "            -h,--help        => show help screen";
 echo "            -v,--verb        => verbose mode";
 echo "            -n, --newline    => print new line delimeter";
 echo "            -p m, --print m  => print message m (as is, i.e. without new line and lead space delimeter)";
 echo "            -d t, --delay t  => delay for t milliseconds (ms)";
 echo "            -b n, --beep n   => Windows default sound n";
 echo "                                n = (. x ? ! i) = (0 16 32 48 64) = (Ok Error Question Exclamation Information)";
 echo "            --show n         => show console window (other options ignored except /menuclose)";
 echo "                     where n =  0:SW_HIDE, 1:SW_SHOWNORMAL, 2:SW_SHOWMINIMIZED, 3:SW_SHOWMAXIMIZED";
 echo "                                4:SW_SHOWNOACTIVATE, 5:SW_SHOW, 6:SW_MINIMIZE, 7:SW_SHOWMINNOACTIVE";
 echo "                                8:SW_SHOWNA, 9:SW_RESTORE, 10:SW_SHOWDEFAULT, 11:SW_FORCEMINIMIZE";
 echo "            -c n, --color n  => set color code n=0xBF where B-beckground color, F-foreground font color";
 echo "                                0:Black 1:Navy  2:Green 3:Teal 4:Maroon 5:Purple  6:Olive  7:Silver";
 echo "                                8:Gray  9:Blue  A:Lime  B:Aqua C:Red    D:Fuchsia E:Yellow F:White";
 echo "                                also '-c 0'  mean 'color reset' and '-c +' means 'set bold font'";
 echo "                for example:    +0a = \"Lime Bold text on Black ground\", 1e = \"Yellow on Navy\" etc";
 echo "            --menuclose n    => n=1/0=enable/disable close menu button";
 echo "            --nospace        => supress space after last printed argument";
 echo "            --center         => restore and center console window on desktop";
 echo "            --focus          => focuse console window (bring window to front)";
 echo "            --min            => minimize the console window (equivalent --show:6)";
 echo "            -f:fmt data      => print data string formatted with fmt specification;";
 echo "                                supported formats: real(f,g,e), integer(d,x), string(s),";
 echo "                                for example:";
 echo "                                -f:%11.5g  \"3.14159\" => \"     3.1416\"";
 echo "                                -f:%-11.5s \"abcdefg\" => \"abcde      \"";
 echo "                                also fmt = ~ uses to unescape data string";
 echo "                                -f:~ \"a\\tb\"          => \"a       b\"";
 echo "Note:       Short and long options style ( i.e. -s -show and --show ) are both allowed,";
 echo "            for example -s:1 is equivalent to -show:1 or --show:1.";
 echo "            Both = and : value separators allowed, i.e. -s=1 is equivalent to -s:1.";
 echo "            Also '--option=value' is equivalent to '--option value' clause.";
 echo "";
 echo "Arguments:  message(s) to print";
 echo "            printc command will print message(s) with last set color";
 echo "            message(s) separated by (single) space so";
 echo "            \"printc a    b     c\" will print \"a b c\"";
 echo "";
 echo "Comment:    PrintC uses to print console message(s) with different color(s).";
 echo "            For example:";
 echo "";
 echo "            printc --color +1e \" Bold Yellow on Navy \" --color 0 --newline --color 0a \" Lime on Black. \" --color 0 --newline";
 echo "";
 echo "            or (short form)";
 echo "";
 echo "            printc -c:+1e \" Bold Yellow on Navy \" -c:0 -n -c:0a \" Lime on Black. \" -c:0 -n";
 echo "";
 echo "            Arguments will print with single space separator (as Linux echo).";
 echo "            Option --print uses to print \"as is\", without space separators, i.e.";
 echo "            printc -p \"text\" -p \"ABC\" will print \"textABC\" without spaces and new line.";
 echo "";
 echo "            Also console window operations supported: (--show,--min,--focus,--center,--menuclose)";
 echo "            options to control console window appearence.";
 echo "";
 echo "            To pass arguments starting from - or /, single quotes ('') uses, for example";
 echo "            printc "'-p is not option'" will print \"-p is not option\".";
 echo "";
 echo "History:    Copyright (c) 2019-2024 Alexey Kuryakin daqgroup@mail.ru https://sourceforge.net/projects/pbtools/";
 echo "";
 echo "Examples:   printc --help";
 echo "            printc --version";
 echo "            printc Just print the text.    Multi-spaces is compressed   to   single   space. -n";
 echo "            printc \"Quoted  text  spaces  is  not  compressed\" --nospace \".\" -n";
 echo "            printc -p \" PI = \" -f:%-11.5s \"3.1415926535897932384626433832795\" -n";
 echo "            printc -p \" PI = \" -f:%11.5e  \"3.1415926535897932384626433832795\" -n";
 echo "            printc -p \" PI = \" -f:%11.5f  \"3.1415926535897932384626433832795\" -n";
 echo "            printc -p \" PI = \" -f:%11.5g  \"3.1415926535897932384626433832795\" -n";
 echo "            printc -p \"Hex(-1) = 0x\" -f:%8.8X ' -1' -n";
 echo "            printc --show 7 --menuclose 0 --delay 7000 --menuclose 1 --show 9 --beep ! --center --focus";
 echo "            printc -c:1e -p:\" Color Message \" -c:0 -n -c=0a \" Usual Message \" -c:0 -n";
 echo "            printc -c 1e -p \" Color Message \" -c 0 -n -c 0a \" Usual Message \" -c 0 -n";
};

########################
# application constants:
########################
readonly SW_HIDE=0;
readonly SW_SHOWNORMAL=1;
readonly SW_SHOWMINIMIZED=2;
readonly SW_SHOWMAXIMIZED=3;
readonly SW_SHOWNOACTIVATE=4;
readonly SW_SHOW=5; #default
readonly SW_MINIMIZE=6;
readonly SW_SHOWMINNOACTIVE=8;
readonly SW_SHOWNA=8;
readonly SW_RESTORE=9;
readonly SW_MAX=10;

########################
# application variables:
########################
declare space="";  # space delimiter
declare -i argn=0; # argument counter
declare -i verb=0; # verbose mode
declare -i nclr=0; # color counter
declare -i tty1=0; # stdout is TTY
declare -i tty2=0; # stdout is TTY

###############################
# handler for fatal errors:
# print $2 to stderr & exit $1.
###############################
function fatal(){
 1>&2 echo -e "$2";
 exit $1;
};

#############################
# print only in verbose mode.
#############################
function verb_out(){
 if [ $verb -gt 0 ]; then echo "$@"; fi;
};
function verb_err(){
 if [ $verb -gt 0 ]; then 1>&2 echo "$@"; fi;
};

#####################
# check $1 is option.
#####################
function is_option(){
 case $1 in
  -*) return 0; ;;
  *)  return 1; ;;
 esac;
};

#####################
# check $1 is number.
#####################
function is_number(){
 case $1 in
  ''|*[!0-9]*) return 1; ;;
  *)           return 0; ;;
 esac;
};

#########################
# check $* stream is TTY.
# by default is_tty 0 1 2
#########################
function is_tty(){
 local args="$*";
 for n in ${args:-0 1 2}; do
  if is_number $n; then
   if [ ! -t $n ]; then
    return 1;
   fi;
  fi;
 done;
 return 0;
};

####################
# print process PID.
####################
function print_pid(){
 echo "$$";
};

#########################
# print parent PID of $1.
#########################
function print_ppid(){
 if is_number "$1"; then cat /proc/$1/status | grep 'PPid:' | sed 's/PPid://' | xargs; fi;
};

###########################
# list PID and parent PIDs.
###########################
function list_ppids(){
 local pid="$$";
 local pids="$$";
 for n in $(seq 20); do
  local ppid="$(print_ppid $pid)";
  if is_number "$ppid"; then true; else break; fi;
  if [ $ppid -le 1 ] || [ $ppid -eq $pid ]; then break; fi;
  pids+=" $ppid";
  pid="$ppid";
 done;
 echo "$pids";
};

###########################
# check $* is valid handle.
###########################
function valid_handle(){
 local x=0; let "x=$*" 2>/dev/null;
 if [ $x -eq  0 ]; then return 1; fi;
 if [ $x -eq -1 ]; then return 1; fi;
 return 0;
};

#############################
# find console window handle.
# scan of parent PID windows.
#############################
function console_handle(){
 local wnd="";
 local host="$(hostname)";
 if which wmctrl >/dev/null 2>&1; then
  for ppid in $(list_ppids); do
   wnd="$(wmctrl -lpx | awk '{print $1,$2,$3,$4,$5}' | grep " $ppid .*term.* $host" | awk '{print $1}')";
   if valid_handle "$wnd"; then break; else wnd=""; fi;
  done;
 fi;
 echo "${wnd:-0}";
};

#######################
# get desktop geometry.
#######################
function desktop_sizes()   { wmctrl -d | grep -P '^\s*\d+\s+\*\s+' | awk '{print $4,$6,$8,$9}' | tr 'x' ' ' | tr ',' ' ' | xargs; };
function desktop_size_x()  { desktop_sizes | awk '{print $1}'; };
function desktop_size_y()  { desktop_sizes | awk '{print $2}'; };
function viewport_pos_x()  { desktop_sizes | awk '{print $3}'; };
function viewport_pos_y()  { desktop_sizes | awk '{print $4}'; };
function workarea_pos_x()  { desktop_sizes | awk '{print $5}'; };
function workarea_pos_y()  { desktop_sizes | awk '{print $6}'; };
function workarea_size_x() { desktop_sizes | awk '{print $7}'; };
function workarea_size_y() { desktop_sizes | awk '{print $8}'; };

############################
# activate window handle $1.
# bring to front, set focus.
############################
function activate_window(){
 local wnd="${1:-0}";
 if valid_handle $wnd; then
  wmctrl -i -a $wnd;
 else
  1>&2 echo "Error on --focus: invalid window handle: $wnd";
  return 1;
 fi;
};

#######################
# apply --focus option.
#######################
function on_opt_menu_focus(){
 activate_window $(console_handle);
};

######################################
# apply --show $2 option to window $1.
######################################
function show_window(){
 local wnd="$1";
 local nsw="$2";
 if valid_handle $wnd; then
  local starg="";
  local active=0;
  case $nsw in
   0) starg="add,hidden"; ;;                                                   # SW_HIDE
   1) starg="remove,hidden remove,maximized_horz,maximized_vert"; active=1; ;; # SW_SHOWNORMAL
   2) starg="add,hidden"; ;;                                                   # SW_SHOWMINIMIZED
   3) starg="remove,hidden add,maximized_horz,maximized_vert"; active=1; ;;    # SW_SHOWMAXIMIZED
   4) starg="remove,hidden"; ;;                                                # SW_SHOWNOACTIVATE
   5) starg="remove,hidden"; active=1; ;;                                      # SW_SHOW
   6) starg="add,hidden"; ;;                                                   # SW_MINIMIZE
   7) starg="add,hidden"; ;;                                                   # SW_SHOWMINNOACTIVE
   8) starg="remove,hidden"; ;;                                                # SW_SHOWNA
   9) starg="remove,hidden remove,maximized_horz,maximized_vert"; active=1; ;; # SW_RESTORE
   *) return 1; ;;
  esac;
  for s in $starg; do wmctrl -i -r $wnd -b $s; done;
  if [ $active -gt 0 ]; then activate_window $wnd; fi;
 else
  1>&2 echo "Error on --show: invalid window handle: $wnd";
  return 1;
 fi;
};

##################################
# move window $1 to destop center.
##################################
function center_window(){
 local wnd="$1";
 if valid_handle $wnd; then
  local ds="$(desktop_sizes)";
  local ws="$(wmctrl -lG | awk '{print $1,$2,$3,$4,$5,$6}' | grep "$wnd ")";
  local dw="$(echo $ds | awk '{print $1}')"; local dh="$(echo $ds | awk '{print $2}')"; # destop   size x,y
  local dx="$(echo $ds | awk '{print $3}')"; local dy="$(echo $ds | awk '{print $4}')"; # viewport pos  x,y
  local ax="$(echo $ds | awk '{print $5}')"; local ay="$(echo $ds | awk '{print $6}')"; # workarea pos  x,y
  local aw="$(echo $ds | awk '{print $7}')"; local ah="$(echo $ds | awk '{print $8}')"; # workarea size x,y
  local ww="$(echo $ws | awk '{print $5}')"; local wh="$(echo $ws | awk '{print $6}')"; # window   size x,y
  if is_number $aw && is_number $ah && is_number $ww && is_number $wh; then
   local wx=0; let "wx=$dx+($dw-$ww)/2"; let "wx=$ax+($aw-$ww)/2";
   local wy=0; let "wy=$dy+($dh-$wh)/2"; let "wy=$ay+($ah-$wh)/2";
   wmctrl -i -r $wnd -e "0,$wx,$wy,-1,-1";
  fi;
 else
  1>&2 echo "Error on --center: invalid window handle: $wnd";
  return 1;
 fi;
};

#################################
# convert string $2 by format $1.
#################################
function format_string(){
 local fmt="$1"; local val="$2";
 if [ -n "$fmt" ] && [ -n "$val" ]; then
  if [ "${fmt:0:1}" = "~" ]; then
   val="$(printf "%b" "$val")";
   fmt="${fmt:1}";
  fi;
 fi;
 local lc_numeric="$LC_NUMERIC";
 export LC_NUMERIC="C";
 printf "$fmt" "$val";
 export LC_NUMERIC="$lc_numeric";
};

#########################
# apply --show $1 option.
# flag $2 uses to center.
#########################
function on_opt_show(){
 local ncmdshow="$1";
 local center="${2:-0}";
 if [ $ncmdshow -ge $SW_HIDE ] && [ $ncmdshow -le $SW_MAX ]; then
  show_window $(console_handle) $ncmdshow;
 fi;
 if [ $center -gt 0 ]; then
  center_window $(console_handle);
 fi;
};

##############################
# apply --menuclose $1 option.
##############################
function on_opt_menu_close(){
 if is_tty 2; then
  verb_err "Option --menuclose is not implemented yet.";
  return 1;
 fi;
};

#########################
# apply --nospace option.
#########################
function on_opt_no_space(){
 space="";
}

##########################
# apply --print $1 option.
# flag $2 to remove space.
##########################
function on_opt_print(){
 if [ ${2:-1} -gt 0 ]; then space=""; fi;
 if [ -n "$space$1" ]; then echo -n "$space$1"; fi;
 space=" ";
};

##########################################
# apply --format $1 option to argument $2.
# optional flag $3 uses to remove space.
##########################################
function on_opt_format(){
 local opt="$1";
 local arg="$2";
 local nospace="${3:-1}";
 arg="$(format_string "$opt" "$arg")";
 if [ $nospace -gt 0 ]; then on_opt_no_space; fi;
 if [ -n "$space$arg" ]; then echo -n "$space$arg"; fi
 space=" ";
};

##########################
# applay --newline option.
##########################
function on_opt_new_line(){
 on_opt_color; # reset
 space="";
 echo "";
};

##########################
# apply --delay $1 option.
##########################
function on_opt_delay(){
 if is_number "$1"; then
  verb_out "sleep $(echo "0.001*$1" | bc)";
  sleep "$(echo "0.001*$1" | bc)";
 fi;
};

#####################
# play beep $1 sound.
#####################
function message_beep(){
 local wav="";
 # arg = n = (. x ? ! i) = (0 16 32 48 64) = (Ok Error Question Exclamation Information)
 case $(echo $1 | tr '[:upper:]' '[:lower:]') in
  .|0|ok)           wav="ok.wav"; ;;
  x|16|error)       wav="error.wav"; ;;
  \?|32|question)   wav="question.wav"; ;;
  !|48|exclamation) wav="exclamation.wav"; ;;
  i|64|information) wav="information.wav"; ;;
  *) ;;
 esac;
 if [ -n "$wav" ]; then
  wav="/opt/crwkit/add/wav/$wav";
 else
  verb_err "Wrong --beep argument: $1";
  return 1;
 fi;
 if [ -e "$wav" ]; then
  local q=""; if [ $verb -eq 0 ]; then q="-q"; fi;
  play $q $wav;
 else
  verb_err "File not found: $wav";
  return 1;
 fi;
};

#########################
# apply --beep $1 option.
#########################
function on_opt_beep(){
 message_beep $1;
};

#########################
# apply --verb $1 option.
#########################
function opt_verbose(){
 if is_number "$1"; then
  let verb=$1;
 fi;
};

###########################################################
# convert CGA color [$0-$f] to foreground ANSI escape code.
###########################################################
readonly cga2fg=("30" "34" "32" "36" "31" "35" "33" "37" "90" "94" "92" "96" "91" "95" "93" "97");
function cga_to_fg(){
 case $1 in             # foregroud
  0)   echo -n "30"; ;; # black
  1)   echo -n "34"; ;; # navy
  2)   echo -n "32"; ;; # green
  3)   echo -n "36"; ;; # teal
  4)   echo -n "31"; ;; # maroon
  5)   echo -n "35"; ;; # purple
  6)   echo -n "33"; ;; # olive
  7)   echo -n "37"; ;; # silver
  8)   echo -n "90"; ;; # gray
  9)   echo -n "94"; ;; # blue
  a|A) echo -n "92"; ;; # lime
  b|B) echo -n "96"; ;; # aqua
  c|C) echo -n "91"; ;; # red
  d|D) echo -n "95"; ;; # fuchsia
  e|E) echo -n "93"; ;; # yellow
  f|F) echo -n "97"; ;; # white
  *) ;;
 esac;
};

###########################################################
# convert CGA color [$0-$f] to background ANSI escape code.
###########################################################
readonly cga2bg=("40" "44" "42" "46" "41" "45" "43" "47" "100" "104" "102" "106" "101" "105" "103" "107");
function cga_to_bg(){
 case $1 in              # backgroud
  0)   echo -n "40";  ;; # black
  1)   echo -n "44";  ;; # navy
  2)   echo -n "42";  ;; # green
  3)   echo -n "46";  ;; # teal
  4)   echo -n "41";  ;; # maroon
  5)   echo -n "45";  ;; # purple
  6)   echo -n "43";  ;; # olive
  7)   echo -n "47";  ;; # silver
  8)   echo -n "100"; ;; # gray
  9)   echo -n "104"; ;; # blue
  a|A) echo -n "102"; ;; # lime
  b|B) echo -n "106"; ;; # aqua
  c|C) echo -n "101"; ;; # red
  d|D) echo -n "105"; ;; # fuchsia
  e|E) echo -n "103"; ;; # yellow
  f|F) echo -n "107"; ;; # white
  *) ;;
 esac;
};

##########################
# apply --color $1 option.
# -c:0f  - white on black.
# -c:+0f - w/b, bold font.
# -c:0   - reset defaults.
##########################
function on_opt_color(){
 if [[ $tty1 -gt 0 ]] && [[ $tty2 -gt 0 ]]; then
  local code="";
  local cc="$1";
  case $cc in
   0|'') code="0"; cc=""; ;;
   +*)   cc="${cc:1}"; code="1"; ;;
   -*)   cc="${cc:1}"; code="0"; ;;
   *)  ;;
  esac;
  if [ -n "$cc" ]; then
   if [ -n "$code" ]; then code+=";"; fi;
   case ${#cc} in
    1) cc="0$cc"; ;;
    2) ;;
    *) verb_err "Error: bad color code $1"; return 1; ;;
   esac;
   local cn=0; # check $cc is valid hexadicamal code, othervise report error
   let cn=0x$cc 2>/dev/null || { verb_err "Error: bad color $1"; return 1; };
   #code="${code}$(cga_to_bg ${cc:0:1});$(cga_to_fg ${cc:1:1})";
   code="${code:-0}${cga2bg[0x${cc:0:1}]};${cga2fg[0x${cc:1:1}]}";
   # cga2bg/cga2fg version are 30% faster then cga_to_bg/cga_to_fg
  fi;
  if [ -n "$code" ]; then
   # put ANSI escape code
   echo -en "\e[${code}m";
   let nclr++;
  fi;
 fi;
};

########################################################
# wait_any_key n - wait upto n sec until any key pressed
# only if stdin and stdout is connected to terminal tty.
########################################################
function wait_any_key(){
 if [ -z "$1" ]; then return 0; fi;
 if [ "$1" = "0" ]; then return 0; fi;
 if [ -t 0 ] && [ -t 1 ]; then
  local msg="$2";
  if [ -z "$msg" ]; then
   msg="Press any key to continue …";
  fi;
  echo "$msg";
  local ans; read -s -n 1 -t $1 ans;
 fi;
};

function pause(){
 wait_any_key 120;
};

function run_demo(){
 local name="$(basename ${BASH_SOURCE[0]} .sh)";
 unix printc -c:a0 " Test of " -c 1e " printc " -c=0a -n -n -c e1 Table of colors: -n --menuclose 0;
 #echo -en "\n\n$name demo:\n\n";
 local arg=();
 local colors="0 1 2 3 4 5 6 7 8 9 a b c d e f";
 for bg in $colors; do
  for fg in $colors; do
   arg+=(-c:0a -p " " -c +$bg$fg -p " $bg$fg ");
  done;
  arg+=("-n");
 done;
 time unix printc "${arg[@]}";
 pause;
 unix printc -n -n -c:a0 -p " Minimize & Show & Center Test " -c 0a -n;
 pause;
 printc --min -p " Minimize " --delay 5000 --show 9 -p " Show " --delay 2000 --center --focus -p " Center " -n;
 pause;
 printc -c 1e " Done. " -c 0a -n --menuclose 1 --beep !;
 printc -n -p "PI = " -f:%-11.5g "3.1415926535897932384626433832795" -p " Hex(-1) = 0x" -f:%8.8x " -1" -n;
 printc -p " PI = " -f:%-11.5s "3.1415926535897932384626433832795" -n \
       -p " PI = " -f:%11.5e  "3.1415926535897932384626433832795" -n \
       -p " PI = " -f:%11.5f  "3.1415926535897932384626433832795" -n \
       -p " PI = " -f:%11.5g  "3.1415926535897932384626433832795" -n;
 pause;
};

function handle_args(){
 local arg="$1";
 if [ -n "$arg" ]; then
  on_opt_print "$arg" 0;
  let argn++;
 fi;
};

function main(){
 if [ $# -le 0 ]; then fatal 1 "Error: No arguments. See --help."; fi;
 if is_tty 1; then let tty1=1; fi; if is_tty 2; then let tty2=1; fi;
 local opt=""; local arg=""; local val=""; local isopt=1;
 while [ -n "$1" ]; do
  arg="$1"; shift;
  if is_option "$arg" && [ $isopt -gt 0 ]; then
   local p=$(expr index "$arg" =); local pp=0;
   if [ $p -le 0 ]; then p=$(expr index "$arg" :); fi; let pp=$p-1;
   if [ $p -gt 0 ]; then val="${arg:$p}"; arg=${arg:0:$pp}; else val=""; fi;
   case $arg in
    --)                     let isopt=0; ;;
    -v|-verb|--verb)        opt_verbose 1; ;;
    -h|-help|--help)        print_help; exit 0; ;;
    -version|--version)     print_version; exit 0; ;;
    -m|-min|--min)          on_opt_show $SW_MINIMIZE; ;;
    -p|-print|--print)      if [ -n "$val" ]; then on_opt_print "$val"; else opt="$arg"; fi; ;;
    -d|-delay|--delay)      if [ -n "$val" ]; then on_opt_delay "$val"; else opt="$arg"; fi; ;;
    -b|-beep|--beep)        if [ -n "$val" ]; then on_opt_beep  "$val"; else opt="$arg"; fi; ;;
    -s|-show|--show)        if [ -n "$val" ]; then on_opt_show  "$val"; else opt="$arg"; fi; ;;
    -c|-color|--color)      if [ -n "$val" ]; then on_opt_color "$val"; else opt="$arg"; fi; ;;
    -menuclose|--menuclose) if [ -n "$val" ]; then on_opt_menu_close "$val"; else opt="$arg"; fi; ;;
    -center|--center)       on_opt_show $SW_RESTORE 1; ;;
    -focus|--focus)         on_opt_menu_focus; ;;
    -nospace|--nospace)     on_opt_no_space; ;;
    -n|-newline|--newline)  on_opt_new_line; ;;
    -f|-format|--format)    if [ -n "$val" ]; then opt="$arg"; fi; ;;
    -demo|--demo)           run_demo "$@"; exit $?; ;;
    *)                      fatal 1 "Error: unknown option $opt. See --help."; ;;
   esac;
  else
   case $opt in
    '')                     handle_args "$arg"; ;;
    -b|-beep|--beep)        on_opt_beep "$arg"; ;;
    -c|-color|--color)      on_opt_color "$arg"; ;;
    -p|-print|--print)      if [ -n "$arg" ]; then on_opt_print "$arg"; fi; ;;
    -s|-show|--show)        if is_number "$arg"; then on_opt_show "$arg"; fi; ;;
    -d|-delay|--delay)      if is_number "$arg"; then on_opt_delay "$arg"; fi; ;;
    -menuclose|--menuclose) if is_number "$arg"; then on_opt_menu_close "$arg"; fi; ;;
    -f|-format|--format)    if [ -n "$arg" ]; then on_opt_format "$val" "$arg"; fi; ;;
    *)                      fatal 1 "Error: unknown option $opt. See --help."; ;;
   esac;
   opt="";
   val="";
  fi;
 done;
 if [ $nclr -gt 0 ]; then
  on_opt_color; # reset
 fi;
};

main "$@";

##############
## END OF FILE
##############
