#!/bin/bash

####################################################################
#
#   The common routines for display a text-based UI using bash
#
####################################################################

#
# global data
#
typeset -a _UI_RES_         # the active UI template
typeset _KEY_               # the key input
typeset _FOCUS_             # the current focus
typeset _DATA_              # the field data
typeset -i _DATA_ATTR_      # the display attribute of the field data (0/1/2)
typeset _UI_UPDATED_        # whether the UI need to be updated (true/false)
typeset _CLEAR_SCREEN_      # whether clear screen before display the UI (true/false)

#
# routine for tty setting
#
typeset old_tty_settings
tty_init() {
    old_tty_settings=$(stty -g)
    stty -icanon -echo min 1 time 0
}

tty_restore() {
    stty "$old_tty_settings"
}

#
# read the UI template into the global variable
#
load_ui_res() {
    unset _UI_RES_[*]
    local -i i=0
    local -i j=0
    local -i k
    local -a patterns
    local -a replacements
    while read line; do
        if [ "${line:0:1}" != "~" ]; then
            patterns[$j]="${line%%=*}"
            replacements[$j]="${line#*=}"
            let j++
        else
            line="${line#[~]}"
            k=0
            while [ $k -lt ${#patterns[*]} ]; do
                if [ ${#line} -gt 0 ] && [ "${line%%*${patterns[$k]}*}" == "" ]; then
                    left="${line%%${patterns[$k]}*}"
                    right="${line#*${patterns[$k]}}"
                    line="$left${replacements[$k]}$right"
                fi
                let k++
            done
            while [ ${#line} -gt 0 ] && [ "${line%%*&[(]*[)]*}" == "" ]; do
                left="${line%%&[(]*}"
                line="${line#*&[(]}"
                hl_txt="${line%%[)]*}"
                line="${line#*[)]}"
                line="$left"'\e[1m'"$hl_txt"'\e[0m'"$line"
            done
            _UI_RES_[$i]="$line"
            let i++
        fi
    done
    _UI_UPDATED_="false"
    _CLEAR_SCREEN_="true"
}

#
# Create the text-based UI using the active UI template and the get_data call back function
#
# $1 -- *get_data func_name;
#
create_ui() {
    shopt -s extglob
    local -i i=0
    while [ $i -lt ${#_UI_RES_[*]} ]; do
        line="${_UI_RES_[$i]}"
        while [ ${#line} -gt 0 ] && [ "${line%%*<[?]*[?]>*}" == "" ]; do
            echo -n "${line%%<[?]*}"
            line="${line#*<[?]}"
            var_info="${line%%[?]>*}"
            line="${line#*[?]>}"
            space_holder="${line%%[^#]*}"
            line="${line##*('#')}"
            name="${var_info%%[[,]*}"
            width="${var_info#*,}"
            if [ "$width" == "#" ]; then
                width=$((${#var_info}+4+${#space_holder}))
            fi
            if [ "$width" == "-#" ]; then
                width='-'$((${#var_info}+4+${#space_holder}))
            fi
            if [ "${var_info%%*[[]*[]]*}" == "" ]; then
                index="${var_info#*[[]}"
                index="${index%[]]*}" 
            else
                index="-1"
            fi
            _DATA_ATTR_=0
            $1 $name $index
            typeset -i w=$width
            if [ $w -lt 0 ]; then
                w=0-$w
            fi
            if [ ${#_DATA_} -gt $w ]; then
                _DATA_="${_DATA_:0:$(($w-3))}"'...'
            fi
            if [ $_DATA_ATTR_ -eq 2 ]; then
                printf '%s%'$width's%s' '\e[1;34;47m' "$_DATA_" '\e[0m'
            else 
                if [ $_DATA_ATTR_ -eq 1 ]; then
                    printf '%s%'$width's%s' '\e[1m' "$_DATA_" '\e[0m'
                else
                    printf '%'$width's' "$_DATA_"
                fi
            fi
        done
        echo "$line"
        let i++
    done
}

#
# create the UI text and show it on the screen
#
update_ui() {
    if [ "$_UI_UPDATED_" == "false" ]; then
        ui_text="`create_ui $*`"
        if [ "$_CLEAR_SCREEN_" == "true" ]; then
            clear
        else
            tput cup 0 0
        fi
        echo -e "$ui_text"
        _UI_UPDATED_="true"
        _CLEAR_SCREEN_="false"
    fi
}


# read the input key and put into the buffer
#
read_key () {
    ch=$(dd bs=1 count=1 <&0 2> /dev/null)
    case $ch in
        $'\x1b')
            read -s -n1 -t1 ch
            if [ "$?" == "0" ]; then
                case $ch in 
                    $'\x5b')
                        read -s -n1 ch
                        case $ch in
                            $'\x41')
                                _KEY_="up"
                                ;;
                            $'\x42')
                                _KEY_="down"
                                ;;
                            H)
                                _KEY_="home"
                                ;;
                            F)
                                _KEY_="end"
                                ;;
                            1)
                                _KEY_="home"
                                read -s -n1 ch
                                ;;
                            4)
                                _KEY_="end"
                                read -s -n1 ch
                                ;;
                            5)
                                _KEY_="pgup"
                                read -s -n1 ch
                                ;;
                            6)
                                _KEY_="pgdown"
                                read -s -n1 ch
                                ;;
                        esac
                    ;;
                esac
            else
                _KEY_="esc"
            fi
            ;;
        '')
            _KEY_="enter"
            ;;
        $'\x09')
            _KEY_="tab"
            ;;
        *)
            _KEY_="$ch"
            ;;
    esac
}

#
# routines for list UI
#
# naming rules for list data
#
# {LIST_NAME}_array
# {LIST_NAME}_height
# {LIST_NAME}_top_index
# {LIST_NAME}_sel_index
#

#
# get list data function
#
# $1 -- list_name
# $2 -- n
#
get_list_data() {
    local list_name=$1
    n=$2
    eval 'top_index=$'$list_name'_top_index'
    eval 'sel_index=$'$list_name'_sel_index'
    eval 'list_height=$'$list_name'_height'
    local -i index=$(($top_index+$n))
    eval list_size='${#'$list_name'_array[*]}'
    _DATA_ATTR_=0
    if [ $index -lt $list_size ]; then
        eval _DATA_='${'$list_name'_array['$index']}'
        if [ "$_FOCUS_" == "$list_name" ] && [ $index -eq $sel_index ]; then
            _DATA_ATTR_=2
        fi
    else
        _DATA_=""
    fi
}

#
# get list range info string
#
# $1 -- list name
# $2 -- list hight
# $3 -- display length
#
get_list_range() {
    local list_name=$1
    local disp_len=$2
    eval 'list_size=${#'$list_name'_array[*]}'
    eval 'top_index=$'$list_name'_top_index'
    eval 'list_height=$'$list_name'_height'
    hz_line="-----------------------------------------------------------------------------------"
    if [ $list_height -lt $list_size ]; then
        _DATA_="($(($top_index+1))-$(($top_index+$list_height))/$list_size)"
        left_len=$(($(($disp_len-${#_DATA_}))/2))
        right_len=$(($disp_len-${#_DATA_}-$left_len))
        _DATA_="${hz_line:0:$left_len}$_DATA_${hz_line:0:$right_len}"
    else
        _DATA_="${hz_line:0:$disp_len}"
    fi
}

#
# scroll list operation
#
# $1 -- list_name
# $2 -- operation (up/down)
#
scroll_list() {
    list_name=$1
    operation=$2
    eval 'list_size=${#'$list_name'_array[*]}'
    top_index=$list_name'_top_index'
    sel_index=$list_name'_sel_index'
    eval 'list_height=$'$list_name'_height'
    eval 'sel_index_v=$'"$sel_index"
    eval 'top_index_v=$'"$top_index"
    local old_sel_index=$sel_index_v
    case $operation in
        "up")
            if [ $sel_index_v -gt 0 ]; then
                eval "$sel_index="'$(($'"$sel_index"'-1))'
            fi
            ;;
        "down")
            if [ $sel_index_v -lt $(($list_size-1)) ]; then
                eval "$sel_index="'$(($'"$sel_index"'+1))'
            fi
            ;;
        "pgup")
            if [ $sel_index_v -ge $list_height ]; then
                eval 'let '"$sel_index-=$list_height" 
            else
                eval "$sel_index=0"
            fi
            if [ $top_index_v -ge $list_height ]; then
                eval 'let '"$top_index-=$list_height" 
            else
                eval "$top_index=0"
            fi
            ;;
        "pgdown")
            if [ $sel_index_v -lt $(($list_size-$list_height-1)) ]; then
                eval 'let '"$sel_index+=$list_height" 
            else
                eval "$sel_index=$(($list_size-1))"
            fi
            if [ $top_index_v -lt $(($list_size-2*$list_height)) ]; then
                eval 'let '"$top_index+=$list_height" 
            else
                eval "$top_index=$(($list_size-$list_height))"
            fi
            ;;
        "home")
            eval "$sel_index=0"
            ;;
        "end")
            eval "$sel_index=$(($list_size-1))"
            ;;
        "valid")
            if [ $list_size -gt 0 ]; then
                if [ $sel_index_v -ge $list_size ]; then
                    eval "$sel_index=$(($list_size-1))"
                fi
            else
                eval "$sel_index=0"
            fi
            ;;
    esac
    eval 'sel_index_v=$'"$sel_index"
    eval 'top_index_v=$'"$top_index"
    if [ $sel_index_v -lt $top_index_v ]; then
        eval "$top_index=$sel_index_v"
    fi
    if [ $sel_index_v -ge $(($top_index_v+$list_height)) ]; then
        eval "$top_index="'$(('"$sel_index_v-$list_height+1"'))'
    fi
    if [ "$old_sel_index" != "$sel_index_v" ]; then
        _UI_UPDATED_="false"
    fi
}


