// See COPYING for copyright and license details
(function() {
    var _modules = {};
    var _requires = {};
    var _callbacks = [];
    var _initialized = false;
    var _pending = [];
    var _contexts = {};
    var _applyRequired = function(names, callback) 
    {
        if (names === null) {
            callback.call(this, _modules);
        }
        else 
        {
            var j, m;
            var modules = [];
            var name, detail;
            for (j=0, m=names.length; j<m; j++) 
            {
                name = names[j];
                if (/^\w*!/.test(name)) 
                {
                    detail = name.split("!");
                    name = detail[0];
                    if (!_modules[name]) {
                        include(detail.slice(1).join("!"));
                    }
                }
                if (_modules[name])
                {
                    modules.push(_modules[name]);
                }
                else 
                {
                    _pending.push({names : names, callback : callback});
                    return;
                }
            } 
            if (callback) {
                callback.apply(callback, modules);
            }
        }
    };
    function _removeTimerHandle()  {
        timer._stop(this.id);
    }

    Object.defineProperties(timer, { 
        "start" : {
            value : function() {
                var id = this._start.apply(null, arguments);
                return { id : id, remove : _removeTimerHandle };
            }
        },
        // FIXME: wrapper for old api, remove the at some point
        "stop" : {
            value : function(handle) {
                return this._stop(handle.id);
            }
        }

    });
    Object.defineProperties(this, { 
        "bind" : {
            value : function() {
                var id = _bind.apply(null, arguments);
                if (id != -1) {
                    return { 
                        id : id, 
                        remove : function() {
                            unbind(id);
                        }
                    };
                }
                return { remove : function() {} };
            }
        },
        "hint" : { 
            value : function(shortcut, callback, command, selector) {
                bind(shortcut, function() {
                    Signal.once("followHint", function(wv, resource) {
                        if (resource == "@abort")
                            return false;
                        return callback(wv, resource);
                    });
                    if (selector)
                    {
                        execute("hints_selector " + selector);
                    }
                    else 
                    {
                        execute("hints");
                    }
                });
            }
        },
        "Glob" : 
        { 
            value : (function() {
                var esc = new RegExp("[-\/\\^$+.()|[\]{}]", "g");
                var matcher = new RegExp("(\\\\)?([*?])", "g");
                var cnstr = function (m, f, s) { 
                    return f ? m : s == "?" ? "." : ".*";
                };
                return function(p) { 
                    var inner = p.replace(esc, '\\$&').replace(matcher, cnstr);
                    var regex = new RegExp("^" + inner + "$");
                    var searchPattern = new RegExp(inner);
                    return Object.create(Object.prototype, {
                            match : { value : function(string) { return regex.test(string); } }, 
                            search : { value : function(string) { return string.search(searchPattern); } },
                            exec : 
                            { 
                                value : function(string) 
                                { 
                                    var match = searchPattern.exec(string);
                                    return match ? match[0] : null;
                                } 
                            }, 
                            toString : { value : function() { return p; } }
                    });
                };
            })()
        },
        "provide" : 
        { 
            value : function(name, module, overwrite) 
            {
                if (overwrite)
                {
                    if (!module && _modules[name])
                    {
                        for (var key in _modules[name]) 
                            delete _modules[name][key];

                        delete _modules[name];
                    }
                }
                if (!overwrite && _modules[name]) 
                {
                    io.debug({ 
                            offset : 1, arguments : arguments,
                            error : new Error("provide: Module " + 
                                              name + " is already defined!")
                    });
                }
                else 
                    _modules[name] = module;

                var pl = _pending.length;
                if (pl > 0)
                {
                    var pending = [];
                    var finished = [];
                    for (var i=0; i<pl; i++) 
                    {
                        if (_pending[i].names.every(function(name) { return _modules[name]; }))
                        {
                            finished.push(_pending[i]);
                        }
                        else 
                            pending.push(_pending[i]);
                    }
                    for (i=0; i<finished.length; i++)
                        _applyRequired(finished[i].names, finished[i].callback);
                    _pending = pending;
                }
            }
        },
        "require" : 
        {
            value : function(names, callback) 
            {
                if (names !== null && ! (names instanceof Array) && !(typeof names == "string")) 
                {
                    io.debug({ 
                            error : new Error("require : invalid argument (" + 
                                                JSON.stringify(names) + ")"), 
                            offset : 1, 
                            arguments : arguments 
                    });
                    return; 
                }
                if (typeof names == "string") {
                    if (_modules[names])
                    {
                        return _modules[names];
                    }
                    else 
                    {
                        io.debug({ 
                            error : new Error("require : module " + names + " hasn't been loaded yet!"),
                            offset : 1, 
                            arguments : arguments 
                        });
                    }

                }
                else {
                if (!_initialized) 
                    _callbacks.push({callback : callback, names : names});
                else 
                    _applyRequired(names, callback);
                }
            }
        },
        "_deprecated" : 
        {
            value : function(on, nn, args, attachScope) 
            {
                var i, l, ns, ctx, actx;
                io.print("\033[31;1mDWB DEPRECATION:\033[0m " + on + "() is deprecated, use " + nn + "() instead!");
                ns = nn.split(".");
                ctx = actx = attachScope || this;
                for (i=0, l=ns.length; i<l-1; i++)
                    ctx = ctx[ns[i]];
                return ctx[ns[l-1]].apply(actx, args);
            }
        },
        "_initNewContext" : 
        {
            value : (function() {
                return function(self, arguments, path) {
                    var generateId = (function() {
                        var id = 0;
                        var timeStamp = new Date().getTime();
                        return function() {
                            id++;
                            return util.checksum(timeStamp + (id) + path, ChecksumType.sha1);
                        };
                    })();
                    var id = "_" + generateId();
                    _contexts[id] = self;
                    Object.defineProperties(self, { 
                            "path" : { value : path },
                            "debug" : { value : io.debug.bind(self) }, 
                            "_arguments" : { value : arguments },
                            "_handles" : { value : [], writable : true },
                            "own" : {
                                value : function() {
                                   this._handles = this._handles.concat(Array.prototype.slice.call(arguments));
                                   return this._handles;
                                }
                            },
                            "removeHandles" : {
                                value : function() {
                                    this._handles.forEach(function(handle) { 
                                        handle.remove();
                                    });
                                }
                            },
                            "generateId" : { value : generateId },
                            "setPrivate" : 
                            { 
                                value : function(id, object, key, value) 
                                {
                                    var realKey = key + id;
                                    if (object[realKey]) 
                                        object[realKey] = value;
                                    else 
                                    {
                                        Object.defineProperty(object, realKey, {
                                                value : value, writable : true
                                        });
                                    }
                                }.bind(self, id)
                            }, 
                            "getPrivate" : 
                            { 
                                value : function(id, object, key) 
                                { 
                                    return object[key + id];
                                }.bind(self, id)
                            },
                             "deletePrivate" : {
                                 value : function(id, object, key) {
                                     var realKey = key + id;
                                     var value = object[realKey];
                                     delete object[realKey];
                                     return value;
                                 }.bind(self, id)
                             },
                            "include" : 
                            {
                                value : function(relPath, global)
                                {
                                    var dirName = path.substring(0, path.lastIndexOf("/") + 1);
                                    return include(dirName + relPath, global);
                                }
                            }
                    });
                    Object.preventExtensions(self);

                };
            })() 
        },
        "_initAfter" : 
        { 
            value : function() 
            {
                var i, l;
                _initialized = true;
                for (i=0, l=_callbacks.length; i<l; i++) 
                {
                    _applyRequired(_callbacks[i].names, _callbacks[i].callback);
                }
            },
            configurable : true
        },
        "_initBefore" : 
        {
            value : function(contexts) 
            {
                Object.freeze(this);
            },
            configurable : true
        }
    });
    
    Object.defineProperties(GObject.prototype, {
            "notify" : 
            { 
                value : function(name, callback, after) 
                { 
                    return this.connect.call(this, "notify::" + util.uncamelize(name), callback, after || false);
                }
            },
            "connectBlocked" : 
            { 
                value : function(name, callback, after) 
                { 
                    var sig = this.connect(name, (function() { 
                        this.blockSignal(sig);
                        var result = callback.apply(this, arguments);
                        this.unblockSignal(sig);
                        return result;
                    }).bind(this));
                    return sig;
                }
            },
            "notifyBlocked" : 
            {
                value : function() 
                {
                    return _deprecated("notifyBlocked", "notify", arguments, this);
                }
            }
    });
    Object.defineProperties(Deferred.prototype, {
            "done" : {
                value : function(method) 
                {
                    return this.then(method);
                }
            },
            "fail" : {
                value : function(method) 
                {
                    return this.then(null, method);
                }
            },
            "always" : {
                value : function(method)
                {
                    return this.then(method, method);
                }
            }
    });
    Object.defineProperty(Deferred, "when", {
            value : function(value, callback, errback)
            {
                if (value instanceof Deferred)
                    return value.then(callback, errback);
                else 
                    return callback(value);
            }
    });
    if (!Function.prototype.debug) 
    {
        Object.defineProperty(Function.prototype, "debug", {
                value : function(scope)
                {
                    return function() {
                        try 
                        {
                            return this.apply(this, arguments);
                        }
                        catch (e) 
                        { 
                            if (scope)
                                scope.debug(e);
                            else 
                                io.debug(e);
                        }
                        return undefined;
                    }.bind(this);
                }
        });
    }

    if (! RegExp.escape)
    {
        Object.defineProperty(RegExp, "escape", 
        {
            value : function(string)
            {
                return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
            }
        });
    }
    Object.defineProperties(tabs, {
         nth : {
             value : function(n) {
                 io.print("\033[31;1mDWB DEPRECATION:\033[0m tabs.nth(n) is deprecated, use tabs[n] instead!");
                     return this[n];
             }
         },
    });
    [ "forEach", "indexOf", "lastIndexOf", "filter", "map", "reduce", "reduceRight", "every", "some" ].forEach(function(method) {
        Object.defineProperty(tabs, method, { value : function() { 
            return Array.prototype[method].apply(tabs, arguments); 
        }});
    });
    Object.freeze(tabs);
})();

Object.preventExtensions(this);
