# -*- shell-script -*-

################################################################################

§class "action" --with-interface \
    --properties "name:r" "description" "input:b=1" \
                 "depends" "bash=4.1" "remote:b=1" "core:rbt"

# Warning: if the "core" property changes it's position, the
# temporary setter in the file 'core' needs to be adjusted.

§function.clone "§action.new" "§action._new"
§function.clone "§actions.dump" "§actions._dump"
§function.clone "§action.code" "§action._code"
§function.clone "§action.get_depends" "§action._get_depends"

################################################################################

function §action.new {

    §action._new "$1"

    if ! §function.defined? "+action-$1"; then
	eval "
	+action-$1 ()
	{ 
	    ! executable? && +handlers
	    return 0
	}"
    fi
}

function §action.get_depends {
    §interpolate "$(§action._get_depends)"
}

function §action.bind {

    local action="$(§action.get_name)"
    local input="$(§action.get_input)"

    local keyseq="$1"
    local keymap="${2:-$bashrun_keymap}"
    local keyname="${3:-$(§ks2kn $keyseq)}"

    local bind=''
    local enter='\C-x00'
    local char=''

    bashrun_keymap="$keymap"

    # if "pass", bind keyseq directly to accept-line (readline) and return
    if [[ "$action" == "pass" ]]; then
	bind="-m $keymap '\"$keyseq\": accept-line'"
	eval "bind $bind"
	§bindings.update "$keyseq" "$keymap" "$action" "$bind"
	return 0
    fi

    # bind -x C-g directly
    # this way C-g will still abort from readline searches etc
    if [[ "$keyseq" == "\C-g" ]]; then
	bind="-m $keymap -x '\"$keyseq\": +action-abort'"
	eval "bind $bind"
	§bindings.update "$keyseq" "$keymap" "$action" "$bind"
	return 0
    fi

    if ! §internals.seek "$action" "$keymap"; then
	§internal.new "$action" "$keymap" "new"
	§internals.seek "$action" "$keymap"
    fi

    if [[ "$(§internal.get_char)" == "new" ]]; then
	
        # no previous internal binding, get next invalid character to bind to
	char=$(printf "%.2X" $bashrun_bindings_next_char)

	if (( bashrun_bindings_next_char > 0x7f )); then
	    §debug warn "maximum number of internal keyseqs reached, skipping..."
	    return 1
	fi
	
	bind="-m $keymap -x \$'\"\C-z\\x$char\":"
	bind="$bind §engine.action $action'"

	§debug -y "int" -v "$action" "->" -v "\C-z\\x$char" "($keymap)" 
	eval "bind $bind"
	
	#save readline binding in internal
	§internal.set_readline "$bind"

	# save char in internal	
	§internal.set_char "$char"

	# next free char
	((bashrun_bindings_next_char+=1))
	until [[ ! "$bashrun_bindings_blacklist" =~ \ $bashrun_bindings_next_char\  ]]; do
	    ((bashrun_bindings_next_char+=1))
	done
    else
	# there's already an internal character bound, use it
	char="$(§internal.get_char)"
	§debug -y "ref" -v "$action" "->" -v "\C-z\\x$char" "($keymap)" 
    fi
    
    # don't trigger accept-line if +action --input == 0
    (( input == 0 )) && enter=''

    # bind the requested keyseq to the internal character
    bind="-m $keymap '\"$keyseq\"':$'\"\C-z\x$char$enter\"'"
    
    # bind
    §debug emph -y "usr" -v "$action" "->" -v "$keyname" "($keymap)" 
    eval "bind $bind"

    §bindings.update "$keyseq" "$keymap" "$action" "$bind"
    
    # create or set keyname
    if §keynames.seek "$keyseq"; then
	§keyname.set_name "$keyname"
    else
	§keyname.new "$keyseq" "$keyname"
    fi
    return 0
}

function §action.unbind {
    
    local action="$(§action.get_name)"
    local keyseq="$1"
    local keymap="${2:-$bashrun_keymap}"
    local unbind=''
    local found=0
    local id=''

    bashrun_keymap="$keymap"

    if [[ -z "$keyseq" ]]; then
        # find the next bound binding
	for id in $(§bindings.select action "$action"); do
	    if §bindings.seek "$id" "$keymap"; then
		if [[ "$id" == "$keyseq" ]]; then
		    if §binding.bound?; then		    
			found=1
			break
		    fi
		fi
	    fi
	done
	((found)) || return 1
	
        # get the keyseq that it's bound to
	keyseq="$(§binding.get_keyseq)"
    fi
    
    # unbind keyseq
    unbind="-m $keymap -r '$keyseq'"
    §debug "unbind" -v "$action" "from" -v "$keyseq" -v "($keymap)"
    eval "bind $unbind"

    # restore original readline binding
    §readline.restore "$keyseq" "$keymap"

    # clear binding
    §bindings.seek "$keyseq" "$keymap"
    §binding.set_is_bound 0
    §binding.set_readline ''

    return 0
}

function §action.rebind {

    local action="$(§action.get_name)"
    local keymap="${1:-$bashrun_keymap}"
    local keyseq=''
    local found=0
    local id=''

    bashrun_keymap="$keymap"

    # find the next unbound binding
    for id in $(§bindings.select action $action); do
	if §bindings.seek "$id" "$keymap"; then
	    found=1
	    break
	fi
    done
    ((found)) || return 1

    # get the keyseq that it was bound to before
    keyseq="$(§binding.get_keyseq)"

    # get the action that's bound to this keyseq and unbind it
    for id in §bindings.select keyseq "$keyseq"; do
	§bindings.seek "$id"
	if §actions.seek "$(§binding.get_action)"; then
	    §action.unbind
	fi
    done

    # rebind action
    §actions.seek "$action"
    §action.bind "$keyseq"

    return $?
} 

function §action.bound? {
    local action="$(§action.get_name)"
    local saved="$(§binding.get_keyseq)"

    local id=''
    for id in $(§bindings.select is_bound 1); do
	§bindings.seek "$id"
	if [[ "$(§binding.get_action)" == "$action" ]]; then
	    §bindings.seek "$saved"
	    return 0
	fi
    done
    §bindings.seek "$saved"
    return 1
}

function §action.function? {
    §function.defined? "+action-$(§action.get_name)"
}

function §action.depends_on? {
    [[ " $(§action.get_depends) " =~ \ $1\  ]]
}

function §action.available? {

    local dep=''
    declare -i result=0

    # check for deps
    for dep in $(§action.get_depends); do
	if ! type -P "$dep" &> /dev/null; then
	    §message "Error" "\"$(§action.get_name)\" requires $dep."
	    result=1
	fi
    done

    # check for bash version
    local current="${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}"
    local wanted="$(§action.get_bash)"
    wanted="${wanted//./}"    

    if (( current < wanted )); then
	§message "Error" "\"$(§action.get_name)\" requires bash>=$(§action.get_bash)." 
	result=1
    fi

    return $result
}

function §action.core? { (( $(§action.get_core) )); }
function §action.input? { (( $(§action.get_input) )); }

function §action.run {
    
    local action="$(§action.get_name)"
    local command="$1"
    local retval=0

    if [[ -n "$command" ]]; then
	bashrun_command="$command"
    fi

    §separator "+action-$action"

    # enable the action interface
    §action.api.enable

    # enable action debugging
    bashrun_debug_action=1

    # run the action function
    +action-$action "$@"
    retval=$?

    # disable action debugging
    bashrun_debug_action=0

    if (( bashrun_action_return_value == -1 )); then
	bashrun_action_return_value="$retval"
	
	local res=""
	[[ retval -eq 0 ]] && res="-c launch"
	[[ retval -eq 1 ]] && res="-y abort (all done)"
	[[ retval -eq 2 ]] && res="-y abort (all done, don't unmap)"
	§debug -y "+action-$action" "returns" -v "$retval" "->" $res
    fi

    §separator "+action-$action complete"

    return 0
}

function §action.api.enable {

    function command {
	if (( $# == 0 )); then
	    printf "%s\n" "$bashrun_command"
	else
	    bashrun_command="$@"
	    §debug "command =" -v "$bashrun_command"
	fi
    }
    function word { 
	§command.get_word; 
	printf "%s\n" "$bashrun_command_word";
    }
    function line { printf "%s\n" "$bashrun_command_line"; }

    function quote-command { §quote bashrun_command; }

    function executable? {
	§command.executable?
    }

    function builtin? {
	§command.builtin?
    }

    function bookmark? {
	§command.bookmark?
    }
    
    function action { +action-$1; }
    function handlers { §handlers.apply; }
    function rules { §rules.apply; }
    function terminal { §command.add_terminal "$1"; }
    function hold { §command.add_hold "$1"; }
    function pager { §command.add_pager; }    
    function user { tput cr; tput el; map; §command.add_su "${1:-root}"; }
    function map { §window.map; tput cr; }
}

function §action.api.disable {

        unset -f \
	    command \
	    word \
	    line \
	    executable? \
	    builtin? \
	    bookmark? \
	    handlers \
	    rules \
	    terminal \
	    hold \
	    pager \
	    user \
	    map
}

function §action.print_depends {

    local dep=''
    local depends="$(§action.get_depends)"
    local bash="$(§action.get_bash)"
    local str=''

    local r="\e[0;31m" # red
    local g="\e[0;32m" # green 
    local n="\e[0m"    # none

    local found="$r"

    if [[ -n "$depends" || "$bash" != "4.1" ]]; then
	
	for dep in $depends; do
	    if type -P "$dep" &> /dev/null; then
		found="$g"
	    else
		found="$r"
	    fi
	    str=", $found$dep$n$str" 
	done

	local current="${BASH_VERSINFO[0]}${BASH_VERSINFO[1]}"
	local wanted="$bash"
	wanted="${wanted//./}"    

	local ok="$g"
	[[ current -lt wanted ]] && ok="$r"

	if [[ "$bash" != "4.1" ]]; then 
	    str=", ${ok}bash>=${bash// /.}$n$str"	
	fi

	str="${str:2}"
	printf "%s" "$str"
    fi
}

function §action.code {

    §action._code

    if §action.function?; then
	printf "\n"
	§function.code "+action-$(§action.get_name)"
    fi
}

function §actions.dump {

    local file="$(§objects.data_home)/${1:-actions.dump}"
    local action=''
    §actions._dump "$1"

    local saved="$(§action.get_name)"

    §actions.seek_start
    while §actions.next?; do
	action="$(§action.get_name)"
	§function.code "+action-$action" >> "$file"
	§actions.next
    done
    §actions.seek "$saved"    

    # bashrun completion helper
    §actions.list > "$bashrun_cache_home/actions"
}

## shortcut functions to §action.(un|re)bind ##

function +bind () {

    local action="$1"
    local keyseq="$2"
    local keyname="${3:-$(§ks2kn $keyseq)}"

    if §actions.seek "$action"; then
	§action.bind "$keyseq" "$bashrun_keymap" "$keyname"
    else
	§debug fail "no such action:" -v "$action"
	return 1
    fi
}

function +unbind () {

    local action="$1"
    local keyseq="$2"

    if §actions.seek "$action"; then
	§action.unbind "$keyseq" "$bashrun_keymap"
    else
	§debug fail "no such action:" -v "$action"
	return 1
    fi
}

function +rebind () {

    local action="$1"

    if §actions.seek "$action"; then
	§action.rebind "$bashrun_keymap"
    else
	§debug fail "no such action:" -v "$action"
	return 1
    fi
}

function +keymap () {
    local keymap="$1"
    if [[ ! "$keymap" =~ (emacs|vi-insert|vi-command) ]]; then
	§debug warn "must be one of 'emacs', 'vi-insert' or 'vi-command'"
	§debug warn "keymap remains set to" -v "$bashrun_keymap"
	return 1
    fi
    bashrun_keymap="$keymap"
}

function §keymaps.complete {
    COMPREPLY=()    
    local cur="${COMP_WORDS[COMP_CWORD]}"
    local keymaps="emacs vi-insert vi-command"
    COMPREPLY=( $(compgen -W "${keymaps}" ${cur}) )
    return 0
}

complete -o default -F §action.complete_names +bind +unbind +rebind
complete -o default -F §keymaps.complete +keymap
