/**
 * ReMooz - Zoomer
 *
 * Inspired by so many boxes and zooms
 *
 * @version                1.0
 *
 * @license                MIT-style license
 * @author                Harald Kirschner <mail [at] digitarald.de>
 * @copyright        Author
 */

var ReMooz = new Class({

        Implements: [Events, Options, Chain],

        options: {
                link: null,
                type: 'image',
                container: null,
                className: null,
                centered: true,
                dragging: false,
                closeOnClick: true,
                shadow: (Browser.Engine.trident) ? 'onOpenEnd' : 'onOpen', // performance
                resize: true,
                margin: 20,
                resizeFactor: 0.60,
                resizeLimit: false, // {x: 440, y: 540}
                fixedSize: false,
                cutOut: false,
                addClick: true,
                opacityLoad: 0.6,
                opacityResize: 1,
                opacityTitle: 0.8,
                resizeOptions: {},
                fxOptions: {},
                closer: true,
                parse: false, // 'rel'
                parseSecure: false,
                temporary: false,
                onBuild: $empty,
                onLoad: $empty,
                onOpen: $empty,
                onOpenEnd: $empty,
                onClose: $empty,
                onCloseEnd: $empty,
                generateTitle: function(el) {
                        var text = el.get('title');
                        if (!text) return false;
                        var title = text.split(' :: ');
                        var head = new Element('h6', {'html': title[0]});
                        return (title[1]) ? [head, new Element('p', {'html': title[1]})] : head;
                }
        },

        initialize: function(element, options) {
                this.element = $(element);
                this.setOptions(options);
                if (this.options.parse) {
                        var obj = this.element.getProperty(this.options.parse);
                        if (obj && (obj = JSON.decode(obj, this.options.parseSecure))) this.setOptions(obj);
                }
                var origin = this.options.origin;
                this.origin = ((origin) ? $(origin) || this.element.getElement(origin) : null) || this.element;
                this.link = this.options.link || this.element.get('href') || this.element.get('src');
                this.container = $(this.options.container) || this.element.getDocument();
                this.bound = {
                        'click': function(e) {
                                this.open.delay(1, this);
                                return false;
                        }.bind(this),
                        'close': this.close.bind(this),
                        'dragClose': function(e) {
                                if (e.rightClick) return;
                                this.close();
                        }.bind(this)
                };
                if (this.options.addClick) this.bindToElement();
        },

        destroy: function() {
                if (this.box) this.box.destroy();
                this.box = this.tweens = this.body = this.content = null;
        },

        bindToElement: function(element) {
                ($(element) || this.element).addClass('remooz-element').addEvent('click', this.bound.click);
                return this;
        },

        getOriginCoordinates: function() {
                var coords = this.origin.getCoordinates();
                delete coords.right;
                delete coords.bottom;
                return coords;
        },

        open: function(e) {
                if (this.opened) return (e) ? this.close() : this;
                this.opened = this.loading = true;
                if (!this.box) this.build();
                this.coords = this.getOriginCoordinates();
                this.coords.opacity = this.options.opacityLoad;
                this.coords.display = '';
                this.tweens.box.set(this.coords);
                this.box.addClass('remooz-loading');
                ReMooz.open(this.fireEvent('onLoad'));
                this['open' + this.options.type.capitalize()]();
                return this;
        },

        finishOpen: function() {
                this.tweens.fade.start(0, 1);
                this.drag.attach();
                this.fireEvent('onOpenEnd').callChain();
        },

        close: function() {
                if (!this.opened) return this;
                this.opened = false;
                ReMooz.close(this.fireEvent('onClose'));
                if (this.loading) {
                        this.box.setStyle('display', 'none');
                        return this;
                }
                this.drag.detach();
                this.tweens.fade.cancel().set(0).fireEvent('onComplete');
                if (this.tweens.box.timer) this.tweens.box.clearChain();
                var vars = this.getOriginCoordinates();
                if (this.options.opacityResize != 1) vars.opacity = this.options.opacityResize;
                this.tweens.box.start(vars).chain(this.closeEnd.bind(this));
                return this;
        },

        closeEnd: function() {
                if (this.options.cutOut) this.element.setStyle('visibility', 'visible');
                this.box.setStyle('display', 'none');
                this.fireEvent('onCloseEnd').callChain();
                if (this.options.temporary) this.destroy();
        },

        openImage: function() {
                var tmp = new Image();
                tmp.onload = tmp.onabort = tmp.onerror = function(fast) {
                        this.loading = tmp.onload = tmp.onabort = tmp.onerror = null;
                        if (!tmp.width || !this.opened) {
                                this.fireEvent('onError').close();
                                return;
                        }
                        var to = {x: tmp.width, y: tmp.height};
                        if (!this.content) this.content = $(tmp).inject(this.body);
                        else tmp = null;
                        this[(this.options.resize) ? 'zoomRelativeTo' : 'zoomTo'].create({
                                'delay': (tmp && fast !== true) ? 1 : null,
                                'arguments': [to],
                                'bind': this
                        })();
                }.bind(this);
                tmp.src = this.link;
                if (tmp && tmp.complete && tmp.onload) tmp.onload(true);
        },

        /**
         * @todo Test implementation
         */
        openElement: function() {
                this.content = this.content || $(this.link) || $E(this.link);
                if (!this.content) {
                        this.fireEvent('onError').close();
                        return;
                }
                this.content.inject(this.body);
                this.zoomTo({x: this.content.scrollWidth, y: this.content.scrollHeight});
        },

        zoomRelativeTo: function(to) {
                var scale = this.options.resizeLimit;
                if (!scale) {
                        scale = this.container.getSize();
                        scale.x *= this.options.resizeFactor;
                        scale.y *= this.options.resizeFactor;
                }
                for (var i = 2; i--;) {
                        if (to.x > scale.x) {
                                to.y *= scale.x / to.x;
                                to.x = scale.x;
                        } else if (to.y > scale.y) {
                                to.x *= scale.y / to.y;
                                to.y = scale.y;
                        }
                }
                return this.zoomTo({x: to.x.toInt(), y: to.y.toInt()});
        },

        zoomTo: function(to) {
                to = this.options.fixedSize || to;
                var box = this.container.getSize(), scroll = this.container.getScroll();
                var pos = (!this.options.centered) ? {
                        x: (this.coords.left + (this.coords.width / 2) - to.x / 2).toInt()
                                .limit(scroll.x + this.options.margin, scroll.x + box.x - this.options.margin - to.x),
                        y: (this.coords.top + (this.coords.height / 2) - to.y / 2).toInt()
                                .limit(scroll.y + this.options.margin, scroll.y + box.y - this.options.margin - to.y)
                } :  {
                        x: scroll.x + ((box.x - to.x) / 2).toInt(),
                        y: scroll.y + ((box.y - to.y) / 2).toInt()
                };
                if (this.options.cutOut) this.element.setStyle('visibility', 'hidden');
                this.box.removeClass('remooz-loading');
                var vars = {left: pos.x, top: pos.y, width: to.x, height: to.y};
                if (this.options.opacityResize != 1) vars.opacity = [this.options.opacityResize, 1];
                else this.box.set('opacity', 1);
                this.tweens.box.start(vars).chain(this.finishOpen.bind(this));
                this.fireEvent('onOpen');
        },

        build: function() {
                this.addEvent('onBlur', function() {
                        this.focused = false;
                        this.box.removeClass('remooz-box-focus').setStyle('z-index', ReMooz.options.zIndex);
                }, true);
                this.addEvent('onFocus', function() {
                        this.focused = true;
                        this.box.addClass('remooz-box-focus').setStyle('z-index', ReMooz.options.zIndexFocus);
                }, true);

                var classes = ['remooz-box', 'remooz-type-' + this.options.type, 'remooz-engine-' + Browser.Engine.name + Browser.Engine.version];
                if (this.options.className) classes.push(this.options.className);
                this.box = new Element('div', {
                        'class': classes.join(' '),
                        'styles': {
                                'display': 'none',
                                'top': 0,
                                'left': 0,
                                'zIndex': ReMooz.options.zIndex
                        }
                });

                this.tweens = {
                        'box': new Fx.Morph(this.box, $merge({
                                        'duration': 400,
                                        'unit': 'px',
                                        'transition': Fx.Transitions.Quart.easeOut,
                                        'chain': 'cancel'
                                }, this.options.resizeOptions)
                        ),
                        'fade': new Fx.Tween(null, $merge({
                                        'property': 'opacity',
                                        'duration': (Browser.Engine.trident) ? 0 : 300,
                                        'chain': 'cancel'
                                }, this.options.fxOptions)).addEvents({
                                        'onComplete': function() {
                                                if (!this.element.get('opacity')) this.element.setStyle('display', 'none');
                                        },
                                        'onStart': function() {
                                                if (!this.element.get('opacity')) this.element.setStyle('display', '');
                                        }
                                }
                        )
                };
                this.tweens.fade.element = $$();

                if (this.options.shadow) {
                        if (Browser.Engine.webkit420) {
                                this.box.setStyle('-webkit-box-shadow', '0 0 10px rgba(0, 0, 0, 0.7)');
                        } else if (!Browser.Engine.trident4) {
                                var shadow = new Element('div', {'class': 'remooz-bg-wrap'}).inject(this.box);
                                ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'].each(function(dir) {
                                        new Element('div', {'class': 'remooz-bg remooz-bg-' + dir}).inject(shadow);
                                });
                                this.tweens.bg = new Fx.Tween(shadow, {
                                        'property': 'opacity',
                                        'chain': 'cancel'
                                }).set(0);
                                this.addEvent(this.options.shadow, this.tweens.bg.set.bind(this.tweens.bg, 1), true);
                                this.addEvent('onClose', this.tweens.bg.set.bind(this.tweens.bg, 0), true);
                        }
                }

                if (this.options.closer) {
                        var closer = new Element('a', {
                                'class': 'remooz-btn-close',
                                'events': {'click': this.bound.close}
                        }).inject(this.box);
                        this.tweens.fade.element.push(closer);
                }
                this.body = new Element('div', {'class': 'remooz-body'}).inject(this.box);

                var title = this.options.title || this.options.generateTitle.call(this, this.element);
                if (title) { // thx ie6
                        var title = new Element('div', {'class': 'remooz-title'}).adopt(
                                new Element('div', {'class': 'remooz-title-bg', 'opacity': this.options.opacityTitle}),
                                new Element('div', {'class': 'remooz-title-content'}).adopt(title)
                        ).inject(this.box);
                        this.tweens.fade.element.push(title);
                }
                this.tweens.fade.set(0).fireEvent('onComplete');

                this.drag = new Drag.Move(this.box, {
                        'snap': 15,
                        'preventDefault': true,
                        'onBeforeStart': function() {
                                if (!this.focused && !this.loading) ReMooz.focus(this);
                                else if (this.loading || this.options.closeOnClick) this.box.addEvent('mouseup', this.bound.dragClose);
                        }.bind(this),
                        'onSnap': function() {
                                this.box.removeEvent('mouseup', this.bound.dragClose);
                                if (!this.options.dragging) this.drag.stop();
                                else this.box.addClass('remooz-box-dragging');
                        }.bind(this),
                        'onComplete': function() {
                                this.box.removeClass('remooz-box-dragging');
                        }.bind(this)
                });
                this.drag.detach();

                this.fireEvent('onBuild', this.box, this.element);
                this.box.inject(this.element.getDocument().body);
        }

});

ReMooz.factory = function(extended) {
        return $extend(this, extended);
};

ReMooz.factory(new Options).factory({

        options: {
                zIndex: 41,
                zIndexFocus: 42,
                query: 'a.remooz',
                modal: false
        },

        assign: function(elements, options) {
                return $$(elements).map(function(element) {
                        return new ReMooz(element, options);
                }, this);
        },

        stack: [],

        open: function(obj) {
                var last = this.stack.getLast();
                this.focus(obj);
                if (last && this.options.modal) last.close();
        },

        close: function(obj) {
                var length = this.stack.length - 1;
                if (length > 1 && this.stack[length] == obj) this.focus(this.stack[length - 1]);
                this.stack.erase(obj);
        },

        focus: function(obj) {
                var last = this.stack.getLast();
                obj.fireEvent('onFocus', [obj]);
                if (last == obj) return;
                if (last) last.fireEvent('onBlur', [last]);
                this.stack.erase(obj).push(obj);
        }

});
