/**
 * ReMooz - Zoomer
 * Inspired by so many boxes and zooms
 *
 * @version		1.0
 * @modified	Mar 7, 2008
 *
 * @license		MIT-style license
 * @author		Harald Kirschner <mail [at] digitarald.de>, Alvis Tang <alvis.sty [at] gmail.com>
 * @copyright	Author
 */
var ReMooz = new Class({
	Implements: [Events, Options, Chain],
	options: {
		caption: true, /* Enable caption */
		centered: true, /* Center the box */
		className: null, /* Extra class applied to ReMooz box */
		closer: true, /* Display closer icon on the box (Note: No transition applies on IE since it has problem on handling opacity) */
		closeOnClick: true, /* Close box by click */
		closeOnDblClick: false, /* Close box by double click */
		container: null, /* Box container */
		dragging: true, /* Enable box dragging */
		size: false, /* Size of the box e.g. {x: 640, y: 640} */
		hideSource: true, /* Hide source element after opening the box */
		loadingOpacity: 0.7, /* Opacity applied on source element */
		margin: 20, /* Margin of the box */
		openOnClick: true, /* Enable click to open */
		openOnDblClick: false, /* Enable click to open */
		resize: true, /* Shrink oversize picture */
		resizeFactor: 0.9, /* Fraction of the screen for displaying the box */
		resizeLimit: false, /* Maximum size of the box e.g. {x: 640, y: 640}*/
		resizeOptions: {
			transition: Fx.Transitions.Back.easeOut
		}, /* Options applied during resize */
		shadow: false, /* Enable shadow */
		type: 'image', /* Type of the box. (element/flash/iframe/image) */
		title: false, /* Caption title */
		content: false, /* Caption content */
		source: false, /* Source of which the box would load (Usually set in 'rel' or by default from 'src' or 'href' of parent) */
		onLoad: $empty,
		onOpen: $empty,
		onOpenEnd: $empty,
		onClose: $empty,
		onCloseEnd: $empty,
		onError: $empty,
		generateCaption: function(el){
			var title = this.options.title || el.getProperty('title');
			var content = this.options.content;
			if (!title && !content) return false;
			var head = new Element('h6', {
				'text': title
			});
			return (content) ? [head, new Element('p', {
				'text': content
			})] : head;
		},
		generateElement: $empty
	},
	initialize: function(element, options){
		this.elements = $(element) || $$(element);
		if ($type((this.elements)) == 'element') this.elements = [this.elements];
		this.setOptions(options);
		this.defaultOptions = this.options;
		this.container = $(this.options.container) || document;
		
		if (this.options.openOnClick) this.elements.each((function(element){
			var open = (function(e){
				this.open.delay(1, this, element);
				return false;
			}).bind(this);
			
			element.addClass('remooz-element')
			
			if (this.options.openOnClick && this.options.type == 'image') element.addClass('remooz-box-zoom-in');
			if (this.options.openOnClick) element.addEvent('click', open);
			if (this.options.openOnDblClick) element.addEvent('dblclick', open);
		}).bind(this));
	},
	open: function(element, source){
		if (this.loading) return;
		
		if (this.opened) {
			this.close();
			this.chain(this.open.pass(element, this));
			this.loading = true;
			
			return;
		}
		
		this.element = element;
		this.options = this.defaultOptions;
		try {
			var options = JSON.decode(this.element.get('rel'));
		} catch (e) {
			var options = {};
		}
		this.setOptions(options);
		this.options.source = source || this.options.source;
		if (!['image', 'iframe', 'flash', 'element'].contains(this.options.type)) {
			this.fireEvent('onError', 'type');
			return;
		}
		this.loading = true;
		this.fireEvent('onLoad');
		
		if (!this.box) this.build();
		var classes = ['remooz-box', 'remooz-type-' + this.options.type, 'remooz-engine-' + Browser.Engine.name, 'remooz-engine-' + Browser.Engine.name + Browser.Engine.version];
		if (this.options.className) classes.push(this.options.className);
		this.styles = $merge(this.getElementCoordinates(), {
			opacity: this.options.loadingOpacity
		});
		this.box.set({
			'class': classes.join(' '),
			styles: $merge(this.styles, {
				display: ''
			})
		}).addClass('remooz-loading');
		this.body.empty();
		this['open' + this.options.type.capitalize()]();
		
		window.addEvent('keydown', (function(e){
			if (e.key == 'esc') this.close();
		}).bind(this));
		
		return this;
	},
	close: function(){
		if (this.loading) this.box.set('styles', {
			display: 'none'
		});
		this.fireEvent('onClose');
		window.removeEvents('keydown');
		this.box.removeEvents('click');
		this.box.removeEvents('dblclick');
		this.content.removeClass('remooz-box-zoom-out');
		if (this.drag) this.drag.detach();
		
		var fadeCloser = this.options.closer ? (function(){
			if (!Browser.Engine.trident) {
				this.closer.set('tween', {
					duration: 'short',
					onComplete: this.callChain.bind(this)
				}).fade('out');
			} else {
				this.closer.fade('hide');
				this.callChain();
			}
		}).bind(this) : this.callChain;
		var fadeCaption = this.options.caption ? (function(){
			this.caption.set('tween', {
				duration: 'short',
				onComplete: this.callChain.bind(this)
			}).fade('out');
		}).bind(this) : this.callChain;
		var fadeBox = (function(){
			if (this.options.type != 'image') this.content.dispose();
			this.caption.getElement('.remooz-caption-content').empty();
			this.box.set('morph', {
				onComplete: this.callChain.bind(this)
			}).morph(this.styles);
			if (this.shadow) this.shadow.fade('out');
		}).bind(this)
		var hideBox = (function(){
			this.box.set('styles', {
				display: 'none'
			});
			this.element.fade('show');
			this.callChain();
		}).bind(this)
		var onCloseEnd = (function(){
			this.fireEvent('onCloseEnd');
			this.loading = false;
			this.opened = false;
			this.callChain();
		}).bind(this)
		this.chain(fadeCloser, fadeCaption, fadeBox, hideBox, onCloseEnd).callChain();
		
		return this;
	},
	openElement: function(){
		this.content = this.options.generateElement(this.element) || $(this.options.source) || $E(this.options.source);
		if (!this.content) {
			this.fireEvent('onError', 'element').close();
			return;
		}
		this.content.getElements('*').addEvent('click', function(){
			this.focus();
		});
		this.content.inject(this.body);
		this.zoomRelative();
	},
	openFlash: function(){
		var inject = (function(){
			var size = this.body.getSize();
			this.content.set({
				height: size.y,
				width: size.x
			}).inject(this.body);
			this.callChain();
		}).bind(this);
		this.content = new Element('object', {
			data: this.options.source,
			type: 'application/x-shockwave-flash'
		}).adopt(new Element('param', {
			name: 'wmode',
			value: 'transparent'
		}));
		
		this.zoomRelative();
		this.chain(inject);
	},
	openIframe: function(){
		var inject = (function(){
			this.content.inject(this.body);
			this.callChain();
		}).bind(this);
		this.content = new IFrame({
			src: this.options.source || this.element.get('href'),
			height: '100%',
			width: '100%'
		});
		
		this.zoomRelative();
		this.chain(inject);
	},
	openImage: function(){
		var prefetch = new Image();
		
		prefetch.onload = (function(fast){
			prefetch.onload = prefetch.onabort = prefetch.onerror = null;
			var size = {
				x: prefetch.width,
				y: prefetch.height
			};
			this.content = $(prefetch).inject(this.body);
			this[(this.options.resize) ? 'zoomRelative' : 'zoom'](size);
		}).bind(this);
		
		prefetch.onabort = prefetch.onerror = (function(){
			prefetch.onload = prefetch.onabort = prefetch.onerror = null;
			this.fireEvent('onError', 'network').close();
		}).bind(this)
		
		prefetch.src = this.options.source || this.element.getParent().get('href') || this.element.get('href') || this.element.get('src');
		
		if (prefetch && prefetch.complete && prefetch.onload) prefetch.onload(true);
	},
	zoomRelative: function(size){
		size = size || this.container.getSize();
		var scale = this.options.resizeLimit;
		if (!scale) {
			scale = this.container.getSize();
			scale = {
				x: (scale.x * this.options.resizeFactor).toInt(),
				y: (scale.y * this.options.resizeFactor).toInt()
			}
		}
		
		var oversize = scale && (size.x > scale.x || size.y > scale.y)
		var xDominant = scale && size.x / size.y > scale.x / scale.y;
		
		if (oversize) {
			size = xDominant ? {
				x: scale.x,
				y: (size.y * scale.x / size.x).toInt()
			} : {
				x: (size.x * scale.y / size.y).toInt(),
				y: scale.y
			}
		}
		
		return this.zoom(size);
	},
	zoom: function(size){
		size = this.options.size || size;
		var container = this.container.getSize(), scroll = this.container.getScroll();
		var position = (this.options.centered) ? {
			x: scroll.x + ((container.x - size.x) / 2).toInt(),
			y: scroll.y + ((container.y - size.y) / 2).toInt()
		} : {
			x: (this.styles.left + (this.styles.width / 2) - size.x / 2).toInt().limit(scroll.x + this.options.margin, scroll.x + container.x - this.options.margin - size.x),
			y: (this.styles.top + (this.styles.height / 2) - size.y / 2).toInt().limit(scroll.y + this.options.margin, scroll.y + container.y - this.options.margin - size.y)
		};
		var to = {
			left: position.x,
			top: position.y,
			width: size.x,
			height: size.y,
			opacity: 1
		};
		
		this.fireEvent('onOpen');
		if (this.options.closeOnClick) this.box.addEvent('click', this.close.bind(this));
		if (this.options.closeOnDblClick) this.box.addEvent('dblclick', this.close.bind(this));
		if (this.options.hideSource) this.element.fade('hide');
		if (this.options.closeOnClick && this.options.type == 'image') this.content.addClass('remooz-box-zoom-out');
		if (this.element.getTag() != 'img') this.box.set('opacity', 0);
		this.box.removeClass('remooz-loading');
		
		if (this.shadow && this.options.shadow) this.shadow.fade('in');
		
		if (this.options.dragging) {
			this.drag = this.drag ? this.drag.attach() : new Drag.Move(this.box, {
				onStart: (function(el){
					this.box.removeEvents('click');
				}).bind(this),
				onSnap: (function(){
					this.box.addClass('remooz-box-dragging');
				}).bind(this),
				onComplete: (function(){
					this.box.removeClass('remooz-box-dragging');
					if (this.options.closeOnClick) this.box.addEvent.delay(100, this.box, ['click', this.close.bind(this)]);
				}).bind(this)
			});
		}
		
		var fadeBox = (function(){
			this.box.set('morph', $merge(this.options.resizeOptions, {
				onComplete: this.callChain.bind(this)
			})).morph(to);
		}).bind(this)
		var fadeCloser = this.options.closer ? (function(){
			if (!Browser.Engine.trident) {
				this.closer.set('tween', {
					duration: 'short',
					onComplete: this.callChain.bind(this)
				}).fade('in');
			} else {
				this.closer.fade('show');
				this.callChain();
			}
		}).bind(this) : this.callChain;
		var fadeCaption = this.options.caption && this.options.type == 'image' ? (function(){
			var caption = this.options.generateCaption.apply(this, [this.element]);
			if (caption) {
				this.caption.getElement('.remooz-caption-content').adopt(caption);
				this.caption.set('tween', {
					duration: 'short',
					onComplete: this.callChain.bind(this)
				}).fade('in');
			} else this.callChain();
		}).bind(this) : this.callChain;
		var onOpenEnd = (function(){
			this.fireEvent('onOpenEnd');
			this.loading = false;
			this.opened = true;
			this.callChain();
		}).bind(this);
		this.chain(fadeBox, fadeCloser, fadeCaption, onOpenEnd).callChain();
	},
	build: function(){
		this.box = new Element('div', {
			styles: {
				display: 'none'
			}
		}).inject(document.body);
		;
		
		this.caption = new Element('div', {
			'class': 'remooz-caption',
			opacity: 0
		}).adopt(new Element('div', {
			'class': 'remooz-caption-bg',
			opacity: 0.75
		}), new Element('div', {
			'class': 'remooz-caption-content'
		})).inject(this.box);
		
		this.closer = new Element('a', {
			'class': 'remooz-btn-close',
			opacity: 0,
			events: {
				click: this.close.bind(this)
			}
		}).inject(this.box);
		
		if (!Browser.Engine.trident || Browser.Engine.trident5) {
			this.shadow = new Element('div', {
				'class': 'remooz-bg-wrap',
				opacity: 0
			}).inject(this.box);
			['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'].each(function(direction){
				new Element('div', {
					'class': 'remooz-bg remooz-bg-' + direction
				}).inject(this.shadow);
			}, this);
		}
		
		this.body = new Element('div', {
			'class': 'remooz-body'
		}).inject(this.box);
	},
	getElementCoordinates: function(){
		var coords = this.element.getCoordinates();
		delete coords.right;
		delete coords.bottom;
		return coords;
	}
});
