/**
 * Root constructor for all municware objects. All municware objects define a
 * _private object that wraps private members. A subclass can attach a private
 * member by calling 
 *    this._private[<fully-qualified-subclass>] = { 
 *              <member>: <value>
 * } 
 * 
 * or once declared by this._private[<fully-qualified-subclass>].<member> = <value>; 
 *
 * In order to keep things simple it is recommended that you use the underscore
 * '_' as a reference to the private members (just like 'this'):
 *   var _ = this._private.<fully-qualified-subclass>;
 *   _<member> = <value>;
 *   alert(_<member>);
 * 
 * When subclassing, do not forget to add a call to the constructor...
 */
municware.lang.RootObject = Class.create({
    
    initialize: function() {
        this._private = {};
    }
    
});

log.debug("Loaded class municware.lang.RootObject");
/**
 * A bean is a fundamental part of a MVC-GUI-system. It adds data
 * encapsulation, properties and listener mechanisms to an object. This enables
 * the view components to synchronize with their respective models. 
 *
 * A bean has a set of properties. You can bind event listeners to these 
 * properties. The listeners will be notified if a property value changes.
 * 
 * YOU CAN'T DO THIS:
 *  var car = new Bean();
 *  car.color = "red";
 *  var color = car.color;
 *
 * DO THIS INSTEAD:
 *  var car = new Bean();
 *  car.setColor("red");
 *  var color = car.getColor("red");
 */

municware.lang.Bean = {};

/** 
 * Generates a bean.
 */
municware.lang.Bean.create = function() {
    var bean = {};
    municware.lang.Bean.make(bean);
    return bean;
}

/**
 * Attachs the bean mechanism to an existing object:
 *  - listeners:Array
 *  - addListener(eventType, listener)
 *  - removeListener(eventType, listener)
 *  - addDeepPropertyListener(property, propertyListener)
 *  - removeDeepPropertyListener(property, propertyListener)
 *  - fireEvent(eventType, event)
 *  - addProperty(property)
 *  - addIndexedProperty(property, singularProperty)
 *  - properties:Array
 *  - setInitialValues(values)
 *  - _private["municware.lang.Bean"]
 */
municware.lang.Bean.make = function(bean) {
    // Define a "private" variable space
    if (!bean._private) {
        bean._private = {};
    }
    var _ = bean._private["municware.lang.Bean"] = {
        values: {}
    }; 
    bean.properties = [];
    
    bean.addListener = function(eventType, listener) {
        if (!_.listeners) {
            _.listeners = new Array();
        }
        if (!_.listeners[eventType]) {
            _.listeners[eventType] = new Array(listener);
        } else {
            _.listeners[eventType].push(listener);
        }
    };

    bean.removeListener = function(eventType, listener) {
        if (_.listeners && _.listeners[eventType]) {
            var typeSpecListeners = _.listeners[eventType];
            typeSpecListeners.splice(typeSpecListeners.indexOf(listener), 1);
        }
    };
    
    bean.addDeepPropertyListener = function(property, changeListener, resetListener) {
        var listener = function(src, event) {
            if (event.property == property) {
                // Remove listener from old model
                if (event.oldValue) {
                    event.oldValue.removeListener("propertyChange", changeListener);
                }
                // Add listener to new model
                if (event.newValue) {
                    event.newValue.addListener("propertyChange", changeListener);
                }
            }
        };
        bean.addListener("propertyChange", listener);
        if (!_.deepPropertyListeners) {
            _.deepPropertyListeners = {};
        }
        _.deepPropertyListeners[changeListener] = listener;
        if (resetListener) {
            if (!_.deepPropertyResetListeners) {
                _.deepPropertyResetListeners = {};
            }
            // TODO: Clean code - refactoring necessary
            var rListener = function(src, event) {
                if (event.property == property) {
                    resetListener(src, event);
                }
            }
            _.deepPropertyResetListeners[resetListener] = rListener;
            bean.addListener("propertyChange", rListener);
        }
    };

    bean.removeDeepPropertyListener = function(property, changeListener, resetListener) {
        if (_.deepPropertyListeners) {
            bean.removeListener("propertyChange", _.deepPropertyListeners[changeListener]);
        }
        if (_.deepPropertyResetListeners && resetListener) {
            bean.removeListener("propertyChange", _.deepPropertyResetListeners[resetListener]);
        }
    };

    bean.fireEvent = function(eventType, event) {
         // Call to toJSON may cause "too much recursion" error...
        // log.debug("Firing event of type " + eventType + ": " + Object.toJSON(event));
        if (_.listeners && _.listeners[eventType]) {
            _.listeners[eventType].each(function(lst) {
                lst(bean, event);
            });
        }
    };
    
    // Adds a property to a bean object and generates accessor methods. 
    // For each property (e.g. 'name') a setter ('setName') and a getter 
    // ('getName') method is added to Bean.this.
    //
    // @param property:string - the property name
    //
    bean.addProperty = function(property) {
        _.values[property] = null;
        
        // The property name is capitalized for the getter and setter methods
        var Property = property.slice(0, 1).toUpperCase() + property.slice(1, property.length);
        
        // Create the standard getter and setter methods. The property values 
        // will be stored within Bean.this.

        // get
        bean["get" + Property] = function() {
            return _.values[property];
        };
        
        // set
        bean["set" + Property] = function(value) {
            // log.debug("Setting property '" + property + "' to '" + value + "'");
            var oldValue = _.values[property];
            _.values[property] = value;
            bean.fireEvent("propertyChange", {
                operation: "replace",
                property: property,
                oldValue: oldValue,
                newValue: value,
                indexedChange: false
            });
        };
    
        bean.properties.push(property);
    };
    
    
    // Adds a property (e.g. 'items', singular 'item) and additionally
    // generates accessors:
    //  - setItemAt(index, item)
    //  - getItemAt(index)
    //  - insertItemAt(index, item)
    //  - addItem(item)
    //  - removeItemAt(index)
    //  - removeItem(item)
    //  - getItemCount()
    //  - eachItem(iterator)
    //
    // The get method returns a clone of the array. Manipulations will not 
    // write through to the actual property.
    //
    // @param singularProperty:string - the singular of the property name
    //
    bean.addIndexedProperty = function(property, singularProperty) {
        bean.addProperty(property);
        
        // Capitalize
        var SingularProperty = singularProperty.slice(0, 1).toUpperCase() + 
                        singularProperty.slice(1, singularProperty.length);
              
        // get at       
        bean["get" + SingularProperty + "At"] = function(index) {
            return _.values[property][index];
        };
        
        // get count
        bean["get" + SingularProperty + "Count"] = function() {
            return _.values[property].length;
        };
         
        // set at
        bean["set" + SingularProperty + "At"] = function(index, value) {
            var oldValue = _.values[property][index];
            _.values[property][index] = value;
            bean.fireEvent("propertyChange", {
                operation: "indexedReplace",
                property: property,
                oldValue: oldValue,
                newValue: value,
                index: index,
                indexedChange: true
            });
        };
        
        // remove at
        var removeAt = bean["remove" + SingularProperty + "At"] = function(index) {
            var oldValue = _.values[property][index];
            _.values[property].splice(index, 1);
            bean.fireEvent("propertyChange", {
                operation: "indexedRemove",
                property: property,
                oldValue: oldValue,
                index: index,
                indexedChange: true
            });
        };
        
        // insert at
       var insert = bean["insert" + SingularProperty + "At"] = function(index, value) {
            _.values[property].splice(index, 0, value);
            bean.fireEvent("propertyChange", {
                operation: "indexedInsert",
                property: property,
                newValue: value,
                index: index,
                indexedChange: true
            });
        };
        
        // add
        bean["add" + SingularProperty] = function(value) {
            insert(_.values[property].length, value);
        };
        
        // remove
        bean["remove" + SingularProperty] = function(value) {
            var index = _.values[property].indexOf(value);
            if (index != -1) {
                removeAt(index);
            }
        };
        
        // each
        bean["each" + SingularProperty] = function(iterator) {
            _.values[property].each(iterator);
        };
    };
    
    bean.setInitialValues = function(values) {
        for (var p in values) {
            // if (this.properties.indexOf(p) != -1) {
            // log.debug("Setting inital value of '" + p + "' to '" + values[p] + "'")
            _.values[p] = values[p];
            // } else {
            //    log.warn(p + " is no valid property");
            // }
        }
    };
    
};

log.debug("Loaded class municware.widget.Bean");
