/**
 * Zbiór zęsto używanych funkcji i modeli.
 **/

function showTextLink(click, page_view, img_source, img_alt)
{
    document.write('<a href="'+click+'" target="_blank" onclick="pageTracker._trackPageview(\''+click+'\');"><img src="'+img_source+'" alt="'+img_alt+'" class="imgPromo"><img src="'+page_view+'" style="visibility: hidden;" /></a>');
};

/** dump **/
function dump(obj, deep)
{
    var out = '\n';
    for (var i in obj) {

        if ( deep && typeof obj[i] == 'object') {
            out += i + ' - {' + dump(obj[i], true) + '} \n';
        } else {
            out += i + ' - ' + obj[i] + '\n';
        }
    }
    return out;
};

(function() {
    var w = window, handler, lastMessage, _time;
    
    _time = new Date().getTime();
    
    handler = function(message, script, line) {
        var url, img, rs, rf;
        
        if ( lastMessage == message ) {
            return true;
        }
        
        script = script || '';
        message = message || '';
        rs = new RegExp((FULLADDR || '').escapeRegExp(), 'i');
        rf = new RegExp((SS || '').escapeRegExp(), 'i');
        if ( !rs.exec(script) && !rf.exec(script) ) {
            return true;
        }
        if ( new RegExp('countBan', 'ig').exec(message) ) {
            return true;
        }
        if ( (new RegExp('Error loading script', 'ig').exec(message) 
            || new RegExp('Script error', 'ig').exec(message)) && Browser.firefox ) {
            return true;
        }

        url = FULLADDR + "/index2.php/log/index/js?";
        url += "url=" + w.location;
        url += "&message=" + message;
        url += "&script=" + script;
        url += "&line=" + line;
        url += "&suid=" + Fbl.Store.get('Session.uid', 'not_logged'),
        url += "&time=" + ((new Date().getTime() - _time) / 1000) + 's';
        
        img = new Image(1, 1);
        img.setAttribute("src", url);
        lastMessage = message;

        return true;
    }
    
    if ( w.location.toString().indexOf('file://') == -1 ) {
        w.onerror = handler;
    }
}());

function test18() {
    if ( Cookie.read("heartcore_pb") != null || Fbl.Store.isOwner() ) {
        $('show_main').setStyle('visibility', '');
        return;
    }
    var confirm = new Fbl.Box({modalOptions: {opacity: 1}, layout_click: false}).confirm({
        header  : 'Dost\u0119p od 18 lat',
        body    : 'Ten fotoblog zawiera materia\u0142y, które mo\u017cesz uzna\u0107 ' +
            'za obsceniczne lub drastyczne. Musisz mie\u0107 18 lat, ' +
            'aby ogl\u0105da\u0107 tego fotobloga.',
        ok      : 'Wpu\u015b\u0107 mnie',
        cancel  : 'Rezygnuj\u0119'
    });
    confirm.addEvent('confirm', function(status) {
        if ( status == 'ok' ) {
            Cookie.write("heartcore_pb", "1", {duration: 30, path: '/' + location.pathname.match(/[a-z0-9]+/i)[0]});
            $('show_main').setStyle('visibility', '');
            this.close();
        } else {
            var addr = document.referrer.match(new RegExp(FULLADDR.escapeRegExp(), 'i')) ?
                document.referrer : FULLADDR;
            location.href = addr == location.href ? FULLADDR : addr;
        }
    });
};

function _initJuniorTips() {
    if ( parent != window ) {
        return;
    }
    var activityObj = Fbl.Store.get('Session.animationActivity', {}), showTips, i,
        tip, tips = [], randomTip, cookieValue, eventFn, loc, panelMode;
        
    if ( Browser.ie6 ) {
        return;
    }
    showTips = {
        comments: {
            target: 'commentform',
            event_name: 'addCommentSuccess',
            offset: {x: -18, y: 58},
            class_name: 'junior-tip-show',
            text: function(count) {
                var text = '{added} ju\u017c {count} {text1}, mo\u017ce czas doda\u0107 {text2}?';
                if ( count == 0 ) {
                    text = 'Dodaj swój pierwszy komentarz!';
                }
                return text.substitute({
                    count: count,
                    text1: ['komentarzy', 'komentarz', 'komentarze'].getByCount(count),
                    text2: ['kolejne', 'kolejny', 'kolejne'].getByCount(count),
                    added: ['Doda\u0142a\u015b', 'Doda\u0142e\u015b'].getByCount(Fbl.Store.get('Session.Profile.sex'))
                });
            }
        },
        friends: {
            target: 'sg_a_addfriend',
            max_views: 3,
            event_name: 'addFriendSuccess',
            offset: {x: -25, y: 35},
            class_name: 'junior-tip-show',
            text: function(count) {
                var text = 'Lubisz zdj\u0119cia u\u017cytkownika <strong>{blogOwner}</strong>? Dodaj go do listy znajomych.';
                return text.substitute({blogOwner: Fbl.Store.get('User.uid')});
            }
        },
        likes: {
            target: 'like_button',
            event_name: 'addLikeSuccess',
            offset: {x: 0, y: 35},
            class_name: 'junior-tip-show',
            text: function(count) {
                var text = 'Kliknij "Fajne", je\u015bli lubisz to zdj\u0119cie.';
                return text;
            }
        },
        photos: {
            base_target: $$('.pcl-bot')[0],
            target: 'junior_photo_add',
            offset: {x: -30, y: 0, h: 17},
            panel_mode: true,
            text: function(count) {
                var text = 'Szybko i \u0142atwo dodaj<br/>kolejny wpis na swój fotoblog.';
                if ( !count ) {
                    text = 'Szybko i \u0142atwo dodaj<br/>pierwszy wpis na swój fotoblog.';
                }
                return text;
            }
        },
        visited_style_creator: {
            base_target: $$('.pcl-bot')[0],
            target: 'junior_style_creator',
            offset: {x: -30, y: 0, h: 17},
            panel_mode: true,
            text: function(count) {
                return 'Zmie\u0144 kolory, tapet\u0119 i czcionk\u0119<br/>w kreatorze wygl\u0105du fotobloga.';
            }
        },
        filled_profile_data: {
            base_target: $$('.pcl-bot')[0],
            target: 'junior_manage_profile',
            offset: {x: -30, y: 0, h: 17},
            panel_mode: true,
            text: function(count) {
                return 'Znajomi \u0142atwiej Ci\u0119 odnajd\u0105, <br/> gdy uzupe\u0142nisz swoje dane.';
            }
        },
        avatar: {
            base_target: $$('.pcl-bot')[0],
            target: 'junior_avatar',
            offset: {x: -30, y: 0, h: 9, w: 9},
            panel_mode: true,
            text: function(count) {
                return 'Dodaj avatar, uzupe\u0142nij <br/>swój wizerunek.';
            }
        }
    };
    loc = location;
    if ( loc.pathname.test(/^\/mojphotoblog/) ) {
        var unsetKeySite = loc.search.replace('?', ''), 
            unsetTips = {
                'Add': 'photos',
                'StyleCreator': 'visited_style_creator',
                'ManageProfile': 'filled_profile_data',
                'ManageProfilePhotos': 'avatar'
            };
        if ( showTips[unsetTips[unsetKeySite]] ) {
            delete showTips[unsetTips[unsetKeySite]];
        }
    }
    var juniorFirstPhotoBox = function() {
        new Fbl.Box().alert({
            className: 'lbx_junior',
            header: '<strong>Brawo!</strong> Pierwsze zdj\u0119cie dodane!',
            body: '<strong>Brawo!</strong> Pierwsze zdj\u0119cie ju\u017c dodane, ale to ' 
                + 'tylko jedno z zadań przygotowanych przez nas dla Ciebie.'
                + '<br /><br /><strong>Ka\u017Cde uko\u0144czone zadanie premiowane jest nagrod\u0105.</strong> '
                + 'Za uko\u0144czenie wszystkich zada\u0144 otrzymasz <strong><a href="' 
                + FULLADDR + '/upgrade.php">pakiet PRO</a></strong> na 2 tygodnie - ' 
                + 'dzi\u0119ki temu Tw\u00F3j fotoblog b\u0119dzie jeszcze lepszy.<br /><br /><img src="' 
                + SS + '/images/animation_lightbox.png" alt="Zadania" />',
            ok: 'Zamknij'
        });
    }
    if ( /first-photo/.test(loc.hash) && Fbl.Store.get('Session.animationActivity.photos') == 1 ) {
        if ( !(Cookie.read('junior-lbx-welcome') >> 0) ) {
            Cookie.write('junior-lbx-welcome', 1, {duration: 30, path: '/'});
            juniorFirstPhotoBox.delay(1500);
        }
        loc.hash = '';
    }
    
    (function() {
        var optTip, getTip;
        for ( var i in activityObj ) {
            optTip = showTips[i];
            if ( !optTip || !$(optTip.target) || Cookie.read('junior-tip-' + i) >> 0 >= (optTip.max_views || 1) ) {
                continue;
            }
            if ( i == 'likes' ) {
                (function() {
                    $$('.like_button').removeEvents('mouseenter');
                }.delay(1000));
            }
            optTip.count = activityObj[i];
            tips.push({id: i, options: optTip});
        }
        getTip = function(options) {
            var tip, opt = Object.merge(options, {
                className: 'junior-tip-wrap junior-tip-' + i + ' ' + (options.class_name || ''),
                force_draw_on_bound2: !options.panel_mode,
                text: options.text.pass([options.count]),
                onShow: function(tip) {
                    var orgTop = tip.getTop(), duration = 800, 
                        width = tip.getStyle('width').toInt() + (options.offset.w || 0);

                    if ( options.panel_mode ) {
                        tip.setStyles({opacity: 0, width: 0, height: 0}).show();
                        new Fx.Morph(tip, {duration: duration, link: 'chain'})
                            .start({opacity: 0.9})
                            .start({height: $(options.target).getTop() - tip.getTop() + (options.offset.h || 0)})
                            .start({width: width});
                    } else {
                        tip.setStyles({opacity: 0}).show();
                    }

                    var loop = function() {
                        if ( options.panel_mode ) {
                            new Fx.Morph(tip, {
                                duration: duration * 2,
                                onComplete: function(tip) {
                                    new Fx.Morph(tip, {
                                        duration: duration,
                                        transition: Fx.Transitions.Bounce.easeOut
                                    }).start({width: width});
                                }
                            }).start({width: width - 30});
                        }else {
                            tip.setStyle('top', orgTop - tip.getHeight() / 2);
                        }
                        new Fx.Morph(tip, {
                            duration: duration,
                            transition: Fx.Transitions.Bounce.easeOut
                        }).start({top: orgTop});
                        new Fx.Morph(tip, {
                            duration: duration / 2
                        }).start({opacity: 0.9});
                    };

                    if ( !options.panel_mode ) {
                        loop.call(this);
                    }
                    this.juniorTimer = loop.periodical(options.panel_mode ? 5000 : 20000, this);
                },
                onHide: function(tip) {
                    clearInterval(this.juniorTimer);
                    new Fx.Morph(tip, {duration: 800})
                        .start({opacity: 0, top: tip.getTop() - tip.getHeight() / 2}); 
                }
            });
            tip = new Fbl.StaticTip(options.base_target || options.target, opt);
            
            if ( opt.event_name ) {
                Fbl.Observer.addEvent(opt.event_name, function() {tip.hide();});
            }

            return tip;
        }
        if ( tips.length ) {
            randomTip = tips[Number.random(0, tips.length - 1)];
            getTip(randomTip.options).show();
            cookieValue = Cookie.read('junior-tip-' + randomTip.id) >> 0;
            Cookie.write('junior-tip-' + randomTip.id, ++ cookieValue, {duration: 0.5}); // 0.5 == 12H
        }
    }.delay(2000));
}

/**
 * Strona główna
 */
function initIndex()
{
    var tip = new Tips('.ttip', {
        className   : 'tool-tip'
    });
    tip.addEvent('show', function(tip) {tip.fade( 0.9 )});
    tip.addEvent('hide', function(tip) {tip.fade( 0.0 )});
};

/**
 * Video thumb animator
 */
window.addEvent('domready', function() {
    var animOpt = {step: {x: 100}}, body = $(document.body), 
        observeElements = '.photo_animator_wrapper';
    
    if ( !body || !body.addEvent ) {
        return;
    }
    body.addEvent('mouseenter:relay(' + observeElements + ')', function() {
        new Fbl.PhotoAnimator(this, animOpt).start();
    });
    body.addEvent('mouseleave:relay(' + observeElements + ')', function() {
        new Fbl.PhotoAnimator(this, animOpt).stop();
    });
});

(function() {
    this.Storage = this.Storage || {};
}.call(Fbl));

(function() {
this.Constant = this.Constant || {};
this.Constant.TEMPLATE_UPDATE_CSS = { // elementy do update CSS-u
    bgdata      : [
        {selector: '#show_main', style: 'backgroundImage', data: 'u'}
    ],
    comments    : [
        {selector: '.com_f, .com, #s_cmf', style: 'fontSize', data: 's'}, // org '.com, #s_cmf'
        {selector: '.com_f, .com, #s_cmf', style: 'fontFamily', data: 'f'}, // do zapisu
        {selector: '.com_f, .com, #s_cmf', style: 'color', data: 'c'},
        {selector: '#gbview .com a, .myname', style: 'fontSize', data: 's'},
        {selector: '.com_f, .reply_show_all', style: 'backgroundColor', data: 'b'},
        {selector: '.com_h, #s_cmf', style: 'borderColor', data: 'bf'},
        {selector: '.tr', style: 'borderRightColor', data: 'b'}
    ],
    general     : [
        //{ selector: '#frame', style: 'fontSize', data: 'ts' }, // tylko do zczytania do font slidera
        {selector: '#body, #show_midoptions', style: 'color', data: 't'},
        {selector: '#show_main', style: 'backgroundColor', data: 'b'},
        {selector: '#frame', style: 'backgroundColor', data: 'b'}, // dodatek
        {selector: '#frame h1', style: 'fontSize', data: 'ps'},
        {selector: '#frame h1, #frame h1', style: 'color', data: 'pc'},
        {selector: '#frame h1', style: 'fontFamily', data: 'pf'}, // do zapisu
        {selector: '#frame h4, #left, #right', style: 'fontSize', data: 'hs'},
        {selector: '#frame h4', style: 'color', data: 'hc'},
        {selector: '#frame h4', style: 'fontFamily', data: 'hf'}, // do zapisu
        {selector: '#user_links a, .options', style: 'color', data: 'oc'},
        {selector: '#user_links a, .options', style: 'fontFamily', data: 'of'},
        {selector: '#user_links a, .options', style: 'fontSize', data: 'os'}, //do zapisu
        {selector: 'h2#blog_title', style: 'fontSize', data: 'ts'}, //wartość stała
        {selector: 'h2#blog_title', style: 'fontFamily', data: 'tf'}, // do zapisu
        {selector: 'h2#blog_title', style: 'color', data: 'tc'},
        {selector: '#show_pic img', style: 'borderColor', data: 'fc'},
        {selector: '#show_pic img', style: 'borderWidth', data: 'fs'},
        {selector: '.avatar_img', style: 'color', data: 'fc'},
        {selector: '#left img, #right img', style: 'borderColor', data: 'fc'},
        {selector: '#right #n_album a img, .avatar_img', style: 'borderColor', data: 'fc'},
    ],
    links       : [
        {selector: '#gbview .com a, .myname, .com_h span, .com_h a', style: 'color', data: 'c'},
        {selector: '#gbview .com a, .myname', style: 'fontFamily', data: 'f'}, // do zapisu
        {selector: '#photo_nav a, #show_menu a, .msgb a, #user_details a', style: 'color', data: 'lc'},
        {selector: '#links_data', style: 'fontSize', data: 's'} // do zapisu
    ],
    notes       : [
        {selector: '#show_main', style: 'fontFamily', data: 'mf'},
        {selector: '#show_main', style: 'fontSize', data: 'ms'},
        {selector: '.msgb, #postcontainer', style: 'color', data: 'mc'},
        {selector: '.msgb, #postcontainer', style: 'fontSize', data: 'ms'},
        {selector: '#show_menu', style: 'borderColor', data: 'dc'}, // dodatek
        {selector: '#frame #menu_left, #frame #menu_right, #frame #prev_next', style: 'color', data: 'dc'}, // dodatek
        {selector: '#right p, #left p, #right div, #left div, #right span, #left span', style: 'fontSize', data: 'ds'},
        {selector: '#right p, #left p, #right div, #left div, #right span, #left span', style: 'color', data: 'dc'},
        {selector: '#right p, #left p, #right div, #left div, #right span, #left span', style: 'fontFamily', data: 'df'} // do zapisu
    ]
};
}.call(Fbl));

(function() {
/**
 * Model do obsługi keszu
 * @class Fbl.Cache
 **/
this.Cache = new Class({

    $data: {},

    /**
     * Pobranie keszu
     * @param {String} id
     **/
    getCache: function(id) {
        return this.$data[id];
    },

    /**
     * Zapis do keszu
     * @param {String} id
     * @param {Mixed} data
     **/
    setCache: function(id, data) {
        this.$data[id] = data;
        
        return this;
    },

    /**
     * Sprawdzenie czy coś znajduje się w keszu
     * @param {String} id
     **/
    hasCache: function(id) {
        return this.$data[id] ? true : false;
    },

    /**
     * Wyczyszczenie keszu
     * @param {String} id
     **/
    clearCache: function(id) {
        this.$data[id] = null;
        
        return this;
    }
});
}.call(Fbl));

(function() {
/**
 * Model do odliczania ilości znaków w elementach formularzowych
 * @class Fbl.Countdown
 * @events
 *  - full
 *  - change
 **/
this.Countdown = new Class({
    Implements: [Options, Events],
    Binds: ['count'],

    options: {
        maxLength   : 1000,
        unsigned    : true, // czy wyświetlać tylko dodatnie wartości
        unsigned_class : '',
        format      : '0' // % lub 0
    },

    /**
     * @constructor
     * @param {Element|String} element  - Element obserwowany
     * @param {Element|String} updateElement - Element updatowany
     * @param {Object} options  - Opcje
     **/
    initialize: function(element, updateElement, options) {
        if (!$(element)) return;

        this.element = $(element);
        if ( typeOf(updateElement) == 'string' ) {
            this.updateElement = $(updateElement);
        }
        this.setOptions(options);
        this.element.addEvent('keyup', this.count);
        this.count();
    },

    /**
     * Metoda licząca ilość znaków oraz wyświetlająca ilość w elemencie
     * aktualizowanym
     **/
    count: function() {
        var value;
        value = this.element.value.length;

        var rest = this.options.maxLength - value;
        if ( this.options.unsigned ) {
            rest = rest <= 0 ? 0 : rest;
        } else {
            if ( this.options.unsigned_class ) {
                if ( this.updateElement ) {
                    this.updateElement.
                        getParent()[rest <= 0 ? 'addClass' : 'removeClass'](this.options.unsigned_class);
                }
            }
        }

        if ( rest <= 0 ) {
            this.fireEvent('full', [rest]);
        }
        this.fireEvent('change', [rest]);
        if ( this.options.format == '%' ) {
            rest = (rest / this.options.maxLength) * 100;
            this._updateElement(rest.toFixed(1));
        }else{
            this._updateElement(value);
        }
    },

    _updateElement: function(count) {
        if ( !this.updateElement ) {
            return;
        }
        this.updateElement.set('text', count);
    }
});
}.call(Fbl));

(function() {
/**
 * Model do sprawdzania dostępności loginu
 * @class Fbl.UsernameChecker
 **/
this.UsernameChecker = new Class({
    Implements: Options,
    Binds: ['onRequest', 'onSuccess', 'check', '_request'],

    options: {
        request_url     : '/backendCheckUserLogin.php',
        request_method  : 'post',
        request_status : {
            LOGIN_INVALID   : {
                status: 'err', msg: ''
            },
            LOGIN_TOO_SHORT : {
                status: 'err', msg: ''
            },
            LOGIN_EXIST     : {
                status: 'err', msg: ''
            },
            LOGIN_NOT_EXIST : {
                status: 'ok', msg: ''
            }
        },
        hash    : '',
        data    : '',
        indicator   : 'checkLoginIndicator',
        submit      : '',
        element_status  : {
            err     : 'imERR',
            ok      : 'imOK',
            msg     : 'infoLogin',
            payment : 'show_access'
        }
    },

    /**
     * @constructor
     * @param {String|Element} username - Element formularzowy lub jego Id
     * @param {Object} options
     **/
    initialize: function(username, options) {

        if ( !$(username) ) return;

        this.setOptions(options);

        this.element_button = $(this.options.submit);
        this.element_username = $(username);

        this.indicator = $(this.options.indicator);

        $H(this.options.element_status).each(function(value, index) {
            this['status_' + index] = $(value);
        }.bind(this));

        this.keyupStoper = false;

        this.initEvents();
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        if ( this.element_button )
            this.element_button.addEvent('click', this.check);
        else {
            this.element_username.addEvent('keyup', this.check);
        }
    },

    /**
     * Metoda sprawdzająca dostępność username (loginu)
     * @param {event} event
     **/
    check: function(event) {
        if ( event.type == 'keyup' ) {
            this.onRequest();
            if ( !this.keyupStoper ) {
                this.keyupStoper = true;
                (function(){
                    this._request( {}, this.onRequest, this.onSuccess );
                }).delay( 2000, this );
            }
        }else{
            this._request( {}, this.onRequest, this.onSuccess );
        }
        event.preventDefault();
    },

    /**
     * Metoda pokazująca indicator podczas rozpoczynania żądania xmlHttpRequest
     **/
    onRequest: function() {
        this.indicator.show();
        this.status_ok.hide();
        this.status_err.hide();
    },

    /**
     * Metoda pokazująca status podczas poprawnej odpowiedzi serwerana żądanie
     * @param {Object} response - Odpowiedź serwera
     **/
    onSuccess: function(response) {
        this.indicator.hide();

        var params = this.options.request_status[response];
        this.setStatus(params);

        this.keyupStoper = false;
    },

    /**
     * Metoda wyświetlająca status dostępności wg. przekazanego parametru
     * @param {Object} params - Obiekt z parametrem status = 'ok' | 'err'
     **/
    setStatus: function(params) {
        if ( this.status_msg ) {
            this.status_msg.set(
                'html',
                params.msg.replace('%LOGIN%', this.element_username.get('value'))
            );
            this.status_msg.show();
        }
        if ( params.status == 'ok' ) {
            this.status_ok.show();
            this.status_err.hide();
            if ( this.status_payment ) {
                this.status_payment.show();
            }
        }
        if ( params.status == 'err' ) {
            this.status_ok.hide();
            this.status_err.show();
            if ( this.status_payment ) {
                this.status_payment.hide();
            }
        }
    },

    /**
     * Metoda do wysyłania żądań do serwera
     * @param {Object} data - Parametry wysyłane do serwera
     * @param {Function} onRequest - Funckcja callback wywoływana w momencie wywołania
     *                               żądania
     * @param {Function} onSuccess - Funckcja callback wywoływana w momencie poprawnej
     *                               odpowiedzi serwera
     **/
    _request: function(data, onRequest, onSuccess) {

        data = $H(data);
        var dataRequired = $H({
            login   : this.element_username.get('value'),
            e       : this.options.data,
            h       : this.options.hash
        });
        var dataComplete = dataRequired.combine( data );
        dataComplete = dataComplete.toQueryString();

        new Request({
            url: this.options.request_url,
            method: this.options.request_method,
            data: dataComplete,
            onRequest: onRequest,
            onSuccess: onSuccess
        }).send();
    }
});
}.call(Fbl));

(function() {
/**
 * Model do pokazywania podpowiedzi pojedyńczego elementu typu input
 * @class Fbl.InputHint
 **/
this.InputHint = new Class({
    Implements: [Options, Events],
    Binds: ['show', 'hide', 'onFxStart', 'onFxComplete'],

    options: {
        duration : 300,
        link : 'cancel'
    },

    /**
     * @constructor
     * @param {String|Element} inputs  - Element typu INPUT
     * @param {String|Element} hints   - Element pokazywany (np div-y)
     * @param {Object} options
     **/
    initialize: function(input, hint, options) {
        this.input = $$(input).length ? $$(input) : $(input);
        this.hint = $(hint);
        if ( !this.input.length ) {
            return;
        }
        if ( !this.hint ) {
            return;
        }
        this.setOptions(options);
        this.fx = new Fx.Morph(this.hint, this.options);
        this.status = 'close';
        this.initEvents();
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        this.input.addEvent('focus', this.show);
        this.input.addEvent('blur', this.hide);
        this.fx.addEvent('complete', this.onFxComplete);
        this.fx.addEvent('start', this.onFxStart);
    },

    /**
     * Pokaż balonik
     **/
    show: function() {
        if ( this._hideTimer ) {
            clearTimeout(this._hideTimer);
            this._hideTimer = null;
            this.hint.setStyles({visibility: 'visible', opacity: 1}).show();
            return;
        }
        this.status = 'open';
        this.hint.setStyles({visibility: 'visible', opacity: 0});
        this.fx.start({opacity: 1});
        this.fireEvent('show');
    },

    /**
     * Ukryj balonik
     **/
    hide: function() {
        this.status = 'close';
        this._hideTimer = (function() {this.fx.start({opacity: 0});}.delay(250, this));
        this.fireEvent('hide');
    },
    
    onFxStart: function() {
        this.hint.show();
    },
    
    onFxComplete: function() {
        if ( this.hint.getStyle('opacity') == 0 ) {
            this.hint.hide();
        }
        clearTimeout(this._hideTimer);
        this._hideTimer = null;
    }
});
}.call(Fbl));

(function() {
/**
 * Model do obsługi wielu checkboxów (zoznacz,odznacz wszystkie itp)
 * @class Fbl.InputChecker
 **/
this.InputChecker = new Class({
    Implements: [Options, Events],
    //Binds: ['count'],

    options: {
        inputs  : 'input[type^=checkbox]',
        form    : '',
        events  : {
            toggle  : {
                elements    : '.pm_select_all'
            },
            submit  : {
                elements    : '.pm_delete'
            }
        }
    },

    /**
     * @constructor
     * @param {Object} options
     **/
    initialize: function(options) {

        this.setOptions(options);

        this.refresh();
        this.events = $H(this.options.events);
        this.form = $(this.options.form);

        this.events.each(function(value, index) {
            this['element_' + index] = $$(value.elements);
        }.bind(this));

        this.initEvents();
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {

        this.events.each(function(value, index) {
            this['element_' + index].addEvent('click', this[index].bind(this));
        }.bind(this));
    },

    /**
     * Metoda do odświeżania liczby checkboxów wg. przekazanego wzoru w konstruktorze
     **/
    refresh: function() {
        this.inputs = $$(this.options.inputs);
    },

    /**
     * Metoda zaz/odz checkboxy
     * @return {Fbl.InputChecker}
     **/
    toggle: function(event) {
        if ( event ) event.stop();

        if ( this.inputs[0].checked ) {
            this.inputs.setProperty('checked', false);
        } else {
            this.inputs.setProperty('checked', true);
        }
        this.onToggle();
        return this;
    },

    onToggle: function() {
        this.fireEvent('toggle', [!this.inputs[0].checked, this.element_toggle, this.inputs]);
    },

    /**
     * Metoda submitująca formularz
     **/
    submit: function() {
        this.form.submit();
    }
});
}.call(Fbl));

(function() {
/**
 * Model do obsługi elementów typu INPUT TYPE="FILE" powodujący
 * automatyczny upload po wybraniu pliku
 * @class Fbl.InputAutoUpload
 **/
this.InputAutoUpload = new Class({
    Implements: [Options, Events],
    Binds: ['change'],

    options: {
        form    : '',
        indicator   : 'progress'
    },

    /**
     * @constructor
     * @param {Object} options
     **/
    initialize: function(options) {
        this.setOptions(options);
        this.form = $(this.options.form);
        if ( !this.form ) {
            return;
        }
        this.file = this.form.getElement('input[type^=file]');
        if ( !this.file ) {
            return;
        }
        this.indicator = $(this.options.indicator);

        this.initEvents();
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        this.file.addEvent('change', this.change);
    },

    /**
     * Metoda pokazująca indicator (jesli był zdefiniowany) oraz
     * submitująca formularz
     **/
    change: function() {
        if ( this.indicator ) {
            this.indicator.show();
        }
        this.fireEvent('change');
        this.form.submit();
    }
});
}.call(Fbl));

(function() {
/**
 * Model do podtrzymywania sesji zalogowanego użytkownika
 * @class Fbl.StayLogger
 **/
this.StayLogged = new Class({
    Implements: Options,

    options : {
        url: (FULLADDR || 'http://photoblog.pl') + '/mojphotoblog/dummy.php',
        method: 'post',
        delay: 60000, // co minute
        params: {ok: 'ok'}
    },

    /**
     * @constructor
     * @param {Object} options
     **/
    initialize: function(options) {
        this.setOptions(options);
        this.url = this.options.url;
        if ( !this.url ) return;
        (function() {
            new Request({
                method  : this.options.method,
                url     : this.url,
                data    : this.options.params
            }).send(this.options.params);
        }.periodical(this.options.delay, this));
    }
});
}.call(Fbl));

(function() {
/**
 * Model do tworzenia klikalnych zakładek
 * @class Fbl.Tabs
 **/
this.Tabs = new Class({
    Implements: [Options, Events],

    options: {
        request: false,
        request_url: '',
        request_method: '',
        tabs_button: 'a.forum_tabs',
        tabs_button_reverse: false,
        cross_mode: false,
        tabs_content: 'div.sideforum',
        cookie: false,
        cookie_name: 'tab',
        default_tab: 0,
        tab_get_parent: false,  // true - nadaje klase 'active' elementowi nadrzędnemu (rodzicowi)
        active_tab_class: '',   // klasa nadawana przyciskowi aktywnemu
        inactive_tab_class: ''  // klasa nadawana przyciskowi nie aktywnemu
    },

    /**
     * @constructor
     * @param {Object} options
     **/
    initialize: function(options) {
        this.setOptions(options);
        this.tabs_button = $$(this.options.tabs_button);
        this.tabs_content = $$(this.options.tabs_content);
        if ( this.options.tabs_button_reverse ) {
            this.tabs_button = this.tabs_button.reverse();
        }
        this.initEvents();
        this.setDefaultTab();
    },

    /**
     * Pokaż zakładke
     * @param {Number} page - Numer zakładki
     **/
    show: function(page) {
        this.tabs_button[page].show();
    },

    /**
     * Ukryj zakładke
     * @param {Number} page - Numer zakładki
     **/
    hide: function(page) {
        var l = this.tabs_button.length;
        if ( l <= 1 ) {
            return;
        }
        this.changeTab(page > 0 ? 0 : 1);
        this.tabs_button[page].hide();
        this.tabs_content[page].hide();
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        this.tabs_button.each(function(element, index) {
            if ( element.get('tag') == 'select' ) {
                element.addEvent('change', function(event) {
                    this.changeTab(element.selectedIndex);
                }.bind(this));
            } else {
                element.addEvent('click', function(event) {
                    this.changeTab(index);
                    event.target.blur();
                    if ( element.get('tag') != 'input' ) {
                        event.stop();
                    }
                }.bind(this));
            }
        }.bind(this));
    },

    /**
     * Zmień zakładkę
     * @param {Number} page - Number zakładki
     **/
    changeTab: function(page) {
        this.tabs_button.each(function(element, index) {
            if ( this.options.tab_get_parent ) {
                element = element.getParent();
            }
            if ( page == index ) {
                element.addClass(this.options.active_tab_class);
                element.removeClass(this.options.inactive_tab_class);
            }else{
                element.removeClass(this.options.active_tab_class);
                element.addClass(this.options.inactive_tab_class);
            }
        }.bind(this));

        this.tabs_content.each(function(element, index) {
            if ( this.options.cross_mode ) {
                element[page != index ? 'show' : 'hide']();
            } else {
                element[page == index ? 'show' : 'hide']();
            }
        }.bind(this));
        this.fireEvent('onChange', [page]);
        if ( this.options.cookie ) {
            Cookie.write(this.options.cookie_name, page, {duration: 100});
        }
        this._request(page);
    },

    /**
     * Ustaw domyslną zakładkę
     **/
    setDefaultTab: function() {
        var tab;
        if ( Cookie.read(this.options.cookie_name) && this.options.cookie ) {
            tab = Cookie.read(this.options.cookie_name);
        } else {
            tab = this.options.default_tab;
        }
        this.changeTab(tab);
    },

    /**
     * Metoda do wysyłania żądań xmlHttpRequest z numerem wybranej zakładki
     * @param {Number} index
     **/
    _request: function(index) {
        if ( !this.options.request ) {
            return;
        }
        new Request.HTML({
            url: this.options.request_url + index,
            method: this.options.request_method,
            update: this.tabs_content[index]
        }).send();
    }
});
}.call(Fbl));

(function() {
/**
 * Model do obsługi elementów typu TEXTAREA. Klasa rozszeża element o możliwość
 * auto rozszeżania się elementu ze względu na ilość tekstu znajdującej się w niej
 * @class Fbl.TextareaScroll
 **/
this.TextareaScroll = new Class({
    Implements: [Options, Events],
    Binds: ['observe'],

    options: {
        minHeight   : 48,
        maxHeight   : 200,
        fx          : {duration: 150}
    },
    scrollbar_visible: false,
    dummy   : null,

    /**
     * @constructor
     * @param {Element|String} element - Element lub jego Id
     * @param {Object} options
     **/
    initialize: function(element, options) {
        if (!$(element)) return;

        this.element = $(element);
        this.setOptions(options);

        this.initEvents();
        this._buildHtml();
        this.observe();
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        this.element.addEvent('keyup', this.observe.bind(this));
    },

    /**
     * Czyszczenie zawartości elementu + ustawienie wysokości do jego nominalnej
     * wartości
     **/
    clear: function() {
        this.element.value = '';
        this.element.fireEvent('keyup');
    },

    /**
     * Metoda obserwująca ilość tekstu znajdujące się w elemencie i na jej
     * podstawie wyznacz nową wysyokość elementu
     **/
    observe: function() {
        var width = this.element.clientWidth - 2;
        this.dummy.setStyle('width', width > 0 ? width : 0);
        this.dummy.set('html', this.element.value.stripTags().replace(/\n/g, '<br />&nbsp;'));
        var heightNew = this.dummy.getHeight() + 10;
        if ( this.options.minHeight <= heightNew ) {
            // Scrollbar widoczny
            if ( heightNew >= this.options.maxHeight ) {
                this.scrollbar_visible = true;
                heightNew = this.options.maxHeight;
                this.element.setStyles({
                    height      : heightNew,
                    overflowY   : 'auto',
                    overflowX   : 'hidden'
                });
            } else {
                this.scrollbar_visible = false;
                this.element.setStyle('overflow-y', 'hidden');
                this._setDimensions({height: [heightNew]});
            }
        }
        if ( this.element.value.length == 0 ) {
            this.scrollbar_visible = false;
            this.element.setStyle('overflow-y', 'hidden');
            if ( this.element.getStyle('height').toInt() > this.options.minHeight ) {
                this._setDimensions({height: [this.options.minHeight]});
            }
            heightNew = this.options.minHeight;
        }
        this.fireEvent('change', [heightNew, this]);
    },

    /**
     * Metoda zwracająca flage czy scrollBar jest widoczny
     * @return {Boolean}
     **/
    isScrollBarVisible: function() {
        return this.scrollbar_visible;
    },

    /**
     * Metoda ustawiająca żądano wysokośc elementu
     * @param {Object} options - Parametry dotyczące rodzaju efektu i jego zakresu
     **/
    _setDimensions: function(options) {
        if ( Browser.opera || Browser.chrome || Browser.safari ) {
            this.element.setStyle(
                'height',
                options.height.length > 2 ? options.height[1] : options.height[0]
            );
        } else {
            this.element.set('morph', this.options.fx).morph(options);
        }
    },

    /**
     * Budowa html-a potrzebnego do zarządzania modelem
     * @private
     **/
    _buildHtml: function() {
        var width = this.element.clientWidth - 2;
        var dummy = new Element('div', {styles: {
            fontFamily  : this.element.getStyle('font-family'),
            fontSize    : this.element.getStyle('font-size'),
            lineHeight  : this.element.getStyle('line-height'),
            padding     : this.element.getStyle('padding'),
            position    : 'absolute',
            width       : width > 0 ? width : 0,
            top         : 0, // 0 // 600
            left        : -1000, // -1000 // this.element.getLeft()
            //border      : '1px solid #F00',
            overflowY   : 'hidden',
            overflowX   : 'hidden'
        }/*, id: 'los_amigos'*/});
        document.body.appendChild(dummy);
        this.dummy = dummy;
    }
});
}.call(Fbl));

(function() {
/**
 * Model do budowy struktury dokumentu HTML wg. przekazanego obiektu
 * @class Fbl.Builder
 **/
this.Builder = new Class({

    /**
     * Metoda budująca document HTML
     * @param {Element|String} parent   - Element rodzic do którego jest wstawiany document
     * @param {Object} data             - Obiekt reprezentujący document HTML
     * @param {String} injectMethod     - (opcjonalnie) Rodzaj wstrzykiwania elementów do dokumentu
     **/
    buildDomModel: function(parent, data, injectMethod, notFragment) {
        var fragment, i;

        if ( $type(parent) == 'string' ) {
            parent = $(parent);
        }
        if ( data["tag"] ) {
            if ( !notFragment ) {
                fragment = document.createDocumentFragment();
            }
            var el;
            if ( data.tag == 'textNode' ) {
                el = document.createTextNode(data.html);
            } else {
                el = new Element(data.tag);
            }
            for ( var p in data ) {
                switch(p) {
                    case null:
                    case "tag":
                    case "childs":
                        break;
                    case "show":
                    case "hide":
                        el[p]();
                        break;
                    case "rel":
                    case "colspan":
                        el.set(p, data[p]);
                        break;
                    case "styles":
                        el.setStyles(data[p]);
                        break;
                    case "html":
                        if ( data.tag != 'textNode' ) {
                            el.innerHTML = data.html;
                        }
                        break;
                    case "events":
                        data[p].each(function(event) {
                            // kompatybilność ze starym systemem nadawania zdarzeń
                            if ( event.name ) {
                                el.addEvent(event.name, event.fn);
                            }else {
                                for (var i in event) {
                                    el.addEvent(i, event[i]);
                                }
                            }
                        }.bind(this));
                        break;
                    default:
                        if ( /^data-/.test(p) ) {
                            el.set(p, data[p]);
                        } else {
                            el[p] = data[p];
                        }
                        break;
                }
            }

            switch (injectMethod) {
                case 'after':
                case 'before':
                case 'top':
                case 'bottom':
                    el.inject(parent, injectMethod);
                    break;
                default:
                    if ( parent ) {
                        if ( !notFragment ) {
                            fragment.appendChild(el);
                        } else {
                            parent.appendChild(el);
                        }
                    }
                    break;
            }

            if ( data.childs ) {
                this.buildDomModel(el, data.childs, null, true);
            }
            if ( !notFragment && parent ) {
                parent.appendChild(fragment);
            }
            return el;

        }else if ( typeof data == "object" ) {
            for( i in data ) {
                this.buildDomModel(parent, data[i], null, true);
            }
        }
        return null;
    }
});
}.call(Fbl));

(function() {
/**
 * Model do obsługi komunikatów typu alert, confirm, iframe
 * @class Fbl.Box
 **/
this.Box = new Class({
    Implements: [Options, Modalizer, Events],
    Binds: ['_close', '_key'],

    options: {
        modalOptions: {
            //width       : (window.getScrollSize().x), webkit
            //height      : (window.getScrollSize().y), webkit
            elementsToHide  : 'select, embed' + (Browser.ie ? '': ', object'),
            hideOnClick : true,
            updateOnResize  : true,
            layerId     : 'modalOverlay',
            onModalHide : $empty,
            onModalShow : $empty,
            opacity     : 0.1,
            modalStyle:  {
                display     : 'block',
                position    : 'fixed',
                top         : 0,
                left        : 0,
                'z-index'   : 2900,
                'background-color':'#000'
            }
        },
        styles: {
            iframe: {
                width   : 750,
                height  : 535
            },
            alert: {
                width   : 470,
                height  : null
            },
            confirm: {
                width   : 470,
                height  : null
            }
        },
        layout_click: true,     // true|false, Klick w layout zamyka okno lightbox-a
        layout_key: true,       // true|false, Escape zamyka okno lightbox-a
        duration: 100
    },
    _keyEvent: 'keydown',
    _keyFunction: null,

    /**
     * @constructor
     * @param {Object} options
     **/
    initialize: function(options) {

        this.setOptions(options);
        this.builder = new Fbl.Builder();
        clearTimeout(this.timeoutId);
        // webkit fix
        this.options.modalOptions.width = (window.getScrollSize().x);
        this.options.modalOptions.height = (window.getScrollSize().y);

        return this;
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        if ( this.options.layout_click ) {
            this.layer().addEvent('click', this._close);
        }else {
            this.layer().removeEvents('click');
        }
        if ( this.options.layout_key ) {
            this._keyFunction = function(event) {
                event._internalEvent = true;
                this._key(event);
            }.bind(this);
            document.addEvent(this._keyEvent, this._keyFunction);
        }
    },

    /**
     * Pokaż iframe
     * @param {String} src      Adres URL który zostanie przekazany do ramki
     * @param {Object} params   Obiekt z własnościami wielkościowymi okna width|height
     **/
    iframe: function(src, params) {
        this.modalShow( this.options.modalOptions );
        this.initEvents();

        var pos = {
            width : this.options.styles.iframe.width,
            height : this.options.styles.iframe.height
        };
        pos = $merge(pos, params);
        var styles = {
            position    : 'absolute',
            visibility  : 'hidden',
            margin      : 0,
            zIndex      : 3000
        };
        var iframe = {tag: 'div', id: 'lbCenter', styles: $merge(styles, pos),
            childs: [
                {tag: 'div', id: 'lbImage', styles: {display: 'block'},
                    childs: [
                        {tag: 'iframe', id: 'webPage', src: src, scrolling: 'no', frameBorder: 'none', 
                            styles: $merge(pos, {display: 'block', 
                                border: 'none', width: pos.width + 20, height: pos.height + 20})}
                    ]
                }
            ]
        };
        this.content = this.builder.buildDomModel(document.body, iframe);
        this.content.getElement('iframe').
            setProperty('allowTransparency', true).
            setProperty('framespacing', 0).
            setProperty('vspace', 0).
            setProperty('hspace', 0).
            setProperty('vspace', 0);
        this.content.setStyles(this._center());
        
        return this;
    },

    /**
     * Pokazanie lightbox-a z automatycznym doposaowanie się do rozmiaru
     * zdjęcia
     * @param {String} src  Adres URL do zdjęcia
     **/
    image: function(src) {
        if ( $('lbx_alert') ) return this;

        this.modalShow( this.options.modalOptions );
        this.initEvents();

        new Asset.image(src, {
            onload: function(img) {
                if ( img.width && img.height ) {
                    this.content.getElement('#lbx_image').setStyles({
                        visibility  : 'hidden'
                    });
                    var coord = this._center({
                        width   : img.width,
                        height  : img.height
                    });
                    new Fx.Morph(this.content, {
                        duration: this.options.duration
                    }).start({
                        top     : coord.top,
                        left    : coord.left
                    });
                    new Fx.Morph(this.content.getElement('.lbx_container'), {
                        duration: this.options.duration,
                        onComplete: function() {
                            var imgCon = this.content.getElement('#lbx_image');
                            if ( !imgCon ) {
                                return;
                            }
                            imgCon.setStyles({
                                visibility  : '',
                                marginTop   : -12
                            });
                            imgCon.set('src', img.src);
                            if ( Browser.ie ) {
                                imgCon.set('width', null).set('height', null);
                            }
                        }.bind(this)
                    }).start({
                        width   : img.width,
                        height  : img.height
                    });
                }
            }.bind(this)
        });

        var model =
        {tag: 'div', id: 'lbx_alert', className: 'lbx_unlogged', styles: {
            position: 'absolute', visibility: 'hidden', zIndex:3000},
            childs: [
                {tag: 'div', className: 'lbx_container', styles: {width: 320, height: 240, backgroundColor: '#EEE'},
                    childs: [
                        {tag: 'a', id: 'exit',
                            events: [
                                {name: 'click', fn: this.close.bind(this)}
                            ]
                        },
                        {tag: 'div', className: 'lbx_content',
                            childs: [
                                {tag: 'img', id: 'lbx_image', src: SS + '/img/indicator-profil.gif', styles:{
                                        display: 'block', margin: '75px auto 0px auto'
                                    },
                                    events: [
                                        {name: 'click', fn: this.close.bind(this)}
                                    ]
                                }
                            ]
                        }
                    ]
                }
            ]
        };
        this.content = this.builder.buildDomModel( document.body, model );
        this.content.setStyles(this._center());

        return this;
    },

    /**
     * Pokaż alert
     * @param {Object} data     Obiekt z własnościami header|body|ok
     * @param {Object} params   Obiekt z własnościami wielkościowymi okna width|height
     **/
    alert: function(data, params) {
        if ( this._isBuilded('alert') ) {
            return this;
        }
        this.modalShow(this.options.modalOptions);
        this.initEvents();
        var pos = {
            width : this.options.styles.alert.width,
            height : this.options.styles.alert.height
        };
        pos = $merge(pos, params);
        data = $merge({
            className: '',
            header  : 'Komunikat',
            body    : '',
            ok      : 'Ok',
            footer  : true,
            className: ''
        }, data);
        data.cancel = data.ok;
        data.onExit = this.close.bind(this);
        data.onPost = null;
        data.onDiscard = this.close.bind(this);
        this._buildModel('alert', data);

        return this;
    },

    /**
     * @param {Object} data     Obiekt z własnościami header|body|ok
     * @param {Object} params   Obiekt z własnościami wielkościowymi okna width|height
     **/
    info: function(data, params) {
        if ( this._isBuilded('info') ) {
            return this;
        }
        this.modalShow(this.options.modalOptions);
        this.initEvents();
        var pos = {
            width : this.options.styles.alert.width,
            height : this.options.styles.alert.height
        };
        pos = $merge(pos, params);
        data = $merge({
            className: '',
            header: 'Informacja',
            body: '',
            ok: 'Ok'
        }, data);
        data.cancel = data.ok;
        data.onExit = this.close.bind(this);
        data.onPost = null;
        data.onDiscard = this.close.bind(this);
        this._buildModel('info', data);

        return this;
    },

    /**
     * Pokaż confirm
     * @param {Object} data - Obiekt z własnościami header|body|ok
     **/
    confirm: function(data) {
        if ( this._isBuilded('confirm') ) {
            return this;
        }
        this.modalShow(this.options.modalOptions);
        this.initEvents();

        data = $merge({
            className: '',
            header: 'Komunikat',
            body: '',
            cancel: 'Anuluj',
            ok: 'Ok'
        }, data);
        data.onExit = function(){this.fireEvent('confirm', ['cancel'])}.bind(this);
        data.onDiscard = function(){this.fireEvent('confirm', ['cancel'])}.bind(this);
        data.onPost = function(){this.fireEvent('confirm', ['ok'])}.bind(this);
        this._buildModel('confirm', data);

        return this;
    },

    /**
     * Pokaż okienko z własnym modelem
     * @param {Object} model - Model dokumentu HTML
     **/
    custom: function(model) {
        this.modalShow(this.options.modalOptions);
        this.initEvents();

        this.content = this.builder.buildDomModel(document.body, model);
        this.content.setStyles(this._center());

        return this;
    },

    /**
     * Zamknij box-a
     * @param {Number} sec  Opcjonalnie ilość sekund po jakich okno ma się automatycznie zamknąć
     **/
    close: function(sec) {
        clearTimeout(this.timeoutId);
        if ( sec ) {
            this.timeoutId = this._close.delay(sec * 1000, this);
            return this;
        }
        this._close.call(this);
        
        return this;
    },

    toggleOkButton: function(type) {
        var el = this.content.getElement('#post');
        if ( !type ) {
            el[el.isVisible() ? 'hide' : 'show']();
            return;
        }
        el[type]();
    },

    toggleCancelButton: function(type) {
        var el = this.content.getElement('#discard');
        if ( !type ) {
            el[el.isVisible() ? 'hide' : 'show']();
            return;
        }
        el[type]();
    },
    
    setOkText: function(text) {
        this.content.getElement('#post').set('value', text);
        
        return this;
    },
    
    setCancelText: function(text) {
        this.content.getElement('#discard').set('value', text);
        
        return this;
    },

    /**
     * Ustawia nową treść lightbox-a
     * @param {String|Object} body  Treść lub Obiekt reprezentujący strukture html
     */
    setBodyText: function(body, highlight) {
        var model = body;
        if ( $type(body) != 'object' ) {
            model = {tag: 'p', html: body};
        }
        if ( highlight || highlight === undefined ) {
            this.content.setOpacity(0).fade(1);
        }
        var content = this.content.getElement('.lbx_content');
        content.innerHTML = '';
        this.builder.buildDomModel(content, model);
    },

    getBodyText: function() {
        return this.content.getElement('.lbx_content').innerHTML;
    },

    _isBuilded: function(id) {
        return $('lbx_' + id) ? true : false;
    },

    /**
     * Metoda zwraca współrządne centrum ekranu
     * @private
     * @param {Object} coord    Opcjonalnie wartości coord do obliczeń top i left
     * @return {Objecy} pos
     **/
    _center: function(coord) {
        if ( !coord ) {
            coord = {
                width   : this.content.getWidth(),
                height  : this.content.getHeight()
            }
        }
        var pos = {top: 0, left: 0};
        pos.left = (window.getWidth() / 2) - (coord.width / 2);
        pos.top = window.getScroll().y + (window.getHeight() / 2) - (coord.height / 2);
        
        return pos;
    },
    
    _close: function() {
        document.removeEvent(this._keyEvent, this._keyFunction);
        this.modalHide(this.options.modalOptions.hideOnClick);
        $$(this.options.modalOptions.elementsToHide).setStyle('visibility', '');
        this.setModalOptions({});
        ['lbCenter', 'lbx_alert', 'lbx_confirm', 'lbx_progress', this.content]
            .each(function(el) {
            if ( $(el) ) $(el).destroy();
        });
        clearTimeout(this.timeoutId);
        this.fireEvent('close');
    },

    /**
     * Metoda obsługuje zdarzenie onkey i zamyka boxy po wciśnięciu
     * klawisza ESC
     * @private
     * @param {Event} event
     **/
    _key: function(event) {
        if ( !event._internalEvent ) {
            return;
        }
        if ( event.code == 27 ) { // esc
            this.close();
        }
        if ( event.code == 13 ) { // enter
            if ( this._isBuilded('confirm') ) {
                this.fireEvent('confirm', ['ok']);
            }
            if ( this._isBuilded('alert') ) {
                this.close();
            }
        }
    },
    
    /**
     * Metoda wywoływana przez Modalizer
     * @private
     **/
    _fxShowComplete: function() {
        if ( this.content ) {
            this.content.setStyle('visibility', '');
        }
    },
    
    _buildModel: function(id, data) {
        var model =
        {tag: 'div', id: 'lbx_' + id, className: 'lbx_unlogged ' + data.className,
            styles: {position:'absolute',visibility  : 'hidden', zIndex: 3000},
            childs: [
                {tag: 'div', className: 'lbx_container',
                    childs: [
                        {tag: 'a', id: 'exit',
                            events: [
                                {name: 'click', fn: data.onExit}
                            ]
                        },
                        {tag: 'span', className: 'lbx_header', html: data.header},
                        {tag: 'div', className: 'lbx_content',
                            childs: [/** Wypełniane html-em lub modelem **/]
                        },
                        {tag: 'div', className: 'lbx_footer',
                            childs: []
                        }
                    ]
                }
            ]
        };
        if ( data.onDiscard ) {
            model.childs[0].childs[3].childs.push(
                {tag: 'input', type: 'button', value: data.cancel, id: 'discard',
                    events: [
                        {name: 'click', fn: data.onDiscard}
                    ]
                });
        }
        if ( data.onPost ) {
            model.childs[0].childs[3].childs.push(
                {tag: 'input', type: 'submit', value: data.ok, id: 'post',
                    events: [
                        {name: 'click', fn: data.onPost}
                    ]
                });
        }
        if ( $type(data.body) == 'object' ) {
            model.childs[0].childs[2].childs.push(data.body);
        } else {
            model.childs[0].childs[2].childs.push({tag: 'p', html: data.body});
        }

        this.content = this.builder.buildDomModel(document.body, model);
        this.content.setStyles(this._center());
    }
});
}.call(Fbl));

(function() {
/**
 * Model do budowania MessageBox-ów generowanych inplace (html bezpośrednio w dokumencie)
 * @class Fbl.MsgBox
 **/
this.MsgBox = new Class({
    Implements: Options,

    options: {
        msg : {icon: 'blue', text: ''},
        node_element    : '',
        inject_method   : 'inside',
        use_show_layout : false
    },

    /**
     * @constructor
     * @param {Object} options
     **/
    initialize: function(options) {
        this.setOptions(options);
        this.builder = new Fbl.Builder();
    },

    /**
     * Renderowanie boxa
     * @param {Object} msg      - Obiekt ustalający wygląd boxa { icon: '', text: '' }
     * @param {Object} options  - Obiekt opcji
     **/
    render: function(msg, options) {
        this.data = $merge(this.options.msg, msg);

        this.options = $merge(this.options, options);
        this.node_element = $(this.options.node_element);

        var icon = this.data.icon == 'blue' ? '' : '_' + this.data.icon;
        if ( this.options.use_show_layout ) {
            var bgS = parseFloat(Fbl.Store.get('Constant.bgLight'));
            bgS = bgS.getPerceivedLightness($(document.body)
                .getStyle('background-color'))
                .getIf(Fbl.Store.get('Constant.bgConditions'), 'b,wh,w');
            var msgBox = {tag: 'div', className : 'msgb_box msgb_' + bgS,
                childs : [
                    {tag: 'div', className: 'msgb_container clearfix',
                        childs : [
                            {tag : 'div', className: 'msgb_icon',
                                childs: [
                                    {tag: 'img', alt: 'info', src: SS + '/images/show/ico_info' + icon + '.png'}
                                ]
                            },
                            {tag: 'div', className: 'msgb_text',
                                childs: []
                            }
                        ]
                    }
                ]
            };
            if ( Browser.ie && navigator.userAgent.match(/MSIE ([7]\.0)/) ) {
                msgBox.childs[0].childs[1].childs[0] = {tag: 'div', className: 'msgb_iehack',
                    childs: [
                        {tag: 'p', html: this.data.text}
                    ]
                };
            }else {
                msgBox.childs[0].childs[1].childs[0] = {tag: 'p', html: this.data.text};
            }
        }
        else {
            var msgBox = {tag: 'div', className : 'msgb_box box' + icon,
                childs : [
                    {tag: 'div', className: 'msgb_left',
                        childs : [
                            {tag: 'img', alt: 'info', src: SS + '/images/mark' + icon + '.png'},
                            {tag: 'div', className: 'msgb_text'}
                        ]
                    },
                    {tag: 'div', className: 'msgb_right msgb_element',
                        childs: [
                            {tag: 'span', className: 'msgb_text', html: this.data.text},
                            {tag: 'br', styles: {clear: 'both'}}
                        ]
                    }
                ]
            };
        }
        this.builder.buildDomModel(this.node_element, msgBox, this.options.inject_method);
    }
});
}.call(Fbl));

(function() {
/**
 * Model do tworzenia elementów 'zaznaczanych' kliknięciem tak jak w rejestracji wybór płci
 * @class Fbl.Chooser
 **/
this.Chooser = new Class({
    Implements: Options,
    Binds: ['check'],

    options: {
        active_class    : '', // klasa nadawana elementowi aktywnemu
        update          : '', // element przechowujący wartość indexu wybranego elementu
        blur_on_click   : true //
    },

    /**
     * @constructor
     * @param {String|Elements} buttons - Elementy które maja być klikalne
     * @param {Object} options
     **/
    initialize: function(buttons, options) {

        if ( !$$(buttons) ) return;

        this.setOptions(options);

        this.buttons = $$(buttons);
        this.update = $(this.options.update);

        this.initEvents();

        if ( this.update ) {
            this.check( {
                target: this.buttons[this.update.get('value')],
                stop: $empty
            } );
        }
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        this.buttons.addEvent('click', this.check);
    },

    /**
     * Metoda obserwująca kliknięty element i na jej podstawie
     * nadaje klase CSS
     * @param {Event} event
     **/
    check: function(event) {
        event.stop();
        this.buttons.each(function(element, index) {
            if ( !$(event.target) ) return;
            if ( event.target == element || $(event.target).getParent() == element )  {
                if ( this.update ) {
                    this.update.set('value', index);
                }
                element.addClass(this.options.active_class);
            } else {
                element.removeClass(this.options.active_class);
            }
            if ( this.options.blur_on_click ) {
                element.blur();
            }
        }.bind(this));
    }
});
}.call(Fbl));

(function() {
/**
 * Manager domyślnych wartości w inputach
 * input rel="wartość domyślna" value="wartość domyślna"
 * @class Fbl.Manager.DefaultValue
 **/
this.Manager = this.Manager || {};
this.Manager.DefaultValue = new Class({
    Implements: Options,

    options: {
        defaultActiveColor    : '#CCC',
        defaultActiveProperty : 'color',
        elementSelector       : 'input[rel],textarea[rel]',
        observeForms          : true
    },
    defaultText : [],

    /**
     * @constructor
     * @param {Object} options
     **/
    initialize: function(options) {
        this.setOptions(options);

        this.elements = $$(this.options.elementSelector);
        this.forms    = $$('form');

        this.initEvents();

        if ( this.options.observeForms ) {
            this.observeForms();
        }
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        this.elements.each(function(el){
            el.addEvent('focus', function(event){
                var element = $(event.target);
                if (element.value == element.get('rel')) {
                    element.setStyle(this.options.defaultActiveProperty, '');
                    element.value = '';
                }
            }.bind(this));
            el.addEvent('blur', function(event) {
                var element = $(event.target);
                if (element.value == '') {
                    element.setStyle(this.options.defaultActiveProperty, this.options.defaultActiveColor);
                    element.value = element.get('rel');
                }
            }.bind(this));
            if ( this.defaultText.indexOf(el.get('rel')) == -1 ) {
                this.defaultText.push(el.get('rel'));
            }
            if (el.value == '') {
                el.setStyle(this.options.defaultActiveProperty, this.options.defaultActiveColor);
                el.value = el.get('rel');
            }
        }.bind(this));
    },

    observeForms: function() {
        this.forms.each(function(form) {
            form.addEvent('submit', function(event) {
                event.target.getElements(this.options.elementSelector).each(function(element) {
                    if (element.value == element.getProperty('rel')) {
                        element.value = '';
                    }
                }.bind(this));
            }.bind(this));
        }.bind(this));
    },

    /**
     * Czyszczenie wartości elementów które mają domyślna wartość
     **/
    check: function(els) {
        els = $type(els) == 'array' ? els : [els];
        for ( var i = 0, len = els.length; i < len; i++ ) {
            for ( var j = 0, len2 = this.elements.length; j < len2; j++ ) {
                if ( els[i] && els[i] != this.elements[j] ) {
                    continue;
                }
                if (this.elements[j].value == this.elements[j].get('rel')) {
                    this.elements[j].value = '';
                }
            }
        }
    },

    /**
     * Ustawianie wartości domyslnych wszystkim elementom
     **/
    uncheck: function(els) {
        els = $type(els) == 'array' ? els : [els];
        for ( var i = 0, len = els.length; i < len; i++ ) {
            for ( var j = 0, len2 = this.elements.length; j < len2; j++ ) {
                if ( els[i] && els[i] != this.elements[j] ) {
                    continue;
                }
                this.elements[j].value = this.elements[j].get('rel');
            }
        }
    },

    getDefaultText: function() {
        return this.defaultText;
    }
});
}.call(Fbl));

(function() {
/**
 * Manager domyślnych wartości w elementach formularzowych (input, textarea).
 * Model wstawia domyślną wartość tylko wtedy kiedy element jest zfocusowany
 * czyli działanie zupełnie inne niż w modelu Fbl.Manager.DefaultValue.
 * Klasa obsługuje pojedyńczy element.
 * @class Fbl.Manager.DefaultValueOnFocus
 **/
this.Manager = this.Manager || {};
this.Manager.DefaultValueOnFocus = new Class({
    Implements: [Options, Events],
    Binds: ['onFocusElement', 'onBlurElement', 'onKeyDownElement', 'onClickElement'],

    options: {
        fontColor: '#CCC',
        hideOnBlur: true    // Czy ukrywać domyślny tekst podczas blurowania elementu
    },
    _textShowed: false,

    /**
     * @constructor
     **/
    initialize: function(el, text, options) {
        this.setOptions(options);
        this.element = $(el);
        if ( this.isReinitialized() ) {
            return;
        }
        this.element.store('fbl:defaultValueOnFocus:init', true);
        this.text = text || '';
        this.orgColor = this.element.getStyle('color');
        this.initEvents();
    },

    initEvents: function() {
        this.element.addEvent('focus', this.onFocusElement);
        if ( this.options.hideOnBlur ) {
            this.element.addEvent('blur', this.onBlurElement);
        }
        this.element.addEvent('keydown', this.onKeyDownElement);
        this.element.addEvent('click', this.onClickElement);
    },

    isReinitialized: function() {
        return this.element.retrieve('fbl:defaultValueOnFocus:init');
    },

    onFocusElement: function(event) {
        var el = this.element;

        if ( (el.value != this.text && String.trim(el.value).length)
            || this._textShowed ) {
            return;
        }
        this._defaultValueStyles('set');
        el.value = this.text;
        el.selectionStart = el.selectionEnd = el.value.length;
        this.fireEvent('focused', [el]);
    },

    onBlurElement: function(event) {
        var el = this.element;

        if ( el.value != this.text || this._textShowed ) {
            return;
        }
        this._defaultValueStyles('remove');
        el.value = '';
        this.fireEvent('blured', [el]);
    },

    onKeyDownElement: function(event) {
        if ( this._textShowed ) {
            return;
        }
        this._defaultValueStyles('remove');
        this._textShowed = true;
        this.element.value = this.element.value.replace(this.text, '');
    },

    onClickElement: function(event) {
        var el = this.element;

        if ( el.value != this.text || this._textShowed ) {
            return;
        }
        el.selectionStart = el.selectionEnd = el.value.length;
    },

    _defaultValueStyles: function(action) {
        if ( action == 'set' ) {
            this.element.setStyle('color', this.options.fontColor);
        }else if ( action == 'remove' ) {
            this.element.setStyle('color', this.orgColor);
        }
    }
});
}.call(Fbl));

(function() {
/**
 * Model efektu Slide bazujący na Morphie
 * @class Fbl.Fx.Slide
 **/
this.Fx = this.Fx || {};
this.Fx.Slide = new Class({
    Implements: Options,
    Binds: ['toggle'],

    options: {
        statusElement   : '',
        onShowText      : '',
        onHideText      : '',
        hideOnStart     : true,
        use_cookie      : false,
        disable_event_on_slideIn: false,
        cookie_name     : '',
        cookie_params   : {
            duration    : 100
        }
    },

    /**
     * @constructor
     * @param {String|Element} element
     * @param {Object} options
     **/
    initialize: function(element, options) {

        if (!$(element)) return;

        this.element = $(element);
        this.setOptions(options);
        this.statusElement = $(this.options.statusElement);

        this.slide = new Fx.Slide(element, options);
        //this.slide.wrapper.setStyle('overflow', 'visible');

        this.initEvents();

        this.element.setStyle('display', 'block');

        if ( this.options.use_cookie ) {
            //(function() {

                //console.log( 1 * Cookie.read(this.options.cookie_name) );
                //console.log( (1 * Cookie.read(this.options.cookie_name)) ? 'hide' : 'show' );
                this.slide[(1 * Cookie.read(this.options.cookie_name)) ? 'hide' : 'show']();

            //}.delay(5000, this));
        }else {
            if ( this.options.hideOnStart ) {
                this.slide.hide();
            }
        }
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        this.statusElement.addEvent('click', this.toggle);

        this.slide.addEvent('complete', function(event) {
            var status = !this.slide.open ? this.options.onHideText : this.options.onShowText;
            if ( status )
                this.statusElement.set('html', status);
//                    if ( this.slide.open && Browser.Engines.trident() )
//                        this.slide.wrapper.setStyle('overflow', 'visible');
        }.bind(this));
    },

    /**
     * Przełączanie efektu
     **/
    toggle: function(event) {
        if ( event )
            event.stop();
        //if ( this.slide.open && Browser.Engines.trident() )
        //        this.slide.wrapper.setStyle('overflow', 'hidden');
        if ( this.options.disable_event_on_slideIn && this.slide.open ) {
            return;
        }
        if ( this.options.use_cookie ) {
            var status = this.slide.wrapper.getStyle(this.slide.layout).toInt() ? 1 : 0;
            Cookie.write(this.options.cookie_name, status, this.cookie_params);
        }
        this.slide.toggle();
    }
});
}.call(Fbl));

(function() {
Fx.Rotate = new Class({
    Extends: Fx.Morph,
    step: function(now) {
        var prop, value;
        if ( Browser.firefox ) {
            prop = '-moz-transform';
        } else if ( Browser.chrome || Browser.safari ) {
            prop = '-webkit-transform';
        } else if ( Browser.opera ) {
            prop = '-o-transform';
        } else {
            prop = 'transform';
        }
        if (this.options.frameSkip) {
            var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval;
            this.time = now;
            this.frame += frames;
        } else {
            this.frame++;
        }

        if (this.frame < this.frames){
            var delta = this.transition(this.frame / this.frames);
            value = this.compute(this.from, this.to, delta)
            this.set(value);
        } else {
            value = this.compute(this.from, this.to, 1);
            this.frame = this.frames;
            this.set(value);
            this.stop();
        }
        var v1 = Math.round(value.test[0].value);
        this.element.setStyle(prop, 'rotate(' + v1 + 'deg)');
    }
});
}.call(Fbl));

(function() {
/**
 * Efekt pulsacyjny
 * @class Fbl.Fx.Pulsate
 **/
this.Fx = this.Fx || {};
this.Fx.Pulsate = new Class({
    Extends: Fx.CSS,

    /**
     * @constructor
     * @param {String|Element} element
     * @param {Object} options
     **/
    initialize: function(element, options){
        this.element = this.subject = $(element);
        this.parent(options);
        this.pulsate = Math.ceil(this.options.pulsate) * 2;
    },

    /**
     * Ustawianie wartosci bespośrednio na elemencie
     * @param {String|Object} now
     **/
    set: function(now){
        if (typeof now == 'string') now = this.search(now);
        for (var p in now) this.render(this.element, p, now[p], this.options.unit);
        return this;
    },

    compute: function(from, to, delta){
        var now = {};
        for (var p in from) now[p] = this.parent(from[p], to[p], delta);
        return now;
    },

    /**
     * Start efektu
     * @param {Object} properties - Obiekt z informacjami o atrybutach jakie mają być
     *                              zmieniane oraz w jakich zakresach
     **/
    start: function(properties, force) {
        if ( this.options.waitForComplete ) {
            if ( this.element.retrieve('fbl.fx.pulsate:anim_progress') && !force ) {
                return this;
            }
            this.element.store('fbl.fx.pulsate:anim_progress', true);
        }
        if ( !this.check(properties) ) return this;
        if (typeof properties == 'string') properties = this.search(properties);
        var from = {}, to = {};
        for (var p in properties){
            var parsed = this.prepare(this.element, p, properties[p]);
            from[p] = parsed.from;
            to[p] = parsed.to;
        }
        var complete = function() {
            this.removeEvent('complete', complete);
            this.fireEvent('pulse', this.subject);
            if ( this.pulsate <= 0 ) {
                this.element.store('fbl.fx.pulsate:anim_progress', false);
                this.pulsate = Math.ceil(this.options.pulsate) * 2;
                this.fireEvent('complete', this.subject);
                if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
                return;
            }
            var reverseProp = {};
            for (var p in properties) {
                reverseProp[p] = [properties[p][1], properties[p][0]];
            }
            this.start(reverseProp, true);
        }.bind(this);
        this.addEvent('complete', complete);
        this.pulsate --;
        return this.parent(from, to);
    }
});
}.call(Fbl));

(function() {
this.Fx = this.Fx || {};
this.Fx.Bubble = new Class({
    Implements: Options,

    options: {
        duration: 25,
        radius: 10,
        speed: 0.05
    },

    initialize: function(element, options) {
        this.element = $(element);
        this.coord = this.element.getCoordinates();
        this.setOptions(options);
    },

    start: function() {
        this.i = 0;
        this.timer = this._loop.periodical(this.options.duration, this);
    },

    stop: function() {
        clearInterval(this.timer);
    },

    _loop: function() {
        if ( this.i == 1 ) {
            this.i = 0;
        }
        this.i += this.options.speed; //(Math.random() / 20);
        var sx = Math.sin(this.i) * Math.cos(- this.i);
        var sy = Math.cos(- this.i) * Math.cos(this.i);

        this.element.setStyles({
            left: this.coord.left + sx * this.options.radius,
            top: this.coord.top + sy * this.options.radius
        });
    }
});
}.call(Fbl));

/** Filters **/
(function() {
/**
 * Model bazowy do tworzenia filtrów
 * @class Fbl.Filter.Base
 **/
this.Filter = this.Filter || {};
this.Filter.Base = new Class({
    Implements: Options,

    options: {
        element_event       : 'keyup',
        element_property    : 'value',
        add_event       : true
    },

    /**
     * @constructor
     * @param {String|Element} element - Element jego Id lub wartość sprawdzana (filtrowana)
     * @param {Object} options
     **/
    initialize: function(element, options) {
        this.element = $(element) ? $(element) : element;
        this.setOptions(options);
        this.initEvents();
    },

    /**
     * Inicjalizacja event-ów.
     **/
    initEvents: function() {
        if ( this.options.add_event && $type(this.element) == 'element' )
            this.element.addEvent(this.options.element_event, this._filter.bind(this));
    },

    /**
     * Metoda ustawiająca wartość już zfiltrowaną elementowi z którego
     * brała wartość do filtrowania
     * @param {String|Number} value
     * @return {String|Number} value
     **/
    set: function(value) {
        if ( $type(this.element) == 'element' ) {
            this.element.set(this.options.element_property, value);
        }
        return value;
    },

    /**
     * Filtrowanie wartości z elementu - jeśli istnieje
     * @private
     * @param {Event} event
     **/
    _filter: function(event) {
        if ( event ) {
            this.filter(event.target.get(this.options.element_property));
        }
    }
});

var polish_chars = 'ąćęłńóśżźĄĆĘŁŃÓŚŻŹ';

/**
 * Model filtra do zamiany wejściowych znaków na ich małe odpowiedniki
 * @class Fbl.Filter.LowerCase
 **/
this.Filter.LowerCase = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        return this.set( value.toLowerCase() );
    }
});

/**
 * Model filtra do zamiany wejściowych znaków na ich duże odpowiedniki
 * @class Fbl.Filter.UpperCase
 **/
this.Filter.UpperCase = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        return this.set( value.toUpperCase() );
    }
});

/**
 * Model filtra do usuwania znaków nie alfa i nie polskich
 * @class Fbl.Filter.String
 **/
this.Filter.String = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        if ( !this.options.allow_uppercase ) {
            value = value.toLowerCase();
            this.set( value );
        }
        var r = new RegExp('[^a-z' + polish_chars + ']', 'g');
        if ( r.exec(value) ) {
            return this.set( value.replace(r, '') );
        }
        return false;
    }
});

/**
 * Model filtra do usuwania znaków nie numerycznych
 * @class Fbl.Filter.String
 **/
this.Filter.Number = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        var r = new RegExp('[^0-9]', 'g');
        if ( r.exec(value) ) {
            return this.set( value.replace(r, '') );
        }
        return false;
    }
})

/**
 * Model filtra do zamiany polskich znaków na ich odpowiedniki angielskie
 * @class Fbl.Filter.ReplaceChars
 **/
this.Filter.ReplaceChars = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        var r = new RegExp('[' + polish_chars + ']', 'g');
        r = r.exec(value);
        if ( r === null ) {
            return value;
        }
        value = value.replace(/\u0105/g, 'a')
             .replace(/\u0107/g, 'c')
             .replace(/\u0119/g, 'e')
             .replace(/\u0142/g, 'l')
             .replace(/\u0144/g, 'n')
             .replace(/ó/g, 'o')
             .replace(/\u015b/g, 's')
             .replace(/\u017c/g, 'z')
             .replace(/\u017a/g, 'z')
             .replace(/\u0104/g, 'A')
             .replace(/\u0106/g, 'C')
             .replace(/\u0118/g, 'E')
             .replace(/\u0141/g, 'L')
             .replace(/\u0143/g, 'N')
             .replace(/Ó/g, 'O')
             .replace(/\u015a/g, 'S')
             .replace(/\u017b/g, 'Z')
             .replace(/\u0179/g, 'Z');
             
        return this.set( value );
    }
});

/**
 * Model filtra do usuwanie znaków nie alfa, nie num
 * @class Fbl.Filter.StringAndNumber
 **/
this.Filter.StringAndNumber = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        if ( !this.options.allow_uppercase ) {
            value = value.toLowerCase();
            this.set( value );
        }
        var r = new RegExp('[^a-z0-9' + polish_chars + ']', 'g');
        if ( r.exec(value) ) {
            return this.set( value.replace(r, '') );
        }
        return false;
    }
});

/**
 * Model filtra do usuwania znaków nie alfa, nie num i nie space
 * @class Fbl.Filter.StringNumberSpace
 **/
this.Filter.StringNumberSpace = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        if ( !this.options.allow_uppercase ) {
            value = value.toLowerCase();
            this.set( value );
        }
        var r = new RegExp('[^a-z0-9 ' + polish_chars + ']', 'g');
        if ( r.exec(value) ) {
            return this.set( value.replace(r, '') );
        }
        return false;
    }
});

/**
 * Model filtra do usuwania znaków według przekazanego wzroca regExp
 * @class Fbl.Filter.Regexp
 **/
this.Filter.Regex = new Class({
    Implements: this.Filter.Base,

    filter: function(value, pattern, flag) {
        var r = new RegExp(pattern, flag);
        if ( r.exec(value) ) {
            return this.set( value.replace(r, '') );
        }
        return false;
    }
});

/**
 * Model filtra do poprawnego pokazywania url (zawsze z http://)
 * @class Fbl.Filter.Url
 **/
this.Filter.Url = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        if ( !value.match(/http:\/\//) ) {
            value = this.set( 'http://' + value );
        }
        return value;
    }
});

/**
 * Model filtra do usuwania znaków o długości więcej niż ...
 * @class Fbl.Filter.MaxLength
 **/
this.Filter.MaxLength = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        var max = this.options.length || false;
        var length = value.length;
        if ( max ) {
            if ( length >= max ) {
                value = this.set(value.substr(0, max));
            }
        }
        return value;
    }
});

/**
 * Model filtra do zamiany znaków nowej lini na odpowiednik HTML-owy (<br/>)
 * @class Fbl.Filter.Nl2Br
 **/
this.Filter.Nl2Br = new Class({
    Implements: this.Filter.Base,

    filter: function(value) {
        if ( !value ) {
            value = $type(this.element) == 'element' ? this.element.get('value') : this.element;
        }
        var repStr = this.options.replace_string || '<br/>';
        if ( this.options.max_lines ) {
            var pattern = "\\n\\s{" + (this.options.max_lines + 1) + ",}";
            value = value.replace(new RegExp(pattern, 'g'), repStr.repeat(this.options.max_lines + 1));
        }
        value = value.replace(/\n/g, repStr);
        return this.set(value);
    }
});
}.call(Fbl));

/** Validators **/
(function() {
/**
 * Model bazowy do tworzenia walidatorów
 * @class Fbl.Validator.Base
 **/
this.Validator = this.Validator || {};
this.Validator.Base = new Class({
    Implements: [Options, Events],

    options: {
        active_element_event: 'click'
    },

    /**
     * @constructor
     * @param {String|Element} element - Element jego Id lub wartość sprawdzana (filtrowana)
     * @param {String|Element} activeElement - (opcjonalnie) Element lub Id, jest to
     *                                         element który aktywuje walidacje
     * @param {Object} options
     **/
    initialize: function(element, activeElement, options) {

        this.setOptions(options);

        this.element = $(element) ? $(element) : element;
        this.activeElement = $(activeElement);

        this.initEvents();
    },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        if ( this.activeElement )
            this.activeElement.addEvent(this.options.active_element_event, this.validate.bind(this))
    },

    /**
     * Walidacja
     * @param {Event} event
     **/
    validate: function(event) {
        this.fireEvent('beforeValid', [this]);
        var value = $type(this.element) == 'element' ? this._getValue() : this.element;
        var valid = this._valid(value);
        if ( valid === true ) {
            this.fireEvent('valid', [this]);
        } else if ( valid === false ) {
            this.fireEvent('invalid', [this]);
        }
        return valid;
    },

    /**
     * Pobieranie wartości do walidacji
     * @return {String|Number} value
     **/
    _getValue: function() {
        var value;
        switch ( this.element.get('type') ) {
            case "text":
            case "radiobutton":
                value = this.element.get('value');
                break;
            case 'checkbox':
                value = this.element.get('checked');
                break;
            default:
                value = this.element.get('value');
                break;
        }
        return value;
    }
});

/**
 * Model walidatora do sprawdzania długości stringa
 * @class Fbl.Validator.String
 **/
this.Validator.StringLength = new Class({
    Implements: this.Validator.Base,

    _valid: function(value) {
        value = String.trim(value);
        return value.length <= this.options.max && value.length >= this.options.min;
    }
});

/**
 * Model walidatora do sprawdzania wartości min max
 * @class Fbl.Validator.Between
 **/
this.Validator.Between = new Class({
    Implements: this.Validator.Base,

    _valid: function(value) {
        value = parseInt(value);
        return value <= this.options.max && value >= this.options.min;
    }
});

/**
 * Model walidatora do sprawdzania poprawności adresu URI zdjęcia
 * Uwaga: Jest tylko sprawdzana poprawność url-a!
 * @class Fbl.Validator.UrlImage
 **/
this.Validator.UrlImage = new Class({
    Implements: this.Validator.Base,

    _valid: function(value) {
        value = String.trim(value);

        if ( this.options.maxWidth && this.options.maxHeight ) {
            var check = function(img) {
                if ( img.width > this.options.maxWidth ) {
                    this.fireEvent('invalid', [this]);
                    return;
                }
                if ( img.height > this.options.maxHeight ) {
                    this.fireEvent('invalid', [this]);
                    return;
                }
                this.fireEvent('valid', [this]);
            }.bind(this);
            new Asset.image(value, {onload: check/*, onabort: error, onerror: error*/});

            return null;
        }
        return value.match(/(http:\/\/)?(www\.)?[a-z0-9\.\/\-\_~]+\.(jpg|jpeg|gif|png)$/ig);
    }
});

/**
 * Model walidatora do sprawdzania poprawności adresu email
 * @class Fbl.Validator.Email
 **/
this.Validator.Url = new Class({
    Implements: this.Validator.Base,

    _valid: function(value) {
        value = String.trim(value);
        return value.test(/^(([\w]+:)?\/\/)(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,4}(:[\d]+)?(\/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?$/);
    }
});

/**
 * Model walidatora do sprawdzania poprawności adresu email
 * @class Fbl.Validator.Email
 **/
this.Validator.Email = new Class({
    Implements: this.Validator.Base,

    _valid: function(value) {
        value = String.trim(value);
        var pattern = /^([a-zA-Z0-9_\.\+-])+@([a-zA-Z0-9_.-])+\.([a-zA-Z])+([a-zA-Z])+/;
        return pattern.test( value );
    }
});

/**
 * Model walidatora do sprawdzania listy adresów email.
 * Lista adresów musi być rozdzielona znakami typu: , : lub spacja
 * @class Fbl.Validator.EmailList
 **/
this.Validator.EmailList = new Class({
    Implements: this.Validator.Base,

    _valid: function(value) {
        if ( !value ) return false;
        var list = value.split(/(?:,|:| )/);
        for ( var i = 0; i < list.length; i++ )
        {
            var EmailValidator = new Fbl.Validator.Email(list[i]);
            if ( ! EmailValidator.validate() )
                return false;
        }
        return true;
    }
});

/**
 * Model walidatora do sprawdzania czy wartość nie jest pusta
 * @class Fbl.Validator.NotEmpty
 **/
this.Validator.NotEmpty = new Class({
    Implements: this.Validator.Base,

    _valid: function(value) {
        value = $type(value) == 'string' ? String.trim(value) : value;
        return ( value.length > 0 || value === true );
    }
});

/**
 * Kolekcja walidatorów.
 * @class Fbl.Validator.Colection
 * @example
 * new Fbl.Validator.Collection({
 *      validator: 'Email', value: 'jakaś wartość lub element', message: 'wartość %X% jest zła', options: 'opcje danego walidatora'
 * });
 **/
this.Validator.Collection = new Class({
    /**
     * @constructor
     * @param {Object} validators
     **/
    initialize: function(validators) {
        if ( $type(validators) != 'array' ) return;

        this.validators = validators;
        this.collection = [];
        this.errors = [];

        this.validators.each(function(v) {
            this.collection.push( new Fbl.Validator[v.validator](v.value, null, v.options) );
        }.bind(this));
    },

    /**
     * Walidacja
     * @return {Boolean} valid
     **/
    validate: function() {
        this.errors = [];
        var valid = true;
        for ( var i = 0; i < this.collection.length; i++ ) {
            this.errors.push( this.collection[i].validate() ? 1 : 0 );

            if ( !this.errors[i] ) {
                valid = false;
            }
            this.validators[i]['valid'] = this.errors[i] ? 1 : 0;
            if ( this.validators[i]['message'] ) {
                var value = $(this.validators[i].value) ?
                    $(this.validators[i].value).get('value') : this.validators[i].value;
                this.validators[i]['message'].replace('%X%', value);
            }
        }
        return valid;
    }
});
}.call(Fbl));

(function() {
/**
 * Singleton, obsługa lightboxów
 * @class Fbl.Lightbox
 **/
this.Lightboxes = this.Lightboxes || {};
this.Lightboxes.showLoginConfirm = function(target, text) {
    if ( !text ) {
        text = {
            header  : 'Zaloguj się, aby wykonać akcje',
            body    : 'Zaloguj się, aby kontynuować. Jeśli nie masz konta, <a href=\'/rejestracja\'>zarejestruj się</a>.',
            ok      : 'Zaloguj się'
        };
    }
    new Fbl.Box().confirm(text).addEvent('confirm', function(status){
        if ( status == 'ok' ) {
            var url = '/mojphotoblog/';
            if ( target ) {
                url += '?target=' + escape(target);
            }
            window.location.href = url;
        } else {
            this.close();
        }
    });
};
/**
 * Pokaż okienko ostrzeżenia o wymaganiu zalogowania z hashem
 * @param {String} hash_action - Strona na którą użytkownik zostanie przekierowany
 *                          po zalogowaniu się
 * @param {Object} text   - Obiekt z tekstem {header:'', body: '', ok: ''}
 **/
this.Lightboxes.showLoginConfirmWithHashAction = function(hash_action, text) {
    var target = window.location.pathname + window.location.search;

    if ( hash_action ) {
        if ( target.indexOf('?') > -1 ) {
            target += '&';
        } else {
            target += '?';
        }
        target += 'hash-action=' + hash_action;
    }
    Fbl.Lightboxes.showLoginConfirm(target, text);
};
}.call(Fbl));

(function(){
var EditInPlace = this.EditInPlace = this.EditInPlace || {};
/**
 * Modele do obsługi edycji InPlace.
 * Model generuje dwa rodzaje przycisków inicjalizujące edycję. Pierwszy rodzaj 'main_actions' jest
 * typem przycisków nie ruchomych, pokazywanych zawsze w tych samych miejscach i są widoczne
 * również w czasie edycji wpisu. Drugi rodzaj 'item_actions' jest typem przycisków które
 * dynamiczne pozycjonują się obok edytowanego elementu. Pokazywane są tylko w momencie
 * najechania myszką na edytowany element i są ukrywane podczas jego edycji.
 * @class Fbl.EditInPlace.Base
 */
EditInPlace.Base = new Class({
    Implements: [Options, Events],
    Binds: ['onWindowMouseMove', 'onWindowClick', 'onFailureRequest', 'onWindowKeyDown',
        'onWindowDblClick'],
    options: {
        base_url: FULLADDR + '/index2.php/edit_inplace/index/',
        action_url: '',             // Predefiniowana akcja do kontrolera obsługującego, domyślnie akcja przyjmuję
                                    // wartość id elementu edytowanego
        cancel_on_clickout: true,   // determinuje czy anulować edycję po kliknięciu w zew. element na stronie, autocancel.
        anim_onhover: false,        // determinuje czy element hoverowany ma być zaznaczany dodatkową animacją.
                                    // Element ten przyjmuje wartość true|false lub string jako id elementu hoverowanego
        hide_item_actions: false,   // determinuje czy ukrywać przyciski dowiązane do itemów, ukrywanie całkowite.
        main_actions_onhover: null, // element nad którym są wyświetlane buttony akcji głównych, opcjonalne
        main_actions_event: 'click',    // Rodzaj eventu dla akcji głównych
        hide_main_actions_on_editing: false,   // determinuje czy ukrywać przyciski główne gdy pole jest edytowane.
        main_actions: null,         // lista widocznych głównych akcji, opcjonalne np: 'edit,add'
//        item_actions: null,         // lista widocznych głównych akcji, opcjonalne np: 'edit,add'
        default_text: false,        // Domyślny tekst dla inputów fokusowanych
        default_text_color: '#CCC', //
        edit_field_event: 'dblclick',
        buttonNames: {              // Predefiniowane nazwy akcji
            'edit': '',
            'add': 'Dodaj',
            'delete': '',
            'cancel': 'Anuluj',
            'save': 'Zapisz',
            'add_banner': 'Dodaj emblemat'
        },
        offset: {x: 0, y: 0},
        shortcut_keys: {escape: 'esc', save: 'enter'},
        draw_from_right: false,     // Pozycjonuje elementy akcyjne od prawej strony, true|false
        error_msg: '',
        post_data: {},              // Dodatkowe dane wysyłane przy żądaniu do serwera
        getter: function(itemCont) {},
        setter: function(itemCont, data) {},
        validate: function(data) {},
        model: function(data) {}
    },
    modelType: '',
    buttonClassPrefix: 'inplace_button',
    itemContentClass: 'inplace_item_width',
    itemHoverClass: 'inplace_item_hover',
    storeBaseKey: 'fbl.editinplace:',
    _editContents: [],
    _editOrgText: [],
    _mainAction: [],
    _hoverAction: [],
    _itemAction: [],
    _confirmedAction: [],
    itemPattern: null,
    isEditMode: false,

    initialize: function(cont, options) {
        this.setOptions(options);
        this.cont = $(cont);
        if ( !this.cont ) {
            return;
        }
        var main_actions = [], hoverActions = [];
        this.definedMainAction = false;
        if ( typeOf(this.options.main_actions) == 'string' ) {
            this.definedMainAction = true;
            main_actions.append(this.options.main_actions.split(','));
        } else {
            main_actions = ['edit'];
        }
        this.options.main_actions = main_actions;
        this.definedHoverAction = false;
        if ( typeOf(this.options.item_actions) == 'string' ) {
            this.definedHoverAction = true;
            hoverActions.append(this.options.item_actions.split(','));
        } else {
            hoverActions = ['edit', 'delete'];
        }
        this.options.item_actions = hoverActions;
        if ( this.options.main_actions_onhover ) {
            this.contHover = $(this.options.main_actions_onhover);
        }
        this.builder = new Fbl.Builder();
    },

    initEvents: function() {
        var doc = document;
        (function() {
            doc.addEvent('mousemove', this.onWindowMouseMove);
        }.delay(500, this));
        doc.addEvent('click', this.onWindowClick);
        doc.addEvent(this.options.edit_field_event, this.onWindowDblClick);
        doc.addEvent('keydown', this.onWindowKeyDown);
    },
    
    setPostData: function(data) {
        this.options.post_data = data || {};  
    },

    setHoverActions: function(main_actions) {
        this.options.item_actions = Array.from(main_actions);
    },
    setMainActions: function(main_actions) {
        this.options.main_actions = Array.from(main_actions);
    },

    onWindowMouseMove: function(event) {
        var el = event.target, id, p, animId;

        if ( event.simulated ) {
            this._hideAnimHover();
            this._hideMainActionButtons();
            return;
        }
        if ( !el || !el.getParent || typeOf(el.getParent) != 'function' ) {
            return;
        }
        if ( this.options.anim_onhover && !this._forceHideFieldBg ) {
            animId = typeOf(this.options.anim_onhover) == 'string' ? this.options.anim_onhover : this.cont.id;
            if ( (el.id == animId || el.getParent('#' + animId)
                || el.hasClass(this.cont.id + '_edit') || el.getParent('.' + this.cont.id + '_edit')) ) {
                this._showAnimHover();
            } else {
                this._hideAnimHover();
            }
        }
        if ( this.definedMainAction && !this._forceHideMainAction ) {
            id = this.options.main_actions_onhover ? this.options.main_actions_onhover : this.cont.id;
            var allowed = true;
            if ( this.options.hide_main_actions_on_editing ) {
                allowed = !this._isEditing;
            }
            // Akcje główne
            if ( (el.id == id || el.getParent('#' + id)) && allowed ) {
                this._showMainActionButtons(el);
            } else {
                this._hideMainActionButtons(el);
            }
        }
        if ( !this.options.hide_item_actions ) {
            // Hover-akcje
            p = el.getParent(this.options.items || '.' + this.itemHoverClass);
            if ( el.get('tag') == this.options.items || p || el.hasClass('inplace_item_hover') ) {
                if ( this.cont.contains(p || el)
                    && (el.hasClass(this.itemContentClass) || el.getParent('.' + this.itemContentClass)
                    || el.hasClass(this.itemHoverClass) || el.getParent('.' + this.itemHoverClass) ) ) {
                    this._showHoverActionButtons(el);
                }
            } else {
                this._hideHoverActionButtons(el);
            }
        }
    },

    onWindowClick: function(event) {
        var el = event.target, elP, editableValues, df;

        if ( !el.hasClass(this.buttonClassPrefix) ) {
            elP = el.getParent('.' + this.buttonClassPrefix);
            if ( elP ) {
                el = elP;
            }
        }
        if ( !this.options.cancel_on_clickout ) {
            return;
        }
        this._editContents.each(function(contEl, i, arr) {
            if ( !contEl ) {
                return;
            }
            if ( !this['_hoverAction'][i] && !this.options.hide_item_actions ) {
                return;
            }
            var hasContentClick = contEl.contains(el);
            if ( !this.options.hide_item_actions ) {
                hasContentClick = (this['_hoverAction'][i].edit == el || this['_hoverAction'][i]['delete'] == el
                    || contEl.contains(el) );
            }
            // Jeśli kliknięto w inny element aktualnej edycji to sprawdź czy można tą edycje ukryć
            if ( !hasContentClick ) {
                // Jeśli kliknięto w inny element niż akcja główna
                // lub gdy click w główną akcje nie należy do edytowanego contentu
                // lub gdy click nie dotyczy ostatniego wpisu (nowego)
                // to usuń/chowaj aktualną edycje
                var isContActionClick = false;
                this['_mainAction'].each(function(actionEl) {
                    if ( actionEl == el ) {
                        isContActionClick = true;
                    }
                });
                if ( !el.hasClass(this.buttonClassPrefix + '_main') || !isContActionClick
                    || ((i + 1) != arr.length && arr.length) ) {
                    if ( this.modelType != 'tinymce' ) {
                        // Wykluczanie domyślnych treści
                        editableValues = this._getEditableElements(i).get('value');
                        if ( this.options.default_text ) {
                            df = this.options.default_text.split('|');
                            editableValues = editableValues.map(function(value) {
                                return df.indexOf(value) === -1 ? value : '';
                            }.bind(this));
                        }
                        if ( JSON.encode(editableValues) == JSON.encode(this._editOrgText[i]) ) {
                            this.onClickCancelAction(event, {itemIndex: i});
                        }
                        return;
                    }
                    this.onClickCancelAction(event, {itemIndex: i});
                }
            }
        }.bind(this));
    },

    onWindowDblClick: function(event) {
        var el = event.target, elP, id;

        if ( !el.hasClass(this.itemHoverClass) ) {
            elP = el.getParent('.' + this.itemHoverClass);
            if ( elP ) {
                el = elP;
            }
        }
        id = this.options.main_actions_onhover || this.cont.id;
        if ( el.getParent('#' + this.cont.id) || id == el.id ) {
            this.onClickEditAction(event, {itemIndex: this._getItemIndex(el)});
            if ( this.options.hide_main_actions_on_editing ) {
                this._hideMainActionButtons(el);
            }
        }
    },

    onWindowKeyDown: function(event) {
        var shortcutName = null, doc = document, i, len, contains = false;

        if ( !this._editContents.clean().length ) {
            return;
        }
        len = this._editContents.length;
        for ( i = 0; i < len; i++ ) {
            if ( !this._editContents[i] || this._editContents[i].contains(doc.activeElement) ) {
                contains = true;
                break;
            }
        }
        if ( !contains ) {
            return;
        }

        Object.each(this.options.shortcut_keys, function(id, name) {
            id = id.split('+');
            if ( id.length == 1 && id[0] == event.key ) {
                shortcutName = name;
            } else if ( id.length > 1 ) {
                if ( id[1] == event.key && event[id[0]] ) {
                    shortcutName = name;
                }
            }
        });
        switch (shortcutName) {
            case 'save':
                this.onClickSaveAction(event, {itemIndex: this._getItemIndex(event.target)});
                break;
            case 'escape':
                if ( this.modelType != 'tinymce' ) {
                    this.onWindowClick({target: doc.body});
                }
                break;
        }
    },

    onFailureRequest: function() {
        new Fbl.Box().alert({
            body: this.options.error_msg || 'Podczas zapisywania wyst\u0105pi'
                + '\u0142 nieoczekiwany b\u0142\u0105d. Spróbuj ponownie.'
        });
    },

    onClickEditAction: function(event, moreData) {
        if ( typeOf(event) == 'domevent' ) {
            event.stop();
        }
        if ( ['input', 'textarea', 'list', 'select'].indexOf(this.modelType) != -1 ) {
            this._editOrgText[moreData.itemIndex] = this._getEditableElements(moreData.itemIndex).get('value');
        }
        this._isEditing = true;
        this.fireEvent('clickEditAction', [event, moreData]);
    },
    onClickSaveAction: function(event, moreData) {
        if ( typeOf(event) == 'domevent' ) {
            event.stop();
        }
        this._isEditing = false;
        this.fireEvent('clickSaveAction', [event, moreData]);
    },
    onClickCancelAction: function(event, moreData) {
        this._editContents[moreData.itemIndex].destroy();
        delete this._editContents[moreData.itemIndex];
        delete this['_itemAction'][moreData.itemIndex];
        //this._editContents = this._editContents.clean();
        if ( typeOf(event) == 'domevent' ) {
            event.stop()
        }
        this._isEditing = false;
        this.fireEvent('clickCancelAction', [event, moreData]);
    },

    _confirmDeleteAction: function(id, data) {
        if ( this._confirmedAction[id] ) {
            delete this._confirmedAction[id];
            return true;
        }
        var box = new Fbl.Box().confirm({
            body: this.options.confirm_delete_msg || 'Czy na pewno chcesz usun\u0105\u0107 wpis?'
        });
        box.addEvent('confirm', function(status) {
            if ( status == 'ok' ) {
                this._confirmedAction[id] = true;
                data.object[data.method].call(data.object, data.args[0], data.args[1]);
            }
            box.close();
            this._isEditing = false;
        }.bind(this));
        return false;
    },

    _getEditableElements: function(index) {
        return this._editContents[index].getElements(this.modelType == 'list'
            ? 'input[type!=hidden],textarea[type!=hidden],select' : this.modelType);
    },

    _showMainActionButtons: function(el) {
        if ( this._forceHideMainAction ) {
            return;
        }
        this.options.main_actions.each(function(action, i) {
            this['_mainAction'][i] = this['_mainAction'][i] || {};
            if ( typeOf(this['_mainAction'][i]) == 'object' ) {
                this['_mainAction'][i] = this._buildButton(action, {
                    className: this.buttonClassPrefix + '_main', type: 'main'
                }).show();
            } else {
                this['_mainAction'][i].show();
            }
            var pos = this._getRightTopPosition(this.contHover ? this.contHover : this.cont);
            if ( this.options.draw_from_right ) {
                pos.left = pos.left - (this['_mainAction'][i].getWidth() * (i+1) + 5);
            } else {
                pos.left = pos.left - (this['_mainAction'][i].getWidth() * (i+1) + 5);
            }
            this['_mainAction'][i].setStyles(pos);
        }.bind(this));
    },

    _hideMainActionButtons: function(el) {
        var contains = false, i, els = [], aEl;

        for ( i = 0; i < this.options.main_actions.length; i++ ) {
            aEl = this['_mainAction'][i];
            if ( !aEl ) {
                continue;
            }
            els.push(aEl);
            if ( !el ) {
                continue;
            }
            if ( el == aEl || aEl.contains(el) ) {
                contains = true;
            }
            if ( !contains ) {
                if ( el.getParent('.' + this.buttonClassPrefix) || el.hasClass(this.buttonClassPrefix) ) {
                    contains = true;
                }
            }
        }
        if ( !contains ) {
            els.invoke('hide');
        }
    },

    _showHoverActionButtons: function(el) {
        var index = this._getItemIndex(el),
            buttonsWidth = 0, prevButton, j, pos;

        this._hideHoverActionButtons(el);
        if ( this._editContents[index] ) {
            return;
        }
        pos = this._getRightTopPosition(this._getItemContentElement(index));
        this.options.item_actions.each(function(action, i) {
            this['_hoverAction'][index] = this['_hoverAction'][index] || {};
            if ( !this['_hoverAction'][index][action] ) {
                this['_hoverAction'][index][action] = this._buildButton(
                    action, 
                    {className: this.buttonClassPrefix + '_hover', type: 'hover'},
                    {itemIndex: index}
                ).show();
            } else {
                this['_hoverAction'][index][action].show();
            }
            if ( !this.options.draw_from_right ) {
                this['_hoverAction'][index][action].setStyles(pos);
                pos.left += (this['_hoverAction'][index][action].getWidth() * (i + 1));
            }
        }.bind(this));
        if ( this.options.draw_from_right ) {
            Object.each(this['_hoverAction'][index], function(el) {
                buttonsWidth += el.getWidth();
            });
            pos.left -= buttonsWidth;
            for ( j in this['_hoverAction'][index] ) {
                if ( j != 'edit' ) {
                    pos.left += prevButton.getWidth();
                }
                prevButton = this['_hoverAction'][index][j].setStyles(pos);
            }
        }
    },

    _hideHoverActionButtons: function(el) {
        var excludeI = -1;

        this['_hoverAction'].each(function(item, i) {
            Object.each(item, function(iel) {
                if ( el == iel || iel.contains(el) ) {
                    excludeI = i;
                }
            });
        }.bind(this));
        this['_hoverAction'].each(function(item, i) {
            if ( excludeI == i ) {
                return;
            }
            this._hideHoverActionButton(i);
        }.bind(this));
    },

    _showItemActionButtons: function(itemIndex) {
        ['save', 'cancel'].each(function(action, i) {
            if ( !this['_itemAction'][itemIndex] ) {
                this['_itemAction'][itemIndex] = {};
            }
            if ( !this['_itemAction'][itemIndex][action] ) {
                this['_itemAction'][itemIndex][action]
                    = this._buildButton(action,
                        {parent: this._editContents[itemIndex],
                            className: this.buttonClassPrefix + '_item',
                            excludeImg: true,
                            type: 'action'
                        },
                        {itemIndex: itemIndex}).show('inline');
            } else {
                this['_itemAction'][itemIndex][action].show('inline');
            }
        }.bind(this));
    },

    _hideItemActionButtons: function(el) {
        var excludeI = -1;
        this['_itemAction'].each(function(item, i) {
            Object.each(item, function(iel) {
                if ( el == iel || iel.contains(el) ) {
                    excludeI = i;
                }
            });
        }.bind(this));
        this['_itemAction'].each(function(item, i) {
            if ( excludeI == i ) return;
            this._hideHoverActionButton(i);
        }.bind(this));
    },

    _hideHoverActionButton: function(id) {
        Object.each(this['_hoverAction'][id], function(el) {
            el.hide();
        });
    },

    _hideAnimHover: function() {
        if ( !this.options.anim_onhover ) {
            return;
        }
        var id = typeOf(this.options.anim_onhover) == 'string' ? this.options.anim_onhover : this.cont.id;
        $(id).setStyle('background', 'none');
    },

    _showAnimHover: function() {
        if ( !this.options.anim_onhover ) {
            return;
        }
        var id = typeOf(this.options.anim_onhover) == 'string' ? this.options.anim_onhover : this.cont.id;
        $(id).setStyle('background', 'transparent url(' + SS + '/images/show/go_edit.png)');
    },

    forceHideFieldBg: function(flag) {
        this._forceHideFieldBg = flag;
    },
    blockMainAction: function(flag) {
        this._forceHideMainAction = flag;
    },

    /**
     * Pobierz index wpisu listy wg. przekazanego elementu z mousemove
     * @param {Element} el
     * @return {Number}
     */
    _getItemIndex: function(el) {
        if ( this.options.items ) {
            return this.contItems.indexOf(this._getItemElement(el));
        }
        return 0;
    },

    /**
     * Pobierz element listy wg. przekazanego elementu z mousemove
     * @param {Element} el
     * @return {Element}
     */
    _getItemElement: function(el) {
        if ( this.options.items ) {
            if ( el.get('tag') == this.options.items ) {
                return el;
            }
            return el.getParent(this.options.items);
        }
        if ( el.hasClass(this.itemContentClass) ) {
            return el;
        }
        return el.getParent('.' + this.itemContentClass);
    },

    _getItemContentElement: function(index) {
        if ( this.options.items ) {
            return this.contItems[index].getElement('.' + this.itemContentClass);
        }
        var item = this.cont.getElement('.' + this.itemContentClass);
        return item.getParent('a') || item;
    },

    _setItemContentText: function(text) {
        var item = this._getItemContentElement();
        if ( item.get('tag') == 'a' ) {
            item = item.getElement('.' + this.itemContentClass);
        }
        if ( this.modelType == 'textarea' ) {
            text = new Fbl.Filter.Nl2Br(text, {replace_string: "\n<br/>", add_event: false}).filter();
        }
        item.innerHTML = text;
    },

    _getItemContentText: function() {
        var item = this._getItemContentElement(), data;
        if ( item.get('tag') == 'a' ) {
            item = item.getElement('.' + this.itemContentClass);
        }
        data = item.get('text');
        if ( this.modelType == 'textarea' && Browser.opera ) {
            data = data.replace(/\n\n(?!\n)/g, '');
        }
        return data;
    },

    _setDefaultText: function(itemIndex) {
        if ( this.options.default_text ) {
            var df = this.options.default_text, inputs, dv;

            df = df.split('|');
            inputs = this._editContents[itemIndex]
                .getElements('input[type!=hidden],textarea[type!=hidden]');
            inputs.each(function(el, i) {
                dv = new Fbl.Manager.DefaultValueOnFocus(el, df[i] || df[0], {
                    fontColor: this.options.default_text_color,
                    hideOnBlur: false
                });
                if ( !el.value ) {
                    el.setStyle('color', this.options.default_text_color);
                    el.value = df[i] || df[0];
                }
            }.bind(this));
        }
    },

    /**
     * Budowanie elementu akcyjnego
     * @param {String} type Rodzaj akcji
     * @param {Object} opt  Opcje dodatkowe dla budowanego elementu
     * @param {Object} eventData    Dodatkowe dane dla eventu elementu
     **/
    _buildButton: function(type, opt, eventData) {
        opt = opt || {};
        var event = {}, events = [], model;
        
        event[opt.type == 'main' ? this.options.main_actions_event : 'click'] = function(event) {
            event.preventDefault();
            if ( opt.type == 'main' ) {
                var el = event.target;
                if ( !el.hasClass(this.buttonClassPrefix) ) {
                    el = el.getParent('.' + this.buttonClassPrefix);
                }
                el.hide();
            }
            var fnName = 'onClick' + type.replace(/_/g, '-').camelCase().ucFirst() + 'Action';
            if ( typeOf(this[fnName]) == 'function' ) {
                this[fnName](event, eventData || {});
            } else {
                this.fireEvent(fnName, [event, eventData || {}]);
            }
        }.bind(this);
        events.push(event);
        if ( !event.click ) {
            event = {};
            event.click = $stop;
            events.push(event);
        }
        model = {
            tag: 'a', href: '#', 
            className: this.buttonClassPrefix + ' ' + (this.cont.id + '_' +  type) + ' ' + (opt.className || '' ),
            styles: {display: 'none'},
            childs: [
                {tag: 'img', src: SS + '/images/show/blank.gif'},
                {tag: 'textNode', data: this.options.buttonNames[type]}
            ], events: events
        };
        if ( opt.excludeImg ) {
            delete model.childs[0];
            model.childs = model.childs.clean();
        }
        return this.builder.buildDomModel(opt.parent || $(document.body), model);
    },

    _focusFirstElement: function(index) {
        var el = this._editContents[index].getElement('input[type!=hidden],textarea[type!=hidden]');
        if ( el.get('tag') == 'input' || el.get('tag') == 'textarea' ) {
            el.selectionEnd = el.value.length; // el.selectionStart = 
//            el.selectionStart = el.selectionEnd = 0;
        }
        el.focus();
    },

    /**
     * Pobranie wartości wpisanych podczas edycji wpisu
     * @param {Number} index
     * @return {Object}
     */
    _getInputValues: function(index) {
        var editCont = this._editContents[index], inputData = {}, data = '', filter;

        filter = function(data) {
            return new Fbl.Filter.MaxLength(null, {length: this.options.max_length}).filter(data);
        }.bind(this);
        editCont.getElements('input, select, textarea').each(function(el) {
            if ( el.get('tag') == 'select' ) {
                inputData[el.name + 'Text'] = String.stripTags(el.getSelected()[0].get('text'));
            }
            data = String.trim(String.stripTags(el.name == 'id'
                ? el.value.replace(this.cont.id + '_id_', '') : el.value));
            inputData[el.name] = this.options.max_length ? filter(data) : data;
        }.bind(this));
        return inputData;
    },

    /**
     * Pobranie wartości wpisanych w elementy docelowe
     * @param {Number} index
     * @return {Object}
     */
    _getElementValues: function(index) {
        var data = this.options.getter(this._getItemContentElement(index));
        data.id = data.id.replace(this.cont.id + '_id_', '');
        return data;
    },

    /**
     * Aktualizacja wartości elementów dokumentu o wartości w obiekcie data
     * @param {Number} index    Index wpisu listy
     * @param {Object} data     Dane
     */
    _setElementValues: function(index, data) {
        if ( !data.id + ''.test(new RegExp(this.cont.id + '_id_')) ) {
            data.id = this.cont.id + '_id_' + data.id;
        }
        this.options.setter(this._getItemContentElement(index), data);
    },

    /**
     * Walidacja danych wprowadzonych przez użytkownika
     * @param {Object} inputData    Dane wejściowe
     * @param {Mixed} index         Id wpisu którego edytujemy
     * @param {Object} invalidFields    Obiekt aktualizowany o wynik walidacji
     * @return {Boolean}
     */
    _isValid: function(inputData, index, invalidFields) {
        var valid = true, editCont = this._editContents[index], i;
        if ( typeOf(this.options.validate) == 'function' ) {
            invalidFields = Object.merge(invalidFields, this.options.validate(inputData));
            for ( i in invalidFields ) {
                if ( invalidFields[i] ) {
                    valid = false;
                    editCont.getElement('[name=' + i + ']').focus();
                    break;
                }
            }
        }
        return valid;
    },

    _highlightInvalidFields: function(invalid, index) {
        var editCont = this._editContents[index];
        Object.each(invalid, function(value, field) {
            if ( !value ) {
                return;
            }
            new Fbl.Fx.Pulsate(editCont.getElement('[name=' + field + ']'), {
                pulsate     : 1,
                duration    : Fbl.Store.get('duration'),
                waitForComplete: true
            }).start({borderColor: ['#FD1919'], color: ['#FD1919']});
        }.bind(this));
    },

    /**
     * Sprawdza ograniczenia liczby itemów. Przy przekroczeniu max.
     * uniemożliwia dodawanie nowych.
     */
    _checkItemsCountRestrictions: function() {
        if ( this.contItems.clean().length >= this.options.max_items ) {
            this._mainAction.invoke('hide');
            this._forceHideMainAction = true;
        } else {
            this._forceHideMainAction = false;
        }
    },
    
    _mergePostData: function() {
        var isAlbum = Fbl.Store.get('User.isAlbum') ? true : false, data;
        
        data = Object.merge({
            is_album: isAlbum ? 1 : 0,
            fid: isAlbum ? Fbl.Store.get('User.albumData.album_id') : Fbl.Store.get('User.fid')
        }, this.options.post_data);
        Array.from(arguments).each(function(item) {
            data = Object.merge(data, item);
        });
        return data;
    },

    _getRequest: function(data, callbackComplete) {
        var request, action = this.options.action_url
            ? this.options.action_url.replace(/_/g, '-') : this.cont.id.replace(/_/g, '-');

        request = new Request.JSON({
            url: this.options.base_url + action,
            method: 'post',
            data: Object.merge(data, Fbl.Store.get('Security')),
            onComplete: function(resp) {
                if ( !resp && data._action != 'get' ) {
                    this.onFailureRequest();
                    return;
                }
                if ( typeOf(callbackComplete) == 'function' ) {
                    callbackComplete.apply(this, [resp]);
                }
            }.bind(this),
            onFailure: function() {
                if ( data._action != 'get' ) {
                    this.onFailureRequest();
                }
            }.bind(this)
        });
        return request;
    },

    _getRightTopPosition: function(el) {
        return {
            // wtf opera!?
            top: el.getTop() + this.options.offset.y,// + (Browser.opera ? el.getHeight() - 20 : 0),
            left: el.getLeft() + el.getWidth() + this.options.offset.x
        };
    },

    _getStorageKey: function() {
        return this.storeBaseKey + this.cont.id + ':';
    }
});
}).call(Fbl);

(function() {
/**
 * Modle bazowy do zarządzania archiwum, znajomych itp
 * @TODO: Przenieść logikę paginacji do osobnego modelu
 * @class Fbl.Manager.Base
 **/
this.Manager = this.Manager || {};
this.Manager.Base = new Class({
    Implements: [Options, Events, Fbl.Cache],

    options: {
        request_url: '/index2.php/blog/ajax/fetch-entries',
        request_method: 'post',
        cache_prefix: 'archive_',
        nav: {
            first: '.nav_first_site',
            previous: '.nav_previous_site',
            next: '.nav_next_site',
            last: '.nav_last_site'
        },
        thumb_indicator: SS + '/img/blankthumb.gif',
        thumb_content: 'arch_wrapp',
        main_indicator: 'arch_indicator',
        pagin_items_per_page: 35,
        pagin_element_status: '.arch_pagin_status', // przekazywany jako string (wtedy brany jest pod uwage
                                                     // parametr 'pagin_status_format' lub przekazywany jako
                                                     // tablica z obiektami ( [{selector:'.cla',status:'%ALL%'}] )
        pagin_status_format: '', // dostępne: %PAGE_ACTUAL%, %PAGE_LAST%, %ALL%
        pagin_count_format: '\u0141\u0105cznie zdj\u0119\u0107 w archiwum: %ALL%',
        msg: {
            msg: {
                icon: 'blue',
                text: 'U\u017cytkownik %U% nie {User.Profile.sex|["dodała","dodał"]} \u017cadnych zdj\u0119\u0107'
            },
            show: true,
            use_show_layout: false
        },
        show_page_in_url: false,
        page_name_in_url: 'strona',
        preview: '',
        clear_first: false // Czy czyścić content przed wypełnieniem HTML?
    },

    /**
     * @constructor
     * @param {Object} options
     **/
    initialize: function(options) {

        this.setOptions(options);

        this.thumb_content = $(this.options.thumb_content);
        if ( !this.thumb_content ) {
            return;
        }

        this.builder = new Fbl.Builder();

        this.pagin_previous_actual = 1; // strona ostatnio wybrana
        this.pagin_last = 0;
        this.is_first_load = true;
        this.request = new Request.JSON();
        this.msg = new Fbl.MsgBox(this.options.msg);

        $H(this.options.nav).each(function(element, index) {
            this['element_nav_' + index] = $$(element);
        }.bind(this));

        if ( $type(this.options.pagin_element_status) == "string" ) {
            this.pagin_element_status = $$(this.options.pagin_element_status);
        } else if ( $type(this.options.pagin_element_status) == "array" ) {
            this.pagin_element_status = this.options.pagin_element_status;
        }
        this.pagin_element_count  = $$(this.options.pagin_element_count);
        this.main_indicator = $(this.options.main_indicator);

        this.initEvents();

        var page = this.options.show_page_in_url ? parseInt(location.hash.split('-')[1]) : 1;
        page = page ? page : 1;
        this.paginOwn(page, true);

        this._initialize();
    },

    /**
     * Część dalasz konstruktora która może być wywołana w modelach
     * które dzidziczą po tej
     **/
    _initialize: function() { },

    /**
     * Inicjalizacja event-ów
     **/
    initEvents: function() {
        this.element_nav_first.each(function(element) {
            element.addEvent('click', function(event) {
                event.preventDefault();
                this.paginFirst();
            }.bind(this));
        }.bind(this));

        this.element_nav_previous.each(function(element) {
            element.addEvent('click', function(event) {
                event.stop();
                this.paginPrevious();
            }.bind(this));
        }.bind(this));

        this.element_nav_next.each(function(element) {
            element.addEvent('click', function(event) {
                event.stop();
                this.paginNext();
            }.bind(this));
        }.bind(this));

        this.element_nav_last.each(function(element) {
            element.addEvent('click', function(event) {
                event.stop();
                this.paginLast();
            }.bind(this));
        }.bind(this));

        if ( this.options.show_page_in_url ) {
            window.addEvent('hashChange', function() {
                var hashSplit = location.hash.substr(1).split('-');
                if ( hashSplit[0] != this.options.page_name_in_url )
                    return;
                var pageHash = parseInt(hashSplit[1]);
                if ( this.pagin_actual != pageHash ) {
                    this.paginOwn(pageHash);
                }
            }.bind(this));
        }
        this._initEvents();
    },

    /**
     * Część dalasz inicjalizacji event-ów która może być wywołana w modelach
     * które dzidziczą po tej
     **/
    _initEvents: function() { },

    /**
     * Paginacja: Przejdź do pierwszej strony
     * @return {Fbl.Manager.Base}
     **/
    paginFirst: function() {
        if ( this.pagin_actual <= 1 ) {
            return false;
        }
        this.pagin_actual = 1;
        this._setPageUrl();
        this._requestPagin();
        this.fireEvent('paginChange', [
            'first', this.pagin_actual, Math.ceil( this.pagin_last / this.options.pagin_items_per_page)
        ], this);
        
        return this;
    },

    /**
     * Paginacja: Przejdź do poprzedniej strony
     * @return {Fbl.Manager.Base}
     **/
    paginPrevious: function() {
        if ( this.pagin_actual > 1 ) {
            this.pagin_actual --;
        } else {
            return false;
        }
        this._setPageUrl();
        this._requestPagin();
        this.fireEvent('paginChange', [
            'previous', this.pagin_actual, Math.ceil( this.pagin_last / this.options.pagin_items_per_page)
        ], this);
        
        return this;
    },

    /**
     * Paginacja: Przejdź do następnej strony
     * @return {Fbl.Manager.Base}
     **/
    paginNext: function() {
        if ( this.pagin_actual * this.options.pagin_items_per_page < this.pagin_last ) {
            this.pagin_actual ++;
        } else {
            return false;
        }
        this._setPageUrl();
        this._requestPagin();
        this.fireEvent('paginChange', [
            'next', this.pagin_actual, Math.ceil( this.pagin_last / this.options.pagin_items_per_page)
        ], this);
        
        return this;
    },

    /**
     * Paginacja: Przejdź do ostatniej strony
     * @return {Fbl.Manager.Base}
     **/
    paginLast: function() {
        if ( this.pagin_actual * this.options.pagin_items_per_page >= this.pagin_last ) {
            return false;
        }
        var lastSite = this.pagin_last / this.options.pagin_items_per_page;
        this.pagin_actual = Math.ceil(lastSite);
        this._setPageUrl();
        this._requestPagin();
        this.fireEvent('paginChange', [
            'last', this.pagin_actual, Math.ceil( this.pagin_last / this.options.pagin_items_per_page)
        ], this);
        
        return this;
    },

    /**
     * Paginacja: Przejdź do własnej strony
     * @param {Number} value    - Numer strony
     * @param {Boolean} force   - Flaga determinująca czy przejście na stronę ma być poprzedzonę
     *                            sprawdzaniem czy taka strona istnieje
     * @return {Fbl.Manager.Base}
     **/
    paginOwn: function(value, force) {
        value = parseInt(value);
        if ( !force ) {
            var max = Math.ceil( this.pagin_last / this.options.pagin_items_per_page);
            value = ( value >= max ) ? max : value;
            value = ( value <= 0 ) ? 1 : value;
        }
        this.pagin_actual = value;
        this._setPageUrl();
        this._requestPagin();
        
        return this;
    },

    /**
     * Paginacja: Obsługa żądać xmlHttpRequest
     **/
    _requestPagin: function() {
        var cacheId = this._getCacheId();

        if ( this.hasCache(cacheId) ) {
            this['onRequest' + this.options.preview.capitalize()].call(this);
            this['onSuccess' + this.options.preview.capitalize()](this.getCache(cacheId));
        } else {
            var userName;
            try {
                userName = Fbl.Store.get('User.uid'); // archiwum w show, tam istnieje User
            } catch (e) {
                userName = Fbl.Stoe.get('Session.uid'); // znajomi, rev. tam istnieje Session
            }
            this._request(
                {user_name: userName},
                this['onRequest' + this.options.preview.capitalize()].bind(this),
                this['onSuccess' + this.options.preview.capitalize()].bind(this)
            );
        }
    },

    /**
     * Zaktualizuj hash strony w url
     **/
    _setPageUrl: function() {
        if ( this.pagin_actual == 1 && this.pagin_previous_actual == 1 ) {
            return;
        }
        if ( this.options.show_page_in_url ) {
            location.hash = this.options.page_name_in_url + '-' + this.pagin_actual;
        }
    },

    /**
     * Ukryj pasek nawigacyjny
     **/
    hideNavigation: function() {
        $H(this.options.nav).each(function(value) {
            $$(value).hide();
        });
        // @TODO if array
        this.pagin_element_status.hide();
    },

    /**
     * Pokaż pasek nawigacyjny
     **/
    showNavigation: function() {
        $H(this.options.nav).each(function(value) {
            $$(value).show();
        });
        // @TODO if array
        this.pagin_element_status.show();
    },

    /**
     * Zaktualizuj wszystkie dane dotyczące paginacji (nr_strony, ost_strona itp)
     * @return {Object} vars
     **/
    setPaginStatus: function() {
        var vars = this.getPaginVars();
        var fn = function(str) {
            if ( !str ) return '';
            return str.replace('%START%', vars.actual_item)
                //.replace('%END%', end)
                .replace('%PAGE_ACTUAL%', vars.actual_page)
                .replace('%PAGE_LAST%', vars.last_page )
                .replace('%ALL%', vars.last_item);
        };

        if ( $type(this.options.pagin_element_status) == "string" ) {
            this.pagin_element_status.set('html', fn(this.options.pagin_status_format));

        } else if ( $type(this.pagin_element_status) == "array" ) {
            this.pagin_element_status.each(function(value) {
                $$(value.selector).each(function(el) {
                    if ( el.get('tag') == 'input' ) {
                        el.set('value', fn(value.status));
                        return;
                    }
                    el.set('text', fn(value.status));
                })
            }.bind(this));
        }
        this.pagin_element_count.each(function(element) {
            element.set('text', this.options.pagin_count_format.replace('%ALL%', this.pagin_last));
        }.bind(this));
        return vars;
    },

    /**
     * Pobierz zmienne dotyczące paginacji
     * @return {Object}
     **/
    getPaginVars: function() {
        /*var end = this.pagin_actual * this.options.pagin_items_per_page >= this.pagin_last ?
            this.pagin_last : this.pagin_actual * this.options.pagin_items_per_page;*/
        this.pagin_previous_actual = this.pagin_actual;
        var start = this.pagin_actual * this.options.pagin_items_per_page - this.options.pagin_items_per_page + 1;
        return {
            last_page   : Math.ceil( this.pagin_last / this.options.pagin_items_per_page),
            actual_page : this.pagin_actual,
            last_item   : this.pagin_last,
            actual_item : start
        };
    },

    /**
     * Metoda pokazująca indicatory przy rozpocząciu żądania xmlHttpRequest
     **/
    onRequest: function() {},

    /**
     * Ciąg dalszy metody która może zostać wykorzystywana w modelach
     * dzidziczących po tej
     **/
    _onRequest: function() {},

    /**
     * Metoda pokazująca wpisy zdjęć po poprawnym zwrocie danych z serwera
     * @param {Object} response - Dane z serwera
     **/
    onSuccess: function(response) {},

    /**
     * Ciąg dalszy metody która może zostać wykorzystywana w modelach
     * dzidziczących po tej
     **/
    _onSuccess: function() {},

    /**
     * Metoda wykonująca preload zdjęć
     * @param {Element} archSingle - Pojedyńczy element reprezentujący jedne blok
     **/
    preloadImg: function(archSingle) {
        var target, indicator;
        
        target = archSingle.getElement('img.arch_thumb');
        indicator = archSingle.getElement('img.arch_indicator');

        var fn = function() {
            target.show();
            if ( indicator ) {
                indicator.hide();
            }
        };
        if ( Browser.opera/* || this.pagin_actual == 1 && Browser.Engines.trident()*/ ) {
            fn.delay(150);
            return;
        }
        fn.delay(150);
        new Asset.image(target.src, {
            onload: function() {fn();}
        });
    },

    /**
     * Ciąg dalszy metody która może zostać wykorzystywana w modelach
     * dzidziczących po tej
     **/
    _preloadImg: function() {},

    /**
     * Metoda zwraca parametry dotyczące filtru wg kategorii i daty dodania
     * @private
     * @return {Object} params - { kategoria:123, miesiac: 122009}
     **/
    _detectFiltr: function() {
        var params = {}, data = location.pathname.match(/archiwum\/(\w+)\/(?:(\d+)(\/(\d+))?|(\w+))/);
        
        if ( data != null ) {
            var type = data[1];
            if ( type == 'kategoria' ) { // kategoria || rodzaj
                params[type] = data[2];
            } else if ( type == 'miesiac' ) { // po dacie
                params[type] = data[2] + data[4];
            } else if ( type == 'rodzaj' ) {
                params[type] = data[5];
            }
        }
        return params;
    },

    /**
     * Kesz Id
     * @private
     * @return {String}
     **/
    _getCacheId: function() {
        return this.options.cache_prefix + this.pagin_actual;
    },

    /**
     * Metoda wykonująca żądania xmlHttpRequest
     * @param {Object} data - Obiekt danych wysyłanych na serwer
     * @param {Function} onRequest  - Funkcja callback wywoływana w momencie
     *                                rozpoczęcia żadania
     * @param {Function} onSuccess  - Funkcja callback wywoływana w momencie
     *                                poprawnego pobrania danych z serwera
     **/
    _request: function(data, onRequest, onSuccess) {
        var required = {
            limit: this.options.pagin_items_per_page,
            page: this.pagin_actual
        };
        data = Object.merge(required, data)
        data = Object.merge(data, this._detectFiltr());
        data = Object.merge(data, Fbl.Store.get('Security', {}));

        if ( this.request.running ) {
            this.request.cancel();
        }
        this.request = new Request.JSON({
            url: this.options.request_url,
            method: this.options.request_method,
            data: Object.toQueryString(data),
            onRequest: onRequest,
            onSuccess: function(data) {
                if ( data.result == 'ok' ) {
                    this.setCache(this._getCacheId(), data.response);
                    onSuccess(data.response);
                }
            }.bind(this)
        }).send();
    }
});
}.call(Fbl));

(function() {
this.Manager = this.Manager || {};
var Storage = this.Manager.Storage = this.Manager.Storage || {};
/**
 * Manager do obsługi historii na poziomie ciasteczke
 * @class Fbl.Manager.Storage.Cookie
 **/
Storage.Cookie = new Class({
    storageOptions: {},
    /**
     * @constructor
     **/
    initialize: function(options) {
        this.storageOptions = options;
    },

    /**
     * Pobranie danych ze storage
     * @param {String} key  - Id danych
     * @return {Mixed}
     **/
    get: function(key) {
        return Cookie.read(key);
    },

    /**
     * Pobranie danych ze storage
     * @param {String} key  - Id danych
     * @param {Mixed} value - Dane do zapisu
     * @return {Fbl.Manager.Storage.Cookie}
     **/
    set: function(key, value) {
        Cookie.write(key, value, this.storageOptions);
        return this;
    },

    /**
     * Usunięcie danych ze storage
     * @param {String} key  - Id danych
     * @return {Fbl.Manager.Storage.Cookie}
     **/
    remove: function(key) {
        return Cookie.dispose(key, this.storageOptions);
    }
});

/**
 * Obudowa do globalnego zapisu danych jesli przeglądarka nie obsługuje local i session storage
 * @class Fbl.Manager.Storage.Global
 **/
Storage.Global = new Class({
    /**
     * @var {String} Nazwa zmiennej globalnej
     **/
    key : '_globalStorage',

    /**
     * @constructor
     **/
    initialize: function() {
        window[this.key] = window[this.key] ? window[this.key] : {};
    },

    /**
     * Usuwanie itema
     * @param {String} key
     * @return {String} Usuwany item
     **/
    removeItem: function(key) {
        var removed = this.getItem(key);
        window[this.key][key] = null;
        return removed;
    },
    remove: function() {return this.removeItem.apply(this, arguments)},

    /**
     * Ustawianie do zmiennej
     * @param {String} key
     * @param {String} value
     * @return {String} Dodawana wartość
     **/
    setItem: function(key, value) {
        window[this.key][key] = value;
        return value;
    },
    set: function() {return this.setItem.apply(this, arguments)},

    /**
     * Pobieranie itema
     * @param {String} key
     **/
    getItem: function(key) {
        return window[this.key][key];
    },
    get: function() {return this.getItem.run(this, arguments)}
});

/**
 * Manager do obsługi historii na poziomie sessionStorage (html5)
 * @class Fbl.Manager.Storage.Web
 **/
Storage.Web = new Class({
    testKey     : 'Fbl.Manager.Storage.Web',
    useStorage  : false,
    durationRegExp  : new RegExp('#-#(\\d+)'),
    storageOptions  : {
        duration    : 1 // 1 dzień
    },
    supported   : false,

    /**
     * @constructor
     **/
    initialize: function(options) {
        this.storageOptions = options;
        this._test();
    },

    /**
     * Pobranie danych ze storage
     * @param {String} key  - Id danych
     * @return {Mixed}
     **/
    get: function(key) {
        if ( !this.useStorage ) return false;
        var data = this.useStorage.getItem(key);
        if ( data ) {
            var match = data.match(this.durationRegExp);
            if ( !match ) return false;
            if ( match.length ) {
                // jeśli wygasło - usuń
                if ( parseInt(match[1]) > new Date().getTime() - this.storageOptions.duration * 86400000 ) {
                    data = data.replace(this.durationRegExp, '');
                } else {
                    this.useStorage.removeItem(key);
                    data = null;
                }
            }
        }
        return data;
    },

    /**
     * Zapis danych do storage
     * @param {String} key      - Id danych
     * @param {String} value    - Dane do zapisu
     * @return {Fbl.Manager.Storage.Web}
     **/
    set: function(key, value) {
        if ( !this.useStorage ) return false;
        value = value ? '' + value + '' : '';
        if ( !String.trim(value) ) return false;
        if ( value.test(this.durationRegExp) ) {
            value.replace(this.durationRegExp, '#-#' + new Date().getTime());
        } else {
            value = value + '#-#' + new Date().getTime();
        }
        this.useStorage.setItem(key, value);
        return this;
    },

    /**
     * Usunięcie danych ze storage
     * @param {String} key  - Id danych
     * @return {Fbl.Manager.Storage.Web}
     **/
    remove: function(key) {
        if ( !this.useStorage ) return false;
        this.useStorage.removeItem(key);
        return this;
    },

    /**
     * Czyszczenie całęgo storaga
     * @return {Fbl.Manager.Storage.Web}
     **/
    clear: function() {
        if ( !this.useStorage ) return false;
        try {
            this.useStorage.clear();
        } catch (e) {}
        return this;
    },

    /**
     * Sprawdzanie czy przeglądarka obsługuje storage
     * @return {Boolean}
     **/
    isSupported: function() {
        return this.supported;
    },

    /**
     * Sprawdzenie dostępności storage przeglądarki
     **/
    _test: function() {
        try {
            localStorage.setItem(this.testKey, "OK"); // ~150ms
            if ( localStorage.getItem(this.testKey) === "OK" ) {
                localStorage.removeItem(this.testKey);
                this.useStorage = localStorage;
                this.supported = true;
            }
        } catch(e) {
            try {
                sessionStorage.setItem(this.testKey, "OK"); //  ~3ms
                if ( sessionStorage.getItem(this.testKey) === "OK" ) {
                    sessionStorage.removeItem(this.testKey);
                    this.useStorage = sessionStorage;
                    this.supported = true;
                }
            } catch(e) {
                if ( Browser.Engines.ie ) { // IE 7 <
                    this.useStorage = {
                        setItem     : $empty,
                        getItem     : $empty,
                        removeItem  : $empty,
                        clear       : $empty
                    }
                    this.supported = false;
                } else {
                    this.useStorage = new Fbl.Manager.Storage.Global();
                    this.supported = false;
                }
            }
        }
    }
});
}.call(Fbl));

(function() {
/**
 * Model do obserwowania ucieczki ze stron na jakich aktualnie użytkownik
 * się znajduje wraz z sprawdzaniem zmian jakie dokonał prze wyjściem
 * @class Fbl.ChangeChecker
 **/
this.ChangeChecker = new Class({
    Implements: [Options, Events],
    Bind: ['check', 'observe'],

    options: {
        init_on_start   : true,
        links   : 'a[href!=#]',
        events : [
            {tag: 'input', event: 'focus'},
            {tag: 'select', event: 'change'},
            {tag: 'div', event: 'mousedown'},
            {tag: 'a', event: 'click'}
        ]

    },
    _activate: true,

    /**
     * @constructor
     * @param {String|Elements} elements
     * @param {Object} options
     **/
    initialize: function(elements, options) {
        if ( !$$(elements) ) return;
        this.setOptions(options);
        this.elements = $$(elements);
        this.changed = false;
        if ( this.options.init_on_start )
            this.initEvents();
    },

    /**
     * Inicjalizacja event-ów
     * @param {String} action - Rodzaj akcji eventów 'addEvent' | 'removeEvent'
     * @return {Fbl.ChangeChecker}
     **/
    initEvents: function(action) {

        if ( !action ) {
            action = 'addEvent';
        }
        this.options.events.each(function(obj) {
            this.elements.each(function(element) {
                if ( element.get('tag') == obj.tag ) {
                    var fn = function(event){this.check(event, this)}.bind(this);
                    element[action](obj.event, fn);
                }
            }.bind(this));
        }.bind(this));

        $$(this.options.links).each(function(element) {
            var fn = function(event){this.observe(event, this)}.bind(this);
            element[action]('click', fn);
        }.bind(this));

        return this;
    },

    /**
     * Reinicjalizacja event-ów
     **/
    reinitEvents: function() {
        this.initEvents('removeEvents').initEvents('addEvent');
    },

    /**
     * Metoda wywoływana w momecie wykrycia zmian
     **/
    check: function() {
        this.changed = true;
        this.fireEvent('change', [this]);
    },

    observe: function(event, checker) {
        if ( checker.changed && this._activate ) {
            this.fireEvent('observeClick', [event.target, this]);
            event.stop();
        }
    },

    activate: function() {
        this._activate = true;
    },
    deactivate: function() {
        this._activate = false;
    }
});
}.call(Fbl));

(function() {
/**
 * Model do rozszeżenia w innych modelach, pomocny przy tworzeniu paginacji
 * @class Fbl.Paginator
 */
this.Paginator = new Class({
    Implements: [Options, Events],
    options: {
        navi_first  : '',
        navi_prev   : '',
        navi_next   : '',
        navi_last   : '',
        max_pages: 0,   // maksymalna ilość stron (jeśli znamy)
        per_page: 0,    // ilość itemów na strone
        items: ''       // itemy
    },
    page: 1,
    max_pages: 1,

    /**
     * @constructor
     */
    initialize: function(options) {
        this.setOptions(options);
        this.max_pages = this.options.max_pages;
        var initEvents = false;
        ['first', 'prev', 'next', 'last'].each(function(item) {
            if ( this.options['navi_' + item] ) {
                initEvents = true;
            }
        }.bind(this));
        if ( initEvents ) {
            this.initEvents();
        }
    },

    initEvents: function() {
        ['first', 'prev', 'next', 'last'].each(function(item) {
            var els = this.options['navi_' + item] || [];
            if ( els.length ) {
                els.invoke('addEvent', ['click', this[item].bind(this)]);
            }
        }.bind(this));
    },

    /**
     * Pierwsza strona
     */
    first: function(event) {
        if ( event instanceof Event ) {
            event.stop();
        }
        this.page = 1;
        this.fireEvent('first', this.page);
    },

    /**
     * Następna strona
     * @return {Boolean}
     */
    next: function(event) {
        if ( event instanceof Event ) {
            event.stop();
        }
        this._calculateMaxPages();
        if ( this.page < this.max_pages ) {
            ++ this.page;
            this.fireEvent('next', this.page);
            return true;
        }
        return false;
    },

    /**
     * Poprzednia strona
     * @return {Boolean}
     */
    prev: function(event) {
        if ( event instanceof Event ) {
            event.stop();
        }
        if ( this.page > 1 ) {
            -- this.page;
            this.fireEvent('prev', this.page);
            return true;
        }
        return false;
    },

    /**
     * Ostatnia strona
     */
    last: function(event) {
        if ( event instanceof Event ) {
            event.stop();
        }
        this.page = this.max_pages;
        this.fireEvent('last', this.page);
    },

    isFirst: function() {
        return this.page == 1;
    },

    isLast: function() {
        this._calculateMaxPages();
        var m = this.options.max_pages || this.max_pages;
        return this.page == m;
    },

    /**
     * Pobieranie aktualnego numeru strony
     * @return {Number}
     */
    getPage: function() {
        this.update();
        return this.page || 1;
    },

    update: function() {
        this._calculateMaxPages();
        if ( this.page > this.max_pages ) {
            this.page = this.max_pages;
        }
        return this._calculateMaxPages();
    },

    /**
     * Oblicz maksymalną ilość stron
     * @private
     */
    _calculateMaxPages: function() {
        if ( this.options.per_page ) {
            this.max_pages = Math.ceil(($$(this.options.items).length || 1 )/ this.options.per_page);
        }
        return this;
    }
});
}.call(Fbl));

(function() {
/**
 * Model obsługujący animacje poklatkowe
 * @class Fbl.PhotoAnimator
 */
this.PhotoAnimator = new Class({
    Implements: [Options],
   
    Binds: ['onMouseEnter', 'onMouseLeave'],

    options: {
       duration: 250,
       step: {x: null, y: null},
       auto_trigger: false
    },
    _blankSrc: SS + '/images/blank.gif',
    _timer: null,
    _ready: false,
    _width: 0,
    _height: 0,
    _lastAction: '',
    _tX: null,
    _tY: null,

    initialize: function(element, options) {
       this.setOptions(options);
       this.wrapperElement = $(element);
       this.canvasElement = this.wrapperElement.getElement('img.photo_animator_canvas');
       if ( this.wrapperElement.retrieve('fbl.photo-animator') ) {
           return this.wrapperElement.retrieve('fbl.photo-animator');
       }
       this.wrapperElement.store('fbl.photo-animator', this);
       this.initEvents();

       return this;
    },

    initEvents: function() {
        if ( this.options.auto_trigger ) {
            this.wrapperElement.addEvent('mouseenter', this.onMouseEnter);
            this.wrapperElement.addEvent('mouseleave', this.onMouseLeave);
        }
    },

    onMouseEnter: function() {
       this.start();
    },

    onMouseLeave: function() {
       this.stop();
    },

    start: function() {
       if ( this._lastAction == 'start' ) {
           return this;
       }
       this._lastAction = 'start';
       if ( !this._ready ) {
           this._preload();
           return this;
       }
       if ( this.canvasElement.get('src') != this._blankSrc ) {
           this.canvasElement.set('src', this._blankSrc);
       }
       if ( this._timer ) {
           this.stop();
       }
       this._start();

       return this;
    },

    stop: function() {
       if ( this._lastAction == 'stop' ) {
           return this;
       }
       this._stop();

       return this;
    },

    _start: function() {
       this._timer = this._loop.periodical(this.options.duration, this);
    },

    _stop: function() {
       this._lastAction = 'stop';
       clearInterval(this._timer);
       this._timer = this._tX = this._tY = null;
       this._updatePosition('0 0');  
    },

    _loop: function() {
       if ( this.options.step.x ) {
           if ( this._tX === null ) {
               this._tX = 0;
           }
           this._tX += this.options.step.x;
           if ( this._tX >= this._width ) {
               this._tX = 0;
           }
       }
       if ( this.options.step.y ) {
           if ( this._tY === null ) {
               this._tY = 0;
           }
           this._tY += this.options.step.y;
           if ( this._tY >= this._height ) {
               this._tY = 0;
           }
       }
       var p, origP = (this.canvasElement.getStyle('backgroundPosition') || '').split(' ');

       p = [
           this._tX !== null ? - this._tX + 'px': origP[0], 
           this._tY !== null ? - this._tY + 'px': origP[1]
       ].join(' ');
       this._updatePosition(p);
    },

    _updatePosition: function(pos) {
       this.canvasElement.setStyle('backgroundPosition', pos);
    },

    _preload: function() {
       Asset.image(this._getSrc(), {
           onload: function(img) {
               this._width = img.width;
               this._height = img.height;
               if ( this._width && this._height ) {
                   this._ready = true;
                   this.canvasElement.setStyle('backgroundImage', 'url("' + img.src + '")');
                   this.canvasElement.set('src', this._blankSrc);
                   if ( this._lastAction ) {
                       this['_' + this._lastAction]();
                   }
               }
           }.bind(this)
       });
    },

    _getSrc: function() {
       return this.canvasElement.get('data-video-preview').replace(/url\((.+)\)/, '$1').replace(/"|'/g, '');
    }
});
}.call(Fbl));

(function() {
this.Implements = this.Implements || {};
this.Implements.Elements = new Class({
    elements: {},

    setElements: function() {
        new Hash(this.elements).each(function(value, key) {
            // jeśli selektor rozszerzony to daj array jako nieznaleziony
            var a = /(\.|\#|\[|\])/.test(value) ? [] : null;
            this[key] = $(value) ? $(value) : ($$(value).length ? $$(value) : a);
        }.bind(this));
    }
});
}.call(Fbl));

/**
 * @class Fbl.Observer
 */
(function() {
var model = new new Class({Implements: Events});

this.Observer = {
    addEvent: function(type, fn, internal) {
        model.addEvent(type, fn, internal);
        
        return this;
    },
    
    fireEvent: function(type, args, delay) {
        model.fireEvent(type, args, delay);
        
        return this;
    }
};
}.call(Fbl));

/**
 * Model do zapisywania/odczytywania zmiennych przekazywane poprzez backend itp.
 * Nowy model, zamiennik dla Fbl.Storage
 * 
 * @class Fbl.Store
 */
(function() {
var data = {}, _get, _set;

_get = function(key, subdata) {
    var result = null, i;

    if ( subdata === undefined ) {
        subdata = data;
    }
    for ( i in subdata ) {
        if ( subdata.hasOwnProperty(i) && key[0] == i ) {
            if ( key.length <= 1 ) {
                result = subdata[i];
                break;
            }
            subdata = subdata[i];
            if ( subdata === null || subdata === undefined ) {
                result = null;
                break;
            }
            key = key.erase(i);
            
            if ( key.length == 1 ) {
                result = subdata[key] === undefined ? null : subdata[key];
            } else {
                result = _get(key, subdata);
            }
            break;
        }
    }

    return result;  
};
_set = function(key, value) {
    var subdata = {}, i, len;

    key = key.reverse();
    len = key.length;
    for ( i = 0; i < len; i++ ) {
        if ( i == 0 ) {
            subdata[key[0]] = value;
        } else {
            subdata[key[i]] = Object.clone(subdata);
            delete subdata[key[i - 1]];
        }
    }
    data = Object.mergeExcludeClass(data, subdata);
    if ( value.$constructor ) {
        eval('data' + '["' + key.reverse().join('"]["') + '"] = value;');
    }
};
this.Store = {
    get: function(key, defaultValue) {
        var value = _get(key.split('.'));
        
        return value !== null ? value : (defaultValue === undefined ? null : defaultValue);
    },
    
    set: function(key, value) {
        _set(key.split('.'), value);
        
        return this;
    },
    
    erase: function(key) {
        _set(key.split('.'), null);
        
        return this;
    },
    
    fromJson: function(jsonData) {
        data = Object.mergeExcludeClass(data, jsonData || {});
        
        return this;
    },
    
    isOwner: function() {
        return this.get('User.uid') == this.get('Session.uid');
    },
    
    isLogged: function() {
        return this.get('Session.uid') ? true : false;
    }
};
// alias
Object.each(this.Store, function(value, key) {
    Fbl[key] = value;
});
}.call(Fbl));

(function() {
var mergeOneExcludeClass = function(source, key, current) {
	switch (typeOf(current)) {
		case 'object':
            if ( current.$constructor ) {
                source[key] = current;
                break;
            }
			if ( typeOf(source[key]) == 'object' ) {
                Object.mergeExcludeClass(source[key], current);
            } else {
                source[key] = Object.clone(current);
            }
            break;
		case 'array':
            source[key] = current.clone(); 
            break;
		default:
            source[key] = current;
	}
	return source;
};

Object.extend({
	mergeExcludeClass: function(source, k, v){
		if (typeOf(k) == 'string') return mergeOneExcludeClass(source, k, v);
		for ( var i = 1, l = arguments.length; i < l; i++ ) {
			var object = arguments[i];
			for ( var key in object ) {
                mergeOneExcludeClass(source, key, object[key]);
            }
		}
		return source;
	}
});
}.call());

/**
 * Implementacja nowych własności obiektu Number
 **/
Number.implement({
    /**
     * Pobieranie słowa z przekazanej tablicy według numeru
     * @see Array#getByCount
     * @param {Array} list - Tablica 3-elementowa
     * @return {String}
     **/
    getByCount: function(list) {
        return list.getByCount(this);
    },
    /**
     * Pobieranie wartości według przekazanego warunku
     * @param {Array|string} conditions - Warunki przekazane jako string rozdzielone przecinkiem lub w tablicy
     *                                    gdzie %X% jest zastępowany numerem (this)
     * @param {Array|String} values - W zależności z których warunków będzie się spełniał taka
     *                                wartość zostanie zwrócona
     * @return {String}
     **/
    getIf: function(conditions, values) {
        if ( $type(conditions) != 'array' ) {
            conditions = conditions.split(',');
        }
        if ( $type(values) != 'array' ) {
            values = values.split(',');
        }
        for (var i = 0; i < conditions.length; i++) {
            if ( conditions[i] != null ) {
                if ( eval(conditions[i].replace(/%X%/g, this)) ) {
                    return values[i];
                }
            } else {
                return values[i];
            }
        }
        return '';
    },
    /**
     * Pobranie wartości mówiącej o percepcji koloru
     * @see Array#getPerceivedLightness
     * @param {String} color - kolor do porównania
     **/
    getPerceivedLightness: function (color) {
        return color.hexToRgb(true).getPerceivedLightness();
    }
});
/**
 * Implementacja nowych własności obiektu Number
 **/
String.implement({
    /**
     * Powtarzanie się tekstu
     * @param {Number} count - Ilość powtórzeń danego stringa
     **/
    repeat: function(count) {
        return new Array(count + 1).join(this);
    },
    stripHtml: function() {
        return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
    },
    htmlSpecialCharsDecode: function() {
        var hash_map = {}, symbol = '', tmp_str = '',
            entity = '';tmp_str = this.toString();

        hash_map = {
            '&': '&amp;',
            '"': '&quot;',
            "'": '&#039;',
            '<': '&lt;',
            '>': '&gt;'
        };
        delete(hash_map['&']);
        hash_map['&'] = '&amp;'; 
        for (symbol in hash_map) {
            if ( !hash_map.hasOwnProperty(symbol) ) {
                continue;
            }
            entity = hash_map[symbol];
            tmp_str = tmp_str.split(entity).join(symbol);
        }
        tmp_str = tmp_str.split('&#039;').join("'");

        return tmp_str;
    },
    /**
     * Funkcja do tworzenia linków HTML-owych według znajdujących się
     * w tekście linków URL
     * @param {String} linkType - Typ linku 'relative'|'all'. 'relative'
     *                            tworzy linki które kierują na tą samą domene co w opcjach
     * @param {Object} options  - Opcje dodatkowe
     **/
    autoLink: function(linkType, options) {
        options = options || {};
        options = $merge({
            domain      : ['nfbl', 'photoblog', 'fbl'], // Domeny dla opcji 'relative'
            global      : ['pl'],                       // Kraj domeny
            max_length  : 65,                           // Maksymalna długość tekstu linku
            post_string : '...',                        // Tekst wstawiany po przekroczeniu długości max_length
            matches     : null,                         // Funkcja callback zwracająca pasujące linki
            link_attr   : ''                            // Dodatkowe atrybuty linku
        }, options);
        linkType = linkType || 'relative';
        var pattern = "(<\\w+.*?>|[^=!:\'\"/]|^)((?:https?://)|(?:www\\.)|(?:https?://www\\.)|(?:[a-zA-Z0-9\\.]+\\.))((?:[a-zA-Z0-9\\.]+\\.)?%DOMAIN%%GLOBAL%(?::\\d+)?(?:\\/(?:(?:[\\~\\w\\+%-]|(?:[,.;:][^\\s$]))+)?)*(?:\\?[\\w\\+%&=.;-]+)?(?:\\#[\\w\\-\\/\\.,]*)?)([!#$%'()*+,\\-./:;<=>?@[\\\\]^_'{|}~]|\\s|<|$)";
        if ( linkType == 'all' ) {
            pattern = pattern.
                replace('%DOMAIN%', '[-\\w]+').
                replace('%GLOBAL%', '(?:\\.[a-zA-Z]+)+');
        } else if ( linkType == 'relative' ) {
            pattern = pattern.
                replace('%DOMAIN%', '(?:' + options.domain.join('|') + ')').
                replace('%GLOBAL%', '(?:\\.(?:' + options.global.join('|') + '))+');
        }
        var result = this;

        var match = this.match(new RegExp(pattern, 'ig'));
        if ( match ) {
            if ( $type(options.matches) == 'function' ) {
                options.matches(match.filter(function(item){return item.replace(/\n*/)}));
                return false;
            }
            // filtrowanie
            for ( var i = 0, len = match.length; i < len; i++ ) {
                match[i] = String.trim( String.clean( match[i] ) );
            }
            // zamienianie
            match.each(function(link) {
                var linkText = link;
                var linkNoHttp = link.replace('http://', '');
                if ( linkText.length >= options.max_length ) {
                    linkText = linkNoHttp.substr(0, options.max_length) + options.post_string;
                } else {
                    linkText = linkText.replace('http://', '');
                }
                result = result.replace(link, ' <a ' + options.link_attr + ' href="http://'
                    + linkNoHttp + '" >http://' + linkText + '</a>');
            });
        }
        return result + '';
    },
    ucFirst: function() {
        return this.substr(0, 1).toUpperCase() + this.substr(1, this.length);
    },
    compareColors: function(compare_color) {
        var rgb1 = this;
        if ( $type(this) == 'string' ) {
            rgb1 = this.hexToRgb(true);
        }
        var r1, r2, g1, g2, b1, b2;
        r1 = rgb1[0];g1 = rgb1[1];b1 = rgb1[2];
        var rgb2 = compare_color;
        if ( $type(compare_color) == 'string' ) {
            rgb2 = compare_color.hexToRgb(true);
        }
        r2 = rgb2[0];g2 = rgb2[1];b2 = rgb2[2];
        return (Math.max(r1, r2) - Math.min(r1, r2)) +
            (Math.max(g1, g2) - Math.min(g1, g2)) +
            (Math.max(b1, b2) - Math.min(b1, b2));
    },
    htmlEncode: function() {
        return this.replace(/&/g, "&amp;").
            replace(/</g, "&lt;").
            replace(/>/g, "&gt;").
            replace(/\"/g, "&quot;");
    },
    htmlDecode: function() {
        return this.replace(/&amp;/g, "&").
            replace(/&lt;/g, "<").
            replace(/&gt;/g, ">").
            replace(/&quot;/g, "\"");
    },
    getIf: function(conditions, values) {
        var value = parseFloat(this);
        if ( $type(value) == 'number' ) {
            return value.getIf(conditions, values);
        }
        return '';
    },
    /**
     * Tworzenie instacji żądania ajaxowego (Request)
     * @param {Object} opt  Opcje dodatkowe przekazane do modelu
     * @param {String} type Typ modelu JSON|HTML|null
     */
    request: function(opt, type) {
        var data = Object.merge(opt || {}, {url : this + ''});
        if ( type ) {
            return new Request[type.toUpperCase()](data);
        }
        return new Request(data);
    },
    /**
     * Zamiana zapytania stringowego (QueryString) na odpowiadający obiekt
     * @param {String} separator    Separator zapytań - domyślnie &
     * @return {Object}
     */
    toQueryParams: function(separator) {
        var match = this.trim().match(/([^?#]*)(#.*)?$/);
        if (!match) return { };

        var result = {};
        match[1].split(separator || '&').each(function(pair) {
            if ((pair = pair.split('='))[0]) {
                var key = decodeURIComponent(pair.shift()),
                value = pair.length > 1 ? pair.join('=') : pair[0];

                if (value != undefined) value = decodeURIComponent(value);

                if (key in result) {
                    if ( $type(result[key]) != 'array' ) {
                        result[key] = [result[key]]
                    }
                    result[key].push(value);
                }
                else result[key] = value;
            }
        });
        return result;
    }
});
(function() {
    var methods = {};
    ['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
        methods[method] = function(data){
            var object = {
                method: method,
                data: this.options.data
            };
            if (data != null) object.data = Object.merge(data, object.data);
            return this.send(object);
        };
    });

    Request.implement(methods);
}());
/**
 * Implementacja nowych własności obiektu Number
 **/
Array.implement({
    /**
     * Pobieranie słowa z przekazanej tablicy według numeru
     * @see Number#getByCount
     * @param {Number} count - Liczebnik
     * @return {String}
     **/
    getByCount: function(count) {
        var strCount = ('' + count), lastDigit;
        if ( count > 20 && strCount.length >= 2 ) {
            lastDigit = strCount.substr(strCount.length - 1, strCount.length) >> 0;
            if ( lastDigit >= 2 && lastDigit <= 4 ) {
                return this[2];
            }
        }
        if ( count == 0 || count >= 5 ) {
            return this[0];
        } else if ( count == 1 ) {
            return this[1];
        } else if ( count >= 2 && count <= 4 ) {
            return this[2];
        }
    },
    /**
     * Pobranie wartości mówiącej o percepcji koloru
     * @see Number#getPerceivedLightness
     * @return {Number} Wartość od 0 do 100
     **/
    getPerceivedLightness: function () {
        return Math.sqrt(this[0] * this[0] * 0.241 +
            this[1] * this[1] * 0.691 +
            this[2] * this[2] * 0.068) / 255
    },
    unique: function(objects){
        if ( objects ) {
            var result = [];
            this.each(function(item) {
                result.push(JSON.encode(item));
            });
            return [].combine(result).map(function(item) {
                return JSON.decode(item);
            });
        }
        return [].combine(this);
    },
    /**
     * Pobranie wartości różnicy między dwoma kolorami
     * @see Number#getPerceivedLightness
     * @return {Number} Wartość od 0 do 100
     **/
    compareColors: function(compare_color) {
        return String.compareColors(this, compare_color);
    },
    /**
     * Wywoływanie metody na każdym itemie w tablicy
     * @param {String} method Nazwa metody wykonywanej na itemie
     * @param {String|Array} arg Argumenty przekazywane do metody
     * @return {Array}
     **/
    invoke : function (method, arg) {
        this.each(function (el) {
            var fn = function (el) {
                el[method].run($type(arg) == 'array' ? arg : [arg], el);
            }
            $type(el) == 'array' ? el.each(fn) : fn(el);
        });
        return this;
    },
    /**
     * Sumowanie wartości
     * @return {Number}
     **/
    sum: function() {
        var result = 0;
        this.each(function(item, index) {
            if ( !index && $type(item) == 'string' ) {
                result = '';
            }
            result += item;
        });
        return result;
    },
    /**
     * Średnia wszystkich wartości w tablicy
     * @return {Number}
     **/
    avg: function() {
        return this.sum() / this.length;
    }
});

Element.implement({
    getPadding: function(type) {
        var prop = 'padding';
        if ( ['left', 'right', 'bottom', 'top'].indexOf(type) != -1 ) {
            return this.getStyle(type).toInt();
        }
        if ( type == 'width' ) {
            return this.getStyle(prop + '-right').toInt() + this.getStyle(prop + '-left').toInt();
        }
        if ( type == 'height' ) {
            return this.getStyle(prop + '-top').toInt() + this.getStyle(prop + '-bottom').toInt();
        }
        return this.getStyle(prop).split(' ').map(function(a) {return a.toInt()}).sum();
    },
    getMargin: function(type) {
        var prop = 'margin';
        if ( ['left', 'right', 'bottom', 'top'].indexOf(type) != -1 ) {
            return this.getStyle(type).toInt();
        }
        if ( type == 'width' ) {
            return this.getStyle(prop + '-right').toInt() + this.getStyle(prop + '-left').toInt();
        }
        if ( type == 'height' ) {
            return this.getStyle(prop + '-top').toInt() + this.getStyle(prop + '-bottom').toInt();
        }
        return this.getStyle(prop).split(' ').map(function(a) {return a.toInt()}).sum();
    }
});
/**
 * Zdarzenie 'documentTitleChange' obserwujące zmiany tytułu okna przeglądarki
 **/
Element.Events.documentTitleChange = {
    onAdd: function(fn) {
        var title = this.title;
        var old = title;
        var now = title;
        (function() {
            now = this.title;
            if ( old != now ) {
                old = this.title;
                fn.call(this, this);
            }
        }.periodical(500, this));
    }
};
Element.Events.popstate = {
    onAdd: function(fn) {
        window[Browser.ie ? 'attachEvent' : 'addEventListener']('popstate', function(event) {
            fn.call(this, event);
        }.bind(this), false);
    }
};

Element.implement({
	getPosition: function(relative){
		if ( (/^(?:body|html)$/i).test(this.tagName) ) {
            return {x: 0, y: 0};
        }
		var offset = this.getOffsets(), scroll = this.getScrolls();
		var position = {
			x: offset.x - scroll.x,
			y: offset.y - scroll.y
		};
        var relativePosition;
        if ( $type(relative) == 'element' && document.id(relative) ) {
            relativePosition = relative.getPosition();
        } else if ( relative === true ) {
            relativePosition = {x: 0, y: 0};
            var parent = this.parentNode;
            while (true) {
                if ( (/^(?:body|html)$/i).test(parent.tagName) ) {
                    break;
                }
                if ( parent.getStyle('position') == 'relative' ) {
                    relativePosition = parent.getPosition();
                    break;
                }
                parent = parent.parentNode;
            }

        } else {
            relativePosition = {x: 0, y: 0};
        }
		return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
	}
});

Elements.implement({
    getHeight: function() {
        var height = 0;
        this.each(function(el) {
            height += el.getHeight();
        });
        return height;
    }
});

/**
 * Funkcja do stopowania eventów
 **/
var $stop = function(e) {e.preventDefault()};

Browser.isMobile = !['mac', 'linux', 'win'].contains(Browser.Platform.name);

// Funkcja do ustawiania miejsca autopromocji 
window.addEvent('domready', function(){
    try {
        if($$('.adsense720')[0] && $('selfpromotion_banner') && !$$('.adsense720')[0].hasClass('empty_selfpromotion')){
            var itt = 0;
            function check4Ada(){
                if (
                    $$('.inside_container object')[0] || $$('.inside_container embed')[0] || $$('.inside_container')[0].getCoordinates().height > 0){
                    //  console.log('jest juz reklama');
                    if ($$('.inside_container')[0].getCoordinates().width < 800){
                        $('selfpromotion_banner').setStyles({
                            'width': $$('.ban_container')[0].getCoordinates().width - $$('.inside_container')[0].getCoordinates().width - $('selfpromotion_banner').getStyle('margin-left').toInt() - 2 + 'px',
                            'height': $$('.inside_container')[0].getCoordinates().height - $('selfpromotion_banner').getStyle('padding-top').toInt() - 2 + 'px' //2 px jakoze wskakuje ramka ktora jest liczona do wysokosci

                        })
                        $('selfpromotion_banner').setStyle('display', 'block')
                        $('selfpromotion_banner').addClass('highlight')

                        if ($('show_midoptions')){ // Show ?
                            if ($('show_midoptions').hasClass('show_midoptions_w'))
                                $('selfpromotion_banner').addClass('show_midoptions_w')
                            if ($('show_midoptions').hasClass('show_midoptions_b'))
                                $('selfpromotion_banner').addClass('show_midoptions_b')
                        }
                    }else{
                        $('selfpromotion_banner').setStyle('display', 'none')
                        $$('.adsense720')[0].addClass('empty_selfpromotion')
                    }
                    return true
                }else{
                    //console.log('nie ma flasza');
                    if (itt == 1){
                        $('selfpromotion_banner').setStyles({
                            'float': 'none', 
                            'margin': 'auto'
                        })
                        $('selfpromotion_banner').setStyle('display', 'block')
                    //  $('selfpromotion_banner').addClass('highlight')
                    }
                    //Reklama dolna
                    itt++
                    return false
                }
            }

            if (check4Ada() != true)
                check4Ada.delay(800);
        }

        if ($$('#banP3 div')[0] && $('banP3').getCoordinates().height > 0 && !$('banP3').hasClass('empty_selfpromotion')  && $('selfpromotion_banner2') ){

            function check4Adb(){
                //Reklama dolna
                try {
                    $('selfpromotion_banner2').setStyles({
                        'height': $('banP3').getCoordinates().height + 'px',
                        'width': $('overCommentsAd').getCoordinates().width - $('banP3').getCoordinates().width +'px'
                    })
                    if ($('show_midoptions').hasClass('show_midoptions_w'))
                        $('selfpromotion_banner2').addClass('show_midoptions_w')
                    if ($('show_midoptions').hasClass('show_midoptions_b'))
                        $('selfpromotion_banner2').addClass('show_midoptions_b')
                    return true;
                } catch(ex) {
                    return false;
                }
            }

            if (check4Adb() != true)
                check4Adb.delay(1600);
        }
    } catch(e) {}
});

