# MIT license, explained here:
#   http://www.tldrlegal.com/l/mit

# Copyright (c) 2020 Cefan Daniel Rubin
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

# Latest:
#   https://github.com/cdrubin/bash_ui


if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
    echo "bash_ui.sh requires bash version 4 or greater"
    exit
fi


declare -A _CHOICES
declare _CHOICES_ORDER
declare _NUMBER_OF_CHOICES
declare _HIGHLIGHTED_CHOICE


function _initialise() {

    local CHOICE_NUMBER=0
    local PARTS=''
    _CHOICES=()
    _CHOICES_ORDER=()
    #unset _CHOICES; declare -A _CHOICES
    #unset _CHOICES_ORDER; declare _CHOICES_ORDER
    local IFS=$'\n';
    for ENTRY in $CHOICES; do
        IFS='=' PARTS=( $ENTRY )
        local KEY="${PARTS[0]}"; local VALUE="${PARTS[1]}"
        if [ "$VALUE" == "" ]; then VALUE="$KEY"; fi
        
        _CHOICES["$KEY"]=$VALUE
        _CHOICES_ORDER+=( "$KEY" )
    done

    _NUMBER_OF_CHOICES="${#_CHOICES[@]}"
}


function set_y() {
    exec < /dev/tty
    local OLDSTTY=$(stty -g)
    stty raw -echo min 0
    echo -en "\033[6n" > /dev/tty
    IFS=';' read -r -d R -a pos
    stty $OLDSTTY
    _Y=$((${pos[0]:2} - 1))
}


function _choose_refresh() {
    echo -e "\033[${_TOP};0H"
    local NUM=1
        
    for ENTRY in "${_CHOICES_ORDER[@]}"; do
        if [ $NUM -eq $_HIGHLIGHTED_CHOICE ]; then
            echo -n -e "\033[7m"; echo -n "$ENTRY"; echo -e "\033[0m"
        else
            echo -n -e "\033[K"; echo $ENTRY
        fi
        ((NUM++))
    done
}


# if CHOSEN key has a value with newlines (when refer to multiple keys) then update CHOSEN to all of those keys
function _chosen_handle_multiple_valuekeys() {

    local KEY="$CHOSEN"
    local VALUE=${_CHOICES[$KEY]}
    
    # if the value at this key contains newlines then convert to an array of keys
    if [[ "$VALUE" == *"\n"* ]]; then
        
        local DELIMITER="\n"
        local ORIG=$VALUE$DELIMITER
        
        CHOSEN=()
        while [[ $ORIG ]]; do
            CHOSEN+=( "${ORIG%%"$DELIMITER"*}" );
            ORIG=${ORIG#*"$DELIMITER"};
        done;
    fi
}


# convert a single key or array of keys to their corresponding values
function _chosen_keys_to_values() {

    local KEYS="${CHOSEN[@]}"
        
    CHOSEN=()
    local IFS=$'\n'
    for KEY in ${KEYS[@]}; do
        CHOSEN+=( ${_CHOICES[$KEY]} )
    done;
}


function _chosen_to_newlines_output() {

    local OUTPUT
    
    printf -v OUTPUT  "%s\n" "${CHOSEN[@]}"
    CHOSEN=${OUTPUT%?}
}


function choose_one() {

    _initialise
    
    _HIGHLIGHTED_CHOICE=1
    
    for ENTRY in "${_CHOICES_ORDER[@]}"; do
        echo $ENTRY
    done

    set_y
    _TOP=$((_Y - _NUMBER_OF_CHOICES))

    _choose_refresh

    # kwy handling thanks to: https://stackoverflow.com/a/56200043
    while read -sn1 key; do

        read -sn1 -t 0.0001 k1; read -sn1 -t 0.0001 k2; read -sn1 -t 0.0001 k3
        key+=${k1}${k2}${k3}

        case "$key" in
            $'\e[A'|$'\e0A')  # up arrow
                if [ $_HIGHLIGHTED_CHOICE -gt 1 ]; then ((_HIGHLIGHTED_CHOICE--)); fi; _choose_refresh;;

            $'\e[B'|$'\e0B')  # down arrow
                if [ $_HIGHLIGHTED_CHOICE -lt $_NUMBER_OF_CHOICES ]; then ((_HIGHLIGHTED_CHOICE++)); fi; _choose_refresh;;

            '')  # whitespace character
                break
        esac
    done

    # CHOSEN set to the selected key
    CHOSEN=${_CHOICES_ORDER[((_HIGHLIGHTED_CHOICE-1))]}

    _chosen_handle_multiple_valuekeys
    _chosen_keys_to_values
    _chosen_to_newlines_output
}


function choose_multiple() {

    _initialise

    _HIGHLIGHTED_CHOICE=1
    
    # add 'submit' faux last choice
    _CHOICES_ORDER+=( ">" )
    ((_NUMBER_OF_CHOICES++))

    # associative array, indexed by choice key, when set it has been selected
    declare -A CHOSEN_KEYS
    
    # print choices for the first time
    for ENTRY in "${_CHOICES_ORDER[@]}"; do
        echo "$ENTRY"
    done
    
    set_y
    _TOP=$((_Y - _NUMBER_OF_CHOICES ))

    _choose_refresh

        # kwy handling thanks to: https://stackoverflow.com/a/56200043
    while read -sn1 key; do

        read -sn1 -t 0.0001 k1; read -sn1 -t 0.0001 k2; read -sn1 -t 0.0001 k3
        key+=${k1}${k2}${k3}

        case "$key" in
            $'\e[A'|$'\e0A')  # up arrow
                if [ $_HIGHLIGHTED_CHOICE -gt 1 ]; then ((_HIGHLIGHTED_CHOICE--)); fi; _choose_refresh;;

            $'\e[B'|$'\e0B')  # down arrow
                if [ $_HIGHLIGHTED_CHOICE -lt $_NUMBER_OF_CHOICES ]; then ((_HIGHLIGHTED_CHOICE++)); fi; _choose_refresh;;

            '')  # whitespace character
            
                # if last entry '> ...' was selected stop
                if [ $_HIGHLIGHTED_CHOICE -eq $_NUMBER_OF_CHOICES ]; then
                    unset CHOSEN;
                    for i in "${_CHOICES_ORDER[@]}"; do
                        if [ -v "CHOSEN_KEYS[$i]" ]; then
                            CHOSEN+=( "$i" )
                        fi
                    done
                    _chosen_keys_to_values
                    _chosen_to_newlines_output
                    
                    break
                fi
                
                unset CHOSEN; CHOSEN=${_CHOICES_ORDER[(($_HIGHLIGHTED_CHOICE - 1))]}
                _chosen_handle_multiple_valuekeys
                 
                for i in "${CHOSEN[@]}"; do
                    if [ -v "CHOSEN_KEYS[$i]" ]; then
                        unset CHOSEN_KEYS["$i"]
                    else
                        CHOSEN_KEYS["$i"]="chosen"
                    fi
                done
                
                # update the last entry to show selection
                local _DISPLAY_CHOSEN=">"
                for i in "${_CHOICES_ORDER[@]}"; do
                    if [ -v "CHOSEN_KEYS[$i]" ]; then
                        _DISPLAY_CHOSEN+=" $i"
                    fi
                done
                
                _CHOICES_ORDER[$((_NUMBER_OF_CHOICES - 1))]=$_DISPLAY_CHOSEN
                _choose_refresh
        esac
    done

}