# -*- shell-script -*-

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

_objects=()
_objects_keys=()
_objects_defaults=()
_objects_types=()
_objects_width=${#_objects_keys[@]}
_objects_position=0
_objects_size=0
_objects_modified=0

_objects_have_interface=0
_objects_interface_methods=''
_objects_interface_options=''

function §objects.seek { # args

    local matched=0
    local start=0
    local saved=$_objects_position
    local cardinal=${#_objects[@]}

    local i=0
    for (( i=$start; i<=$cardinal; i+=$_objects_width )); do
	matched=0
	if [[ "${_objects[$i]}" == "$1" ]]; then
	    matched=1
	    if (( $# == 2 )); then
		if [[ "${_objects[$i+1]}" == "$2" ]]; then
		    matched=1
		else
		    matched=0		    
		fi
	    fi		
	fi

	if (( matched )); then
	    _objects_position=$i
	    break
	fi	
    done

    if ! (( matched )); then
	_objects_position=$saved
	return 1
    else
	return 0
    fi
}

function §objects.seek_start {
    _objects_position=0
}

function §objects.seek_end {
    (( _objects_position = (_objects_size-1) * _objects_width ))
}

function §objects.next? {
    (( _objects_position + _objects_width <= ${#_objects[@]} ))
}

function §objects.current {
    printf '%s' "${_objects[$_objects_position]}"
}

function §objects.next {
    (( _objects_position += _objects_width ))
}

function §objects.previous? {
    (( _objects_position + 1 >= 0 )) && return 0
    _objects_position=0
    return 1
}

function §objects.previous {
    (( _objects_position -= _objects_width ))
}

function §objects.select { # key value [which=0]-> "id1 id2..."

    local key=$1
    local wanted=$2
    local which=${3:-1} # 0: any, 1: all
    local regexp=''
    local value=''
    local ids=''
    local i=0
    local saved=${_objects[$_objects_position]}

    for ((i=0; i<=${#_objects[@]}; i+=${#_objects_keys[@]})); do
	_objects_position=$i
	
	value=$(§object.get $key)
	if [[ "$value" == "$wanted" ]]; then
	    # literal match
	    ids="$ids ${_objects[$i]}"
	    [[ which -eq 0 ]] && break
	    
	elif [[ "$wanted" =~ ^/.+/$ ]]; then
	    # regexp given
	    regexp=${wanted:1:${#wanted}-2}
	    
	    if [[ "$value" =~ $regexp ]]; then
		# regexp match
		ids="$ids ${_objects[$i]}"
		(( which )) || break
	    fi
	fi
    done
    §objects.seek "$saved"
    [[ "${ids:0:1}" == " " ]] && ids="${ids:1}"
	
    printf '%s' "$ids"
}

function §objects.modified? { (( _objects_modified )); }

function §objects.modified { # value
    _objects_modified=$1
}

function §objects.interface? { (( _objects_have_interface )); }

function §objects.size {
    printf '%s' "$_objects_size"
}

function §objects.empty? {
    (( _objects_size == 0 ))
}

function §objects.list { # [-l] [ids]

    local current=$_objects_position
    local opt=''

    while [[ "${1:0:1}" == "-" ]]; do
	if [[ "$1" == "-l" ]]; then
	    opt="$1"
	fi
	shift
    done

    if [[ "$1" != '' ]]; then
	for id in $@; do
	    if [[ "$id" == "." ]]; then
		id=${_objects[$current]}
	    fi
	    
	    if §objects.seek $id; then
		§object.list -l $id
	    fi
	done
    else
	§objects.seek_start
	while §objects.next?; do
	    §object.list $opt
	    §objects.next
	done
    _objects_position=$current
    fi
}

function §objects.all {
    
    local i=0
    local all=""
    for ((i=0; i<=${#_objects[@]}; i+=${#_objects_keys[@]})); do
	all="$all ${_objects[$i]}"
    done
    printf '%s' "${all:1}"
}

function §objects.init {
    
    local i=0
    for ((i=0; i<${#_objects[@]}; i+=${#_objects_keys[@]})); do
	_objects_position=$i
	§object.init
    done
    _objects_position=0
}

function §objects.dump {
    
    local dumpfile="$_oBjEcTs_data_home/${1:-objects.dump}"

    if §objects.empty?; then
	touch $dumpfile
	return 0
    fi

    local data="$(printf "%q " "${_objects[@]}")"

    local dump="
        _objects_keys=(${_objects_keys[@]})
        _objects_width=${_objects_width}
        _objects_position=0
        _objects_size=${_objects_size}
        _objects_modified=0
    
        _objects_have_interface=${_objects_have_interface}
        _objects_interface_methods='${_objects_interface_methods}'
        _objects_interface_options='${_objects_interface_options}'
        _objects_interface_options_found=()

        _objects=($data)
        "

    if [[ "$2" == "append" ]]; then
        printf '%s\n' "$dump" >> $dumpfile
    else
        printf '%s\n' "$dump" > $dumpfile
    fi
}

function §objects.restore {
    local file=$_oBjEcTs_data_home/${1:-objects.dump}
    [[ -f "$file" ]] && . "$file"
}

function §objects.clear {
    _objects=()
    _objects_position=0
    _objects_size=0
    _objects_modified=1
}

# "instance methods" (always operate on the object at the current position) ####

function §object.new {
    local i

    for ((i=1; i<=${#_objects_keys[@]}; i++)); do
	if (( $# >= i )); then
	    _objects[${#_objects[@]}]="${!i}"
	else
	    _objects[${#_objects[@]}]="${_objects_defaults[$i-1]}"
	fi
    done
    (( _objects_size += 1 ))
    _objects_modified=1
    §objects.seek "$1"
}
function §object.init { :; }

function §object.delete {

    local start=$_objects_position
    local width=$_objects_width
    local i=0

    for((i=$start; i<=$start+$width-1; i++)); do
	unset _objects[$i]
    done

    local objects="$(printf '%q ' "${_objects[@]}")"
    eval "_objects=($objects)"

    _objects_position=0
    (( _objects_size-=1 ))
    _objects_modified=1
}

function §object.id {
    §objects.current
}

function §object.get { # key -> "value"
    local key=$1
    local index=-1
    local i=0
 
    for ((i=0; i<=${#_objects_keys[@]}; i++)); do
	if [[ "${_objects_keys[$i]}" == "$key" ]]; then
	    index=$i
	    break
	fi
    done

    if (( index >= 0 )); then 
	printf '%s' "${_objects[$_objects_position+$index]}"
    fi
}

function §object.set { # key value

    local key=$1
    local value=$2
    local index=-1
    local i=0

    for ((i=0; i<=${#_objects_keys[@]}; i++)); do
	if [[ "${_objects_keys[$i]}" == "$key" ]]; then
	    index=$i
	    break
	fi
    done

    if (( index >= 0 )); then
	_objects[$_objects_position+$index]=$value
	_objects_modified=1
    fi
}

function §object.first? { # [key]
    if (( $# == 1 )); then
	[[  "$1" == "$(objects.current)" ]]
    else
	(( _objects_position == 0 ))
    fi
}

function §object.last? { # [key]
    local i
    (( i = (_objects_size-1) * _objects_width ))
    local id=${_objects[$i]}

    if (( $# == 1 )); then
	[[ "$id" == "$1" ]]
    else
	[[ "$id" == "$(objects.current)" ]]
    fi
}

function §object.code {

    local i=0;
    local pos=0
    
    if ! (( _objects_have_interface )); then
	local args=''
	
	for ((i=0; i<${_objects_width}; i++)); do
	    (( pos = _objects_position + i ))
	    args+=" $(printf '%q' "${_objects[$pos]}")"
	done
	args="${args:1}"
	
	printf '%s\n' "§object.new $args"
    else
	local key=""
	local val="" v=""
	local def=""
	local type=""
	declare -a vals

	for ((i=0; i<${_objects_width}; i++)); do
	    (( pos = _objects_position + i ))
	    key="${_objects_keys[$i]}"
	    def="${_objects_defaults[$i]}"
	    type="${_objects_types[$i]}"
	    val="${_objects[$pos]}"

	    if (( i == 0 )); then
		printf '%s\n' "+object '$val'"
	    else
		[[ "$type" =~ t ]] && continue
		
		if [[ "$type" =~ a ]]; then
		    declare -a vals=$val
		    for v in "${vals[@]}"; do
			if [[ "$v" != "$def" ]]; then
			    if [[ "$v" =~ \' ]]; then
				printf '%s\n' "  --$key $(printf '%q' "$v")"
			    else
				printf '%s\n' "  --$key '$v'"
			    fi
			fi
		    done
		    continue
		fi
		
		if [[ "$val" != "$def" ]]; then
		    if [[ "$type" =~ b ]]; then
			[[ "$val" == "0" ]] && val="false"
			[[ "$val" == "1" ]] && val="true"
			printf '%s\n' "  --$key $val"
			continue
		    fi

		    if [[ "$val" =~ \' ]]; then
			printf '%s\n' "  --$key $(printf '%q' "$val")"
		    else
			printf '%s\n' "  --$key '$val'"
		    fi
		fi
	    fi
	done
    fi
}

function §objects.code {

    §objects.seek_start
    while §objects.next?; do
	§object.code
	§objects.next
	printf '\n'
    done
    §objects.seek_start
}
    
function §object.list {
    local opt="$1"

    if [[ "$opt" == '-l' ]]; then
	local key
	for key in ${_objects_keys[@]}; do
	    printf '%s\n' "$key: $(§object.get $key)"
	done;
	printf '\n'
    else
	printf '%s\n' "$(§object.id)"
    fi
}

################################################################################
## INTERFACE

if (( _objects_have_interface )); then

    function §objects.ui.enable {
	local opt='' optname=''

	for opt in $_objects_interface_options; do
	    optname="${opt/--/}"
	    
	    if [[ " ${_objects_keys[@]} " =~ " $optname " ]]; then
		eval "function $opt {
                    if (( \$# == 0 )); then
		      §object.get_$optname
                    else
                      §object.set_$optname \"\$@\"
                    fi
	        }"
	    else
		eval "function $opt {                     
                     §object.$optname \"\$@\"
                }"
	    fi
	done
    }
    
    function §objects.ui.disable {
	local opt=''
	for opt in $_objects_interface_options; do
	    unset -f -- $opt
	done
    }
        
    function +object {

	declare -a args
	declare -a cargs
	declare remove arg next 
	
        # remove a previous type's interface
	remove="§${_oBjEcTs_focus}s.ui.disable"

	if [[ "$_oBjEcTs_focus" != "object" ]]; then
	    if [[ -n "$_oBjEcTs_focus" ]]; then
		if type -t $remove &> /dev/null; then
		    $remove
		fi
	    fi
	    §objects.ui.enable
	fi
	
        # set global current object type
	_oBjEcTs_focus="object"
	
        # no arg -> return (setting focussed type is sideeffect)
	if (( $# == 0 )); then
	    return 0
	fi

        # parse arguments and call ui functions	

	# check if the first argument is the object's name
	if [[ "$1" =~ ^[^\-] ]]; then 
	    # seek to the named object or create it
	    §objects.seek "$1" || §object.new "$1"
	    shift
	fi

	args=("$@")
	declare -i i=0
	for (( i=0; i<${#args[@]}; i++ )); do
	    arg="${args[i]}"
	    next="${args[i+1]}"

	    if [[ "$arg" =~ ^\-\- ]]; then		
                # get the command arguments
		cargs=()
		until [[ -z "$next" || "$next" =~ ^\-\- ]]; do	    
		    cargs[${#cargs[*]}]="$next"
		    i+=1; next="${args[i+1]}"
		done
		$arg "${cargs[@]}"
	    fi
	done
    }

    # completion
    function §object.complete {

	local cur opts
	COMPREPLY=()

	cur="${COMP_WORDS[COMP_CWORD]}"
	opts="$(§objects.all)"

	if [[ ${cur} == * ]]; then
	    if [[ ${cur} =~ ^\- ]]; then
		cur=${cur//-/\\-}
		COMPREPLY=( $(compgen -W "${_objects_interface_options}" ${cur}) )
	    else
		cur=${cur//-/\\-}
		COMPREPLY=( $(compgen -W "${opts} ${_objects_interface_options}" ${cur}) )
	    fi
	fi
        return 0
    }
    complete -o default -F §object.complete +object

    function §object.complete_names {

	local cur="${COMP_WORDS[COMP_CWORD]}"
	local opts="$(§objects.all)"

	COMPREPLY=( $(compgen -W "${opts}" ${cur}) )
        return 0
    }
    
    function +objects {
	if (( $# > 0 )); then
	    local opt="$1"
	    shift	
	    opt="${opt/--/}"
	    if [[ "$(type -t §objects.$opt 2> /dev/null)" == "function" ]]; then
		§objects.$opt "$@"
	    fi
	fi
    }
    
    function §objects.complete {

	local cur="${COMP_WORDS[COMP_CWORD]}"
	local opts="--list --code"

	COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
        return 0
    }
    complete -o default -F §objects.complete +objects

fi
