try {

    if (top.location.href != window.location.href) {
        App = top.App;
        throw('exit');
    }

    var Debug = false;

    var App = {

        config: {
            name: null,
            version: null,
            path: null,
            date: {
                core: null,
                modules: null
            },
            debug: false
        },

        loaded: '',

        modules: [],

        onReadyFunctions: [],

        bootstraps: {},

        dataWatchers: [],
        
        isReady: false,

        init: function(in_config)
        {
            Ext.apply(this.config, in_config);
            Ext.QuickTips.init();
        },

        close: function() {},

        checkVersion: function(in_level)
        {
            var installedVersion = new Ext.state.CookieProvider().get(this.config.name);

            if (installedVersion) {
                if (in_level && installedVersion.modules != this.config.date.modules) {
                    App.getModule('core/refresh', { force: 1, level: 1, date: installedVersion.modules }).run();
                    return false;
                } else if (installedVersion.core != this.config.date.core) {
                    App.getModule('core/refresh', { force: 1, level: 0 }).run();
                    return false;
                }
            } else {
                new Ext.state.CookieProvider().set(this.config.name, this.config.date);
                return true;
            }

            return true;
        },

        updateVersion: function(in_level)
        {
            var installedVersion = new Ext.state.CookieProvider().get(this.config.name);
            if (in_level) {
                installedVersion.modules = this.config.date.modules;
            }
            installedVersion.core = this.config.date.core;

            new Ext.state.CookieProvider().set(this.config.name, installedVersion);
        },

        getAppId: function()
        {
            return this.config.name + ' ' + this.config.version
        },

        loadUi: function(in_path)
        {
            if (this.loaded.indexOf(in_path + ' ') != -1) {
                return true;
            }

            var tag = document.createElement('script');
            tag.setAttribute('type', 'text/javascript');
            tag.setAttribute('defer', 'defer');
            tag.setAttribute('src', this.getUrl(in_path + '/ui.js'));

            document.getElementsByTagName('head').item(0).appendChild(tag);

            return false;
        },

        getData: function(in_path, in_params, in_grouping, in_onlyOnce)
        {
            in_path = in_path.replace(':', '~');

            var cfg = {
                proxy: new Ext.data.HttpProxy({
                    url: this.getUrl(in_path + (in_path.split('/').length < 3 ? '/data' : ''), in_params)
                }),
                remoteSort: true,
                reader: new Ext.data.JsonReader()
            }

            if (in_grouping) {
                cfg.groupField = in_grouping;
                cfg.remoteGroup = true;
                var dataStore = new Ext.data.GroupingStore(cfg);
            } else {
                var dataStore = new Ext.data.Store(cfg);
            }

            dataStore.tries = 0;

            dataStore.on('load', function(in_this) {
                if (in_this.reader && in_this.reader.jsonData && in_this.reader.jsonData.success == false) {
                    in_this.tries = 3;
                    return in_this.fireEvent('loadexception');
                }
                if (in_this.reader.meta.sortInfo) {
                    Ext.apply(in_this, {sortInfo: in_this.reader.meta.sortInfo});
                }
                in_this.tries = 0;
            });

            dataStore.on('loadexception', function() {

                if (in_onlyOnce) {
                    return;
                }

                if (dataStore.tries < 3) {
                    dataStore.tries++;
                    dataStore.load(dataStore.lastOptions);
                    return;
                }
                dataStore.tries = 0;
                if (this.reader && this.reader.jsonData && this.reader.jsonData.message) {
                    App.showError(this.reader.jsonData.message);
                } else {
                    if (this.reader && this.reader.jsonData) {
                        App.showError("Can't load data: " + this.reader.jsonData.toSource().substring(0, 150));
                    } else {
                        App.showError("Can't load data...");
                    }
                }
            });

            return dataStore;
        },
        
        commitData: function(in_path, in_params)
        {
            in_path = in_path.replace(':', '~');
            
            var cfg = {
                proxy: new Ext.data.HttpProxy({
                    url: this.getUrl(in_path + (in_path.split('/').length < 3 ? '/data' : ''), in_params)
                }),
                remoteSort: true,
                reader: new Ext.data.JsonReader()
            }

            var dataStore = new Ext.data.Store(cfg);

            dataStore.tries = 0;

            dataStore.on('load', function(in_this) {
                if (in_this.reader && in_this.reader.jsonData && in_this.reader.jsonData.success == false) {
                    return in_this.fireEvent('loadexception', this);
                }
            });

            return dataStore;
        },
        
        getModuleVar: function(in_path)
        {
            in_path = in_path.replace(/[:\-\/~]/g, '_');
            return 'App.modules.' + in_path;
        },

        getModule: function(in_path, in_params, in_dontLoad)
        {
            var name = this.getModuleVar(in_path);
            var module = eval(name);
            var params = null;

            in_path = in_path.replace(':', '~');
            in_params = in_params ? in_params : {};
            params = convertToSource(in_params);
            var path = in_path;

            if (!module && !in_dontLoad) {
                if (in_path.indexOf('/') == -1) {
                    path = 'default/' + path;
                }

                setTimeout('App.loadUi(\'' + path + '\')', 1);
            }

            this.bootstraps[path] = this.bootstraps[path] ? this.bootstraps[path] : {};

            if (!this.bootstraps[path][params]) {
                App.log('[' + path + '] create bootstrap');
                this.bootstraps[path][params] = new App.Bootstrap(path, in_params);
                if (module) {
                    this.bootstraps[path][params].init();
                }
            }

            return this.bootstraps[path][params];
        },

        runModule: function(in_path)
        {
            in_path = in_path.replace(':', '~');

            this.loaded += in_path + ' ';
            App.log('[' + in_path + '] UI loaded');

            if (!this.bootstraps[in_path]) {
                App.log('[' + in_path + '] no bootstrap');
                return;
            }

            for (key in this.bootstraps[in_path]) {
                if (!this.bootstraps[in_path][key]) {
                    continue;
                }
                this.bootstraps[in_path][key].init();
            }
        },

        moduleError: function(in_path, in_message)
        {
            in_path = in_path.replace(':', '~');

            if (!this.bootstraps[in_path]) {
                return
            }

            for (key in this.bootstraps[in_path]) {
                if (this.bootstraps[in_path][key].error) {
                    this.bootstraps[in_path][key].error(in_message);
                }
            }

            this.unloadModule(in_path);
        },

        unloadModule: function(in_path)
        {
            in_path = in_path.replace(':', '~');

            this.loaded = this.loaded.replace(in_path + ' ', '');
            eval(this.getModuleVar(in_path) + ' = false');

            if (this.bootstraps[in_path]) {
                for (key in this.bootstraps[in_path]) {
                    App.log('[' + in_path + '] unload (' + key + ') instance');
                    this.bootstraps[in_path][key] = false;
                }
            }

            App.log('[' + in_path + '] unloaded');
        },

        getUrl: function(in_path, in_params)
        {
            if (!in_path) {
                in_path = '';
            } else {
                if (in_path.charAt(0) == '/') {
                    in_path = in_path.substring(1);
                }

                if (in_path.length > 1 && in_path.indexOf('/') == -1) {
                    in_path = 'default/' + in_path;
                }
                in_path = in_path.replace(':', '~');
            }

            if (in_params) {
                in_path += '?' + Ext.urlEncode(in_params);
            }

            if (this.config.path != null) {
                in_path = this.config.path + '/' + in_path;
            }

            return in_path;
        },

        getFullUrl: function(in_path, in_params, in_url)
        {
            in_path = in_path.replace(':', '~');

            if (!in_url) {
                var basehref = document.getElementsByTagName('base')[0].href;
            } else {
                var basehref = in_url;
            }

            basehref = basehref.replace(this.config.path, '');

            if (basehref.charAt(basehref.length - 1) == '/') {
                basehref = basehref.substr(0, basehref.length - 1);
            }
            
            var url = this.getUrl(in_path, in_params);
            
            if (url.charAt(0) != '/') {
                url = '/' + url;
            }

            return basehref + url;
        },

        showError: function(in_message)
        {
            if (typeof(in_message) == 'object') {
                return App.JavaScriptError.show(in_message);
            }
            setTimeout(function() {
                Ext.Msg.show({
                   title: 'Error',
                   msg: in_message,
                   buttons: Ext.Msg.OK,
                   icon: Ext.MessageBox.ERROR
                });
            }, 1);
        },

        setReady: function()
        {
            this.isReady = true;
            this.onReady();
        },

        onReady: function(in_callback)
        {
            if (in_callback) {
                this.onReadyFunctions.push(in_callback);
            }

            if (this.isReady && this.onReadyFunctions.length) {
                for (var i = 0; i < this.onReadyFunctions.length; i++) {
                    this.onReadyFunctions.pop()();
                }
            }
        },

        watch: function(in_space, in_callback, in_scope)
        {
            if (!this.dataWatchers[in_space] || typeof(this.dataWatchers[in_space]) == 'undefined') {
                this.dataWatchers[in_space] = [];
            }

            if (typeof(in_callback) == 'object') {
                in_scope = in_callback;
                in_callback = in_callback.reload;
            }

            if (!in_scope) {
                in_scope = this;
            } else {
                var key = this.dataWatchers[in_space].length;
                in_scope.unWatch = function() {
                    App.unWatch(in_space, key);
                }
            }

            this.dataWatchers[in_space].push([ in_callback, in_scope ]);
            App.log('<' + in_space + '> [' + (this.dataWatchers[in_space].length - 1) + '] data watcher set');

            return this.dataWatchers[in_space].length - 1;
        },

        unWatch: function(in_space, in_index)
        {
            if (this.dataWatchers[in_space] && (typeof(this.dataWatchers[in_space]) == 'object' && this.dataWatchers[in_space][in_index])) {
                App.log('<' + in_space + '> [' + in_index + '] data watcher unset');
                this.dataWatchers[in_space][in_index] = false;
                if (Debug) {
                    Debug.removeWatcher(in_space);
                }
            }
        },

        dataChanged: function(in_space, in_params)
        {
            setTimeout(function()
            {
                if (!in_space) {
                    return;
                }
    
                if (!in_params) {
                    in_params = [];
                } else if (typeof(in_params) != 'object') {
                    in_params = [in_params];
                } else if (in_params.constructor != Array) {
                    in_params = [in_params];
                }

                if (typeof(in_space) != 'object') {
                    in_space = [in_space];
                }

                var completed = {};
    
                for (var z = 0; z < in_space.length; z++) {

                    spaces = in_space[z].split('/');
                    path = '';
                    App.log('<' + in_space[z] + '> data changed');

                    for (var i = 0; i < spaces.length; i++) {

                        path += spaces[i];

                        if (App.dataWatchers[path] && typeof(App.dataWatchers[path]) == 'object' && App.dataWatchers[path]['length']) {

                            var l = App.dataWatchers[path].length;

                            for (var j = 0; j < l; j++) {

                                if (!completed[path + '[' + j + ']'] && App.dataWatchers[path] && App.dataWatchers[path][j]) {
                                    App.log('<' + path + '> [' + j + '] run callback');
                                    App.dataWatchers[path][j][0].apply(App.dataWatchers[path][j][1], in_params);
                                    completed[path + '[' + j + ']'] = true;
                                }
                            }
                        }
                        path += '/';
                    }
                }
            }, 1);
        },

        installMessagePanel: function(in_dataStore)
        {
            var messagePanel = new Ext.Panel({
                cls: 'MessagePanel',
                border: false,
                tabIndex: -1
            });
            messagePanel.hide();

            var callback = function(in_result) {
                if (messagePanel.items) {
                    messagePanel.items.each(function(in_field) {
                        messagePanel.remove(in_field);
                    });
                }

                if (!in_result.messages) {
                    return;
                }

                var l = in_result.messages.length;
                for (var i = 0; i < l; i++) {
                    var msg = in_result.messages.pop();
                    messagePanel.add({
                        cls: msg.type,
                        html: msg.body,
                        border: false,
                        tabIndex: -1
                    });
                    delete msg
                }
                delete l;

                if (messagePanel.items && messagePanel.items.length) {
                    messagePanel.show();
                } else {
                    messagePanel.hide();
                }
                messagePanel.ownerCt.doLayout();
            }

            if (in_dataStore instanceof Ext.form.FormPanel) {
                in_dataStore.on('actioncomplete', function(in_this, in_action) {
                    if (in_action.type == 'load') {
                        callback(in_this.reader.jsonData);
                    }
                });
                in_dataStore.on('actionfailed', function(in_this, in_action) {
                    callback(in_action.result);
                });
            } else {
                in_dataStore.on('load', function(in_this) { callback(in_this.reader.jsonData); });
            }

            return messagePanel;
        },

        redirect: function(in_path, in_params)
        {
            if (in_path.indexOf('://') == -1) {
                in_path = this.getFullUrl(in_path, in_params);
            }
            document.location.href = in_path;
        },

        openWindow: function(in_path, in_params)
        {
            if (in_path.indexOf('://') == -1) {
                in_path = this.getFullUrl(in_path, in_params);
            }

            return window.open(in_path);
        },
        
        log: function(in_message)
        {
            if (typeof(console) == 'undefined') {
                return;
            }
            return console.log(in_message);
        }
    };

    App.Module = {

        hasAcl: false,

        isAllow: function(in_permission)
        {
            return this.bootstrap.isAllow(in_permission);
        },

        getId: function()
        {
            return this.bootstrap.path.replace(/[^A-Z0-9]/i, '-');
        },

        watch: function(in_space, in_callback, in_scope)
        {
            return this.bootstrap.watch(in_space, in_callback, in_scope);
        },

        destroy: function()
        {
            return this.bootstrap.destroy();
        }
    };

    App.Bootstrap = function(in_path, in_params)
    {
        this.path = in_path;

        this.params = in_params || {};

        this.perms = false;

        return {

            path: this.path,

            params: this.params,

            perms: false,

            instance: false,

            onReadyFunctions: [],

            onErrorFunctions: [],

            runAfterInit: false,

            corrupted: false,

            watched: [],
            
            hasAcl: false,

            onReady: function(in_callback)
            {
                if (this.instance) {
                    if (!this.loadAcl(in_callback, this.instance)) {
                        return this;
                    }
                    in_callback.apply(this.instance, [this.instance, this.params]);
                } else if (in_callback) {
                    this.onReadyFunctions.push(in_callback);
                }

                return this;
            },

            onError: function(in_callback)
            {
                if (this.instance && this.corrupted) {
                    in_callback.apply(this.instance, [this.instance, this.params]);
                } else if (in_callback) {
                    this.onErrorFunctions.push(in_callback);
                }

                return this;
            },

            run: function(in_params)
            {
                if (this.instance && this.instance.run) {
                    if (!this.loadAcl(this.run)) {
                        return;
                    }
                    App.log('[' + this.path + '] run module' + (this.params && convertToSource(this.params) != convertToSource({}) ? ' with params ' + convertToSource(this.params) : ''));
                    return this.instance.run.apply(this.instance, [in_params]);
                }

                this.runAfterInit = in_params || {};
            },

            getInstance: function()
            {
                return this.instance || this;
            },

            reloadAcl: function(in_callback)
            {
                var path = this.path;
                if (this.path.indexOf('/') == -1) {
                    path = 'default/' + path;
                }
                    
                var aclSource = new Ext.data.JsonStore({
                    url: App.getUrl(path + '/acl', this.params),
                    root: 'rows',
                    totalProperty: 'results',
                    successProperty: 'success',
                    fields: []
                });

                aclSource.on('load', function(in_this) {
                    this.perms = in_this.reader.jsonData.rows;
                    App.log('[' + this.path + '] ACL reloaded: ' + convertToSource(this.perms));
                    if (in_callback) {
                        in_callback.apply(this.getInstance());
                    }
                }, this);

                aclSource.load();

                return aclSource;
            },

            loadAcl: function(in_afterLoadFunction, in_scope)
            {
                if (this.perms || !this.getInstance().hasAcl) {
                    return true;
                }

                var path = this.path;
                if (this.path.indexOf('/') == -1) {
                    path = 'default/' + path;
                }

                var aclSource = new Ext.data.JsonStore({
                    url: App.getUrl(path + '/acl', this.params),
                    root: 'rows',
                    totalProperty: 'results',
                    successProperty: 'success',
                    fields: []
                });
                
                if (!in_scope) {
                    in_scope = this;
                }
                
                aclSource.tries = 0;

                aclSource.on('load', function(in_this) {
                    if (in_this.reader && in_this.reader.jsonData && in_this.reader.jsonData.success == false) {
                        in_this.tries = 3;
                        return in_this.fireEvent('loadexception');
                    }
                    if (in_this.reader.jsonData.rows) {
                        this.perms = in_this.reader.jsonData.rows;
                    } else {
                        this.perms = {};
                    }
                    App.log('[' + this.path + '] ACL loaded: ' + convertToSource(this.perms));
                    in_afterLoadFunction.apply(in_scope);
                }, this);

                aclSource.on('loadexception', function() {
                    this.perms = false;
                    App.log('[' + this.path + '] cannot load ACL');
                    if (aclSource.tries < 3) {
                        aclSource.tries++;
                        aclSource.load(aclSource.lastOptions);
                        return;
                    }
                    aclSource.tries = 0;
                    App.showError('Cannot retrieve user permission.');
                    this.error();
                    this.corrupted = false;
                }, this);

                aclSource.load();

                return false;
            },

            isAllow: function(in_permission)
            {
                if (this.perms == true) {
                    return false;
                }

                if (typeof(this.perms[in_permission]) == 'undefined') {
                    return true;
                }

                return this.perms[in_permission];
            },

            watch: function(in_space, in_callback, in_scope)
            {
                var obj = {
                    space: in_space,
                    index: App.watch(in_space, in_callback, in_scope)
                };

                this.watched.push(obj);

                if (typeof(in_scope) == 'object' && typeof(in_scope.unWatch) == 'undefined') {

                    if (typeof(in_scope.dataWatchers) == 'undefined') {
                        in_scope.dataWatchers = [];
                    }
                    in_scope.dataWatchers.push([in_space, obj.index]);
                    in_scope.unWatch = function()
                    {
                        var el;
                        while (el = in_scope.dataWatchers.pop()) {
                            App.unWatch(el[0], el[1]);
                        }
                    }
                }

                if (Debug) {
                    Debug.addWatcher(in_space, this.path, this.params)
                }

                return obj.index;
            },

            unWatch: function()
            {
                var el;
                while (el = this.watched.pop()) {
                    App.unWatch(el.space, el.index);
                }
            },

            destroy: function()
            {
                App.log('[' + this.path + '] destroy');
                this.perms = false;
                this.unWatch();
            },

            initInstance: function()
            {
                var className = eval(App.getModuleVar(this.path));

                if (!className) {
                    App.log('[' + this.path + '] cannot create module instance - ' + App.getModuleVar(this.path));
                    return false;
                }
                
                App.log('[' + this.path + '] init module instance - ' + App.getModuleVar(this.path));
                
                this.instance = new (className)(this.params);
                this.instance.bootstrap = this;

                return true;
            },

            init: function()
            {
                if (!this.instance) {
                    App.log('[' + this.path + '] init module');
                    if (!this.initInstance()) {
                        return this.error();
                    }
                }

                if (!this.loadAcl(this.init)) {
                    return;
                }

                if (this.runAfterInit) {
                    this.run(this.runAfterInit);
                }

                var j = this.onReadyFunctions.length;
                for (var i = 0; i < j; i++) {
                    var func = this.onReadyFunctions.pop();
                    func.apply(this.instance, [this.instance, this.params]);
                }
            },

            error: function(in_message)
            {
                in_message = in_message ? in_message : {};
                in_message.module = this.path;
                this.corrupted = true;
                var j = this.onErrorFunctions.length;

                if (j) {
                    for (var i = 0; i < j; i++) {
                        var func = this.onErrorFunctions.pop();
                        func.apply(this.instance, [this.params, in_message]);
                    }
                }

                if (in_message.force) {
                    App.showError(in_message);
                }
            }
        }
    };

    App.JavaScriptError =
    {
        getMessage: function(in_object)
        {
            var msg = 'JavaScript error occured: ' + in_object.message;
            msg += '<ul>';
            if (in_object.module) {
                msg += '<li>in module <strong>' + in_object.module +'</strong></li>';
            }
            if (in_object.fileName) {
                msg += '<li>in file <strong>' + in_object.fileName +'</strong></li>';
            }
            if (in_object.lineNumber && Debug) {
                msg += '<li>in line <strong>' + (in_object.lineNumber - 17) +'</strong></li>';
            }
            return msg += '</ul>';
        },

        getWindow: function(in_data)
        {
            if (typeof(in_data) == 'object') {
                in_data = this.getMessage(in_data);
            }

            return new Ext.Window({
                title: 'JavaScript Error',
                width: 400,
                autoHeight: true,
                resizable: false,
                items: new Ext.Panel({
                    frame: true,
                    html: in_data
                })
            });
        },

        show: function(in_object)
        {
            var dlgWindow = this.getWindow(this.getMessage(in_object));

            dlgWindow.show();
            dlgWindow.toFront();
            dlgWindow.enable();
        }
    };

    App.Session = {

        set: function(in_name, in_value, in_callback)
        {
            Ext.Ajax.request({
                url: App.getUrl('core/session/set'),
                success: in_callback,
                params: {name: in_name, value: in_value}
            });
        },

        get: function(in_name, in_callback)
        {
            Ext.Ajax.request({
                url: App.getUrl('core/session/get'),
                success: function(in_response) {
                    in_callback(in_response.responseText);
                },
                params: {name: in_name}
            });
        }
    }

    App.Loader = {

        start: function()
        {
            if (!Ext.get('loading')) {
                var text = document.createElement('div');
                text.id = 'loading';
                text.appendChild(document.createTextNode('Loading...'));
                var mask = document.createElement('div');
                mask.id = 'loading-mask';
                mask.appendChild(text);
                Ext.getBody().appendChild(mask);
            } else {
                Ext.get('loading').show();
            }
        },

        stop: function()
        {
            if (Ext.get('loading')) {
                setTimeout(function() {
                    Ext.get('loading').hide();
                    Ext.get('loading-mask').fadeOut({remove: false});
                }, 250);
            }
        }
    };

    App.Waiter = {

        component: false,

        start: function(in_msg)
        {
            if (this.component) {
                return this.change(in_msg);
            }

            this.component = Ext.Msg.wait(in_msg, 'Please wait');
        },

        change: function(in_msg)
        {
            if (this.component) {
                this.component.updateText(in_msg);
            }
        },

        stop: function()
        {
            if (this.component) {
                this.component.hide();
                this.component = false;
            }
        }
    }

    function convertToSource(obj)
    {
        if (!obj) {
            return;
        }

        if (obj.toSource) {
            return obj.toSource();
        }

        // ECMAScript and IE 5.00 do not support "toSource()", so lets try
        // implementing its functionality here

        var isArray = false; // its an Array  'object'
        var isHash  = false; // its an Object 'object'

        var i = 0;
        if (obj == null) {
            return "Error in  convertToSource function: no valid object passed in parameter 0."
        }

        var sourceString = "";

        isArray = ((obj.length) && (typeof(obj) == "object")) ? true : false
        isHash = (!(obj.length) && (typeof(obj) == "object")) ? true : false

        if (typeof(obj) == "string" || typeof(obj) == "number" || typeof(obj) == "function") {

            // simple string or number or straight up
            // function reference, KEEP IT ONE
            return( "\"" + obj + "\"" )
        }

        else if (isArray)
        {
            var arraySource = "[";

            for (i=0;i<obj.length;i++) {

                arraySource = arraySource +  convertToSource(obj[i])
                if (i < (obj.length-1)) arraySource = arraySource + ","
            }
            arraySource = arraySource + "]";
            return( arraySource );
        }

        else if (isHash)
        {
            var hashSource = "{"

            for (var key in obj) {

                if (hashSource != "{") hashSource = hashSource + ","

                hashSource = hashSource + "\"" + key + "\":" + convertToSource(obj[key])
            }
            hashSource = hashSource + "}";
            return( hashSource );
        }

        else {
            return(typeof(obj) + ":  convertToSource encountered unexpected object type")
        }
    }

    function cloneObject(in_object)
    {
        for (i in in_object) {
            this[i] = in_object[i];
        }
    }
    
    Ext.BLANK_IMAGE_URL = App.getFullUrl('lib/core/gfx/s.gif');

} catch (err) {}
