#
# framework
#

function hex_encode {
    typeset src="$@"
    typeset dest
    integer i

    for ((i = 0; i < ${#src}; i++)); do
        dest+="${ printf "%02X\n" "'${src:${i}:1}'" }"
    done

    print "${dest}"
    return 0
}

function get_edit_mode {
    if [[ -o vi ]]; then
        if [[ ${.sh.edmode} == $'\E' ]]; then
            print "vi_input"
        else
            print "vi_control"
        fi
    elif [[ -o emacs ]]; then
        print "emacs"
    elif [[ -o gmacs ]]; then
        print "gmacs"
    else
        return 1
    fi

    return 0
}

function keybind_map_term {
    print -- "${TERM}"
}

function keybind_match_key_binding {
    nameref key_sequence=$1
    nameref callback=$2
    nameref callback_data=$3
    typeset term=${ keybind_map_term } || return 1
    nameref path=keybind_bindings[${term}]

    for key in "${key_sequence[@]}"; do
        nameref path="${!path}.nodes[${ hex_encode "${key}" }]"

        # if the current node has children the current path exists, thus
        # traverse further
        (( ${#path.nodes[@]} > 0 )) && continue

        # if the current node contains a callback it is a leaf node otherwise
        # the current path does not exist
        if [[ ${ typeset -p path.key_binding } != "" ]]; then
            callback="${path.key_binding.callback}"
            callback_data="${path.key_binding.callback_data}"
            return 0
        else
            return 1
        fi
    done
    return 2
}

function keybind_keybd_handler {
    typeset -S -a keybd_buffer
    typeset -a keybd_buffer_temp
    integer retval
    typeset callback
    typeset callback_data

    keybd_buffer+=( "${.sh.edchar}" )
    .sh.edchar=

    while (( ${#keybd_buffer[@]} > 0 )); do
        keybind_match_key_binding keybd_buffer callback callback_data
        case $? in
            0)
                # matched a key sequence, now call callback function and clear
                # the buffer
                ${callback:-true} callback_data
                unset keybd_buffer
                ;;
            1)
                # no match, print the first element, pop it and try again
                .sh.edchar+="${keybd_buffer[0]}"
                if (( ${#keybd_buffer[@]} > 1 )); then
                    keybd_buffer_temp=( "${keybd_buffer[@]:1}" )
                    unset keybd_buffer
                    typeset -S -a keybd_buffer=( "${keybd_buffer_temp[@]}" )
                else
                    unset keybd_buffer
                fi
                ;;
            *)
                # partial match, do nothing
                break
                ;;
        esac
    done

    return 0
}

function keybind_add_key_binding {
    typeset term=$1
    nameref callback=$2
    typeset callback_data="$3"
    shift 3
    typeset -a key_sequence=( $@ )
    typeset key

    if [[ ${ typeset -p keybind_bindings[${term}] } == "" ]]; then
        compound keybind_bindings[${term}]
    fi
    nameref path=keybind_bindings[${term}]

    for key in "${key_sequence[@]}" ; do
        if [[ ${ typeset -p path.nodes } == "" ]]; then
            compound -A path.nodes
        fi
        nameref path="${!path}.nodes[${ hex_encode "${key}" }]"
    done

    if [[ ${ typeset -p path.key_binding } == "" ]]; then
        compound path.key_binding
    fi

    path.key_binding.callback="${!callback}"
    path.key_binding.callback_data="${callback_data}"
}

#
# example callbacks
#

function keybind_map_term {
    typeset term

    case ${TERM} in
        xterm*|screen*)
            print "xterm"
            ;;
        linux*)
            print "linux"
            ;;
        *)
            return 1
            ;;
    esac

    return 0
}

function keybind_cb_ignore {
    .sh.edchar=""
}

function keybind_cb_literal {
    nameref literal="$1"

    .sh.edchar="${literal}"
}

function keybind_cb_delete {
    typeset edit_mode

    edit_mode="${ get_edit_mode }" || return 1

    case ${edit_mode} in
        emacs|gmacs)
            if [[ ${.sh.edtext} != "" ]]; then
                .sh.edchar=$'\004'
            else
                .sh.edchar=
            fi
            ;;
        vi_control)
            .sh.edchar="x"
            ;;
        vi_input)
            .sh.edchar=$'\Exi'
            ;;
    esac
}

function keybind_cb_sequence {
    nameref sequence=$1
    typeset edit_mode
    typeset option
    compound -S -A sequences=(
        [forward]=(
            emacs=$'\006'
            gmacs=$'\006'
            vi_control="l"
            vi_input=$'\Eli'
        )
        [forward-word]=(
            emacs=$'\Ef'
            gmacs=$'\Ef'
            vi_control='w'
            vi_input='\Ewi'
        )
        [backward]=(
            emacs=$'\002'
            gmacs=$'\002'
            vi_control='h'
            vi_input='\Ehi'
        )
        [backward-word]=(
            emacs=$'\Eb'
            gmacs=$'\Eb'
            vi_control='b'
            vi_input='\Ebi'
        )
        [beginning-of-line]=(
            emacs=$'\001'
            gmacs=$'\001'
            vi_control='0'
            vi_input=$'\E0i'
        )
        [end-of-line]=(
            emacs=$'\005'
            gmacs=$'\005'
            vi_control='$'
            vi_input=$'\E$a'
        )
    )

    edit_mode="${ get_edit_mode }" || return 1

    nameref sequence_result=sequences[${sequence}].${edit_mode}
    .sh.edchar="${sequence_result}"
}

#
# example bindings
#

unset keybind_bindings
typeset -C -A keybind_bindings

# left
#keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[D'
# ctrl+left
keybind_add_key_binding "xterm" keybind_cb_sequence "backward-word" $'\E[1;' "5" "D"
# shift+left
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '2' 'D'
# ctrl+shift+left
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '6' 'D'
# right
#keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[C'
# ctrl+right
keybind_add_key_binding "xterm" keybind_cb_sequence "forward-word" $'\E[1;' "5" "C"
# shift+right
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '2' 'C'
# ctrl+shift+right
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '6' 'C'
# up
#keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[A'
# ctrl+up
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '5' 'A'
# shift+up
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '2' 'A'
# ctrl+shift+up
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '6' 'A'
# down
#keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[B'
# ctrl+down
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '5' "B"
# shift+down
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '2' "B"
# ctrl+shift+down
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '6' "B"
# delete
keybind_add_key_binding "xterm" keybind_cb_sequence "delete-char" $'\E[3~'
# ctrl+delete
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[3;' '5' '~'
# shift+delete
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[3;' '2' '~'
# ctrl+shift+delete
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[3;' '6' '~'
# insert
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[2~'
# ctrl+insert
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[2;' '5' '~'
# shift+insert
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[2;' '2' '~'
# ctrl+shift+insert
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[2;' '6' '~'
# home
keybind_add_key_binding "xterm" keybind_cb_sequence "beginning-of-line" $'\E[H'
# ctrl+home
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '5' 'H'
# shift+home
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '2' 'H'
# ctrl+shift+home
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '6' 'H'
# end
keybind_add_key_binding "xterm" keybind_cb_sequence "end-of-line" $'\E[F'
# ctrl+end
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[4;' '5' 'F'
# shift+end
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[4;' '2' 'F'
# ctrl+shift+end
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[4;' '6' 'F'
# pageup
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[5~'
# ctrl+pageup
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[5;' '5' '~'
# shift+pageup
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[5;' '2' '~'
# ctrl+shift+pageup
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[5;' '6' '~'
# pagedown
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[6~'
# ctrl+pagedown
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[6;' 5 '~'
# shift+pagedown
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[6;' 2 '~'
# ctrl+shift+pagedown
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[6;' 6 '~'
# menu
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[29~'
# ctrl+menu
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[29;' '5' '~'
# shift+menu
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[29;' '2' '~'
# ctrl+shift+menu
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[29;' '6' '~'
# F1
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\EOP'
# ctrl+F1
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '5' 'P'
# shift+F1
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '2' 'P'
# ctrl+shift+F1
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '6' 'P'
# F2
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\EOQ'
# ctrl+F2
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '5' 'Q'
# shift+F2
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '2' 'Q'
# ctrl+shift+F2
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '6' 'Q'
# F3
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\EOR'
# ctrl+F3
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '5' 'R'
# shift+F3
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '2' 'R'
# ctrl+shift+F3
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '6' 'R'
# F4
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\EOS'
# ctrl+F4
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '5' 'S'
# shift+F4
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '2' 'S'
# ctrl+shift+F4
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[1;' '6' 'S'
# F5
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[15~'
# ctrl+F5
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[15;' '5' '~'
# shift+F5
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[15;' '2' '~'
# ctrl+shift+F5
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[15;' '6' '~'
# F6
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[17~'
# ctrl+F6
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[17;' '5' '~'
# shift+F6
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[17;' '2' '~'
# ctrl+shift+F6
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[17;' '6' '~'
# F7
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[18~'
# ctrl+F7
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[18;' '5' '~'
# shift+F7
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[18;' '2' '~'
# ctrl+shift+F7
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[18;' '6' '~'
# F8
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[19~'
# ctrl+F8
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[19;' '5' '~'
# shift+F8
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[19;' '2' '~'
# ctrl+shift+F8
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[19;' '6' '~'
# F9
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[20~'
# ctrl+F9
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[20;' '5' '~'
# shift+F9
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[20;' '2' '~'
# ctrl+shift+F9
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[20;' '6' '~'
# F10
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[21~'
# ctrl+F10
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[21;' '5' '~'
# shift+F10
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[21;' '2' '~'
# ctrl+shift+F10
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[21;' '6' '~'
# F11
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[23~'
# ctrl+F11
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[23;' '5' '~'
# shift+F11
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[23;' '2' '~'
# ctrl+shift+F11
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[23;' '6' '~'
# F12
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[24~'
# ctrl+F12
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[24;' '5' '~'
# shift+F12
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[24;' '2' '~'
# ctrl+shift+F12
keybind_add_key_binding "xterm" keybind_cb_ignore "" $'\E[24;' '6' '~'
# left
#keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[D'
# right
#keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[C'
# up
#keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[A'
# down
#keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[B'
# insert
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[2~'
# delete
keybind_add_key_binding "linux" keybind_cb_sequence "delete-char" $'\E[3~'
# home
keybind_add_key_binding "linux" keybind_cb_sequence "beginning-of-line" $'\E[1~'
# end
keybind_add_key_binding "linux" keybind_cb_sequence "end-of-line" $'\E[4~'
# page up
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[5~'
# page down
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[6~'
# F1
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[[' 'A'
# F2
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[[' 'B'
# F3
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[[' 'C'
# F4
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[[' 'D'
# F5
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[[' 'E'
# F6
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[17~'
# F7
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[18~'
# F8
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[19~'
# F9
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[20~'
# F10
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[21~'
# F11
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[23~'
# F12
keybind_add_key_binding "linux" keybind_cb_ignore "" $'\E[24~'


