/*
---
name: XtLightbox

description: extendable lightbox Base

license: MIT-style

authors:
- Anton Suprun <kpobococ@gmail.com>

requires:
- Core:1.3/*
- More/Mask

provides: XtLightbox

...
*/
XtLightbox = new Class(
{
    Implements: [Options, Events],

    options: {
        // onAttach: function(element) {},
        // onDetach: function(element) {},
        // onShow: function(element) {},
        // onHide: function() {},
        // onNext: function(element) {},
        // onPrevious: function(element) {},
        // onClear: function() {},
        // onDestroy: function() {},
        adaptors: ['Image'],
        adaptorOptions: {},
        renderer: 'Lightbox',
        rendererOptions: {},
        preload: false,
        loop: false,
        closeKeys: ['esc'],
        nextKeys: ['right', 'space'],
        prevKeys: ['left'],
        hideArrowsFor: []
    },

    initialize: function(elements, options)
    {
        this.setOptions(options);
        this.loadAdaptors();
        this.loadRenderer();
        var self = this;
        this.onElementClick = function(e) {
            e.preventDefault();
            self.show(this);
        };
        $(document).addEvents({
            'keydown': function(e) {
                if (this.shown) {
                    if (this.options.closeKeys.contains(e.key)) {
                        e.stop();
                        this.hide();
                    } else if (this.options.prevKeys.contains(e.key)) {
                        e.stop();
                        this.previous();
                    } else if (this.options.nextKeys.contains(e.key)) {
                        e.stop();
                        this.next();
                    }
                }
            }.bind(this),
            'keypress': function(e) {
                if (this.shown) {
                    if (this.options.closeKeys.contains(e.key)) {
                        e.stop();
                        this.hide();
                    } else if (this.options.prevKeys.contains(e.key)) {
                        e.stop();
                        this.previous();
                    } else if (this.options.nextKeys.contains(e.key)) {
                        e.stop();
                        this.next();
                    }
                }
            }
        });
        this.attach(elements);
    },

    loadAdaptors: function()
    {
        if (this.adaptors && this.adaptors.length > 0) return this;
        var adaptors = this.options.adaptors || ['Image'];
        this.adaptors = {};
        var valid = [];
        adaptors.each(function(name) {
            if (!XtLightbox.Adaptor[name]) return;
            var options = {};
            if (this.options.adaptorOptions && this.options.adaptorOptions[name]) options = this.options.adaptorOptions[name];
            var a = new XtLightbox.Adaptor[name](options);
            this.adaptors[name] = a
            valid.push(a.$name);
            if (a.hideArrows) this.options.hideArrowsFor.push(a.$name);
        }, this);
        this.options.adaptors = valid;
        return this;
    },

    loadRenderer: function()
    {
        var name = this.options.renderer;
        if (!name || !XtLightbox.Renderer[name]) name = 'Lightbox';
        this.renderer = new XtLightbox.Renderer[name](this.options.rendererOptions);
        this.renderer.addEvents({
            next: this.next.bind(this),
            previous: this.previous.bind(this),
            close: this.hide.bind(this)
        });
        return this;
    },

    attach: function(elements)
    {
        if (!instanceOf(elements, Elements)) elements = $$(elements);
        var i, l, a, n, e = new Elements;
        elements.each(function(el) {
            if (el.$xtlightbox && el.$xtlightbox.adaptor) {
                return;
            }
            for (i = 0, l = this.options.adaptors.length; i < l; i++)
            {
                n = this.options.adaptors[i];
                a = this.adaptors[n];
                if (a.check(el)) {
                    el.$xtlightbox = el.$xtlightbox || {};
                    el.$xtlightbox.adaptor = a.$name;
                    e.push(el);
                    el.addEvent('click', this.onElementClick);
                    if (this.options.preload) a.load(el);
                    break;
                }
            }
        }, this);
        if (e.length == 0) return this;
        if (this.elements) this.elements.append(e);
        else this.elements = e;
        this.fireEvent('attach', e);
        return this;
    },

    detach: function(elements)
    {
        if (!instanceOf(elements, Elements)) elements = $$('elements');
        elements.each(function(el) {
            this.elements.erase(el);
            el.removeEvent('click', this.onElementClick);
            delete el.$xtlightbox.adaptor;
        });
        this.fireEvent('detach', elements);
        return this;
    },

    show: function(element)
    {
        if (!element.$xtlightbox || !element.$xtlightbox.adaptor) return this;
        if (this.shown && this.current == element) return this;
        $$('object').setStyle('visibility', 'hidden');
        var name = element.$xtlightbox.adaptor;
        if (!this.adaptors[name]) return this;
        this.renderer.show();
        this.renderer.empty();
        var adaptor = this.adaptors[name];
        this.renderer.toElement().addClass('loading');
        adaptor.load(element, function(el) {
            this.renderer.toElement().removeClass('loading');
            var c = adaptor.getContent(el),
                o = {
                    size: adaptor.getSize(el),
                    title: adaptor.getTitle(el),
                    total: this.elements.length,
                    position: this.elements.indexOf(el) + 1
                }
            if (!this.options.hideArrowsFor.contains(element.$xtlightbox.adaptor)) {
                if (this.options.loop || o.position > 1) o.prev = true;
                if (this.options.loop || o.position < o.total) o.next = true;
            }
            this.renderer.render(c, o);
        }.bind(this));
        this.current = element;
        this.shown = true;
        this.fireEvent('show', element);
        return this;
    },

    hide: function()
    {
        $$('object').setStyle('visibility', 'visible');
        this.renderer.hide();
        this.current = null;
        this.shown = false;
        this.fireEvent('hide');
        return this;
    },

    next: function()
    {
        if (!this.elements || this.elements.length == 0) return this;
        if (!this.current) return this.show(this.elements[0]);
        var i = this.elements.indexOf(this.current);
        if (i + 1 == this.elements.length) {
            if (this.options.loop) return this.show(this.elements[0]);
            return this;
        }
        this.fireEvent('next', this.elements[i + 1]);
        this.show(this.elements[i + 1]);
        return this;
    },

    previous: function()
    {
        if (!this.elements || this.elements.length == 0) return this;
        if (!this.current) return this.show(this.elements[0]);
        var i = this.elements.indexOf(this.current);
        if (i == 0) {
            if (this.options.loop) return this.show(this.elements.getLast());
            return this;
        }
        this.fireEvent('previous', this.elements[i - 1]);
        this.show(this.elements[i - 1]);
        return this;
    },

    clear: function()
    {
        if (!this.elements) return this;
        this.elements.each(function(el) {
            el.removeEvent('click', this.onElementClick);
            delete el.$xtlightbox.adaptor;
        });
        delete this.elements;
        this.fireEvent('clear');
        return this;
    },

    destroy: function()
    {
        this.clear();
        for (var i = this.adaptors.length; i--;) this.adaptors[i].destroy();
        this.adaptors.empty();
        this.renderer.destroy();
        delete this.renderer;
        this.fireEvent('destroy');
        return null;
    },

    toElement: function()
    {
        return this.renderer.toElement();
    }
});

/*
---
name: XtLightbox.Adaptor

description: extendable lightbox Adaptor base

license: MIT-style

authors:
- Anton Suprun <kpobococ@gmail.com>

requires:
- XtLightbox

provides: XtLightbox.Adaptor

...
*/
(function()
{
    var Adaptor = this.XtLightbox.Adaptor = new Class(
    {
        Implements: Options,

        $name: '',

        options: {},

        initialize: function(options)
        {
            this.setOptions(options);
        },

        check: function(element)
        {
            return element.rel.test(/^lightbox/);
        },

        getContent: function(element)
        {
            return '';
        },

        getTitle: function(element)
        {
            return element.title;
        },

        getSize: function(element)
        {
            return {x:0,y:0};
        },

        load: function(element, callback)
        {
            callback(element);
            return this;
        },

        destroy: function()
        {
            return null;
        }
    });

    var count = 0;
    var cache = {};

    Adaptor.cache = function(element, content)
    {
        if (!element.$xtlightbox) throw new Error('Element must be attached to a lightbox');
        var a = element.$xtlightbox.adaptor, i = element.$xtlightbox.id;
        if (!i) i = element.$xtlightbox.id = ++count;
        cache[a + '-' + i] = content;
        return element;
    }

    Adaptor.load = function(element)
    {
        if (!element.$xtlightbox) throw new Error('Element must be attached to a lightbox');
        if (!Adaptor.cached(element)) return null;
        var a = element.$xtlightbox.adaptor, i = element.$xtlightbox.id;
        return cache[a + '-' + i];
    }

    Adaptor.clear = function(element)
    {
        if (!Adaptor.cached(element)) return element;
        var a = element.$xtlightbox.adaptor, i = element.$xtlightbox.id;
        cache[a + '-' + i] = null;
        return element;
    }

    Adaptor.cached = function(element)
    {
        if (!element.$xtlightbox) return false;
        var i = element.$xtlightbox.id, a = element.$xtlightbox.adaptor;
        return i && a && cache[a + '-' + i];
    }
})();

/*
---
name: XtLightbox.Adaptor.Image

description: extendable lightbox Image Adaptor class

license: MIT-style

authors:
- Anton Suprun <kpobococ@gmail.com>

requires:
- XtLightbox.Adaptor

provides: XtLightbox.Adaptor.Image

...
*/
XtLightbox.Adaptor.Image = new Class(
{
    Extends: XtLightbox.Adaptor,

    $name: 'Image',

    options: {
        extensions: ['jpg', 'png', 'gif'],
        lightboxCompat: true
    },

    initialize: function(options)
    {
        this.parent(options);
        var e = this.options.extensions || [];
        if (e.contains('jpg') && !e.contains('jpeg')) e.push('jpeg');
    },

    check: function(element)
    {
        return this.options.lightboxCompat ? this.parent(element) : element.href.test('\\.(?:' + this.options.extensions.join('|') + ')$', 'i');
    },

    getContent: function(element)
    {
        if (!XtLightbox.Adaptor.cached(element)) throw new Error('Element content must be loaded first');
        return XtLightbox.Adaptor.load(element);
    },

    getSize: function(element)
    {
        if (!XtLightbox.Adaptor.cached(element)) throw new Error('Element content must be loaded first');
        var img = XtLightbox.Adaptor.load(element);
        return {
            x: img.naturalWidth,
            y: img.naturalHeight
        }
    },

    load: function(element, callback)
    {
        callback = callback || function() {}
        if (XtLightbox.Adaptor.cached(element)) {
            callback(element);
            return this;
        }
        new Element('img').addEvent('load', function() {
            if (!this.naturalWidth) this.naturalWidth = this.width;
            if (!this.naturalHeight) this.naturalHeight = this.height;
            XtLightbox.Adaptor.cache(element, this);
            callback(element);
        }).set({
            src: element.href,
            alt: ''
        });
        return this;
    }
});

/*
---
name: XtLightbox.Renderer

description: extendable lightbox Renderer base

license: MIT-style

authors:
- Anton Suprun <kpobococ@gmail.com>

requires:
- XtLightbox

provides: XtLightbox.Renderer

...
*/
XtLightbox.Renderer = new Class(
{
    Implements: [Options, Events],

    options: {
        // onPrevious,
        // onNext,
        // onClose,
        // onShow,
        // onHide,
        // onDestroy,
        positionText: 'Slika {x} od {total}',
        useMask: true,
        maskOptions: {}
    },

    initialize: function(options)
    {
        this.setOptions(options);
    },

    create: function()
    {
        if (this.element) return this;
        this.element = new Element('div.xt-lightbox').grab(
            this.elWrapper = new Element('div.xt-lightbox-wrapper').adopt(
                new Element('div.xt-lightbox-content-wrapper').adopt(
                    this.elContent = new Element('div.xt-lightbox-content'),
                    this.elArrows  = new Element('div.xt-lightbox-arrows').adopt(
                        this.btnPrev = new Element('span.button.xt-lightbox-prev').addEvent('click', this.fireEvent.pass('previous', this)),
                        this.btnNext = new Element('span.button.xt-lightbox-next').addEvent('click', this.fireEvent.pass('next', this))
                    )
                ),
                this.elFooter = new Element('div.xt-lightbox-footer').grab(
                    new Element('div.xt-lightbox-footer-wrapper').adopt(
                        this.btnClose = new Element('span.button.xt-lightbox-close').addEvent('click', this.fireEvent.pass('close', this)),
                        this.elTitle    = new Element('div.xt-lightbox-title'),
                        this.elPosition = new Element('div.xt-lightbox-position'),
                        new Element('div.xt-clear')
                    )
                )
            ).addEvent('click', function(e) {
                e.stopPropagation();
            })
        ).addEvent('click', this.fireEvent.pass('close', this));
        return this;
    },

    inject: function()
    {
        if (this.injected) return this;
        if (!this.element) this.create();
        var i = this.options.inject,
            t = i && i.target ? i.target : document.body,
            w = i && i.where  ? i.where  : 'inside';
        this.element.setStyle('display', 'none').inject(t, w);
        if (this.options.useMask && window.Mask) {
            this.mask = new Mask(document.body, Object.merge({
                'class': 'xt-lightbox-mask',
                onClick: this.fireEvent.pass('close', this)
            }, this.options.maskOptions));
            this.addEvents({
                show: this.mask.show.bind(this.mask),
                hide: this.mask.hide.bind(this.mask),
                destroy: this.mask.destroy.bind(this.mask)
            });
        }
        this.injected = true;
        return this;
    },

    show: function()
    {
        if (!this.injected) this.inject();
        if (this.shown) return this;
        this.element.setStyle('display', '');
        this.shown = true;
        this.resize();
        this.fireEvent('show');
        return this;
    },

    hide: function()
    {
        if (!this.injected || !this.shown) return this;
        this.reset();
        this.element.setStyle('display', 'none');
        this.shown = false;
        this.fireEvent('hide');
        return this;
    },

    reset: function()
    {
        if (!this.injected) return this;
        this.resize();
        this.empty();
        this.elFooter.setStyle('display', 'none');
        return this;
    },

    empty: function()
    {
        if (!this.element) return this;
        this.elTitle.empty();
        this.elPosition.empty();
        this.elContent.empty();
        return this;
    },

    render: function(content, options)
    {
        if (!content) return this;
        options = Object.append({
            close: true
        }, options);
        this.empty();
        this.elTitle.set('text', options.title || '');
        if (options.position && options.total && options.total > 1) {
            this.elPosition.set('text', this.options.positionText.substitute({
                x: options.position,
                total: options.total
            }));
        }
        this.resize(options.size);
        this.elFooter.setStyle('display', '');
        this.elContent.empty().grab(content);
        this.btnPrev.setStyle('display', options.prev ? '' : 'none');
        this.btnNext.setStyle('display', options.next ? '' : 'none');
        if (!options.next && !options.prev) this.elArrows.setStyle('display', 'none');
        else this.elArrows.setStyle('display', '');
        this.btnClose.setStyle('display', options.close ? '' : 'none');
        return this;
    },

    resize: function(size, callback)
    {
        if (!this.shown) this.show();
        size = size || {};
        this.element.setStyles({
            width: size.x || '',
            height: size.y || ''
        });
        if (callback) callback();
        return this;
    },

    toElement: function()
    {
        if (!this.element) this.create();
        return this.element;
    },

    destroy: function()
    {
        this.element.destroy();
        this.fireEvent('destroy');
        return null;
    }
});

/*
---
name: XtLightbox.Renderer.Lightbox

description: extendable lightbox default Lightbox Renderer

license: MIT-style

authors:
- Anton Suprun <kpobococ@gmail.com>

requires:
- XtLightbox.Renderer

provides: XtLightbox.Renderer.Lightbox

...
*/
XtLightbox.Renderer.Lightbox = new Class(
{
    Extends: XtLightbox.Renderer,

    options: {
        maskFxOptions: {},
        widthFxOptions: {},
        heightFxOptions: {},
        contentFxOptions: {},
        footerFxOptions: {}
    },

    create: function()
    {
        this.parent();
        this.fxWidth = new Fx.Tween(this.elWrapper, Object.merge({}, this.options.widthFxOptions, {
            property: 'width',
            onStart: function() {},
            onCancel: function() {},
            onComplete: function() {
                this.onWidthChange();
            }.bind(this)
        }));
        this.fxHeight = new Fx.Tween(this.elContent, Object.merge({}, this.options.heightFxOptions, {
            property: 'height',
            onStart: function() {},
            onCancel: function() {},
            onComplete: function() {
                this.onHeightChange();
            }.bind(this)
        }));
        this.fxTop = new Fx.Tween(this.element, Object.merge({}, this.options.heightFxOptions, {
            property: 'top',
            onStart: function() {},
            onCancel: function() {},
            onComplete: function() {}
        }));
        this.fxContent = new Fx.Tween(this.elContent, Object.merge({}, this.options.contentFxOptions, {
            property: 'opacity',
            onStart: function() {},
            onCancel: function() {},
            onComplete: function() {
                this.onContentRender();
            }.bind(this)
        }));
        this.fxFooter = new Fx.Tween(this.elFooter, Object.merge({}, this.options.footerFxOptions, {
            property: 'height',
            onStart: function() {
                this.elFooter.setStyle('overflow', 'hidden');
            }.bind(this),
            onCancel: function() {},
            onComplete: function() {
                this.elFooter.setStyle('overflow', '');
            }.bind(this)
        }));
    },

    inject: function()
    {
        this.parent();
        this.mask.addEvent('click', this.fireEvent.pass('close', this));
        this.removeEvents('show').removeEvents('hide');
        var fxShow = new Fx.Tween(this.mask, Object.merge({}, this.options.maskFxOptions, {
            property: 'opacity',
            onStart: function() {
                this.show();
            }.bind(this.mask),
            onCancel: function() {},
            onComplete: function() {}
        }));
        var fxHide = new Fx.Tween(this.mask, Object.merge({}, this.options.maskFxOptions, {
            property: 'opacity',
            onStart: function() {},
            onCancel: function() {},
            onComplete: function() {
                this.hide();
            }.bind(this.mask)
        }));
        var mo = this.options.maskOpacity || this.mask.toElement().getStyle('opacity') || 1;
        this.mask.toElement().setStyle('opacity', 0);
        this.addEvents({
            show: function() {
                fxHide.cancel();
                fxShow.start(mo);
            },
            hide: function() {
                fxShow.cancel();
                fxHide.start(0);
            }
        });
    },

    empty: function()
    {
        this.parent();
        this.elFooter.setStyle('display', 'none');
        this.btnPrev.setStyle('display', 'none');
        this.btnNext.setStyle('display', 'none');
        this.rOpts = {};
        this.rCont = null;
        this.rX = null;
        this.fxFooter.cancel();
        return this;
    },

    render: function(content, options)
    {
        if (!content) return this;
        options = Object.append({
            close: true
        }, options);
        this.empty();
        this.elTitle.set('text', options.title || '');
        if (options.position && options.total && options.total > 1) {
            this.elPosition.set('text', this.options.positionText.substitute({
                x: options.position,
                total: options.total
            }));
        }
        this.rOpts = options;
        this.rCont = content;
        this.resize(options.size);
        return this;
    },

    renderContent: function(callback)
    {
        callback = callback || function() {};
        this.fxContent.set(0).start(1);
        return this;
    },

    onContentRender: function()
    {
        this.btnPrev.setStyle('display', this.rOpts.prev ? '' : 'none');
        this.btnNext.setStyle('display', this.rOpts.next ? '' : 'none');
        if (!this.rOpts.next && !this.rOpts.prev) this.elArrows.setStyle('display', 'none');
        else this.elArrows.setStyle('display', '');
        this.btnClose.setStyle('display', this.rOpts.close ? '' : 'none');
        this.renderFooter();
    },

    renderFooter: function()
    {
        this.elFooter.setStyles({
            visibility: 'hidden',
            display: ''
        });
        var y = this.elFooter.getSize().y;
        this.elFooter.setStyles({
            visibility: 'visible',
            height: 0
        });
        this.fxFooter.start(y);
        return this;
    },

    resize: function(size)
    {
        if (!this.shown) this.show();
        if (size && size.x && size.y) {
            var winY = window.getSize().y;
            this.elFooter.setStyles({
                display: '',
                height: ''
            });
            var wrpY = this.elWrapper.getSize().y;
            this.elFooter.setStyle('display', 'none');
            var wrpH = this.elWrapper.getStyle('height').toInt();
            var top = Math.round((winY - (wrpY - wrpH + size.y)) / 2);
            this.rX = size.x;
            this.fxHeight.start(size.y);
            this.fxTop.start(top);
        } else {
            // Reset size
            size = size || {};
            this.elWrapper.setStyle('width', size.x || '');
            this.elContent.setStyle('height', size.y || '');
            this.elFooter.setStyle('display', '');
            this.element.setStyle('top', Math.round((window.getSize().y - this.elWrapper.getSize().y) / 2));
            this.elFooter.setStyle('display', 'none');
        }
        return this;
    },

    onWidthChange: function()
    {
        this.elContent.grab(this.rCont);
        this.renderContent();
        return this;
    },

    onHeightChange: function()
    {
        this.fxWidth.start(this.rX);
        return this;
    }
});

