# -*- mode: sh; sh-shell: bash -*-
# vim: set ft=bash:
# shellcheck shell=bash

### Module for terminal control codes
###
### These codes are stored in the 'TTYCTL' associative array.  The 'TTYNIL' mirrors the keys
### from 'TTYCTL' but maps them to empty strings.  Finally 'TTYOUT' and 'TTYERR' refer to
### 'TTYCTL' when the associated output stream is a non dumb terminals and refer to 'TTYNIL'
### when it is not a terminal or a dumb terminal.
###
### The keys in 'TTYCTL/TTYNIL' are as following.
###
### Two character control sequences:
###  'ce' - Clears from cursor to the end of the line.
###  'cl' - Puts the cursor in column 1 and clears the line.
###  'cs' - Clears the entire screen.
###  'cr' - Puts the cursor in column 1.
###
### Three character styles:
###   The first two characters set the foreground and background color:
###    '_' - no change.
###    'k' - black
###    'r' - red.
###    'g' - green
###    'b' - blue.
###    'y' - yellow.
###    'm' - magenta.
###    'c' - cyan.
###    'K' - bright black
###    'R' - bright red.
###    'G' - bright green
###    'B' - bright blue.
###    'Y' - bright yellow.
###    'M' - bright magenta.
###    'C' - bright cyan.
###   The third character set the style uppercase enables, lowercase disables:
###    '_' - no change.
###    'B' - bold.
###    'D' - dim.
###    'U' - underline.
###    'I' - italic.
###    'R' - reverse.
###    'S' - strike-through.
###    'n' - normal (has no uppercase).
###
### Finally there is a the one character 'n' code that resets the style completely.
###
### For an example to see how this is used look at the 'std_lib' module.

# compute lookup table for styles once
tty_init_maps() {
    [[ -v TTYCTL ]] && return 0

    # fg / bg / style maps
    declare -A fg_colors=(
        [k]=30 [r]=31 [g]=32 [y]=33 [b]=34 [m]=35 [c]=36 [w]=37
        [K]=90 [R]=91 [G]=92 [Y]=93 [B]=94 [M]=95 [C]=96 [W]=97
        [_]=''
    )
    declare -A bg_colors=(
        [k]=40 [r]=41 [g]=42 [y]=43 [b]=44 [m]=45 [c]=46 [w]=47
        [K]=100 [R]=101 [G]=102 [Y]=103 [B]=104 [M]=105 [C]=106 [W]=107
        [_]=''
    )
    declare -A styles=(
        [B]=1  [D]=2  [U]=4  [I]=3  [R]=7  [S]=9
        [b]=22 [d]=22 [u]=24 [i]=23 [r]=27 [s]=29
        [n]=0  [_]=''
    )

    # build maps
    declare -gxA TTYCTL=() TTYNIL=()
    for fg in "${!fg_colors[@]}"; do
        for bg in "${!bg_colors[@]}"; do
            [[ $fg == "$bg" && $fg != _ ]] && continue
            for st in "${!styles[@]}"; do
                code=()
                [[ -n ${fg_colors[$fg]} ]] && code+=("${fg_colors[$fg]}")
                [[ -n ${bg_colors[$bg]} ]] && code+=("${bg_colors[$bg]}")
                [[ -n ${styles[$st]}   ]] && code+=("${styles[$st]}")
                key="${fg}${bg}${st}"
                if ((${#code[@]})); then
                    local IFS=";"
                    val=$'\033['"${code[*]}"'m'
                else
                    val=''
                fi
                TTYCTL[$key]="$val"
                TTYNIL[$key]=''
            done
        done
    done

    # specials
    TTYCTL[ce]=$'\033[K'        TTYNIL[ce]=''
    TTYCTL[cl]=$'\r\033[K'      TTYNIL[cl]=''
    TTYCTL[cs]=$'\033[2J\033[H' TTYNIL[cs]=''
    TTYCTL[cr]=$'\r'            TTYNIL[cr]='\r'
    TTYCTL[n]=$'\033[0m'        TTYNIL[n]=''

    # assign TTYOUT/TTYERR
    unset TTYOUT TTYERR
    [[ "${TERM:-dumb}" = "dumb" ]] && COLOR=never
    case "${COLOR:-auto}" in
        always) declare -gn TTYOUT=TTYCTL; declare -gn TTYERR=TTYCTL ;;
        never)  declare -gn TTYOUT=TTYNIL; declare -gn TTYERR=TTYNIL ;;
        auto|*)
            # shellcheck disable=2015
            [[ -t 1 ]] && declare -gn TTYOUT=TTYCTL || declare -gn TTYOUT=TTYNIL
            # shellcheck disable=2015
            [[ -t 2 ]] && declare -gn TTYERR=TTYCTL || declare -gn TTYERR=TTYNIL
            ;;
    esac

    # Mark the arrays readonly
    declare -gxrA TTYCTL TTYNIL
    declare -gxrn TTYOUT TTYERR
}

tty_init_maps
unset -f tty_init_maps

function tty_echo ## [echo_args] - Echos only when the output is a tty.
{
    [[ -t 1 ]] && echo "$@"
}

[[ "${TERM:-dumb}" = "dumb" ]] || function tty_newline ## Clears line when at column 0, outputs newline otherwise.
{
    [[ -t 0 && -t 1 ]] || return 0
    
    local col
    # Use 0.05 second timeout to prevent hanging, discard stderr in case of issues
    if IFS=';' read -t 0.05 -srdR -p $'\033[6n' _ col 2>/dev/null; then
        col="${col%R}"
        if [[ "$col" = 1 ]]; then
            # At column 1 (0-based), clear the line to remove any progress message
            echo -ne "${TTYERR[cl]:-}"
        else
            # Not at column 1, output newline
            echo
        fi
    fi
    return 0
}
