if (!Function.prototype.apply) {

    function __ApplyCallbackName__() {
        return '__apply__' + new Date().getTime() + '__' + Math.floor(Math.random() * 88998899) + '__';
    }

    Function.prototype.apply = function (context, args) {
        var args_func = [];
        if (args) {
            var l = args.length;
            args_func = new Array(l);
            for (var i = 0; i < l; i++) {
                args_func[i] = 'arguments[1][' + i + ']';
            }
        }

        context = context || window;

        var callback_name = __ApplyCallbackName__();
        context[callback_name] = this;

        var ret = eval('context.' + callback_name + '(' + args_func.join(',') + ')');
        eval('context.' + callback_name + ' = null');
        return ret;
    };

}

if (!Function.prototype.call) {
    Function.prototype.call = function (context) {
        return this.apply(context, Array.prototype.slice.apply(arguments, [1]));
    }
}

if (!Array.prototype.push) {

    Array.prototype.push = function () {
        for (var i = 0, l = arguments.length; i < l; i++) {
            this[this.length] = arguments[i];
        }

        return this.length;
    };

}

if (!Array.prototype.splice) {

    Array.prototype.splice = function (start, deleteCount) {
        var l = this.length;
        var removed = [];

        start = parseInt(start) || 0;
        deleteCount = parseInt(deleteCount) || 0;

        start = start < 0 ? Math.max(l + start, 0) : Math.min(start, l);
        deleteCount = Math.min(Math.max(deleteCount, 0), l - start);

        if (deleteCount) {
            removed = this.slice(start, start + deleteCount);

            l -= deleteCount;
            for (var i = start; i < l; i++) {
                this[i] = this[i + deleteCount];
            }
            this.length = l;
        }

        if (arguments.length > 2) {
            var arr = this.slice(start);
            this.length = start;

            for (var i = 2, il = arguments.length; i < il; i++) {
                this.push(arguments[i]);
            }
            for (var i = 0, il = arr.length; i < il; i++) {
                this.push(arr[i]);
            }
        }

        return removed;
    }

}

if (!Array.prototype.pop) {

    Array.prototype.pop = function () {
        var element, length = this.length;

        if (length != 0) {
            element = this[length - 1];
            this.length--;
        }

        return element;
    };

}

if (!Array.prototype.shift) {

    Array.prototype.shift = function () {
        var element;

        if (this.length != 0) {
            element = this[0];
            this.splice(0, 1);
        }

        return element;
    };

}

if (!Array.prototype.indexOf) {

    Array.prototype.indexOf = function (searchElement, fromIndex) {
        fromIndex = fromIndex || 0;

        for (var length = this.length; fromIndex < length; fromIndex++) {
            if (this[fromIndex] == searchElement) {
                return fromIndex;
            }
        }

        return -1;
    };

}

if (!Array.prototype.lastIndexOf) {

    Array.prototype.lastIndexOf = function (searchElement, fromIndex) {
        var length = this.length;

        fromIndex = fromIndex || length - 1;

        if (fromIndex < 0) {
            fromIndex += length;
        }

        for (; fromIndex >= 0; fromIndex--) {
            if (this[fromIndex] == searchElement) {
                return fromIndex;
            }
        }

        return -1;
    };

}

if (!Array.prototype.every) {

    Array.prototype.every = function (callback, thisObject) {
        thisObject = thisObject || window;

        var index = 0, length = this.length;

        for (; index < length; index++) {
            if (!callback.apply(thisObject, [this[index], index, this])) {
                break;
            }
        }

        return (index == length);
    };

}

if (!Array.prototype.filter) {

    Array.prototype.filter = function (callback, thisObject) {
        thisObject = thisObject || window;

        var length = this.length, count = 0, filtered = new Array(length);

        for (var index = 0; index < length; index++) {
            if (callback.apply(thisObject, [this[index], index, this])) {
                filtered[count++] = this[index];
            }
        }

        filtered.length = count;

        return filtered;
    };

}

if (!Array.prototype.forEach) {

    Array.prototype.forEach = function (callback, thisObject) {
        thisObject = thisObject || window;

        for (var index = 0, length = this.length; index < length; index++) {
            callback.apply(thisObject, [this[index], index, this]);
        }
    };

}

if (!Array.prototype.map) {

    Array.prototype.map = function (callback, thisObject) {
        thisObject = thisObject || window;

        var index = 0, length = this.length, map = new Array(length);

        for (; index < length; index++) {
            map[index] = callback.apply(thisObject, [this[index], index, this]);
        }

        return map;
    };

}

if (!Array.prototype.some) {

    Array.prototype.some = function (callback, thisObject) {
        thisObject = thisObject || window;

        var index = 0, length = this.length;
        for (; index < length; index++) {
            if (callback.apply(thisObject, [this[index], index, this])) {
                break;
            }
        }

        return (index != length);
    };

}

if (typeof(decodeURIComponent) == 'undefined')
    decodeURIComponent = unescape;

if (typeof(encodeURIComponent) == 'undefined')
    encodeURIComponent = escape;

if (!String.charCodeAt) {
    /*
        Такая реализация работает только с первыми 256 символами
    */
    String.prototype.charCodeAt = function (index) {
        var c = this.charAt(index);
        var begin = 0,
            end = 256,
            cur = 0,
            curChar = "";

        while (end - begin > 1) {
            cur = parseInt((end + begin) / 2);
            curChar = String.fromCharCode(cur);
            if (curChar > c) {
                end = cur;
            } else if (curChar < c) {
                begin = cur;
            } else {
                return cur;
            }
        }
    }
}

/**
 * y5 core object contains methods for dynamic loading of scripts.
 *
 * Для загрузки скриптов можно использовать удобный (в большинстве случаев) механизм динамической загрузки.
 * Для этого используем метод:
 *     y5.require(moduleNames, listener);
 *     moduleNames — строка или массив строк с именами модулей для загрузки
 *     listener — обработчик на загрузку модулей
 *
 * <strong>Пример</strong>
 * &lt;script src="http://img.yandex.net/js/ver/y5.js"&gt;&lt;/script&gt; &lt;!-- подключим ядро --&gt;
 * &lt;script&gt;
 *     function onload1() {
 *         // тут пишем то, что хотим сделать сразу после загрузки модуля Events
 *     }
 *     y5.require('Events', onload1); // грузим модуль Events с обработчиком загрузки onload1
 *     function onload2() {
 *         // тут пишем то, что хотим сделать сразу после загрузки модулей Events и Dom
 *     }
 *     y5.require(['Events', 'Dom'], onload2); // грузим модули Events и Dom с обработчиком загрузки onload2
 * &lt;/script&gt;
 *
 * <strong>Tests:</strong>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Suite.html&amp;autorun=true">Все тесты JSUnit</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../y5.html&amp;autorun=true">y5.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Strings.html&amp;autorun=true">Strings.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Dates.html&amp;autorun=true">Dates.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Classes.html&amp;autorun=true">Classes.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../CallBacks.html&amp;autorun=true">CallBacks.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Components.html&amp;autorun=true">Components.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Dom.html&amp;autorun=true">Dom.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Elements.html&amp;autorun=true">Elements.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Events.html&amp;autorun=true">Events.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../ShortCuts.html&amp;autorun=true">ShortCuts.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Hashes.html&amp;autorun=true">Hashes.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Arrays.html&amp;autorun=true">Arrays.js</a>
 *   <a href="../tests/jsunit/testRunner.html?testPage=../Location.html&amp;autorun=true">Location.js</a>
 * @alias y5
 */
var y5 = new function () {
    var _this = this;
    this.aliases = [];
    this.expectedAliases = {};
    this.charsets = [];
    this._getAliasRegexp = /^\{[^\}]+\}/;
    /**
     * This method loads a script file and executes listeners after script finishes loading.
     * @alias y5.require
     * @param {String} String like "foo.bar". Loads file bar.js from subfolder foo.
     * @param {Function} Function to execute after script finishes loading.
     * @method
     */
    this.require = function (url, listener) {
        this.Loader.require(url, listener);
    };
    /**
     * Scripts must execute this method (y5.loaded) after all initialization functions.
     * @alias y5.loaded
     * @param {String} String like "foo.bar". Loads file bar.js from subfolder foo.
     * @method
     */
    this.loaded = function (url) {
        this.Loader.loaded(url);
    };
    /**
     * Return path to file in src attribute
     * @alias y5.getBase
     * @param {String} file name
     * @method
     */
    this.getBase = function getBase (file) {
        var script = null, scripts = document.getElementsByTagName('script');
        for (var i = 0, l = scripts.length; i < l; i++) {
            var src = scripts[i].getAttribute('src');
            if (src && src.indexOf(file) >= 0) {
                script = scripts[i];
                break;
            }
        }
        if (script === null) {
            return false;
        }
        var src = script.getAttribute('src');
        return src.substring(0, src.lastIndexOf('/') + 1);
    };
    /**
     * Sets the alias as path to file
     * @alias y5.getBaseAndSetAlias
     * @param {String} alias
     * @param {String} file name
     * @method
     */
    this.getBaseAndSetAlias = function (alias, file, charset,  tries) {
        tries = tries || 0;
        charset = charset || null;
        if (tries >= 1000) {
            return;
        }

        var base = this.getBase(file);
        if (!base) {
            // timeout for FireFox to prevent script execution
            // before script tag is added to the DOM.
            window.setTimeout(function () {_this.getBaseAndSetAlias(alias, file, charset, ++tries);}, 1);
        } else {
            this.setAliasCharset(alias, charset);
            this.setAlias(alias, base);
        }
    };
    /**
     * Sets the alias
     * @alias y5.setAlias
     * @param {String} Alias
     * @param {String} Path
     * @method
     */
    this.setAlias = function (name, value) {
        this.aliases[name] = value;
        // maybe some scripts are waiting for this alias
        if (this.expectedAliases[name]) {
            var listener = null;
            while ((listener = this.expectedAliases[name].shift())) {
                listener();
            }
        }
    };
    /**
     * Sets the charset for scripts with a given alias
     * @alias y5.setAliasCharset
     * @param {String} Alias
     * @param {String} Charset
     * @method
     */
    this.setAliasCharset = function (alias, charset) {
        this.charsets[alias] = charset;
    };
    /**
     * Returns the alias
     * @alias y5.getAlias
     * @param {String} location
     * @method
     */
    this.getAlias = function (location) {
        var alias = this._getAliasRegexp.exec(location);
        return (alias ? alias[0].substr(1, alias[0].length - 2) : null);
    };
};

/**
 * Contains global variables used for browser identification.
 * @alias y5.Vars
 */
y5.Vars = new function () {
    this.UNDEF = 'undefined';
    this.DEBUG = false;

    // detect browser
    var agt           = navigator.userAgent.toLowerCase();
    var is_major      = parseInt(navigator.appVersion);
    var is_minor      = parseFloat(navigator.appVersion);
    this.is_safari    = (agt.indexOf('safari') != -1 || agt.indexOf('khtml') != -1);
	this.safari_ver   = (this.is_safari ? parseFloat(agt.substr(agt.indexOf('safari') + 7)) : 0);
    this.is_konq      = (agt.indexOf('konqueror') != -1 || agt.indexOf('khtml') != -1);
    this.is_win       = (agt.indexOf('windows') != -1);
    this.is_opera     = (agt.indexOf('opera') != -1);
    this.is_opera_7   = (agt.indexOf('opera') != -1 && is_major < 8);
    this.is_gecko     = (agt.indexOf('gecko') != -1);
    this.is_mac       = (agt.indexOf('mac') != -1);
    this.is_ie        = ((agt.indexOf('msie') != -1) && (agt.indexOf('opera') == -1));
    this.is_ie4       = (this.is_ie && (is_major == 4) && (agt.indexOf('msie 5') == -1) && (agt.indexOf('msie 6') == -1));
    this.is_ie5       = (this.is_ie && (/msie 5/.test(agt)));
    this.is_ie5up     = (this.is_ie && (/msie [56789]/.test(agt)));
    this.is_ie55      = (this.is_ie && (/msie 5\.5/.test(agt)));
    this.is_ie6up     = (this.is_ie && (/msie [6789]/.test(agt)));
    this.is_ie7up     = (this.is_ie && (/msie [789]/.test(agt)));
    this.is_ie7down   = (this.is_ie && !this.is_ie7up);
    this.is_ff10      = (!this.is_ie && (/firefox\/1\.0\.\d?/.test(agt)));
    this.is_ie55up    = (this.is_ie55 || this.is_ie6up);
    this.d_ie         = (this.is_ie ? 2 : 0);
    this.is_ie6down   = (this.is_ie && !this.is_ie6up);

    // test for security version
    this.is_ie6sv     = (this.is_ie6up && (/sv[1-9]/.test(agt)));

    this.right_agt    = !((agt.indexOf('mac') != -1) || (agt.indexOf('khtml') != -1));
    this.opera_ver    = parseFloat(agt.substr(agt.indexOf('opera') + 6));
    this.opera_ver    = isNaN(this.opera_ver) ? 10 : this.opera_ver;
    this.is_gecko_ver = this.is_gecko ? (
        (function () {
            var f = agt.match(/rv:(\d\.\d)/);
            return f ? f[1] : 0;
        })()
    ) : 0;



    delete agt;
    delete is_major;
    delete is_minor;

    this.FALSE        = function () { return false; };
    this.TRUE         = function () { return true; };
    this.NULL         = function () { return null; };
};

/**
 * Creates the SCRIPT tags.
 * @alias y5.Scripts
 */
y5.Scripts = new function () {
    var _this = this,
        createScript = null;

    /*
        Если head не определен в теле документа,
        во всех браузерах, кроме Opera он создается автоматически,
        а Opera нужно помочь.
    */
    try {
        if(!document.getElementsByTagName('head')[0]) {
            document.getElementsByTagName('html')[0].appendChild(document.createElement('head'));
        }
    } catch (e) {}

    /**
     * This method creates the SCRIPT tag.
     * @alias y5.Scripts.createScript
     * @param {String} src
     * @param {String} Charset
     * @param {Function} Listerner for script creation.
     */
    this.createScript = function (src, charset, listener, tries) {
        tries = tries || 0;
        listener = (typeof(listener) == 'function' ? listener : y5.Vars.NULL);
        var script = createScript(src, charset);
        if (script) {
            listener(script);
        } else if (tries < 10) {
            var _this = this;
            window.setTimeout(function () {_this.createScript(src, charset, listener, ++tries)}, 10);
        }
    };

    if (y5.Vars.is_opera_7) {
        createScript = createScriptForOpera7;
    } else if (y5.Vars.is_konq) {
        createScript = createScriptForSafari;
    } else {
        createScript = defaultCreateScript;
    }

    function defaultCreateScript (src, charset) {
        var script = document.createElement('script');
        setScriptAttributes(script, src, charset);
        appendScript(script, document.getElementsByTagName('head').item(0));
        return script;
    }
    function createScriptForOpera7 (src, charset) {
        if (!document.body) {
            return null;
        }
        var span = document.createElement('span');
        span.style.display = 'none';
        span.innerHTML = '<s'+'cript></'+'script>';
        var script = span.getElementsByTagName('script').item(0);
        setScriptAttributes(script, src, charset);
        appendScript(span, document.body);
        span = null;
        return script;
    }
    function createScriptForSafari(src, charset) {
        if (!document.body) {
            return null;
        }
        var span = document.body.appendChild(document.createElement('span'));
        span.style.display = 'none';
        var script = document.createElement('script');
        setScriptAttributes(script, src, charset);
        appendScript(script, span);
        span = null;
        return script;
    }
    function setScriptAttributes (script, src, charset) {
        script.setAttribute('type', 'text/javascript');
        script.setAttribute('src', src);
        if (charset) {
            script.setAttribute('charset', charset);
        }
    }
    function appendScript (script, container) {
        // insertBefore for IE.
        // If head is not closed, IE crashes if appendChild is used.
        container.insertBefore(script, container.firstChild);
    }
};

/**
 * Constructs URLs from strings.
 * @alias y5.ConstructURL
 */
y5.ConstructURL = new function () {
    /**
     * Creates URLs from strings.
     * @alias y5.ConstructURL.construct
     * @param {String} String, like "foo.bar"
     * @param {String} File extension, like "js"
     * @return {String} URL, like http://www.yandex.ru/foo/bar.js
     */
    this.construct = function (string, type) {
        type = type || 'js';
        string = string.replace(/\./g, '/');

        var alias = string.match(/\{([^\}]+)\}/);
        if (alias && alias[1]) {
            string = string.replace(alias[0], y5.aliases[alias[1]]);
        }
        var http = string.match(/^https?\:\/\//i) || '';
        string = string.replace(/^https?\:\/\//i, '');
        string = string.replace(/\/\//ig, '/');
        return http + string + '.' + type;
    };
};

/**
 * Dynamically loads scripts.
 * @alias y5.Loader
 */

y5.Loader = new function () {
    this.scripts = {};
    /**
     * This method loads the script file and executes listeners after script finishes loading.
     * @alias y5.Loader.require
     * @param {String} String like "foo.bar". Loads file bar.js from subfolder foo.
     * @param {Function} Function to execute after script finishes loading.
     */
    this.require = function (urls, listener) {
        urls = (typeof(urls) == 'string') ? [urls] : urls;
        requireList(urls, listener);
    };
    var _this = this;

    function requireList (urls, listener) {
        var counter = 0,
            length = urls.length;

        listener = listener || y5.Vars.NULL;

        function callBack () {
            // increase files counter
            counter++;
            // exec listener if all loaded
            if (counter == length) {
                listener();
            }
        }

        for (var i = 0; i < length; i++) {
            require(urls[i], callBack);
        }
    }

    function require (url, listener) {
        var alias = y5.getAlias(url);
        if (!alias) {
            url = '{y5}.' + url;
            alias = 'y5';
        }

        if (!y5.aliases[alias]) {
            // if alias is not defined, wait until it appears
            waitForAlias(alias, url, listener);
        } else {
            startLoad(url, listener, y5.charsets[alias]);
        }
    }
    function startLoad (url, listener, charset) {
        if (!_this.scripts[url]) {
            _this.scripts[url] = createScriptFake(listener);
            y5.Scripts.createScript(y5.ConstructURL.construct(url), charset);
        } else {
            putOnWaitingList(url, listener);
        }
    }
    function waitForAlias (alias, url, listener) {
        if (!y5.expectedAliases[alias]) {
            y5.expectedAliases[alias] = [];
        }
        y5.expectedAliases[alias].push(function () {require(url, listener);});
    }
    /**
     * Scripts must execute this method (y5.loaded) after all initialization functions
     * @alias y5.Loader.loaded
     * @param {String} String like "foo.bar". Loads file bar.js from subfolder foo.
     */
    this.loaded = function (url) {
        if (!y5.getAlias(url)) {
            url = '{y5}.' + url;
        }
        if (!this.scripts[url]) {
            this.scripts[url] = createScriptFake(y5.Vars.NULL);
        }
        var listener;
        while ((listener = this.scripts[url].listeners.shift())) {
            listener();
        }
        this.scripts[url].ready = true;
    };
    function createScriptFake (listener) {
        return {
            listeners: [listener],
            ready: false
        }
    }
    function putOnWaitingList (url, listener) {
        if (_this.scripts[url].ready) {
            listener();
        } else {
            _this.scripts[url].listeners.push(listener);
        }
    }
};

// Set alias 'y5' from path to y5.js file.
y5.getBaseAndSetAlias('y5', 'y5.js', 'utf-8');

// Файла Arrays.js больше не существует,
// но чтобы не искать всех кто на него ссылался,
// мы скажем что он уже загружен
y5.loaded('Arrays');

y5.Console = new function () {
    this.log      = y5.Vars.NULL;
    this.info     = y5.Vars.NULL;
    this.warn     = y5.Vars.NULL;
    this.error    = y5.Vars.NULL;
    this.trace    = y5.Vars.NULL;
    this.dir      = y5.Vars.NULL;
    this.dirxml   = y5.Vars.NULL;
    this.group    = y5.Vars.NULL;
    this.groupEnd = y5.Vars.NULL;
};

/**
 * Exception
 * @alias y5.Exception
 */
y5.Exception = function (message, method, module) {
    if (!y5.Vars.DEBUG) {
        return true;
    }

    var ret = 'y5.' + module + '.' + method + ': ' + message;
    var e = new Error(ret);

    if (e.stack) {
        e.message += "\nStack:\t" + e.stack.replace(/\n/ig, '\n\t');
    }

    return e;
};

y5.Exception.prototype = new Error();
/*
window.onerror = function () {
    return !y5.Vars.DEBUG;
};
*/
// If you want to debug something, add the y5debug=on parameter to the URL.
// To turn debugging off, add the y5debug=off parameter.

if (/y5debug/.test(window.location.search) || /y5debug/.test(document.cookie)) {
    y5.require('Debug');
}