if (!window.inkinru) window.inkinru = {};

inkinru.HtmlPage = function ()
{
    this.modules = {};
    this.modulesBySection = {};
    this.modulesByName = {};
    this.dialogs = {};
    this.controls = {};
    this.dataSavers = [];
    this.changeListeners = [];
    this.changedCount = 0;
    this.changedDataSavers = {};
    this.dataSaversSavingCount = 0;
    this.nextControlId = 0;
    this.nextModuleId = 0;
    this.nextDialogId = 0;
    this.fullHeightModuleIds = [];
    //this.adminLayout = false;
    this.eventSet = new inkinru.EventSet();
//    this.context    = typeof(strContextPath) == 'undefined' ? '/' : strContextPath;
};

    inkinru.HtmlPage.prototype.init = function ()
    {
        var that = this;

        window.onbeforeunload = function (e) { return that.onBeforeUnload(e); };
    }

    inkinru.HtmlPage.prototype.addModuleJavaScript = function (module, section)
    {
        this.modules[module.id] = module;
        module.page = this;
        module.section = section;

        if (module.name)
            this.modulesByName[module.name] = module;

        if (!(section in this.modulesBySection))
            this.modulesBySection[section] = {};

        this.modulesBySection[section][module.id] = module;
    };

    inkinru.HtmlPage.prototype.addDialogJavaScript = function (dialog)
    {
        this.dialogs[dialog.id] = dialog;

        this.ensureDialogsDivExists();
        dialog.page         = this;
        dialog.dialogsDiv   = this.dialogsDiv;

        if (this.adminLayout)
        {
            this.ensureDialogIconsVisible();
            dialog.iconsElement = this.dialogIconsElement;
        }
    }

    inkinru.HtmlPage.prototype.addSection = function (name, section)
    {
        if (this.sections)
        {
            var that = this;

            this.sections[name] = section;

            $(section)
                .addClass('inkinru-section-adminLayout')
                .sortable({
                    'connectWith':  '.inkinru-section',
                    update:         function (event, ui) {that.onModuleOrderChange(event, ui);}
                });
        }
    }

    inkinru.HtmlPage.prototype.addControl = function (control)
    {
//        console.log('Adding control: ' + control.id);
        this.controls[control.id] = control;
    };

    inkinru.HtmlPage.prototype.getModule = function (id)
    {
        return this.modules[id];
    };

    inkinru.HtmlPage.prototype.getControl = function (id)
    {
        return this.controls[id];
    };

    inkinru.HtmlPage.prototype.getDialog = function (id)
    {
        return this.dialogs[id];
    }

    inkinru.HtmlPage.prototype.getDialogByIntName = function (intName)
    {
        for (var dialogId in this.dialogs)
            if (this.dialogs[dialogId].info.intName == intName)
                return this.dialogs[dialogId];

        return null;
    }

    inkinru.HtmlPage.prototype.addDataSaver = function (module, dataSaver)
    {
        dataSaver.setModule(module);

        if (!this.dataSavers.length)
            for (var i in this.changeListeners)
                this.changeListeners[i].setHasDataSavers();

        this.dataSavers.push(dataSaver);
        dataSaver.addListener(this);

        if (dataSaver.changed)
            this.markChanged(dataSaver);
    };

    inkinru.HtmlPage.prototype.removeDataSaver = function (dataSaver)
    {
        if (dataSaver.changed)
            this.markUnchanged(dataSaver);

        for (var i in this.dataSavers)
        {
            if (this.dataSavers[i].id == dataSaver.id)
            {
                this.dataSavers.splice(i, 1);
                break;
            }
        }
    };

    inkinru.HtmlPage.prototype.markChanged = function (dataSaver)
    {
        if (!this.changedCount++)
        {
            for (var i in this.changeListeners)
                if (this.changeListeners[i].notifyChanged)
                    this.changeListeners[i].notifyChanged();
        }

        this.changedDataSavers[dataSaver.id] = dataSaver;
    }

    inkinru.HtmlPage.prototype.markUnchanged = function (dataSaver)
    {
        if (--this.changedCount == 0)
        {
            for (var i in this.changeListeners)
                if (this.changeListeners[i].notifyUnchanged)
                    this.changeListeners[i].notifyUnchanged();
        }

        delete this.changedDataSavers[dataSaver.id];
    }

    inkinru.HtmlPage.prototype.onSaveSuccess = function (dataSaver)
    {
        var i;

        this.markUnchanged(dataSaver);

        if (--this.dataSaversSavingCount == 0)
        {
            if (++this.dataSaversCurrentSavingGroup == this.dataSaversByPriority.length)
            {
                for (i in this.changeListeners)
                    if (this.changeListeners[i].notifySaved())
                        this.changeListeners[i].notifySaved();

                for (i in this.dataSaversThatAreSaving)
                    this.dataSaversThatAreSaving[i].onPageSaveSuccess();

                delete this.dataSaversThatAreSaving;
            }
            else
            {
                this.saveNextGroup();
            }
        }

        //this.dataSaverDone();

    }

    inkinru.HtmlPage.prototype.onSaveError = function (dataSaver)
    {
        //this.dataSaverDone();
    }

//    inkinru.HtmlPage.prototype.dataSaverDone = function ()
//    {
//        this.dataSaversSavingCount--;
//
//        //  TODO: Stop the animation here if we ever define one.
//    }

    inkinru.HtmlPage.prototype.addChangeListener = function (changeListener)
    {
        this.changeListeners.push(changeListener);

        if (this.dataSavers.length)
            if (changeListener.setHasDataSavers)
                changeListener.setHasDataSavers();
    }

    inkinru.HtmlPage.prototype.isModified = function ()
    {
        return mapLength(this.changedDataSavers) > 0;
    }

    inkinru.HtmlPage.prototype.save = function (dataSaver)
    {
        var i;
        var priority;

        var dataSaversByPriorityHash = {};
        var dataSaversToValidate = dataSaver ? [dataSaver] : this.dataSavers;

        for (i in dataSaversToValidate)
            if (!dataSaversToValidate[i].validate())
                return false;

//        for (i in this.dataSavers)
//            if (!this.dataSavers[i].validate())
//                return false;

//        for (i in this.changedDataSavers)
//            if (!this.changedDataSavers[i].validate())
//                return false;

        if (!this.isModified())
            return false;

        this.dataSaversThatAreSaving = dataSaver
                ? [dataSaver]
                : jQuery.extend({}, this.changedDataSavers);//this.changedDataSavers.slice(0);

        for (i in this.dataSaversThatAreSaving)
        {
            priority = this.dataSaversThatAreSaving[i].priority;

            if (!(priority in dataSaversByPriorityHash))
                dataSaversByPriorityHash[priority] = [];

            dataSaversByPriorityHash[priority].push(this.dataSaversThatAreSaving[i]);
        }
//        for (i in this.changedDataSavers)
//        {
//            priority = this.changedDataSavers[i].priority;
//
//            if (!(priority in dataSaversByPriorityHash))
//                dataSaversByPriorityHash[priority] = [];
//
//            dataSaversByPriorityHash[priority].push(this.changedDataSavers[i]);
//        }

        this.dataSaversByPriority = [];

        for (priority in dataSaversByPriorityHash)
        {
            this.dataSaversByPriority.push({
                    priority:       priority,
                    dataSavers:     dataSaversByPriorityHash[priority]});
        }

        this.dataSaversByPriority.sort(function (a, b) {return b.priority - a.priority;});
        this.dataSaversCurrentSavingGroup = 0;
        this.saveNextGroup();

        return true;
    }

    inkinru.HtmlPage.prototype.saveNextGroup = function ()
    {
        var toSave = this.dataSaversByPriority[this.dataSaversCurrentSavingGroup].dataSavers;
        var i;

        this.dataSaversSavingCount = toSave.length;

        for (i in toSave)
            toSave[i].save();
    }

    inkinru.HtmlPage.prototype.isAdminLayout = function ()
    {
        return this.adminLayout;
    }

    inkinru.HtmlPage.prototype.removeModuleJavaScript = function (module)
    {
        for (var id in this.dataSavers)
        {
            var dataSaver = this.dataSavers[id];

            if (dataSaver.getModule() == module)
                this.removeDataSaver(dataSaver);
        }

        delete this.modules[module.id];
        delete this.modulesBySection[module.section][module.id];
    }

    inkinru.HtmlPage.prototype.ensureDialogsDivExists = function ()
    {
        if (!this.dialogsDiv)
        {
            this.dialogsDiv = document.getElementById('inkinru-dialogsDiv');

            if (!this.dialogsDiv)
            {
                this.dialogsDiv = document.createElement('div');
                $(this.dialogsDiv).appendTo('body');
            }
        }
    }

    inkinru.HtmlPage.prototype.checkAjaxResult = function (result, retryCallback)
    {
        //  TODO: Localize error messages, add retry ability.

        if (result.status == 'ok')
            return true;

        switch (result.error)
        {
            case 'unauthenticated':
                alert('Unauthenticated');
        }

        return false;
    };

    inkinru.HtmlPage.prototype.allowNavigateAway = function ()
    {
        this.canNavigateAway = true;
    }

    inkinru.HtmlPage.prototype.onBeforeUnload = function (e)
    {
        if (!this.isModified() || this.canNavigateAway)
            return undefined;

        var message = 'Not Saved!';
        var event = e || window.event;

        if (event)
            event.returnValue = message;

        return message;
    };

    inkinru.HtmlPage.prototype.stringToJQueryEffect = function (str)
    {
        if (!str)
            return null;

        var c = str.charAt(0);

        switch (c)
        {
            case '"':
            case "'":
                return str.substring(1, str.length - 1);
            case '{':
                return eval('[' + str + ']')[0];
        }

        return str;
    }

    inkinru.HtmlPage.prototype.transliterateIntName = function (values)
    {
        var str = '';

        if ('en' in values && values['en'] !== '')
            str = values['en'];
        else
        {
            for (var language in values)
            {
                if (values[language] !== '')
                {
                    str = transliterate(language, values[language]);
                    break;
                }
            }
        }

        str = str.replace(/[^-a-zA-Z0-9]/g, '_');
        return str;
    }

    inkinru.HtmlPage.prototype.setFullHeightModules = function (arr)
    {
        var that = this;

        this.fullHeightModuleIds = arr;
        this.fullHeightModules = null;

        if (!this.oldOnResizeSet)
        {
            this.oldOnResize = window.onresize;
            this.oldOnResizeSet = true;
        }

        window.onresize = function () {that.onResize();};
        this.onResize();
    }

    inkinru.HtmlPage.prototype.onResize = function ()
    {
        var that = this;
        var i;

        if (this.fullHeightModuleIds)
        {
            var height = $(window).height();
            var contentsHeight = height - $('#header').height() - $('#footer').height();

            if (!this.fullHeightModules)
            {
                var arr = [];

                for (i in this.fullHeightModuleIds)
                    arr.push('#mdl' + this.fullHeightModuleIds[i]);

                this.fullHeightModules = $(arr.join(','));
                this.fullHeightSections = this.fullHeightModules.parent();

                this.fullHeightModulesBySection = {};
                this.nonFullHeightModulesBySection = {};

                this.fullHeightSections.each(function () {
                    var sectionId = this.id;
                    var children = $(this).children();

                    that.fullHeightModulesBySection[sectionId] = children.filter(that.fullHeightModules);
                    that.nonFullHeightModulesBySection[sectionId] = children.not(that.fullHeightModules);
                });
            }

            for (i in this.fullHeightModulesBySection)
            {
                var fixedHeight = 0;

                this.nonFullHeightModulesBySection[i].each(function () {
                    fixedHeight += $(this).height();
                });

                var eachHeight = (contentsHeight - fixedHeight) / this.fullHeightModulesBySection[i].length;

                this.fullHeightModulesBySection[i].each(function () {
                    var moduleId = /^mdl(.+)$/.exec(this.id)[1];
                    var module = that.getModule(moduleId);
                    module.beforeResize(null, eachHeight);

                    $(this).height(eachHeight);
                });
            }
        }

        if (this.oldOnResize)
            this.oldOnResize();
    }

    inkinru.HtmlPage.prototype.isCssFileLoaded = function (fullPath)
    {
        if (!this.cssFiles)
            this.parseCssTags();

        return (fullPath in this.cssFiles);
    }

    inkinru.HtmlPage.prototype.getCssFiles = function ()
    {
        if (!this.cssFiles)
            this.parseCssTags();

        var result = [];

        for (var file in this.cssFiles)
            result.push(file);

        return result;
    }

    inkinru.HtmlPage.prototype.parseCssTags = function ()
    {
        var that = this;
        this.cssFiles = {};

        $('link[rel="stylesheet"]').each(function () {
            that.cssFiles[this.getAttribute('href')] = 1;
        });
    }

    inkinru.HtmlPage.prototype.markCssFileLoaded = function (fullPath)
    {
        if (!this.cssFiles)
            this.parseCssTags();

        this.cssFiles[fullPath] = 1;
    }

    inkinru.HtmlPage.prototype.loadCssFile = function (file)
    {
        var fullPath;

        if (file.indexOf('http://') == 0 || file.indexOf('https://') == 0) // || file.charAt(0) == '/')
            fullPath = file;
        else
            fullPath = this.getSystemAbsolutePath(file);

        $("head").append("<link>");
        css = $("head").children(":last");
        css.attr({
            rel:  "stylesheet",
            type: "text/css",
            href: fullPath
        });

        this.markCssFileLoaded(fullPath);
    }

    inkinru.HtmlPage.prototype.getNextControlId = function ()
    {
        return 'ctl' + this.nextControlId++;
    }

    inkinru.HtmlPage.prototype.isJavaScriptFileLoaded = function (fullPath)
    {
        if (!this.javaScriptFiles)
            this.parseJavaScriptTags();

        return (fullPath in this.javaScriptFiles);
    }

    inkinru.HtmlPage.prototype.parseJavaScriptTags = function ()
    {
        var that = this;
        this.javaScriptFiles = {};

        $('script[src]').each(function () {
            that.javaScriptFiles[this.getAttribute('src')] = 1;
        });
    }

    inkinru.HtmlPage.prototype.markJavaScriptFileLoaded = function (fullPath)
    {
        if (!this.javaScriptFiles)
            this.parseJavaScriptTags();

        this.javaScriptFiles[fullPath] = 1;
    }

    inkinru.HtmlPage.prototype.loadJavaScriptFiles = function (arr, callback, currentIndex)
    {
        //var flag;
        var that = this;
        var load = true;

        if (typeof(currentIndex) == 'undefined')
            currentIndex = 0;

        if (currentIndex == arr.length)
        {
            if (callback)
                callback();

            return;
        }

        var file = arr[currentIndex];
        var fullPath;

        if (file.indexOf('http://') == 0 || file.indexOf('https://') == 0 || file.charAt(0) == '/')
            fullPath = file;
        else
            fullPath = this.getSystemAbsolutePath('/javascript/' + file);
            //fullPath = this.context + 'javascript/' + file;


        if (this.isJavaScriptFileLoaded(fullPath))
            load = false;

        if (!load)
            this.loadJavaScriptFiles(arr, callback, currentIndex + 1);
        else
        {
            $.getScript(fullPath, function () {
                    that.markJavaScriptFileLoaded(fullPath);
                    that.loadJavaScriptFiles(arr, callback, currentIndex + 1);
            });
        }
    }

    inkinru.HtmlPage.prototype.getSystemAbsolutePath = function (pathWithoutSystemContext)
    {
        return this.systemContextPath + (pathWithoutSystemContext === undefined ? '' : pathWithoutSystemContext);
    }

    inkinru.HtmlPage.prototype.getSiteAbsolutePath = function (pathTail)
    {
        return this.systemAndSiteContextPath + (pathTail === undefined ? '' : pathTail);
    }

    inkinru.HtmlPage.prototype.callServerMethod = function (strClass, strMethod, arrParams, callbackSuccess, callbackError)
    {
        var url = this.systemAndSiteContextPath + '/inkinru-ajax-listener/' + strClass + '/' + strMethod + '/';

        if (!callbackError)
            return $.post(url, arrParams, callbackSuccess);

        return $.ajax({
            type:       'POST',
            data:       arrParams,
            url:        url,
            success:    callbackSuccess,
            error:      callbackError
        });
    }

    inkinru.HtmlPage.prototype.getStringHash = function ()
    {
        if (!this.stringHash)
            this.stringHash = new inkinru.AjaxStringHash();

        return this.stringHash;
    }

    inkinru.HtmlPage.prototype.getInputSourceStringHash = function ()
    {
        if (!this.inputSourceStringHash)
            this.inputSourceStringHash = new inkinru.PrefixedStringHashWrapper(this.getStringHash(), 'InputSourceSwitchControl_');

        return this.inputSourceStringHash;
    }

    inkinru.HtmlPage.prototype.ensureStringHashLoaded = function (callback)
    {
//        if (this.stringHash)
//            callback(this.stringHash);

        var keys = [
            'HtmlPage_removeModule',
            'HtmlPage_moduleProperties',
            'HtmlPage_moduleProperties_dialogTitle',
            'HtmlPage_moduleProperties_tab_general',
            'HtmlPage_moduleProperties_tab_parameters',
            'HtmlPage_moduleProperties_section',
            'HtmlPage_moduleProperties_sectionDialog',
            'HtmlPage_moduleProperties_title',
            'HtmlPage_moduleProperties_height',
            'HtmlPage_moduleProperties_height_auto',
            'HtmlPage_moduleProperties_height_full',
            'HtmlPage_moduleProperties_height_set',
            'HtmlPage_moduleProperties_name',
            'HtmlPage_moduleProperties_frameTemplate',
            'HtmlPage_moduleProperties_contentTemplate',
            'HtmlPage_moduleProperties_onError',
            'HtmlPage_moduleProperties_onError_ignore',
            'HtmlPage_moduleProperties_onError_display',
            'HtmlPage_moduleProperties_onError_skip',
            'HtmlPage_moduleProperties_onError_403',
            'HtmlPage_moduleProperties_onError_404',
            'HtmlPage_moduleProperties_onError_500',
            'HtmlPage_moduleProperties_none',
            'HtmlPage_moduleProperties_advanced',
            'HtmlPage_moduleProperties_advanced_title',
            'HtmlPage_dialogProperties_dialogTitle',
            'HtmlPage_dialogProperties_tab_general',
            'HtmlPage_dialogProperties_name',
            'HtmlPage_dialogProperties_intName',
            'HtmlPage_dialogProperties_title',
            'HtmlPage_dialogProperties_width',
            'HtmlPage_dialogProperties_hide',
            'HtmlPageDialog_properties',
            'HtmlPageDialog_test',
            'HtmlPageDialog_remove',
            'HtmlPageDialog_nameTemplate'
        ];

        this.getStringHash().load(keys, callback);
    }

    inkinru.HtmlPage.prototype.getModuleById = function (id)
    {
        if (id in this.modules)
            return this.modules[id];

        return null;
    }

    inkinru.HtmlPage.prototype.getModuleByName = function (name)
    {
        if (name in this.modulesByName)
            return this.modulesByName[name];

        return null;
    }

    inkinru.HtmlPage.prototype.getPageModuleProvider = function ()
    {
        if (!this.pageModuleProvider)
        {
            var that = this;

            this.pageModuleProvider = {
                getModuleById: function (id)
                {
                    return that.getModuleById(id);
                },

                getModuleByName: function (name)
                {
                    return that.getModuleByName(name);
                }
            }
        }

        return this.pageModuleProvider;
    }

    inkinru.HtmlPage.prototype.validateForm = function (selector, options)
    {
        var captchaCheckUrl = this.getSiteAbsolutePath('/inkinru-ajax-listener/Core/testCaptcha/?plain=1');

        if (!options) options = {};
        if (!('rules' in options)) options.rules = {};
        if (!('messages' in options)) options.messages = {};

        var oldErrorPlacement = options.errorPlacement;

        options.rules.captcha = {
            required:       true,
            rangelength:    [5, 5],
            remote:         captchaCheckUrl
        };

        options.errorPlacement = function (error, element)
        {
            if (element.attr('name') == 'captcha')
                $('#captchaError').append(error);
            else if (oldErrorPlacement)
                oldErrorPlacement(error, element);
            else
                error.insertAfter(element);
        }

        var sh = this.getStringHash();

        var onStringsLoad = function ()
        {
            options.messages.captcha = {
                required:       sh.get('form_captcha_error_required'),
                rangelength:    sh.get('form_captcha_error_length')
            };

            $(selector).validate(options);
        }

        sh.load(
                ['form_captcha_error_required', 'form_captcha_error_length'],
                onStringsLoad);
    }

    inkinru.HtmlPage.prototype.attachEvent = function (name, handler)
    {
        this.eventSet.attachEvent(name, handler);
    }

    inkinru.HtmlPage.prototype.onAddressHashChange = function (hash)
    {
        if (hash !== '')
        {
            var moduleFragments = hash.split('|');

            for (var i in moduleFragments)
            {
                var moduleFragment  = moduleFragments[i];
                var moduleId        = moduleFragment.split('-', 1)[0];
                var moduleState     = moduleFragment.substring(moduleId.length + 1);
                var module          = this.modules[moduleId];

                if (module)
                    module.showAjaxState(moduleState);
            }
        }
    }

    inkinru.HtmlPage.prototype.onModuleAjaxStateChange = function (changedModule, newState)
    {
        var states = [];

        for (var id in this.modules)
        {
            var module = this.modules[id];
            var state = (module == changedModule ? newState : module.getAjaxState());

            if (state !== undefined)
                states.push(id + '-' + state);
        }

        var hash = states.join('|');

        if (hash != this.addressHash)
        {
            this.addressHash = hash;
            $.history.load(hash);
        }
    }

    inkinru.HtmlPage.prototype.parseListenersArray = function (listeners)
    {
        //  |module1|method1|module2|method2

        if (!listeners)
            return [];

        var splitted = listeners.split('|');
        var arr = [];

        for (var i = 1; i < splitted.length; i += 2)
            arr.push(createFunctionCallingMethod(this.getModuleById(splitted[i]), splitted[i + 1]));

        return arr;
    }

    inkinru.HtmlPage.prototype.askServerAndCreateObject = function (className, methodName, params, callback)
    {
        var that = this;
        var data;

        var onDataFetched = function (result)
        {
            if (!that.checkAjaxResult(result))
                return;

            data = result.data;
            that.loadJavaScriptFiles(data ? data.js : [], onFilesLoaded);

            if (data)
                for (var i in data.css)
                    that.loadCssFile(data.css[i]);
        }

        var onFilesLoaded = function ()
        {
            if (!data)
            {
                if (callback)
                    callback(null, null);
            }
            else
            {
                var createCallback = (new Function("return " + data.createCallback))();
                var obj = createCallback();

                if (callback)
                    callback(obj, data.variables);
            }
        }

        this.callServerMethod(className, methodName, params, onDataFetched);
    }

    inkinru.HtmlPage.prototype.createCategorySelect = function (container, name, treeId, multiselect, width, onChange, selectedIds, callback)
    {
        var onObjectCreated = function (obj, variables)
        {
            var id      = inkinru.page.getNextControlId();
            var div     = document.createElement('div');

            div.id = id;
            $(div).css({width: width, overflow: 'hidden'});
            container.appendChild(div);

            obj.setTreeId(treeId);
            obj.setMultiselect(multiselect);
            obj.setOnChange(onChange);
            obj.setVariables(variables);
            obj.create({id: id, name: name, width: width});
            obj.setSelectedIds(selectedIds);

            if (callback)
                callback(obj);
        }

        this.askServerAndCreateObject('Core', 'getCategorySelect', {id: treeId}, onObjectCreated);
    }

    inkinru.HtmlPage.prototype.createCatalogCategorySelect = function (container, name, catalogId, multiselect, width, onChange, selectedIds, callback)
    {
        var onObjectCreated = function (obj, variables)
        {
            if (obj)
            {
                var id      = inkinru.page.getNextControlId();
                var div     = document.createElement('div');

                div.id = id;
                $(div).css({width: width, overflow: 'hidden'});
                container.appendChild(div);

                obj.setTreeId(variables.treeId);
                obj.setMultiselect(multiselect === null ? variables.multiselect : multiselect);
                obj.setOnChange(onChange);
                obj.setVariables(variables);
                obj.create({id: id, name: name, width: width});
                obj.setSelectedIds(selectedIds);
            }

            if (callback)
                callback(obj);
        }

        this.askServerAndCreateObject('Core', 'getCatalogCategorySelect', {id: catalogId}, onObjectCreated);
    }


inkinru.Event = function ()
{
    this.handlers = {};
}

    inkinru.Event.prototype.addHandler = function (handler)
    {
        this.handlers[handler] = handler;
    }

    inkinru.Event.prototype.fire = function ()
    {
        for (var i in this.handlers)
            this.handlers[i].apply(null, arguments);
    }


inkinru.EventSet = function ()
{
    this.events = {};
}

    inkinru.EventSet.prototype.attachEvent = function (name, handler)
    {
        if (!(name in this.events))
            this.events[name] = new inkinru.Event();

        this.events[name].addHandler(handler);
    }

    inkinru.EventSet.prototype.get = function (name)
    {
        if (!(name in this.events))
            this.events[name] = new inkinru.Event();

        return this.events[name];
    }

    inkinru.EventSet.prototype.fire = function (name)
    {
        if (name in this.events)
        {
            var eventArguments = new Array(arguments.length - 1);

            for (var i = arguments.length; --i >= 1; )
                eventArguments[i - 1] = arguments[i];

            this.events[name].fire(eventArguments);
        }
    }


inkinru.SimpleStringHash = function (hash)
{
    this.construct(hash);
}

    inkinru.SimpleStringHash.prototype.construct = function (hash)
    {
        this.hash = hash ? hash : {};
    }

    inkinru.SimpleStringHash.prototype.addStrings = function (hash)
    {
        for (var key in hash)
            this.hash[key] = hash[key];
    }

    inkinru.SimpleStringHash.prototype.get = function (key)
    {
        return (key in this.hash) ? this.hash[key] : 'js error';
    }

    inkinru.SimpleStringHash.prototype.getAndSubstitute = function (key, map)
    {
        var value = this.get(key);

        for (var i in map)
            value = value.replace('{@' + i + '}', map[i]);

        return value;
    }

    inkinru.SimpleStringHash.prototype.getAndCall = function (arr)
    {
        for (var i in arr)
        {
            var key = arr[i][0];
            var callback = arr[i][1];

            callback(this.get(key));
        }
    }

    inkinru.SimpleStringHash.prototype.load = function (arr, callback)
    {
        if (callback)
            callback(this);
    }


inkinru.AjaxStringHash = function (hash)
{
    this.construct(hash);
}

    inkinru.AjaxStringHash.inherits(inkinru.SimpleStringHash);

    inkinru.AjaxStringHash.prototype.load = function (arr, callback)
    {
        var that = this;
        var arrToLoad = [];

        for (var i in arr)
            if (!(arr[i] in this.hash))
                arrToLoad.push(arr[i]);

        if (!arrToLoad.length)
        {
            if (callback)
                callback(this);
            return;
        }

        inkinru.page.callServerMethod(
                'Core',
                'getStrings',
                {keys: serialize(arr)},
                function (result) {
                    if (!inkinru.page.checkAjaxResult(result))
                        return;

                    that.addStrings(result.data);

                    if (callback)
                        callback(that);
                });
    }


inkinru.PrefixedStringHashWrapper = function (stringHash, prefix)
{
    this.wrappee    = stringHash;
    this.prefix     = prefix;
}

    inkinru.PrefixedStringHashWrapper.prototype.get = function (key)
    {
        return this.wrappee.get(this.prefix + key);
    }

    inkinru.PrefixedStringHashWrapper.prototype.getAndSubstitute = function (key, map)
    {
        return this.wrappee.getAndSubstitute(this.prefix + key, map);
    }

    inkinru.PrefixedStringHashWrapper.prototype.getAndCall = function (arr)
    {
        var prefixedArray = [];

        for (var i in arr)
        {
            var key = arr[i][0];
            var callback = arr[i][1];

            prefixedArray.push([this.prefix + key, callback]);
        }

        this.wrappee.getAndCall(prefixedArray);
    }

    inkinru.PrefixedStringHashWrapper.prototype.load = function (arr, callback)
    {
        var that = this;
        var prefixedArray = [];

        for (var i in arr)
            prefixedArray.push(this.prefix + arr[i]);

        this.wrappee.load(prefixedArray, function () {if (callback) callback(that);});
    }


inkinru.Module_Abstract = function ()
{
};

    inkinru.Module_Abstract.prototype.controlId = function (id)
    {
        return 'mdl' + this.id + '_' + id;
    };

    inkinru.Module_Abstract.prototype.getElementById = function (id)
    {
        return document.getElementById('mdl' + this.id + '_' + id);
    };

    inkinru.Module_Abstract.prototype.getFrameByName = function (name)
    {
        return window.frames['mdl' + this.id + '_' + name];
    };

    inkinru.Module_Abstract.prototype.initBase = function ()
    {
        var that = this;
        this.element = document.getElementById('mdl' + this.id);

        if ($().button)
        {
            $('#mdl' + this.id + ' .inkinru-module-toolbar').children('div[name]').each(function (){
                var name = this.name;

                $(this).find('button')
                        .each(function () {this.toolbarName = name;})
                        .click(function () {that.onToolbarButtonClickBase(this);})
                        .button();

                $(this).find('div.buttonset').buttonset();
            });
        }

        this.init();
    };

    inkinru.Module_Abstract.name = 'Module_Abstract';
    inkinru.Module_Abstract.prototype.init = function () {};
    inkinru.Module_Abstract.prototype.beforeResize = function () {};
    inkinru.Module_Abstract.prototype.onToolbarButtonClick = function (button, toolbarName) {};

    inkinru.Module_Abstract.prototype.onToolbarButtonClickBase = function (button)
    {
        this.onToolbarButtonClick(button, button.toolbarName);
    }

    inkinru.Module_Abstract.prototype.getButton = function (buttonName, toolbarName)
    {
        var s = $('#mdl' + this.id + ' .inkinru-module-toolbar button[name="' + buttonName + '"]');
        return s.length ? s[0] : null;
    }

    inkinru.Module_Abstract.prototype.getElement = function ()
    {
        return document.getElementById('mdl' + this.id);
    }

    inkinru.Module_Abstract.prototype.getDialog = function ()
    {
        if (this.section.substring(0, 7) == 'dialog:')
            return this.page.getDialog(this.section.substring(7));

        return null;
    }

    inkinru.Module_Abstract.prototype.updateContentsHash = function ()
    {
        var contents = this.getElement().innerHTML;

        this.contentsHash = md5(contents);
        return this.contentsHash;
    }

    inkinru.Module_Abstract.prototype.getContentsHash = function ()
    {
        if (!this.contentsHash)
            this.updateContentsHash();

        return this.contentsHash;
    }

    inkinru.Module_Abstract.prototype.setAjaxState = function (state)
    {
        this.page.onModuleAjaxStateChange(this, state);
    }

    inkinru.Module_Abstract.prototype.showAjaxStateBase = function (state)
    {
        if (state != this.ajaxState)
        {
            this.ajaxState = state;
            this.showAjaxState(state);
        }
    }

    inkinru.Module_Abstract.prototype.showAjaxState = function (state) {}

    inkinru.Module_Abstract.prototype.getAjaxState = function ()
    {
        return this.ajaxState;
    }

    inkinru.Module_Abstract.prototype.blinkCreated = function(scroll)
    {
        this.blink('#00ff00', scroll);
    }

    inkinru.Module_Abstract.prototype.blinkUpdated = function (scroll)
    {
        this.blink('#0000ff', scroll);
    }

    inkinru.Module_Abstract.prototype.blink = function (color, scroll)
    {
        var div = document.createElement('div');

        $(div)
            .css({
                position:           'absolute',
                width:              '100%',
                height:             '100%',
                top:                0,
                left:               0,
                'background-color': color
            })
            .fadeTo(0, 0.4)
            .prependTo(this.getElement())
            .fadeOut(1500, function () {$(div).remove();});

        if (scroll)
        {
            var pos = $(this.getElement()).position();
            $('html,body').animate({scrollTop: pos.top}, 1000);
            //$(window).scrollTop(pos.top);
        }
    }

    inkinru.Module_Abstract.prototype.blinkDeleted = function ()
    {
        var div = document.createElement('div');

        $(div)
            .css({
                position:           'absolute',
                width:              '100%',
                height:             '100%',
                top:                0,
                left:               0,
                'background-color': '#ff0000'
            })
            .fadeTo(0, 0.4)
            .prependTo(this.getElement());

        $(this.getElement())
            .fadeOut(700, function () {$(this).remove();});
    }

    inkinru.Module_Abstract.prototype.showLoading = function ()
    {
        this.loadingDiv = document.createElement('div');
        this.loadingImg = document.createElement('img');

        $(this.loadingImg)
                .attr({src: this.page.templatePath + '/images/loading.gif'})
                .css({border: 0, position: 'absolute', top: '45%', left: '45%', 'z-index': 11});

        $(this.loadingDiv)
                .css({
                    position:           'absolute',
                    width:              '100%',
                    height:             '100%',
                    top:                0,
                    left:               0,
                    'background-color': '#888888',
                    'z-index':          10,
                    'text-align':       'center'
                })
                .fadeTo(0, 0.2);
                //.prependTo(this.getElement());

        $(this.getElement()).append([this.loadingDiv, this.loadingImg]);
    }

    inkinru.Module_Abstract.prototype.stopShowLoading = function ()
    {
        if (this.loadingDiv)
            $([this.loadingDiv, this.loadingImg]).remove();
    }


inkinru.HtmlPageDialog = function ()
{
}

    inkinru.HtmlPageDialog.prototype.create = function (info)
    {
        var that = this;
        this.info = info;

        if (this.iconsElement)  //  Might be set in HtmlPage.addDialog
            this.ensureIconExists(this.iconsElement);

        this.element = document.getElementById('dlg' + this.id);

        if (!this.element)
        {
            this.element = document.createElement('div');

            $(this.element)
                .appendTo(this.dialogsElement);  //  Set in HtmlPage.addDialog

            var section = document.createElement('div');
            var sectionName = 'dialog:' + this.id;

            $(section)
                .attr('id', 'section' + sectionName)
                .addClass('inkinru-section')
                .appendTo(this.element);

            this.page.addSection(sectionName, section);
        }

        var options = {
                bgiframe:   true,
                modal:      true,
                autoOpen:   false,
                height:     this.info.height == 'auto' ? this.info.height : this.info.height - 0,
                title:      this.info.title,
                hide:       inkinru.page.stringToJQueryEffect(info.hide)
                //buttons:    {OK: function () {that.onModulePropertiesOk(module);}}
            };

        if (this.info.width != 'auto')
            options.width = this.info.width - 0;

//        if (this.info.hide)
//            options.hide = eval('[' + info.hide + ']')[0];

        $(this.element)
            .dialog(options);
    }

    inkinru.HtmlPageDialog.prototype.open = function ()
    {
        $(this.element).dialog('open');
    }

    inkinru.HtmlPageDialog.prototype.close = function ()
    {
        $(this.element).dialog('close');
    }

    inkinru.HtmlPageDialog.prototype.getName = function ()
    {
        return this.info.name;
    }


inkinru.DataSaver = function ()
{
}

    inkinru.DataSaver.nextId = 0;

    inkinru.DataSaver.prototype.construct = function ()
    {
        this.id                 = inkinru.DataSaver.nextId++;
        this.listeners          = [];
        this.changed            = false;
        this.changedItems       = {};
        this.changedCount       = 0;
        this.priority           = 0;
        this.module             = null;
    }

    inkinru.DataSaver.prototype.addListener = function (listener)
    {
        this.listeners.push(listener);
    }

    inkinru.DataSaver.prototype.onSaveSuccess = function (data)
    {
        if (data.refresh)
        {
            inkinru.page.allowNavigateAway();
            window.location.reload(true);
        }
        if (data.redirect)
        {
            inkinru.page.allowNavigateAway();
            window.location.href = data.redirect;
        }
        else
        {
            this.changed = false;
            this.changedItems = {};
            this.changedCount = 0;

            for (var i in this.listeners)
                if (this.listeners[i].onSaveSuccess)
                    this.listeners[i].onSaveSuccess(this, data);
        }
    }

    inkinru.DataSaver.prototype.onSaveError = function (data)
    {
        for (var i in this.listeners)
            if (this.listeners[i].onSaveError)
                this.listeners[i].onSaveError(this);

        alert('Error while saving.');
    }

    inkinru.DataSaver.prototype.onPageSaveSuccess = function ()
    {
        for (var i in this.listeners)
            if (this.listeners[i].onPageSaveSuccess)
                this.listeners[i].onPageSaveSuccess(this);
    }

    inkinru.DataSaver.prototype.addChangedItem = function (key, item)
    {
        this.changedItems[key] = item;

        if (!this.changed)
        {
            this.changed = true;

            for (var i in this.listeners)
                if (this.listeners[i].markChanged)
                    this.listeners[i].markChanged(this);
        }
    }

    inkinru.DataSaver.prototype.removeChangedItem = function (key)
    {
        delete this.changedItems[key];

        if (this.changed && !this.getChangedItemsCount() && !this.changedCount)
        {
            this.changed = false;

            for (var i in this.listeners)
                if (this.listeners[i].markUnchanged)
                    this.listeners[i].markUnchanged(this);
        }
    }

    inkinru.DataSaver.prototype.getChangedItem = function (key)
    {
        var v = this.changedItems[key];

        if (typeof(v) == 'undefined')
            return null;

        return v;
    }

    inkinru.DataSaver.prototype.getChangedItemsCount = function ()
    {
        var result = 0;

        for (var i in this.changedItems)
            result++;

        return result;
    }

    inkinru.DataSaver.prototype.incrementChangedCount = function ()
    {
        this.changedCount++;

        if (!this.changed)
        {
            this.changed = true;

            for (var i in this.listeners)
                if (this.listeners[i].markChanged)
                    this.listeners[i].markChanged(this);
        }
    }

    inkinru.DataSaver.prototype.decrementChangedCount = function ()
    {
        this.changedCount--;

        if (this.changedCount == 0 && this.changed && this.getChangedItemsCount() == 0)
        {
            this.changed = false;

            for (var i in this.listeners)
                if (this.listeners[i].markUnchanged)
                    this.listeners[i].markUnchanged(this);
        }
    }

    inkinru.DataSaver.prototype.markChanged = function ()
    {
        if (!this.changed)
        {
            this.changed = true;

            for (var i in this.listeners)
                if (this.listeners[i].markChanged)
                    this.listeners[i].markChanged(this);
        }
    }

    inkinru.DataSaver.prototype.markUnchanged = function ()
    {
        if (this.changed)
        {
            this.changed = false;
            this.changedItems = {};
            this.changedCount = 0;

            for (var i in this.listeners)
                if (this.listeners[i].markUnchanged)
                    this.listeners[i].markUnchanged(this);
        }
    }

    inkinru.DataSaver.prototype.validate = function ()
    {
        return true;
    }

    inkinru.DataSaver.prototype.setModule = function (module)
    {
        this.module = module;
    }

    inkinru.DataSaver.prototype.getModule = function ()
    {
        return this.module;
    }


inkinru.AjaxDataSaver = function ()
{
}

    inkinru.AjaxDataSaver.inherits(inkinru.DataSaver);

    inkinru.AjaxDataSaver.prototype.onAjaxSaveSuccess = function (data, textStatus, request)
    {
        switch (data.status)
        {
            case 'ok':
                this.onSaveSuccess(data);
                break;

            default:
                //  TODO: Distinguish unauthenticated case
                this.onSaveError(data);
        }
    }

    inkinru.AjaxDataSaver.prototype.onAjaxSaveError = function (request, textStatus, errorThrown)
    {
        this.onSaveError({});
    }


inkinru.ControlsDataSaver = function ()
{
    this.construct();
}

    inkinru.ControlsDataSaver.inherits(inkinru.AjaxDataSaver);

    inkinru.ControlsDataSaver.prototype.construct = function ()
    {
        var that = this;
        this.uber('construct');
        this.elements = {};
        this.radiosByName = {};
        this.nextElementId = 0;

        this.addListener({
            onSaveSuccess:  function () {that.markAllControlsAsNotChanged();}
        });
    }

    inkinru.ControlsDataSaver.prototype.addElement = function (e)
    {
        var that = this;
        e.formDataSaverAssignedId = this.nextElementId++;
        e.changed = false;
        e.dataSaver = this;

        this.elements[e.formDataSaverAssignedId] = e;

        switch (e.type)
        {
            case 'radio':
                if (!(e.name in this.radiosByName))
                    this.radiosByName[e.name] = {};

                this.radiosByName[e.name][e.value] = e;
                //  no break;

            case 'checkbox':
                e.originalChecked = e.checked;
                e.oldOnClick = e.onclick;
                e.onclick = function () {that.onElementChanged(this);if (this.oldOnClick) this.oldOnClick();};
                break;

            default:
                e.originalValue = e.value;
                e.oldOnChange = e.onchange;
                e.onchange = function () {that.onElementChanged(this);if (this.oldOnChange) this.oldOnChange();};
                e.oldOnKeyUp = e.onkeyup;
                e.onkeyup = function () {that.onElementChanged(this);if (this.oldOnKeyUp) this.oldOnKeyUp();};
        }

        if (e.type == 'hidden' && $(e.parentNode).hasClass('dhx_combo_box'))
        {
            e.parentNode._self.attachEvent("onChange", function(){
                if (!this.inkinruDontFireChangeFlag)
                    e.onchange();
            });
        }

        if (e.type == 'textarea' && e.tinymce)
        {
            var editor = e.tinymce;
            var fn = function (editor, event) {that.checkTinyMCEDirty(e, editor)};

            editor.onChange.add(fn);
            editor.onKeyDown.add(fn);
            editor.onUndo.add(fn);
            editor.onRedo.add(fn);
        }
    }

    inkinru.ControlsDataSaver.prototype.getElementKey = function (e)
    {
        return e.formDataSaverAssignedId;
    }

    inkinru.ControlsDataSaver.prototype.removeElement = function (e)
    {
        if (e.type == 'radio')
            delete this.radiosByName[e.name][e.value];

        if (e.changed)
            this.removeChangeFlag(e);

        delete this.elements[e.formDataSaverAssignedId];
        delete e.dataSaver;
    }

    inkinru.ControlsDataSaver.prototype.checkTinyMCEDirty = function (textarea, editor)
    {
        var changedNow = true; //= (editor.getContent({format:'raw'}) != textarea.originalValue);

        if (!textarea.changed && changedNow)
            this.setChangeFlag(textarea);
        else if (textarea.wasDirty && !changedNow)
            this.removeChangeFlag(textarea);
    }

    inkinru.ControlsDataSaver.prototype.onElementChanged = function (e)
    {
        var same;

        switch (e.type)
        {
            case 'checkbox':
            case 'radio':
                same = (e.checked == e.originalChecked);
                break;
            default:
                same = (e.value == e.originalValue);
        }

        if (same)
        {
            if (e.changed)
                this.removeChangeFlag(e);
        }
        else
        {
            if (!e.changed)
                this.setChangeFlag(e);
        }

        if (e.type == 'radio' && e.checked)
            for (var value in this.radiosByName[e.name])
                if (value != e.value)
                    this.onElementChanged(this.radiosByName[e.name][value]);
    }

    inkinru.ControlsDataSaver.prototype.removeChangeFlag = function (e)
    {
        e.changed = false;
        this.removeChangedItem(this.getElementKey(e));
    }

    inkinru.ControlsDataSaver.prototype.setChangeFlag = function (e)
    {
        e.changed = true;
        this.addChangedItem(this.getElementKey(e), e);
    }

    inkinru.ControlsDataSaver.prototype.markControlAsNotChanged = function (e)
    {
        this.removeChangeFlag(e);

        switch (e.type)
        {
            case 'checkbox':
            case 'radio':
                e.originalChecked = e.checked;
                break;
            default:
                e.originalValue = e.value;
        }
    }

    inkinru.ControlsDataSaver.prototype.markAllControlsAsNotChanged = function ()
    {
        for (var i in this.elements)
        {
            var e = this.elements[i];

            this.markControlAsNotChanged(e);
        }
    }

    inkinru.ControlsDataSaver.prototype.validate = function ()
    {
        if (!$().validate)
            return true;

        var i;
        var toValidate = [];

        //  If a validator exists in the form the element is from
        //  AND it contains rules for the element
        //  (If there's no validator, obtaining rules will fail)

        for (i in this.elements)
            if ($.data(this.elements[i].form, 'validator') && mapLength($(this.elements[i]).rules()))
                toValidate.push(this.elements[i]);

        for (i in toValidate)
        {
            $(toValidate[i].form).validate().form();

            if ($(toValidate[i].form).validate().numberOfInvalids())
            {
                return false;
            }
        }
//return false;
        return true;
        //return $(toValidate).valid();
    }


inkinru.FormDataSaver = function (forms)
{
    this.construct();

    this.forms = [];

    var url = window.location.href;
    url = url.replace(/#(.*)$/, '');

    this.saveUrl = addParamToUrl(url, 'inkinru-ajax-post', 1);

    if (forms)
        for (var i in forms)
            this.addForm(forms[i]);
}

    inkinru.FormDataSaver.inherits(inkinru.ControlsDataSaver);

    inkinru.FormDataSaver.prototype.addForm = function (form)
    {
        var that = this;
        form.dataSaver = this;
        this.forms.push(form);

        for (var i = 0; i < form.elements.length; i++)
        {
            var e = form.elements[i];

            if (typeof(e) == 'object' && e.name && !e.excludeFromSaving)
                this.addElement(e);
        }

        form.onsubmit = function () {return that.onFormSubmit(this);};
    }

    inkinru.FormDataSaver.prototype.removeForm = function (form)
    {
        var i;

        for (i in this.forms)
        {
            if (this.forms[i] == form)
            {
                this.forms.splice(i, 1);
                break;
            }
        }

        for (i in form.elements)
        {
            var e = form.elements[i];

            if (typeof(e) == 'object' && e.name && !e.excludeFromSaving)
                this.removeElement(e);
        }
    }

    inkinru.FormDataSaver.prototype.updateForm = function (form)
    {
        var found = false;

        for (var i in this.forms)
        {
            if (this.forms[i] == form)
            {
                found = true;
                break;
            }
        }

        if (!found)
            return false;

        return this.updateFormChecked(form);
    }

    inkinru.FormDataSaver.prototype.updateFormChecked = function (form)
    {
        var i;

        for (i = 0; i < form.elements.length; i++)
        {
            var e = form.elements[i];

            if (!e.dataSaver && typeof(e) == 'object' && e.name && !e.excludeFromSaving)
                this.addElement(e);
        }

        for (i in this.elements)
            if (!this.elements[i].form)
                this.removeElement(this.elements[i]);
                //console.log(this.elements[i]);

        return true;
    }

    inkinru.FormDataSaver.prototype.updateForms = function ()
    {
        for (var i in this.forms)
            this.updateFormChecked(this.forms[i]);
    }

    inkinru.FormDataSaver.prototype.setModule = function (module)
    {
        this.uber('setModule', module);

        if (module)
            this.saveUrl = this.saveUrl.replace('inkinru-ajax-post=1', 'inkinru-ajax-post=' + module.id);
    }

    inkinru.FormDataSaver.prototype.onFormSubmit = function (form)
    {
        inkinru.page.save();
        return false;
    }

    inkinru.FormDataSaver.prototype.save = function ()
    {
        var toSerialize;
        var i;
        var that = this;

        for (i in this.elements)
            if (this.elements[i].beforeSave)
                this.elements[i].beforeSave();

        //  TODO: Add beforeSave for tinymce to not hande the special case.
        for (i in this.changedItems)
        {
            var e = this.changedItems[i];

            if (e.tinymce)
                e.value = e.tinymce.getContent();
        }

        for (i in this.forms)
        {
            toSerialize = toSerialize ? toSerialize.add($(this.forms[i])) : $(this.forms[i]);
        }

        $.ajax({
            type:       'POST',
            data:       toSerialize.serialize(),
            url:        this.saveUrl,
            success:    function (d,t,r) {that.onAjaxSaveSuccess(d,t,r);},
            error:      function (r,t,e) {that.onAjaxSaveError(r,t,e);}
        });
    }


inkinru.CommandDataSaver = function ()
{
    var that = this;

    this.construct();
    this.commands = [];
    this.addListener({onSaveSuccess: function () {that.commands = [];}});
}

    inkinru.CommandDataSaver.inherits(inkinru.AjaxDataSaver);

    inkinru.CommandDataSaver.prototype.addCommand = function (command)
    {
        this.commands.push(command);
        this.incrementChangedCount();
    }

    inkinru.CommandDataSaver.prototype.getCommands = function ()
    {
        return this.commands;
    }

    inkinru.CommandDataSaver.prototype.save = function ()
    {
        var that = this;

        this.saveCallback(
                this.commands,
                function (d,t,r) {that.onAjaxSaveSuccess(d,t,r);},
                function (r,t,e) {that.onAjaxSaveError(r,t,e);}
            );
    }

    inkinru.CommandDataSaver.prototype.filterCommands = function (callback)
    {
        var n = this.commands.length;

        for (var i = 0; i < n; i++)
        {
            if (!callback(this.commands[i]))
            {
                this.commands.splice(i, 1);
                n--;
                i--;
                this.decrementChangedCount();
            }
        }
    }


inkinru.page = new inkinru.HtmlPage();
inkinru.page.init();

