function JQueryWrapper(oElement) {
    var _oElement = oElement || null;
    this.getElement = function() {
        return _oElement;
    }
    this.setElement = function(oElement) {
        _oElement = oElement;
    }
}

function ContainerSupport() {
    var _oContainerContext = null;
    var _this = this;

    this.setContainerContext = function(oContainerContext) {
        _oContainerContext = oContainerContext.register(_this);
    }

    this.getContainerContext = function(oContainerContext) {
        return _oContainerContext;
    }
}

function IContainer(oContainerDefinition) {
    var _oContainerDefinition = oContainerDefinition;
    var _oInstances = {};
    var _oContainerSupport = {};
    var _oContextSupportInstances = [];
    var _oContainerContext = new ContainerContext();
    var _this = this;


    this.load = function(oContainerDefinition) {
        _oInstances['containerContext'] = new ContainerContextProxy();

        autoWire(oContainerDefinition);

    }

    /**
     *   @name   :   autoWire
     *   @desc   :   Takes a container definition as a parameter, creates class instances and wires them by reference
     */
    function autoWire(oContainerDefinition) {
        //  Create instances in order of dependancy and wire constructor args
        forEach(oContainerDefinition, function(i, n) {
            createInstance(i, n);
        });
        
        //  Set properties
        forEach(oContainerDefinition, function(i, n) {
            var oNode = _oInstances[i];

            if (n.props != null) {
                forEach(n.props, function(i, n) {
                    var methodToInvoke = 'set' + getMethodName((n.name != null) ? n.name : n.ref);
                    oNode[methodToInvoke](_oInstances[n.ref]);
                });
            }
        });
        
        //  Invoke ContextSupport method on instances that support it
        forEach(_oContextSupportInstances, function(i, n) {
            (n.onContextSupport == null) ? function() {
                throw("Interface not supported");
            } : n.onContextSupport(ContainerContext);
        });
    }

    /**
     *   @name   :   createInstance
     *   @para   :   [String]    i   An alphnumeric id used to hash the instance in the instance hash object
     *   @para   :   [Object]    n   The node definition instance
     */
    function createInstance(i, n) {
        var _aReferences = new Array();

        if (_oInstances[i] == null) {
            if (n.args != null) _aReferences = findReferences(n);
            _oInstances[i] = {};

            n.type.apply(_oInstances[i], _aReferences);
        }

        return _oInstances[i];
    }

    /**
     *   @name   :   findReferences
     *   @para   :   [Object]    n   The node definition instance
     */
    function findReferences(n) {
        var _aReferences = new Array();

        forEach(n.args, function(i, n) {
            _aReferences.push(createInstance(n.ref, n));
        });

        return _aReferences;
    }

    /**
     *   @name   :   ContainerContextProxy
     *   @desc   :   An inner class that encapsulates the Container context and allows instances to
     register for Context Support
     */
    function ContainerContextProxy() {
        this.register = function(oObject) {
            _oContextSupportInstances.push(oObject);
            return _oContainerContext;
        }
    }

    /**
     *   @name   :   ContainerContext
     *   @desc   :   An inner class that encapsulates container context access
     */
    function ContainerContext() {
        //  Get a bean by id
        this.getBean = function(sBeanId) {
            return _oInstances[sBeanId];
        }
    }


    function getMethodName(str) {
        return String(str).replace(/\b\w/gi, function(c, i, s) {
            return c.toUpperCase();
        });
    }


    function IContainer() {
        if (_oContainerDefinition != null) {
            _this.load(_oContainerDefinition);
        }
    }

    IContainer();
}

function forEach(oCollection, fCallback) {
    if (isArray(oCollection)) {
        for (iIndex = 0; iIndex < oCollection.length; iIndex++) {
            callBack(iIndex, oCollection[iIndex]);
        }
    } else {
        for (each in oCollection) {
            callBack(each, oCollection[each]);
        }
    }

    function callBack(i, n) {
        try {
            fCallback(i, n);
        } catch(e) {
            alert(e.message);
        }
    }
}

function isArray(obj) {
    return (obj.constructor.toString().indexOf("Array") == -1) ? false : true;
}

//alert("LKJ");