//MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2009 Aaron Newton <http://clientcide.com/>, Valerio Proietti <http://mad4milk.net> & the MooTools team <http://mootools.net/developers>, MIT Style License.

/*
 ---

 script: More.js

 description: MooTools More

 license: MIT-style license

 authors:
 - Guillermo Rauch
 - Thomas Aylott
 - Scott Kyle

 requires:
 - core:1.2.4/MooTools

 provides: [MooTools.More]

 ...
 */

MooTools.More = {
	'version': '1.2.4.4',
	'build': '6f6057dc645fdb7547689183b2311063bd653ddf'
};

/*
 ---

 script: Element.Delegation.js

 description: Extends the Element native object to include the delegate method for more efficient event management.

 credits:
 - "Event checking based on the work of Daniel Steigerwald. License: MIT-style license.	Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"

 license: MIT-style license

 authors:
 - Aaron Newton
 - Daniel Steigerwald

 requires:
 - core:1.2.4/Element.Event
 - core:1.2.4/Selectors
 - /MooTools.More

 provides: [Element.Delegation]

 ...
 */

(function(addEvent, removeEvent) {

	var match = /(.*?):relay\(([^)]+)\)$/,
	combinators = /[+>~\s]/,
	splitType = function(type) {
		var bits = type.match(match);
		return !bits ? {event: type} : {
			event: bits[1],
			selector: bits[2]
		};
	},
	check = function(e, selector) {
		var t = e.target;
		if (combinators.test(selector = selector.trim())) {
			var els = this.getElements(selector);
			for (var i = els.length; i--; ) {
				var el = els[i];
				if (t == el || el.hasChild(t))
					return el;
			}
		}
		else {
			for ( ; t && t != this; t = t.parentNode) {
				if (Element.match(t, selector))
					return document.id(t);
			}
		}
		return null;
	};
	Element.implement({

		addEvent: function(type, fn) {
			var splitted = splitType(type);
			if (splitted.selector) {
				var monitors = this.retrieve('$moo:delegateMonitors', {});
				if (!monitors[type]) {
					var monitor = function(e) {
						var el = check.call(this, e, splitted.selector);
						if (el)
							this.fireEvent(type, [e, el], 0, el);
					}.bind(this);
					monitors[type] = monitor;
					addEvent.call(this, splitted.event, monitor);
				}
			}
			return addEvent.apply(this, arguments);
		},
		removeEvent: function(type, fn) {
			var splitted = splitType(type);
			if (splitted.selector) {
				var events = this.retrieve('events');
				if (!events || !events[type] || (fn && !events[type].keys.contains(fn)))
					return this;

				if (fn)
					removeEvent.apply(this, [type, fn]);
				else
					removeEvent.apply(this, type);

				events = this.retrieve('events');
				if (events && events[type] && events[type].keys.length == 0) {
					var monitors = this.retrieve('$moo:delegateMonitors', {});
					removeEvent.apply(this, [splitted.event, monitors[type]]);
					delete monitors[type];
				}
				return this;
			}
			return removeEvent.apply(this, arguments);
		},
		fireEvent: function(type, args, delay, bind) {
			var events = this.retrieve('events');
			if (!events || !events[type])
				return this;
			events[type].keys.each( function(fn) {
				fn.create({bind: bind || this, delay: delay, arguments: args})();
			}, this);
			return this;
		}
	});

})(Element.prototype.addEvent, Element.prototype.removeEvent);
/*
 ---

 script: Element.Measure.js

 description: Extends the Element native object to include methods useful in measuring dimensions.

 credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"

 license: MIT-style license

 authors:
 - Aaron Newton

 requires:
 - core:1.2.4/Element.Style
 - core:1.2.4/Element.Dimensions
 - /MooTools.More

 provides: [Element.Measure]

 ...
 */

Element.implement({

	measure: function(fn) {
		var vis = function(el) {
			return !!(!el || el.offsetHeight || el.offsetWidth);
		};
		if (vis(this))
			return fn.apply(this);
		var parent = this.getParent(),
		restorers = [],
		toMeasure = [];
		while (!vis(parent) && parent != document.body) {
			toMeasure.push(parent.expose());
			parent = parent.getParent();
		}
		var restore = this.expose();
		var result = fn.apply(this);
		restore();
		toMeasure.each( function(restore) {
			restore();
		});
		return result;
	},
	expose: function() {
		if (this.getStyle('display') != 'none')
			return $empty;
		var before = this.style.cssText;
		this.setStyles({
			display: 'block',
			position: 'absolute',
			visibility: 'hidden'
		});
		return function() {
			this.style.cssText = before;
		}.bind(this);
	},
	getDimensions: function(options) {
		options = $merge({computeSize: false},options);
		var dim = {};
		var getSize = function(el, options) {
			return (options.computeSize)?el.getComputedSize(options):el.getSize();
		};
		var parent = this.getParent('body');
		if (parent && this.getStyle('display') == 'none') {
			dim = this.measure( function() {
				return getSize(this, options);
			});
		}
		else if (parent) {
			try { //safari sometimes crashes here, so catch it
				dim = getSize(this, options);
			}
			catch(e) {
			}
		}
		else {
			dim = {x: 0, y: 0};
		}
		return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
	},
	getComputedSize: function(options) {
		options = $merge({
			styles: ['padding','border'],
			plains: {
				height: ['top','bottom'],
				width: ['left','right']
			},
			mode: 'both'
		}, options);
		var size = {width: 0,height: 0};
		switch (options.mode) {
			case 'vertical':
				delete size.width;
				delete options.plains.width;
				break;
			case 'horizontal':
				delete size.height;
				delete options.plains.height;
				break;
		}
		var getStyles = [];
		//this function might be useful in other places; perhaps it should be outside this function?
		$each(options.plains, function(plain, key) {
			plain.each( function(edge) {
				options.styles.each( function(style) {
					getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
				});
			});
		});
		var styles = {};
		getStyles.each( function(style) {
			styles[style] = this.getComputedStyle(style);
		}, this);
		var subtracted = [];
		$each(options.plains, function(plain, key) { //keys: width, height, plains: ['left', 'right'], ['top','bottom']
			var capitalized = key.capitalize();
			size['total' + capitalized] = size['computed' + capitalized] = 0;
			plain.each( function(edge) { //top, left, right, bottom
				size['computed' + edge.capitalize()] = 0;
				getStyles.each( function(style, i) { //padding, border, etc.
					//'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
					if (style.test(edge)) {
						styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
						size['total' + capitalized] = size['total' + capitalized] + styles[style];
						size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
					}
					//if width != width (so, padding-left, for instance), then subtract that from the total
					if (style.test(edge) && key != style &&
					(style.test('border') || style.test('padding')) && !subtracted.contains(style)) {
						subtracted.push(style);
						size['computed' + capitalized] = size['computed' + capitalized]-styles[style];
					}
				});
			});
		});
		['Width', 'Height'].each( function(value) {
			var lower = value.toLowerCase();
			if(!$chk(size[lower]))
				return;

			size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
			size['total' + value] = size[lower] + size['total' + value];
			delete size['computed' + value];
		}, this);
		return $extend(styles, size);
	}
});

/*
 ---

 script: Element.Position.js

 description: Extends the Element native object to include methods useful positioning elements relative to others.

 license: MIT-style license

 authors:
 - Aaron Newton

 requires:
 - core:1.2.4/Element.Dimensions
 - /Element.Measure

 provides: [Elements.Position]

 ...
 */

(function() {

	var original = Element.prototype.position;

	Element.implement({

		position: function(options) {
			//call original position if the options are x/y values
			if (options && ($defined(options.x) || $defined(options.y)))
				return original ? original.apply(this, arguments) : this;
			$each(options||{}, function(v, k) {
				if (!$defined(v))
					delete options[k];
			});
			options = $merge({
				// minimum: { x: 0, y: 0 },
				// maximum: { x: 0, y: 0},
				relativeTo: document.body,
				position: {
					x: 'center', //left, center, right
					y: 'center' //top, center, bottom
				},
				edge: false,
				offset: {x: 0, y: 0},
				returnPos: false,
				relFixedPosition: false,
				ignoreMargins: false,
				ignoreScroll: false,
				allowNegative: false
			}, options);
			//compute the offset of the parent positioned element if this element is in one
			var parentOffset = {x: 0, y: 0},
			parentPositioned = false;
			/* dollar around getOffsetParent should not be necessary, but as it does not return
 			* a mootools extended element in IE, an error occurs on the call to expose. See:
 			* http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
			var offsetParent = this.measure( function() {
				return document.id(this.getOffsetParent());
			});
			if (offsetParent && offsetParent != this.getDocument().body) {
				parentOffset = offsetParent.measure( function() {
					return this.getPosition();
				});
				parentPositioned = offsetParent != document.id(options.relativeTo);
				options.offset.x = options.offset.x - parentOffset.x;
				options.offset.y = options.offset.y - parentOffset.y;
			}
			//upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
			//topRight, topLeft, centerTop, centerBottom, center
			var fixValue = function(option) {
				if ($type(option) != 'string')
					return option;
				option = option.toLowerCase();
				var val = {};
				if (option.test('left'))
					val.x = 'left';
				else if (option.test('right'))
					val.x = 'right';
				else
					val.x = 'center';
				if (option.test('upper') || option.test('top'))
					val.y = 'top';
				else if (option.test('bottom'))
					val.y = 'bottom';
				else
					val.y = 'center';
				return val;
			};
			options.edge = fixValue(options.edge);
			options.position = fixValue(options.position);
			if (!options.edge) {
				if (options.position.x == 'center' && options.position.y == 'center')
					options.edge = {x:'center', y:'center'};
				else
					options.edge = {x:'left', y:'top'};
			}

			this.setStyle('position', 'absolute');
			var rel = document.id(options.relativeTo) || document.body,
			calc = rel == document.body ? window.getScroll() : rel.getPosition(),
			top = calc.y, left = calc.x;

			var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
			var pos = {},
			prefY = options.offset.y,
			prefX = options.offset.x,
			winSize = window.getSize();
			switch(options.position.x) {
				case 'left':
					pos.x = left + prefX;
					break;
				case 'right':
					pos.x = left + prefX + rel.offsetWidth;
					break;
				default:
					//center
					pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
					break;
			}
			switch(options.position.y) {
				case 'top':
					pos.y = top + prefY;
					break;
				case 'bottom':
					pos.y = top + prefY + rel.offsetHeight;
					break;
				default:
					//center
					pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
					break;
			}
			if (options.edge) {
				var edgeOffset = {};

				switch(options.edge.x) {
					case 'left':
						edgeOffset.x = 0;
						break;
					case 'right':
						edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
						break;
					default:
						//center
						edgeOffset.x = -(dim.totalWidth/2);
						break;
				}
				switch(options.edge.y) {
					case 'top':
						edgeOffset.y = 0;
						break;
					case 'bottom':
						edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
						break;
					default:
						//center
						edgeOffset.y = -(dim.totalHeight/2);
						break;
				}
				pos.x += edgeOffset.x;
				pos.y += edgeOffset.y;
			}
			pos = {
				left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
				top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
			};
			var xy = {left: 'x', top: 'y'};
			['minimum', 'maximum'].each( function(minmax) {
				['left', 'top'].each( function(lr) {
					var val = options[minmax] ? options[minmax][xy[lr]] : null;
					if (val != null && pos[lr] < val)
						pos[lr] = val;
				});
			});
			if (rel.getStyle('position') == 'fixed' || options.relFixedPosition) {
				var winScroll = window.getScroll();
				pos.top+= winScroll.y;
				pos.left+= winScroll.x;
			}
			if (options.ignoreScroll) {
				var relScroll = rel.getScroll();
				pos.top-= relScroll.y;
				pos.left-= relScroll.x;
			}
			if (options.ignoreMargins) {
				pos.left += (
					options.edge.x == 'right' ? dim['margin-right'] :
					options.edge.x == 'center' ? -dim['margin-left'] + ((dim['margin-right'] + dim['margin-left'])/2) :
					- dim['margin-left']
				);
				pos.top += (
					options.edge.y == 'bottom' ? dim['margin-bottom'] :
					options.edge.y == 'center' ? -dim['margin-top'] + ((dim['margin-bottom'] + dim['margin-top'])/2) :
					- dim['margin-top']
				);
			}
			pos.left = Math.ceil(pos.left);
			pos.top = Math.ceil(pos.top);
			if (options.returnPos)
				return pos;
			else
				this.setStyles(pos);
			return this;
		}
	});

})();
/*
 ---

 script: Fx.Elements.js

 description: Effect to change any number of CSS properties of any number of Elements.

 license: MIT-style license

 authors:
 - Valerio Proietti

 requires:
 - core:1.2.4/Fx.CSS
 - /MooTools.More

 provides: [Fx.Elements]

 ...
 */

Fx.Elements = new Class({

	Extends: Fx.CSS,

	initialize: function(elements, options) {
		this.elements = this.subject = $$(elements);
		this.parent(options);
	},
	compute: function(from, to, delta) {
		var now = {};
		for (var i in from) {
			var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
			for (var p in iFrom)
				iNow[p] = this.parent(iFrom[p], iTo[p], delta);
		}
		return now;
	},
	set: function(now) {
		for (var i in now) {
			var iNow = now[i];
			for (var p in iNow)
				this.render(this.elements[i], p, iNow[p], this.options.unit);
		}
		return this;
	},
	start: function(obj) {
		if (!this.check(obj))
			return this;
		var from = {}, to = {};
		for (var i in obj) {
			var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
			for (var p in iProps) {
				var parsed = this.prepare(this.elements[i], p, iProps[p]);
				iFrom[p] = parsed.from;
				iTo[p] = parsed.to;
			}
		}
		return this.parent(from, to);
	}
});

(function() {
	Number.implement({
		'circulate': function(min, max) {
			if (!(this > max || this < min)) {
				return this;
			}
			var gap = max - min + 1;
			return (this > max ? min - 1 + (((this - max) % gap) || gap) : max + 1 - (((min - this) % gap) || gap));
		}
	});

	SpaceSlider = new Class({
		Implements: [Options, Events],
		options: {
			'offset': 0,
			'interval': 1000,
			'autoPlay': false,
			'autoPlayDirection': 'next',
			'elementsBefore': 1,
			'elementsAfter': 1,
			'slideDirection': 'left' //can be left,right,up,down
		},
		initialize: function(container, opts) {
			this.setOptions(opts);
			this.container = (document.id(container) || document.getElement(container));
			this.elements = this.container.getChildren();
			this.curIndex = 0;
			this.curEl = this.elements[this.curIndex];
			this.fx = new Fx.Elements(this.elements, this.options.fxOpts);
			this.fx.addEvent('complete', (function() {
				this.fireEvent('showComplete', [this.curEl, this.curIndex]);
			}).bind(this));
			var probsTpl = {
				'offset': Type.isObject,
				'relPositionFirst': Type.isString,
				'relEdgeFirst': Type.isString,
				'relPosition': Type.isString,
				'relEdge': Type.isString,
				'nextPosition': Type.isString,
				'nextEdge': Type.isString,
				'prevPosition': Type.isString,
				'prevEdge': Type.isString,
				'fxProp': Type.isString,
				'dirIndicator': Type.isNumber,
				'dirProp': Type.isString
			};

			switch(this.options.slideDirection) {
			  case 'up':
          this.slideDirProps = [
            {'x': 0, 'y': this.options.offset},
            'topleft',
            'topleft',
            'bottomleft',
            'topleft',
            'bottomleft',
            'topleft',
            'topleft',
            'bottomleft',
            'top',
            -1,
            'y'
          ].link(probsTpl);
          break;      
				case 'left':
				default:
				  this.slideDirProps = [
				    {'x': this.options.offset, 'y': 0},
				    'topleft',
				    'topleft',
				    'topright',
				    'topleft',
				    'topright',
				    'topleft',
				    'topleft',
				    'topright',
				    'left',
				    -1,
				    'x'
				  ].link(probsTpl);
					break;
			}

			this.init();

			if (this.options.autoPlay) {
				this.play(this.options.interval, this.options.autoPlayDirection);
			}
		},
		init: function() {
			var relativeTo = this.container;
			this.elements.each( function(el) {
				if(relativeTo === this.container) {
					el.position({
						'relativeTo': relativeTo,
						'position': this.slideDirProps.relPositionFirst,
						'edge': this.slideDirProps.relEdgeFirst,
						'offset': this.slideDirProps.offset
					});
				}
				else {
					el.position({
						'relativeTo': relativeTo,
						'position': this.slideDirProps.relPosition,
						'edge': this.slideDirProps.relEdge
					});
				}
				relativeTo = el;
			}, this);
		},
		prepare: function(dir) {
			if(dir === 'next') {
				this.container.getFirst().position({
					'relativeTo': this.container.getLast(),
					'position': this.slideDirProps.nextPosition,
					'edge': this.slideDirProps.nextEdge
				}).inject(this.container.getLast(), 'after');
			}
			else {
				this.container.getLast().position({
					'relativeTo': this.container.getFirst(),
					'position': this.slideDirProps.prevPosition,
					'edge': this.slideDirProps.prevEdge
				}).inject(this.container.getFirst(), 'before');
			}
			return this;
		},
		slideTo: function(index) {
			var slideTo = ($type(index) === 'element' ? document.id(index) : this.elements[index]);
			var slideOffset = (this.slideDirProps.dirIndicator * slideTo.getPosition(this.container)[this.slideDirProps.dirProp]) + this.options.offset;
      
			if(slideOffset < 0) {
				(this.options.elementsAfter - slideTo.getAllNext().length).times(this.prepare.bind(this, 'next'));
			}
			else if(slideOffset > 0) {
				(this.options.elementsBefore - slideTo.getAllPrevious().length).times(this.prepare.bind(this));
			}

			var fxOpts = {};
			this.elements.each( function(el, i) {
				fxOpts[i] = {};
				fxOpts[i][this.slideDirProps.fxProp] = el.getPosition(this.container)[this.slideDirProps.dirProp] + slideOffset;
			}, this);
			this.fireEvent('show', [slideTo, this.elements.indexOf(slideTo), this.curEl, this.curIndex]);
			this.curEl = slideTo;
			this.curIndex = this.elements.indexOf(slideTo);
			this.fx.start(fxOpts);
			return this;
		},
		slide: function(dir) {
			var slideTo = (this.curEl.getPrevious() || this.elements.getLast());
			if(dir === 'next') {
				slideTo = (this.curEl.getNext() || this.elements[0]);
			}
			return this.slideTo(slideTo);
		},
		next: function() {
			return this.slide('next');
		},
		prev: function() {
			return this.slide('prev');
		},
		getNext: function() {
			return (this.curIndex + 1).circulate(0, this.itemCount-1);
		},
		getPrev: function() {
			return (this.curIndex - 1).circulate(0, this.itemCount-1);
		},
		play: function(interval, direction) {
			this.stop();
			this._play = this[(direction || this.options.autoPlayDirection)].periodical((interval || this.options.interval), this);
			return this;
		},
		playDelayed: function(delay, interval, direction) {
			this.stop();
			this._delay = this.play.delay(delay, this, [interval, direction]);
			return this;
		},
		stop: function() {
			$clear(this._delay);
			$clear(this._play);
			return this;
		}
	});
})();
