/*
Script: Drag.js
	The base Drag Class. Can be used to drag and resize Elements using mouse events.

License:
	MIT-style license.
*/

var Drag = new Class({

	Implements: [Events, Options],

	options: {/*
		onBeforeStart: $empty,
		onStart: $empty,
		onDrag: $empty,
		onCancel: $empty,
		onComplete: $empty,*/
		snap: 6,
		unit: 'px',
		grid: false,
		style: true,
		limit: false,
		handle: false,
		invert: false,
		preventDefault: false,
		modifiers: {x: 'left', y: 'top'}
	},

	initialize: function(){
		var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
		this.element = $(params.element);
		this.document = this.element.getDocument();
		this.setOptions(params.options || {});
		var htype = $type(this.options.handle);
		this.handles = (htype == 'array' || htype == 'collection') ? $$(this.options.handle) : $(this.options.handle) || this.element;
		this.mouse = {'now': {}, 'pos': {}};
		this.value = {'start': {}, 'now': {}};
		
		this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';
		
		this.bound = {
			start: this.start.bind(this),
			check: this.check.bind(this),
			drag: this.drag.bind(this),
			stop: this.stop.bind(this),
			cancel: this.cancel.bind(this),
			eventStop: $lambda(false)
		};
		this.attach();
	},

	attach: function(){
		this.handles.addEvent('mousedown', this.bound.start);
		return this;
	},

	detach: function(){
		this.handles.removeEvent('mousedown', this.bound.start);
		return this;
	},

	start: function(event){
		if (this.options.preventDefault) event.preventDefault();
		this.fireEvent('beforeStart', this.element);
		this.mouse.start = event.page;
		var limit = this.options.limit;
		this.limit = {'x': [], 'y': []};
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
			else this.value.now[z] = this.element[this.options.modifiers[z]];
			if (this.options.invert) this.value.now[z] *= -1;
			this.mouse.pos[z] = event.page[z] - this.value.now[z];
			if (limit && limit[z]){
				for (var i = 2; i--; i){
					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
				}
			}
		}
		if ($type(this.options.grid) == 'number') this.options.grid = {'x': this.options.grid, 'y': this.options.grid};
		this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
		this.document.addEvent(this.selection, this.bound.eventStop);
	},

	check: function(event){
		if (this.options.preventDefault) event.preventDefault();
		var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
		if (distance > this.options.snap){
			this.cancel();
			this.document.addEvents({
				mousemove: this.bound.drag,
				mouseup: this.bound.stop
			});
			this.fireEvent('start', this.element).fireEvent('snap', this.element);
		}
	},

	drag: function(event){
		if (this.options.preventDefault) event.preventDefault();
		this.mouse.now = event.page;
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
			if (this.options.invert) this.value.now[z] *= -1;
			if (this.options.limit && this.limit[z]){
				if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
					this.value.now[z] = this.limit[z][1];
				} else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
					this.value.now[z] = this.limit[z][0];
				}
			}
			if (this.options.grid[z]) this.value.now[z] -= (this.value.now[z] % this.options.grid[z]);
			if (this.options.style) this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
			else this.element[this.options.modifiers[z]] = this.value.now[z];
		}
		this.fireEvent('drag', this.element);
	},

	cancel: function(event){
		this.document.removeEvent('mousemove', this.bound.check);
		this.document.removeEvent('mouseup', this.bound.cancel);
		if (event){
			this.document.removeEvent(this.selection, this.bound.eventStop);
			this.fireEvent('cancel', this.element);
		}
	},

	stop: function(event){
		this.document.removeEvent(this.selection, this.bound.eventStop);
		this.document.removeEvent('mousemove', this.bound.drag);
		this.document.removeEvent('mouseup', this.bound.stop);
		if (event) this.fireEvent('complete', this.element);
	}

});

Element.implement({
	
	makeResizable: function(options){
		return new Drag(this, $merge({modifiers: {'x': 'width', 'y': 'height'}}, options));
	}

});
/*
Script: Drag.Move.js
	A Drag extension that provides support for the constraining of draggables to containers and droppables.

License:
	MIT-style license.
*/

Drag.Move = new Class({

	Extends: Drag,

	options: {
		droppables: [],
		container: false
	},

	initialize: function(element, options){
		this.parent(element, options);
		this.droppables = $$(this.options.droppables);
		this.container = $(this.options.container);
		if (this.container && $type(this.container) != 'element') this.container = $(this.container.getDocument().body);
		element = this.element;
		
		var current = element.getStyle('position');
		var position = (current != 'static') ? current : 'absolute';
		if (element.getStyle('left') == 'auto' || element.getStyle('top') == 'auto') element.position(element.getPosition(element.offsetParent));
		
		element.setStyle('position', position);
		
		this.addEvent('start', function(){
			this.checkDroppables();
		}, true);
	},

	start: function(event){
		if (this.container){
			var el = this.element, cont = this.container, ccoo = cont.getCoordinates(el.offsetParent), cps = {}, ems = {};

			['top', 'right', 'bottom', 'left'].each(function(pad){
				cps[pad] = cont.getStyle('padding-' + pad).toInt();
				ems[pad] = el.getStyle('margin-' + pad).toInt();
			}, this);

			var width = el.offsetWidth + ems.left + ems.right, height = el.offsetHeight + ems.top + ems.bottom;
			var x = [ccoo.left + cps.left, ccoo.right - cps.right - width];
			var y = [ccoo.top + cps.top, ccoo.bottom - cps.bottom - height];

			this.options.limit = {x: x, y: y};
		}
		this.parent(event);
	},

	checkAgainst: function(el){
		el = el.getCoordinates();
		var now = this.mouse.now;
		return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
	},

	checkDroppables: function(){
		var overed = this.droppables.filter(this.checkAgainst, this).getLast();
		if (this.overed != overed){
			if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
			if (overed){
				this.overed = overed;
				this.fireEvent('enter', [this.element, overed]);
			} else {
				this.overed = null;
			}
		}
	},

	drag: function(event){
		this.parent(event);
		if (this.droppables.length) this.checkDroppables();
	},

	stop: function(event){
		this.checkDroppables();
		this.fireEvent('drop', [this.element, this.overed]);
		this.overed = null;
		return this.parent(event);
	}

});

Element.implement({

	makeDraggable: function(options){
		return new Drag.Move(this, options);
	}

});




var ReMooz = new Class({



	Implements: [Events, Options, Chain],



	options: {

		link: null,

		type: 'image',

		container: null,

		className: null,

		centered: false,

		dragging: true,

		closeOnClick: true,

		shadow: (Browser.Engine.trident) ? 'onOpenEnd' : 'onOpen', // performance

		resize: true,

		margin: 20,

		resizeFactor: 0.95,

		resizeLimit: false, // {x: 640, y: 640}

		fixedSize: false,

		cutOut: true,

		addClick: true,

		opacityLoad: 0.6,

		opacityResize: 1,

		opacityTitle: 0.9,

		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');

		}

	},



	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);

			} else if (Browser.Engine.gecko) {

				this.box.setStyle('-moz-box-shadow', '0 0 10px rgba(0, 0, 0, 0.7)');
				}

		}



		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);

	}



});
