/*
---
name: Fx.Animate
description: Classes for creating easy transitions between elements in a container.
license: MIT license
authors: [Tim Wienk]
requires: [Core/Object, Core/Fx.Tween, Core/Fx.Transitions, Core/Element.Event, Core/Element.Dimensions Core/DomReady, Core/Selectors]
provides: [Fx.Animate, Fx.Animate.Fade, Fx.Animate.Scroll, Fx.Animate.Slides]
...
*/

Fx.Animate = new Class({

	Implements: [Options, Events],

	options: {/*
		onStartAutoplay: function(){},
		onStopAutoplay: function(){},
		onStart: function(element){},
		onComplete: function(element){},*/
		transition: 'sine:in:out',
		interval: 5000,
		duration: 1000,
		autoplay: true,
		loop: true
	},

	initialize: function(container, elements, options){
		this.setOptions(options);
		this.container = document.id(container);
		this.elements = this.container.getElements(elements);
		if (this.container.getStyle('position') == 'static') this.container.setStyle('position', 'relative');

		this.fx = [];
		this.current = 0;

		this.setup();
		if (this.options.autoplay) this.startAutoplay();
	},

	setup: function(){
		this.bound = {
			previous: this.previous.bind(this),
			next: this.next.bind(this)
		};

		this.build();

		var prefix = this.container.getProperty('id');
		if (prefix) this.attach(prefix);
	},

	build: function(){
		this.elements.each(function(o, i){
			o.setStyle('position', 'absolute');
			this.fx[i] = new Fx.Tween(o, {
				property: 'opacity',
				duration: this.options.duration,
				transition: this.options.transition
			}).set(i ? 0 : 1);
		}, this);
		return this;
	},

	attach: function(prefix){
		var prev = document.id(prefix + '_prev') || $$('.' + prefix + '_prev'),
			next = document.id(prefix + '_next') || $$('.' + prefix + '_next'),
			pagingContainer = document.id(prefix + '_paging'),
			paging = pagingContainer ? pagingContainer.getElements('a') : $$('.' + prefix + '_paging');
		if (prev) prev.addEvent('click', this.bound.previous);
		if (next) next.addEvent('click', this.bound.next);
		if (paging){
			paging.each(function(p){
				p.store(prefix + '_animate', function(e){
					if (e) e.stop();
					var to = p.getProperty('href').replace(/[^0-9]/gi,'').toInt();
					this.start(to);
				}.bind(this));
				p.addEvent('click', p.retrieve(prefix + '_animate'));
			}, this);
		}
		return this;
	},

	detach: function(prefix){
		var prev = document.id(prefix + '_prev') || $$('.' + prefix + '_prev'),
			next = document.id(prefix + '_next') || $$('.' + prefix + '_next'),
			pagingContainer = document.id(prefix + '_paging'),
			paging = pagingContainer ? pagingContainer.getElements('a') : $$('.' + prefix + '_paging');
		if (prev) prev.removeEvent('click', this.bound.previous);
		if (next) next.removeEvent('click', this.bound.next);
		if (paging){
			paging.each(function(p){
				p.removeEvent('click', p.retrieve(prefix + '_animate'));
			}, this);
		}
		return this;
	},

	getElements: function(from, to){
		if (from == null) return this.elements;
		if (to == null) return $$(this.elements[from]);

		var elements = [], len = this.fx.length;
		for (; from <= to; ++from){
			var element = this.elements[from];
			if (!element && this.options.loop) element = this.elements[from - len];
			if (element) elements.push(element);
		}
		return $$(elements);
	},

	toElement: function(){
		return this.container;
	},

	autoplay: function(){
		clearInterval(this.timer || null);
		this.timer = this.start.periodical(this.options.interval, this, []);
	},

	startAutoplay: function(e){
		if (e) e.stop();
		this.fireEvent('startAutoplay');
		this.autoplay();
		return this;
	},

	stopAutoplay: function(e){
		if (e) e.stop();
		if (this.timer){
			this.timer = clearInterval(this.timer);
			this.fireEvent('stopAutoplay');
		}
		return this;
	},

	doStart: function(to){
		var element = this.elements[to];
		this.onStart(element);
		this.fireEvent('navigate', [this.current, to]);
		this.fx[this.current].start(0);
		this.fx[to].start(1).chain(function(){
			this.onComplete(element);
		}.bind(this));
	},

	doSet: function(to){
		this.fx[this.current].set(0);
		this.fx[to].set(1);
		this.onComplete(this.elements[to]);
	},

	onStart: function(){
		this.running = true;
		this.fireEvent.apply(this, ['start', arguments]);
	},

	onComplete: function(){
		this.fireEvent.apply(this, ['complete', arguments]);
		this.running = false;
	},

	start: function(to){
		if (this.running) return this;

		if (to == null) to = this.current + 1;

		var len = this.fx.length, modifier = to > this.current ? 1 : -1;
		if (to >= len){
			to = this.options.loop ? to - len : len - 1;
		} else if (to < 0){
			to = this.options.loop ? to + len : 0;
		}
		if (this.current === to) return this;

		this.doStart(to, modifier);
		this.current = to;
		if (this.timer) this.autoplay();
		return this;
	},

	set: function(to){
		if (this.running){
			this.fx.each(function(fx){
				fx.cancel();
			});
		}

		var len = this.fx.length;
		if (to >= len){
			to = 0;
		} else if (to < 0){
			to = len - 1;
		}

		this.running = true;
		this.doSet(to);
		this.current = to;
		if (this.timer) this.autoplay();
		return this;
	},

	previous: function(e){
		if (e) e.stop();
		if (this.running) return;
		this.start(this.current - 1);
		if (this.timer) this.autoplay();
		return this;
	},

	next: function(e){
		if (e) e.stop();
		if (this.running) return;
		this.start(this.current + 1);
		if (this.timer) this.autoplay();
		return this;
	}

});

Fx.Animate.Fade = Fx.Animate;

Fx.Animate.Scroll = new Class({

	Extends: Fx.Animate,

	options: {/*
		onStart: function(elements){},
		onComplete: function(elements){},
		perStep: null,*/
		perPage: 1,
		direction: 'left'
	},

	build: function(){
		var containerSize = this.container.getSize();

		if (!this.options.perStep) this.options.perStep = this.options.perPage;

		if (this.options.direction == 'top' || this.options.direction == 'bottom'){
			this.containerSize = containerSize.y;
		} else {
			this.containerSize = containerSize.x;
		}
		this.scrollSize = this.containerSize / this.options.perPage;

		this.container.setStyles({
			'height': containerSize.y,
			'width': containerSize.x,
			'overflow': 'hidden'
		});

		this.elements.each(function(o, i){
			o.setStyle('position', 'absolute');
			this.fx[i] = new Fx.Tween(o, {
				property: this.options.direction,
				duration: this.options.duration,
				transition: this.options.transition
			}).set(this.options.perPage > i ? i * this.scrollSize : this.containerSize);
		}, this);
		return this;
	},

	getTweens: function(from, to){
		var tweens = [], len = this.fx.length, i = this.options.perPage + to - from + (from > to ? len : 0);
		while (i--){
			tweens[i] = this.fx[from + i];
			if (!tweens[i] && this.options.loop) tweens[i] = this.fx[from + i - len]
		}
		return tweens;
	},

	doStart: function(to, modifier){
		var elements = this.getElements(to, to + this.options.perPage - 1),
			tweens = modifier > 0 ? this.getTweens(this.current, to) : this.getTweens(to, this.current),
			delta = tweens.length - this.options.perPage;

		this.onStart(elements);

		tweens.each(function(fx, i){
			var position = this.scrollSize * i - (modifier > 0 ? 0 : delta * this.scrollSize);
			if (fx){
				fx.setOptions({
					link: modifier > 0 ? 'cancel' : 'ignore'
				}).set(position).start(position - this.scrollSize * delta * modifier);
			}
		}, this);

		this.fx[to].chain(function(){
			this.onComplete(elements);
		}.bind(this));
	},

	doSet: function(to){
		var len = this.fx.length;
		this.fx.each(function(fx, i){
			var modifier = i - to;
			modifier = Math.min(this.options.perPage, (modifier < 0 ? modifier + len : modifier));
			fx.set(this.scrollSize * modifier);
		}, this);

		this.onComplete(this.getElements(to, to + this.options.perPage - 1));
	},

	start: function(to){
		if (to == null) to = this.current + this.options.perStep;
		if (!this.options.loop && to >= this.fx.length) return this;
		return this.parent(to);
	},

	previous: function(e){
		if (e) e.stop();
		if (this.running) return this;
		this.start(this.current - this.options.perStep);
		if (this.timer) this.autoplay();
		return this;
	},

	next: function(e){
		if (e) e.stop();
		if (this.running) return this;
		this.start(this.current + this.options.perStep);
		if (this.timer) this.autoplay();
		return this;
	}

});

Fx.Animate.Slides = new Class({

	Extends: Fx.Animate,

	options: {/*
		wrapperProperties: {},*/
		wrapperElement: 'div',
		animation: ['horizontal', 'vertical'],
		direction: 'out',
		selectRandomAnimation: false,
		reversePreviousDirection: true,
		zIndexOffset: 0
	},

	build: function(){
		if (!this.wrappers) this.wrappers = [];
		this.iteration = 0;

		var containerSize = this.container.getSize(),
			partSize = {
				x: containerSize.x / 2,
				y: containerSize.y / 2
			},
			partStyles = [
				{
					outside: {'top': 0, 'right': 'auto', 'bottom': 'auto', 'left': 0},
					inside: {'top': 0, 'left': 0}
				}, {
					outside: {'top': 0, 'right': 0, 'bottom': 'auto', 'left': 'auto'},
					inside: {'top': 0, 'left': -partSize.x}
				}, {
					outside: {'top': 'auto', 'right': 'auto', 'bottom': 0, 'left': 0},
					inside: {'top': -partSize.y, 'left': 0}
				}, {
					outside: {'top': 'auto', 'right': 0, 'bottom': 0, 'left': 'auto'},
					inside: {'top': -partSize.y, 'left': -partSize.x}
				}
			],
			baseStyles = {
				horizontal: {
					'width': containerSize.x * 2,
					'left': -partSize.x
				},
				vertical: {
					'height': containerSize.y * 2,
					'top': -partSize.y
				},
				corners: {
					'width': containerSize.x * 2,
					'height': containerSize.y * 2,
					'top': -partSize.y,
					'left': -partSize.x
				},
				fade: {
					'opacity': 0
				},
				top: {
					'top': -containerSize.x
				},
				right: {
					'left': containerSize.x
				},
				bottom: {
					'top': containerSize.x
				},
				left: {
					'left': -containerSize.x
				},
				zoom: {
					'-moz-transform': 'scale(2)',
					'-webkit-transform': 'scale(2)'
				}
			},
			splitHorizontal = false,
			splitVertical = false;

		this.defaultStyles = {
			'width': containerSize.x,
			'height': containerSize.y,
			'opacity': 1,
			'-moz-transform': 'scale(1)',
			'-webkit-transform': 'scale(1)',
			'top': 0,
			'left': 0
		};

		this.styles = Array.from(this.options.animation).map(function(a){
			return Object.merge.apply(this, a.split('-').map(function(s){
				if (s == 'horizontal' || s == 'corners') splitHorizontal = true;
				if (s == 'vertical' || s == 'corners') splitVertical = true;
				return baseStyles[s];
			}));
		}, this);

		if (!splitHorizontal){
			partStyles.splice(1,1);
			partStyles.pop();
		}
		if (!splitVertical){
			partStyles.splice(2,2);
		}

		this.container.setStyles({
			'width': containerSize.x,
			'height': containerSize.y,
			'overflow': 'hidden'
		});

		this.elements.each(function(o, i){
			if (splitHorizontal || splitVertical){
				var wrapper = new Element(this.options.wrapperElement, this.options.wrapperProperties || null),
					parts = [],
					getPart = function(outside, index){
						var inside = index ? o.clone() : o;
						inside.setStyles(partStyles[index].inside).inject(outside);
						return outside;
					};

				for (var j = 0, k = partStyles.length; j < k; ++j){
					parts[j] = new Element('div', {
						styles: {
							'width': splitHorizontal ? partSize.x : containerSize.x,
							'height': splitVertical ? partSize.y : containerSize.y,
							'overflow': 'hidden',
							'position': 'absolute'
						}
					}).setStyles(partStyles[j].outside).inject(wrapper);
				}

				wrapper.setStyles(this.defaultStyles);
				o.setStyles({
					'width': containerSize.x,
					'height': containerSize.y
				});

				this.fx[i] = new Fx.Morph(wrapper, {
					duration: this.options.duration,
					transition: this.options.transition
				}).set({'z-index': i ? this.options.zIndexOffset : this.options.zIndexOffset + 1});

				if (this.wrappers[i]){
					this.wrappers[i] = wrapper.adopt(parts.map(getPart)).replaces(this.wrappers[i]);
				} else {
					this.wrappers[i] = wrapper.inject(o, 'before').adopt(parts.map(getPart));
				}
			} else {
				this.fx[i] = new Fx.Morph(o, {
					duration: this.options.duration,
					transition: this.options.transition
				}).set({'z-index': i ? this.options.zIndexOffset : this.options.zIndexOffset + 1});

				if (this.wrappers[i]){
					o.replaces(this.wrappers[i]);
					this.wrappers[i] = null;
				}
			}
		}, this);
		return this;
	},

	doStart: function(to, modifier){
		var element = this.elements[to],
			cur = this.current,
			chain = function(){
				this.fx[cur].set({
					'z-index': this.options.zIndexOffset
				});
				this.onComplete(element);
			}.bind(this),
			styles = this.options.selectRandomAnimation ? this.styles.getRandom() : this.styles[this.iteration++];
		this.onStart(element);

		if (this.iteration >= this.styles.length) this.iteration = 0;
		if (!this.options.reversePreviousDirection) modifier = 1;
		if (this.options.direction == 'in'){
			modifier *= -1;
		} else if (this.options.direction == 'random'){
			modifier = Math.random() - 0.5;
		}

		if (modifier < 0){
			this.fx[cur].set({
				'z-index': this.options.zIndexOffset + 1
			}).set(this.defaultStyles);
			this.fx[to].set({
				'z-index': this.options.zIndexOffset + 2
			}).set(this.defaultStyles).set(styles).start(this.defaultStyles).chain(chain);
		} else {
			this.fx[to].set({
				'z-index': this.options.zIndexOffset + 1
			}).set(this.defaultStyles);
			this.fx[cur].set({
				'z-index': this.options.zIndexOffset + 2
			}).set(this.defaultStyles).start(styles).chain(chain);
		}
	},

	doSet: function(to){
		this.fx[this.current].set({
			'z-index': this.options.zIndexOffset
		}).set(this.defaultStyles);
		this.fx[to].set({
			'z-index': this.options.zIndexOffset + 2
		}).set(this.defaultStyles);
		this.onComplete(this.elements[to]);
	}
});

if (typeof Fx.CSS.Parsers.Scale == null){
	Fx.CSS.Parsers.Scale = {
		parse: function(value){
			return (value = value.match(/^scale\(([0-9\.]+)\)$/i)) ? value[1].toFloat() : false;
		},
		compute: function(from, to, delta){
			return Fx.compute(from, to, delta);
		},
		serve: function(value){
			return 'scale('+value+')';
		}
	}
}
