# Zerocat Coreboot Machines --- Create very satisfying free software devices.
#
# Copyright (C) 2019, 2020, 2021, 2022  Kai Mertens <kmx@posteo.net>
#
# This file is part of Zerocat Coreboot Machines.
#
# Zerocat Coreboot Machines is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# Zerocat Coreboot Machines 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with Zerocat Coreboot Machines.  If not, see <http://www.gnu.org/licenses/>.


# global variables
declare DEBUG=0   # Do not edit, use command line option --debug instead!

# global constants
declare -r NL=$'\n'   # NL=$(printf "\n") does not work?!!
declare -r ESC=`printf "\e"`
declare -r TAB=`printf "\t"`
declare -r RET=`printf "\r"`
declare -r MARGIN="`printf "\r\t\t\t"`"
declare -r RTAB="${MARGIN}`printf "\t\t\t"`"
declare -r INDENT="  "
declare -r LIITEM="· "
declare -r -i WIDTH_MARGIN="$((${MARGIN//[$RET$TAB]/+8}-8))"
declare -r -i WIDTH_RTAB="$((${RTAB//[$RET$TAB]/+8}-8))"
declare -r -i WIDTH_TERM=124  # meet default terminal width on an ZC-X60 with Trisquel OS
declare -r -i WIDTH_LINE=$WIDTH_TERM-$WIDTH_MARGIN-1
declare -r -i LIM_BREAK=$WIDTH_LINE+$WIDTH_MARGIN-$WIDTH_RTAB
declare -r TEMPLATE_DATE='date +%s.%3N'
declare -r STRING_DATE="[${FG_DEFAULT}%s${FG_DEFAULT}]"
declare -r READ=`printf "[${FG_BLUE}READ${FG_DEFAULT}]"`
declare -r PASS=`printf "[${FG_GREEN}PASS${FG_DEFAULT}]"`
declare -r WARN=`printf "[${FG_YELLOW}WARN${FG_DEFAULT}]"`
declare -r FAIL=`printf "[${FG_RED}FAIL${FG_DEFAULT}]"`
declare -r DBUG=`printf "[${FG_MAGENTA}DBUG${FG_DEFAULT}]"`
declare -r HEAD=`printf "[${FG_DEFAULT}HEAD${FG_DEFAULT}]"`
declare -r INFO=`printf "[${FG_DEFAULT}INFO${FG_DEFAULT}]"`

# $1: title
# $2: fill character
# $3: total length
# $4: preadd space lines
# $5: color escape string
# $6: tag
print_headline()
{
  declare t=`${TEMPLATE_DATE}`
  declare post=5
  declare pre=$((${3}${1:+-2}-${#1}${1:+-2}-post))
  declare xnl=${4}
  declare i
  declare color=${5}
  declare tag=${6}

  color=${color:="${SET_BOLD}"}
  tag=${tag:="${HEAD}"}

  [[ $pre -gt -1 ]] ||
    go_exit "$BASH_SOURCE" "$FUNCNAME" "$LINENO" "title too long for headline"
  for (( i=${xnl:=1} ; i>0 ; i-- )) ; do printf "\n"; done;
  printf "${SET_NORMAL}${STRING_DATE} %s ${color}${MARGIN}" "$t" "${tag}"
  for (( i=$pre ; i>0 ; i-- )) ; do printf "%s" "$2"; done;
  printf "${1:+  }%s${1:+  }" "$1"
  for (( i=$post ; i>0 ; i-- )) ; do printf "%s" "$2"; done;
  printf "${SET_NORMAL}\n"
}

# $1: source string
# $2: dest string (name reference)
# $3: max length
_str_limit()
{
  declare -r source=$1
  declare nc
  declare -n dest=$2
  declare -i max=$3
  declare -i d2

  # strip color escapes
  nc="${source//$ESC\[?m}"
  nc="${nc//$ESC\[??m}"
  d2=(${#source}-${#nc})/2
  if [ ${#nc} -gt $max ]; then
    declare -r sdis='...'
    declare -i half=($max-${#sdis})/2
    declare -i discard=${#nc}-2*$half
    dest="${source:0:$(($half+$d2))}$sdis${source:$(($half+$d2+$discard))}"
  else
    dest=$source
  fi
}

# $1: source
# $2: dest (name reference)
_wrap()
{
  declare s=$1
  declare s0
  declare s1
  declare s2
  declare s2_color
  declare nlx
  declare -n dest=$2
  declare indent
  declare indent_new
  declare liitem

  # strip all color codes
  s0="${s//[$ESC][\[][0-9]m}"
  s0="${s0//[$ESC][\[][0-9][0-9]m}"

  # get indentation
  indent="${s0%%[^$INDENT]*}"   # how to improve??
  indent_new="${s0/#${LIITEM}/${INDENT}}"
  indent_new="${indent_new%%[^$INDENT]*}${INDENT}"

  # strip indentation
  s0="${s0:${#indent}}"
  s="${s:${#indent}}"

  # get liitem
  liitem="${s0%%[^$LIITEM]*}"

  # strip littem
  s0="${s0:${#liitem}}"
  s="${s:${#liitem}}"

  # test for NL in message: ${#nlx} should be > 0
  nlx="${s0//[^$NL]/}"

  # test for ${RTAB}: $s1 or $s2 should differ $s0
  s1="${s0%%${RTAB}*}"
  s2="${s0#*${RTAB}}"
  c1="${s%%${RTAB}*}"
  c2="${s#*${RTAB}}"

  if [ "${#nlx}" -ne 0 ]; then    # message contains NL characters
    declare sx
    declare sline
    declare -a sn
    declare -i i=0
    dest=''
    sx=$s0
    i=${#nlx}
    until [ "$i" -lt 0 ]; do
      sn[$i]="${sx/#*$NL}"
      sx="${sx%$NL*}"
      _str_limit "${sn[$i]}" sline $WIDTH_LINE
      dest="${sline}${dest:+$NL}${MARGIN}$dest"
      i=$i-1
    done
  elif [ ${#s1} -ne ${#s0} ]; then    # message contains ${RTAB} character
    if [ ${#s2} -gt $(($WIDTH_LINE-${#indent_new})) ]; then  # bigger than next line?
      declare slim
      _str_limit "$c2" slim $(($WIDTH_LINE-${#indent_new}))
      if [ -f "$s2" ] || [ -d "$s2" ]; then
        slim="${SET_BOLD}$slim${CLR_INTENSITY}"
      fi
      dest="$indent$liitem$s1$NL${MARGIN}${indent_new}$slim"
    elif [ ${#s2} -gt $LIM_BREAK ]; then  # bigger than lim_break?
      if [ -f "$s2" ] || [ -d "$s2" ]; then
        dest="$indent$liitem$s1$NL${MARGIN}$indent_new${SET_BOLD}$s2${CLR_INTENSITY}"
      else
        dest="$indent$liitem$c1$NL${MARGIN}${indent_new}$c2"
      fi
    else  # fit into lim_break?
      if [ -f "$s2" ] || [ -d "$s2" ]; then
        dest="$indent$liitem$c1${RTAB}${SET_BOLD}$c2${CLR_INTENSITY}"
      else
        dest="$indent$liitem$s"
      fi
    fi
  elif [ $((${#s0}+${#indent}+${#liitem})) -gt $WIDTH_LINE ]; then
    declare slim
    _str_limit "${s}" slim $(($WIDTH_LINE-${#indent}-${#liitem}))
    [ -f "${s2}" ] || [ -d "${s2}" ] &&
      slim="${SET_BOLD}$slim${CLR_INTENSITY}"
    dest="$indent$liitem$slim"
  else
    if [ -f "${s2}" ] || [ -d "${s2}" ]; then
      dest="$indent$liitem${SET_BOLD}${s}${CLR_INTENSITY}"
    else
      dest="$indent$liitem$s"
    fi
  fi
}

# $1: funcname
_funcstart()
{
  [ "$DEBUG" -eq 0 ] ||
    print_headline "$1" '=' "$WIDTH_LINE" 0 "${FG_LIGHTMAGENTA}" "${DBUG}"
}

# $1: string
_head()
{
  print_headline "$1" '#' "$WIDTH_LINE" 0 "${SET_BOLD}" "${HEAD}"
}

# $1: string
_mesg()
{
  declare swrap
  _wrap "$1" swrap
  printf "${SET_NORMAL}${MARGIN}%s\n" "$swrap"
}

# $1: string
_dbug()
{
  [ "$DEBUG" -eq 0 ] || {
    declare swrap
    _wrap "$1" swrap
    printf "${SET_NORMAL}${STRING_DATE} ${DBUG} ${MARGIN}%s\n" "$($TEMPLATE_DATE)" "$swrap"
  }
}

# $1: string
_info()
{
  declare swrap
  _wrap "$1" swrap
  printf "${SET_NORMAL}${STRING_DATE} ${INFO} ${MARGIN}%s\n" "$($TEMPLATE_DATE)" "$swrap"
}

# $1: string
_pass()
{
  declare swrap
  _wrap "$1" swrap
  printf "${SET_NORMAL}${STRING_DATE} ${PASS} ${MARGIN}%s\n" "$($TEMPLATE_DATE)" "$swrap"
}

# $1: string
_warn()
{
  declare swrap
  _wrap "$1" swrap
  printf "${SET_NORMAL}${STRING_DATE} ${WARN} ${MARGIN}%s\n" "$($TEMPLATE_DATE)" "$swrap"
}

# $1: string
_fail()
{
  declare swrap
  _wrap "$1" swrap
  printf "${SET_NORMAL}${STRING_DATE} ${FAIL} ${MARGIN}%s\n" "$($TEMPLATE_DATE)" "$swrap"
}

# $1: string
_read()
{
  printf "${SET_NORMAL}${STRING_DATE} ${READ} ${MARGIN}%s" "$($TEMPLATE_DATE)" "$1"
}

# $1: funcname
# $2: return value
_funcstop()
{
  declare -i retval=0

  [ -z "$2" ] ||
    retval="$2"

  [ "$DEBUG" -eq 0 ] ||
    print_headline "... return $retval from $1" ' ' "$WIDTH_LINE" 0 "${FG_MAGENTA}" "${DBUG}"

  return "$retval"
}

# $1: array with list of commands to look for (name reference)
check_commands()
{
  _funcstart "$FUNCNAME"

  declare -n aref=$1
  declare -a miss=()
  declare i=

  _info "Test availability of required commands ($1) ..."
  for i in "${aref[@]}"; do
    command -v "$i" &>/dev/null || miss+="$i "
  done

  [ "${#miss}" -ne 0 ] && {
    _fail "Please install missing commands: ${miss[*]}"
    exit 1
  }
  _pass "... availability tested."

  _funcstop "$FUNCNAME"
}

# $1: $BASH_SOURCE (as of script in use)
# $2: original name of the script
test_scriptname()
{
  _funcstart "$FUNCNAME"

  [ "$#" -eq 2 ] ||
    go_exit "$BASH_SOURCE" "$FUNCNAME" "$LINENO" "missing parameter"

  declare -i retval=0   # true

  [ "${1#\.\/}" != "${2#\.\/}" ] && {
    _warn "This script doesn’t carry its original name."
    _mesg "To retain consistent, project-wide documentation, please name it back to:"
    _mesg "${INDENT}${2#\.\/}"
    retval=1    # false
  }

  _funcstop "$FUNCNAME" $retval
}

# $1: scriptname
# $2: funcname
# $3: line number
# $4: command or optional message
go_exit()
{
  declare msg="$1: $2${3:+: exit in line $3}${4:+: $4}"
  if [ ${#msg} -gt $WIDTH_LINE ]; then
    _fail "${1}${2:+: $2}${3:+: exit in line $3}:"
    # color tags are placed at start, at end of string, and
    # string can be limited without corrupting color information
    _mesg "$INDENT${FG_RED}${4}${FG_DEFAULT}"
  else
    # line matches width, color tags can be placed anywhere
    _fail "${1}${2:+: $2}${3:+: exit in line $3}${4:+: ${FG_RED}$4${FG_DEFAULT}}"
  fi
  echo
  unset msg
  exit 1
}

# $1: Variable to hold OS ID String (name reference)
get_OSID()
{
  _funcstart "$FUNCNAME"

  declare -n p_osid=$1

  _info "Detect Operating System ..."
  if [ -f /etc/os-release ]; then
    p_osid=`cat /etc/os-release | sed -r -e '/^ID=/!d; s/^ID=//;' -`
  elif [ -n "$(command -v guix)" -o -n "$GUIX_ENVIRONMENT" ]; then
    p_osid='guix'
  else
    p_osid='unknown_OS'
  fi
  case "$p_osid" in
    ('trisquel')
      ;;
    ('guix')
      _warn "Support for GNU Guix is work in progress,"
      _mesg "this toolchain will fail very likely."
      _mesg "Please try it on Trisquel GNU/Linux-libre."
      ;;
    (*)
      _fail "Unsupported OS. Please try Trisquel GNU/Linux-libre."
      exit 1
      ;;
  esac
  _pass "... Operating System detected: $p_osid"

  _funcstop "$FUNCNAME"
}
