# -*- shell-script -*-

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

_objects_namespace='boop'
_objects_site="."
_objects_data_home='/tmp'
_objects_cache_home='/tmp'

_objects_types=''
_objects_focus=''

function §objects.site {
    
    if (( $# == 0 )); then
	printf '%s' "$_objects_site"
    else
	_objects_site="$1"
    fi
}

function §objects.cache_home {
    
    if (( $# == 0 )); then
	printf '%s' "$_objects_cache_home"
    else
	_objects_cache_home="$1"
    fi
}

function §objects.data_home {
    
    if (( $# == 0 )); then
	printf '%s' "$_objects_data_home"
    else
	_objects_data_home="$1"
    fi
}

function §objects.namespace {
    
    if (( $# == 0 )); then
	printf '%s' "$_objects_namespace"
    else
	_objects_namespace="$1"
    fi
}

function §objects.types {    
    printf '%s' " $_objects_types "
}

function §objects.register_type {

    _objects_types+=" $1"
    if [[ "${_objects_types:0:1}" == ' ' ]]; then
	_objects_types="${_objects_types:1}"
    fi
}

function §objects.focus {
    
    if (( $# == 0 )); then
	printf '%s' "$_objects_focus"
    else
	_objects_focus="$1"
    fi
}

function §objects.seek_start {
    local type=""
    for type in $_objects_types; do
	§${type}s.seek_start
    done    
}

function §objects.init {
    local callback="$1"
    local type=""
    for type in $_objects_types; do
	[[ -n "$callback" ]] && eval "$callback"
	eval "§${type}s.init"
    done
    §objects.seek_start
}

function §objects.dump {
    local type=""
    for type in $_objects_types; do
	eval "§${type}s.dump"
    done
}

function §objects.restore {
    local type=""
    for type in $_objects_types; do
	eval "§${type}s.restore"
    done
}

function §objects.clear {
    local type=""
    for type in $_objects_types; do
	eval "§${type}s.clear"
    done
}

function §class {

    # §class 'name' --properties <propspecs>... --with-interface [<methods>]...

    local type="$1"
    shift

    local has_interface=0
    local arg="" target="ignored" property=""
    declare -a ignored properties methods property_names defaults types

    # parse arguments...
    for arg; do
	if [[ "$arg" == "--properties" ]]; then
	    target="properties"
	    continue

	elif [[ "$arg" == "--with-interface" ]]; then
	    has_interface=1
	    target="methods"
	    continue

	else
	    eval "$target[\${#$target[@]}]=\"\$arg\""
	fi
    done

    # warn about ignored arguments...
    if (( ${#ignored[@]} )); then
	printf '%s\n' "warning: §class $type: ignored arguments: ${ignored[@]}" >&2
    fi

    §objects.register_type "$type"

    # get property names, default values and types from property specs
    for property in "${properties[@]}"; do
	if [[ "$property" =~ ([^:=]+)(:([^=]+))?(=(.+))? ]]; then
	    property="${BASH_REMATCH[1]}"
	    types[${#types[@]}]="${BASH_REMATCH[3]}"
	    defaults[${#defaults[@]}]="${BASH_REMATCH[5]}"
	fi
	property_names[${#property_names[@]}]="$property"
    done

    # prepare interface options (prefix method names with --)
    if (( has_interface )); then

	local interface=(new code ${property_names[@]} ${methods[@]})

	local i=0
	for ((i=0; i<${#interface[@]}; i++)); do
	    interface[$i]="--${interface[$i]}"
	done
	interface="${interface[@]}"
    fi

    # the object source file
    local file="$_objects_cache_home/$type.bash"

    # source it if it already exists, and return
    if [[ -f "$file" ]]; then
	source "$file"
	return 0
    fi

    # create object source file from object template...
    local names="${property_names[@]}"

    local defaults="$(declare -p defaults)"
    defaults="${defaults:21:${#defaults}-22}"

    local types="$(declare -p types)"
    types="${types:18:${#types}-19}"
    
    cat "$_objects_site/object" | \
	sed "s|_objects_keys=()|_objects_keys=($names)|" | \
	sed "s|_objects_defaults=()|_objects_defaults=$defaults|" | \
	sed "s|_objects_types=()|_objects_types=$types|" | \
	sed "s|_objects_interface_options=''|_objects_interface_options='$interface'|" | \
	sed "s|_objects_have_interface=0|_objects_have_interface=$has_interface|" | \
	sed "s|_objects_interface_methods=''|_objects_interface_methods='${methods[@]}'|" | \
	sed "s|object|$type|g;s|_$type|_$(§objects.namespace)_$type|g" | \
	sed "s|oBjEcT|object|g" \
	> "$file"

    # create object methods and append them to the object source file    
    local pos=0
    local access='rw'

    for property in "${properties[@]}"; do
	if [[ "$property" =~ ([^:=]+)(:([^=]+))?(=(.+))? ]]; then
	    property="${BASH_REMATCH[1]}"
	    access="${BASH_REMATCH[3]}"
	    [[ "$access" == "b" ]] && access="rwb"
	    [[ "$access" == "a" ]] && access="rwa"
	    [[ "$access" == "t" ]] && access="rwt"
	fi
	[[ -z "$access" ]] && access="rw"
	[[ "$access" =~ t ]] && access="${access/t/}"

	if [[ "$access" =~ r ]]; then
	    if [[ "$access" =~ a ]]; then
      		§object.create_array_reader "$type" "$property" "$pos" >> "$file"
	    else
		§object.create_reader "$type" "$property" "$pos" >> "$file"
	    fi
	fi
	if [[ "$access" =~ w ]]; then
	    if [[ "$access" =~ b ]]; then
		§object.create_bool_writer "$type" "$property" "$pos" >> "$file"
	    elif [[ "$access" =~ a ]]; then
		§object.create_array_writer "$type" "$property" "$pos" >> "$file"
	    else
		§object.create_writer "$type" "$property" "$pos" >> "$file"
	    fi
	fi
	let pos++
    done
    
    source "$file"

    return 0
}

function §object.create_reader {
    local name="$1"
    local NAME="$(§objects.namespace)_$name"
    local key="$2"
    local field="$3"

    cat <<EOF
function §${name}.get_${key} {
    printf '%s' "\${_${NAME}s[\$_${NAME}s_position+$field]}";
}
EOF
}

function §object.create_writer {
    local name="$1"
    local NAME="$(§objects.namespace)_$name"
    local key="$2"
    local field="$3"

    cat <<EOF
function §${name}.set_${key} {
    _${NAME}s[\$_${NAME}s_position+$field]="\$@";
    _${NAME}s_modified=1;
}
EOF
}

function §object.create_array_reader {
    local name="$1"
    local NAME="$(§objects.namespace)_$name"
    local key="$2"
    local field="$3"

    cat <<EOF
function §${name}.get_${key} {
    local var="\$1";
    local data="\${_${NAME}s[\$_${NAME}s_position+$field]}";
    
    if [[ -z "\$data" ]]; then
      printf '%s' "declare -a \$var";
    else
      printf '%s' "declare -a \$var=\$data";
    fi
}
EOF

}

function §object.create_array_writer {
    local name="$1"
    local NAME="$(§objects.namespace)_$name"
    local key="$2"
    local field="$3"

    cat <<EOF
function §${name}.set_${key} {
    declare -a a=("\$@");
    
    local data="\$(declare -p a)";
    data="\${data:14:\${#data}-15}";    

    _${NAME}s[\$_${NAME}s_position+$field]="\$data";
    _${NAME}s_modified=1;
}
EOF
}

function §object.create_bool_writer {
    local name="$1"
    local NAME="$(§objects.namespace)_$name"
    local key="$2"
    local field="$3"

    cat <<EOF
function §${name}.set_${key} {

     local input="\$1";
     local value=0;

     [[ -n "\$input" ]] && value=1;
     [[ "\$input" =~ ^([Yy]es|[Tt]rue|[Oo]n|1)\$ ]] && value=1;
     [[ "\$input" =~ ^([Nn]o|[Ff]alse|[Oo]ff|0)\$ ]] && value=0;

    _${NAME}s[\$_${NAME}s_position+$field]=\$value;
    _${NAME}s_modified=1;
}
EOF

}


