var Core = function() {
    // ustawienia zachowania strony i modulow
    var config = {};

    // lista zarejestrowanych modulow
    var modules = {};

    // kolejka komunikatow
    var listeners = {};

    // czy nastapila zmiana layout'u
    var isLayoutChanged = false;

    // aktualny layout stronki
    var currentLayout = null;

    // przechowuje layout'y strony
    var layoutTpl = {};

    // moduly, ktore zostana aktywowane
    var modulesToActivate = null;

    // ustawienie co ma wyswietlic
    var app = null;

    // przetwarza zadania
    var router = function() {
        // parametry wyciagniete z url
        var params = {};

        // czy dokonano procesu rozkladu ;)
        var isProcess = false;

        // po przetworzeniu zawiera nazwe app
        var appName = null;

        // przetwarzany url
        var url = '';

        // przetwarza url
        // od ostatniego do pierwszego
        function _process() {
            var route = null;
            var matches = null;

            for (var i = config.routes.length - 1; i > -1;  --i) {
                route = config.routes[i];

                if ((matches = url.match(route.pattern)) !== null) {
                    // mapujemy parametry
                    params = {};

                    for (var paramName in route.map) {
                        params[paramName] = matches[route.map[paramName]];
                    }

                    appName = route.appName;
                    isProcess = true;

                    return;
                }
            }

            // nic nie znalazl wiec podajemy pierwszy index app
            appName = null;
            isProcess = true;
        }

        return {
            getUrl: function() {
                return url;
            },

            getParam: function(name) {
                this.process();

                return params[name];
            },

            getAppName: function() {
                return this.process();
            },

            process: function(newUrl) {
                if (typeof(newUrl) != 'undefined') {
                    url = newUrl;
                    isProcess = false;
                }

                if (!isProcess) {
                    _process();
                }

                return appName;
            }
        }
    }();

    // interfejs, przez ktory moduly komunikuja sie z core i router'em
    var sandbox = {
        listen: function(messagesType, handler) {
            _addListener(messagesType, handler);
        },

        removeListen: function(messagesType, moduleId) {
            _removeListener(messagesType, moduleId);
        },

        notify: function(messageInfo) {
            _notifyModules(messageInfo);
        },

        redirect: function(newUrl) {
            _redirect(newUrl);
        },

        loadFiles: function(files) {

        },

        reportError: function(moduleId, error) {
            _handleError(moduleId, error);
        },

        request: {
            getParam: function(paramName) {
                return router.getParam(paramName);
            },

            getUrl: function() {
                return router.getUrl();
            }
        }
    };

    // dogrywanie potrzebnych plikow
    function _loadFiles(files) {

    }

    // dodaje modul do koleki komunikatow
    function _addListener(messagesType, moduleId, handler) {
        if (!(messagesType instanceof Array)) {
            messagesType = [messagesType];
        }

        for (var i in messagesType) {
            if (listeners[messagesType[i]] == null) {
                listeners[messagesType[i]] = {};
            }

            listeners[messagesType[i]][moduleId] = handler;
        }
    }

    // usuwa modul z kolejki komunikatow
    // dodac obsluge nulla
    function _removeListener(messagesType, moduleId) {
        if (messagesType === null) {
            for (var i in listeners) {
                delete listeners[i][moduleId];
            }

            return;
        }
        if (!(messagesType instanceof Array)) {
            messagesType = [messagesType];
        }

        for (var i in messagesType) {
            delete listeners[messagesType[i]][moduleId];
        }
    }

    // powiadamia moduly o komunikacie danego typu
    // {type: string, message: mix}
    function _notifyModules(messageInfo) {
        // jest taka kolejka komunikatow ?
        if (listeners[messageInfo.type] == null) {
            return;
        }

        // listeners = {type: {moduleId: handler}}
        for (var moduleId in listeners[messageInfo.type]) {
            try {
                listeners[messageInfo.type][moduleId](messageInfo);
            } catch(e) {
                _handleError(moduleId, e);
            }
        }
    }

    // ustawienia defaultowe modulu
    function _setModuleDefaults(moduleId) {
        modules[moduleId] = {
            cfg: config.modules[moduleId],
            instance: null,
            active: false
        };
    }

    // dodaje modul do listy zarejestrowanych i ustawia potrzebne rzeczy
    // nie tworzy instancji modulu
    function _registerModules() {
        for (var m in config.modules) {
            _setModuleDefaults(m);
        }
    }

    // inicjalizuje modul
    function _initModule(moduleId) {
        var mod = modules[moduleId];

        if (mod.instance == null) {
            mod.instance = new window[mod.cfg['className']](
                moduleId,
                mod.cfg.domId,
                sandbox,
                mod.moduleCfg
            );
            mod.instance.init();
        }
    }

    // aktywuje modul tworzy instancje, jezeli jeszcze jej nie ma
    function _activateModule(moduleId) {
        _initModule(moduleId);

        var mod = modules[moduleId];

        // jezeli jest juz aktywny to tylko przemaluj
        if (mod.active) {
            mod.instance.refresh(isLayoutChanged);
        } else {
            mod.instance.activate();
            mod.active = true;
        }
    }

    // deaktywuje modul. nie usuwa instancji
    function _deactivateModule(moduleId) {
        var mod = modules[moduleId];

        if (mod.active) {
            mod.instance.deactivate();
            mod.active = false;
        }
    }

    // niszczy instancje modulu i ustawia rzeczy do stanu poczatowego
    function _destroyModule(moduleId) {
        modules[moduleId].instance.destroy();

        _removeListener(null, moduleId);
        _setModuleDefaults(moduleId);
    }

    // ustawia nowy adres w przegladarce
    function _setBrowserUrl(newUrl) {
        window.location.hash = '#!' + newUrl;
    }

    // pobiera adres z przegladarki
    function _getBrowserUrl() {
        return window.location.hash.replace(/^\#!/, '');
    }

    // zmiana zawartosci stronki na podstawie adresu url
    // BEZ #!
    function _redirect(newUrl) {
        _setBrowserUrl(newUrl);
        _refreshPage();

        return false;
    }

    // ustawia app
    function _setApp() {
        var appName = router.process(_getBrowserUrl());
        if (appName == null) {
            app = config.apps[config.defaultApp];
        } else {
            app = config.apps[appName];
        }
    }

    // zapisuje wbudowany layout
    function _saveEmbeddedLayout() {
        for (var layoutName in config.layouts) {
            if (config.layouts[layoutName].url == null) {
                layoutTpl[layoutName] =
                    $('#' + config.layoutContainerId).html();

                currentLayout = layoutName;

                return;
            }
        }
    }

    // pobiera nowy layout
    function _getLayout() {
        if (layoutTpl[currentLayout] == null) {
            $.ajax({
                url: config.layouts[currentLayout].url,
                dataType: 'html',
                async: false,
                success: function(tpl) {
                    layoutTpl[currentLayout] = tpl;
                }
            });
        }
    }

    // ustawia nowy layout
    function _changeLayout() {
        var newLayout = app.layout;

        if (currentLayout != newLayout) {
            isLayoutChanged = true;
            currentLayout = newLayout;

            _getLayout();

            // wyczyszczenie
            $('#' + config.layoutContainerId).text('');

            // wstawienie nowego
            $('#' + config.layoutContainerId).html(layoutTpl[currentLayout]);
        } else {
            isLayoutChanged = false;
        }
    }

    // ustala jakie moduly trzeba aktywowac
    function _determineModulesToActivate() {
        modulesToActivate = {};

        var i;

        // zawsze aktywne moduly
        for (i in config.layouts[currentLayout].alwaysActiveModules) {
            modulesToActivate[config.layouts[currentLayout].alwaysActiveModules[i]] = true;
        }

        // dodajemy do tego moduly aktywne w danym app
        for (i in app.activeModules) {
            modulesToActivate[app.activeModules[i]] = true;
        }

        // usuwamy z force
        for (i in app.forceDeactivateModules) {
            delete modulesToActivate[app.forceDeactivateModules[i]];
        }
    }

    // MAKE IT WORKS!!!!
    function _transformPage() {
        var domId;

        // najpierw deaktywujemy by nie bylo zgrzytow
        for (var moduleId in modules) {
            domId = '#' + config.modules[moduleId].domId;

            if (modulesToActivate[moduleId] == null) {
                try {
                    _deactivateModule(moduleId);
                    $(domId).text('');
                } catch(e) {
                    _handleError(moduleId, e);
                }
            }
        }

        // teraz mozemy aktywowac z czystym sercem
        for (var moduleId in modules) {
            if (modulesToActivate[moduleId] == true) {
                try {
                    _activateModule(moduleId);
                } catch(e) {
                    _handleError(moduleId, e);
                }
            }
        }
    }

    // odswieza wyglad strony. Deaktywuje i aktywyje moduly
    // zawiera cala magie klasy Core
    function _refreshPage() {
        try{
            _setApp();
            _changeLayout();
            _determineModulesToActivate();
            _transformPage();
        } catch (e) {
            _handleError('Core', e);
        }
    }

    function _takeoverLinks() {
        $('a').live('click', function() {
           _redirect($(this).attr('href'));

           return false;
        });
    }

    // obsluga bledow
    function _handleError(who, e) {
        if (console != null) {
            console.log('Blad ' + who + ':');
            for (var i in e) {
                console.log(i + ': ' + e[i]);
            }
        }
        _reportError(e);
    }

    // wysyla blad do serwera
    function _reportError(e) {
        if (config.reportError.report) {
            $.ajax({
                type: config.reportError.type,
                url: config.reportError.url,
                dataType: 'json',
                data: e
            });
        }
    }

    // metody publiczne
    return {
        // laduje config calej aplikacji
        loadConfig: function(cfg) {
            config = cfg;
        },

        redirect: function(newUrl) {
            _redirect(newUrl);
        },

        // uruchamia cala strone
        run: function() {
            _saveEmbeddedLayout();
            _takeoverLinks();
            _registerModules();
            _refreshPage();
        }
    }
}();
