package muesli

type ObjectGetter func() Value
type ObjectSetter func(Value) Value
type ObjectMethod func(...Value) []Value


// Object is an interface for a Value that has fields 
// which can be get, set or invoked. The field names must be strings.
// It is allowed, but not mandatory that names for Get, Set and Invoke
// are stored separately.   
type Object interface {
	Value
	Get(key string) Value
	Set(key string, to Value) Value 
	Invoke(key string, args... Value) []Value
}



// BasicObject is strut that implements Object.
// An object may have a parent it inherit's it's fields and methods from.
// Include it in your own struct to endow it with getters and setters, 
// and methods more easily.
type BasicObject struct {
    RedispatchCallable
    Parent * BasicObject
	Object interface{}
    Getters map[string] ObjectGetter
    Setters map[string] ObjectSetter
    Methods map[string] ObjectMethod
}

const ObjectType = TypeValue("Object")

func (v BasicObject) Type() TypeValue    { return ObjectType }

func (val BasicObject) String() string {
	res := "{"
	sep := ""
	for k, v := range val.Getters {
		res = res + sep + k + "=>" + v().String()
		sep = ", "
	}
	res += "}"
	return res
}

// Adds a getter to an object
func (o *BasicObject) Getter(name string, getter ObjectGetter) *BasicObject {
    o.Getters[name] = getter
    return o
}

// Adds a setter to an object
func (o *BasicObject) Setter(name string, setter ObjectSetter) *BasicObject {
    o.Setters[name] = setter
    return o
}

// Adds a method to an object
func (o *BasicObject) Method(name string, method ObjectMethod) *BasicObject {
    o.Methods[name] = method
    return o
}

func NewBasicObject(object interface {}, parent *BasicObject) *BasicObject {
	mv :=  &BasicObject{Object:object, 
        Parent: parent,
        Getters: make(map[string] ObjectGetter),
        Setters: make(map[string] ObjectSetter),
        Methods: make(map[string] ObjectMethod),
    }    
	mv.RedispatchCallable = NewRedispatchCallable("Object", mv)
	return mv
}

func (m *BasicObject) Get(key string) Value {
	var res ObjectGetter
	var ok bool
	if res, ok = m.Getters[key] ; !ok {
        if m.Parent != nil {
            return m.Parent.Get(key)
        }
		return NilValue
	}
	return res()
}

func (m *BasicObject) Set(key string, to Value) Value {
	var res ObjectSetter
	var ok bool
	if res, ok = m.Setters[key] ; !ok {
        if m.Parent != nil {
            return m.Parent.Set(key, to)
        }
		return NilValue
	}
	return res(to)
}

func (m *BasicObject) Invoke(key string, args... Value) []Value {
	var res ObjectMethod
	var ok bool
	if res, ok = m.Methods[key] ; !ok {
        if m.Parent != nil {
            return m.Parent.Invoke(key, args...)
        }
		return Fail(NewErrorValuef("No such method %s", key))
	}
	return res(args...)
}

func (from *BasicObject) Convert(to interface{}) error {
	switch toPtr := to.(type) {
		case **BasicObject:
			(*toPtr) = from
		case *Value:
			(*toPtr) = from			
		default:
			return NewErrorValuef("Cannot convert value %v to %v", from, to)
	}
	return nil
}

// BasicObject implements Object
var _ Object = &BasicObject{}


// Flex can have any field just by setting it, it can be wrapped in 
// an BasicObject.
type Flex struct {
    Fields map [string]Value
}