if (typeof(jinj) == 'undefined') {
    (function() {
        var Jinj = function() {
            this.construct.apply(this, arguments);
        };
    
        Jinj.modules = Jinj.prototype = {
            name: 'jinj JavaScript Framework',
            version: '1.0',
            document: window.document,
            element: null,
    
            construct: function()
            {
                this.cache.init();
            },
    
            Package: function(namespace)
            {
                var root       = this;
                var packages   = [];
                var namespaces = namespace.split('.') || [namespace];
            },
    
            Class: function()
            {
                var type, parent, implementation, interfaces = new Array();
    
                if (arguments.length > 0) {
                    for (var i = 0; i < arguments.length; i++) {
                        if (typeof(arguments[i]) == 'string') {
                            arguments[i] = arguments[i].toLowerCase();
    
                            if (arguments[i] == 'abstract' || arguments[i] == 'concrete' || arguments[i] == 'static') {
                                type = arguments[i];
                            } else {
                                throw new Error('Invalid class type.');
                            }
                        } else if (typeof(arguments[i]) == 'object') {
                            if (typeof(arguments[i]['extends']) != 'undefined'
                                && typeof(arguments[i]['extends']) == 'function')
                            {
                                parent = arguments[i]['extends'].prototype;
                            }
    
                            if (typeof(arguments[i]['implements']) != 'undefined') {
                                if (typeof(arguments[i]['implements']) == 'object' && arguments[i]['implements'].length > 0) {
                                    for (var obj in arguments[i]['implements']) {
                                        interfaces.push(arguments[i]['implements'][obj].prototype);
                                    }
                                } else if (typeof(arguments[i]['implements']) == 'function') {
                                    interfaces.push(arguments[i]['implements'].prototype);
                                } else {
                                    throw new Error('Invalid interface type.');
                                }
                            }
                        } else {
                            var typeErrorMessage = 'Invalid paremeter sent. Define the type of the Class with a string '
                                                 + '("abstract" or "concrete"), or parent and interfaces as objects with '
                                                 + 'the format "{extends: ClassObject, implements: [InterfaceObject1, '
                                                 + 'InterfaceObject2]}".';
                            throw new Error(typeErrorMessage);
                        }
                    }
                }
    
                return function()
                {
                    /**
                     * Method to register plugins to the class at runtime
                     *
                     * @param {Object|Function} plugin [Required] The plugin itself
                     * @param {String}          name   [Optimal]  A name for the extension
                     * @param {Boolean}         global [Optimal]  If the extension is going to be registered on the
                     *                                            window global environment (then it be called as its name only)
                     */
                    this.addPlugin = function(pluginName, plugin, global)
                    {
                        if (typeof(plugin) != 'object' && typeof(plugin) != 'function') {
                            throw new Error('Only objects and functions can extend this class.');
                        }
    
                        if (pluginName) {
                            if (typeof(this[pluginName]) == 'undefined') {
                                if (global) {
                                    window[pluginName] = plugin;
                                }
    
                                this[pluginName] = plugin;
                            }
                        } else {
                            if (typeof(plugin) != 'object') {
                                var objectErrorMessage = 'Only objects can extend without a name. '
                                                       + 'Try setting a name or instantiating using '
                                                       + '"new Object()" syntax.';
                                throw new Error(objectErrorMessage);
                            }
    
                            (function(parent) {
                                for (var i in plugin) {
                                    if (typeof(parent[i]) == 'undefined') {
                                        if (global) {
                                            window[i] = plugin[i];
                                        }
    
                                        parent[i] = plugin[i];
                                    }
                                };
                            })(this);
                        }
    
                        return this;
                    };
    
                    if (parent) {
                        this.parent = function()
                        {
                            if (arguments.length > 0 && typeof(parent.construct) != 'undefined') {
                                parent.construct.apply(this, arguments);
                            }
    
                            return parent;
                        }
    
                        for (var object in parent) {
                            if (typeof(this[object]) != 'undefined') {
                                this[object] = parent[object];
                            }
                        }
                    }
    
                    if (interfaces.length > 0) {
                        for (var iface in interfaces) {
                            if (interfaces[iface].length > 0) {
                                for (var method in interfaces[iface]) {
                                    if (typeof(interfaces[iface][method]) != typeof(this[method])) {
                                        throw new Error('Interfaces methods must be implemented.');
                                    }
                                }
                            }
                        }
                    }
    
                    if (type != 'abstract') {
                        // Apply the constructor if available
                        if (typeof(this.construct) != 'undefined') {
                            this.construct.apply(this, arguments);
                        }
                    } else {
                        throw new Error('Abstract classes can not be instantiated.');
                    }
                };
            },
    
            Interface: function()
            {
                return function()
                {
                    throw new Error('Interfaces can not be instantiated.');
                };
            },
    
            assemble: function()
            {
                // @todo implementation
                return this;
            },
    
            attach: function(module, name)
            {
                var head = this.get('head', 'tag').item(0);
                var scripts = this.get('script', 'tag');
                var jinjPattern = /\/jinj\/core\.js/;
                var modulePattern = /\./;
                var source = '';
    
                while (modulePattern.test(module)) {
                    module = module.replace(/\./, '/');
                }
    
                if (scripts.length > 0) {
                    for (var script in scripts) {
                        if (scripts[script].src && jinjPattern.test(scripts[script].src)) {
                            source = scripts[script].src.replace(jinjPattern, '/' + module + '.js');
                            break;
                        }
                    }
                }
    
                var js = this.create('script');
                    js.src = source;
                    js.language = 'javascript';
                    js.type = 'application/x-javascript';
    
                head.appendChild(js);
    
                return this;
            },
    
            get: function(element, type)
            {
                switch (type) {
                    case 'name':
                        return this.document.getElementsByName(element);
                        break;
                    case 'tag':
                        return this.document.getElementsByTagName(element);
                        break;
                    case 'class':
                        return this.document.getElementsByClassName(element);
                        break;
                    default:
                        return this.document.getElementById(element);
                        break;
                }
            },
            
            create: function(type, id)
            {
                var element = (typeof(document.standardCreateElement) == 'undefined') ?
                    document.createElement(type) :
                    document.standardCreateElement(type);
    
                if (element && id) {
                    element.id = id;
    
                    if (typeof(element.name) != 'undefined') {
                        element.name = id;
                    }
                }
    
                return element;
            },
    
            load: function(moduleName)
            {
                this.attach('jinj.' + moduleName, moduleName);
                return this;
            },
    
            cache: 
            {
                init: function() {
                    var storage = {};
    
                    this.store = function(obj, name)
                    {
                        if (obj && name) {
                            storage[name] = obj;
                        }
    
                        return this;
                    };
    
                    this.read = function()
                    {
                        return storage;
                    };
                    
                    this.clear = function(reference)
                    {
                        if (typeof(reference) == 'undefined') {
                            storage = {};
                        } else {
                            delete storage[reference];
                        }
                    };
    
                    this.restoreAll = function(parent)
                    {
                        if (!parent) {
                            parent = window;
                        }
    
                        for (var i in storage) {
                            parent[i] = storage[i];
                        }
    
                        storage = {};
    
                        return this;
                    };
    
                    this.restore = function(reference, parent)
                    {
                        if (!parent) {
                            parent = window;
                        }
                        
                        if (typeof(storage[reference]) != 'undefined') {
                            parent[reference] = storage[reference];
                            delete storage[reference];
                        }
    
                        return this;
                    };
                }
            }
        }
    
        window.jinj = new Jinj();
    })();
} else if (typeof(jinj) != 'object') {
    throw new Error('Incompatibilities found while trying to create the jinj library.');
}
