/*------------------------------------------------------------------------------
Class: adobe.Carousel
Author:	
btapley
------------------------------------------------------------------------------*/
adobe.link("lib/animator.js");
adobe.use("adobe.DataWheel");
adobe.use("adobe.u");
adobe.use("adobe.u.element");

adobe.Carousel = Class.create((function() {
	
	var _orient = {
		x: 0,
		y: 1
	}
	
	var _states = {
		FORWARD: 1,
		BACKWARD: 0,
		ANIMATING: 2
	};
	
	var _attributes = [
		{
			offset: "offsetWidth",
			scroll: "scrollWidth",
			style: "width",
			pos: "left",
			cssname: "carousel-x",
			floatvalue: "left"
		},
		{
			offset: "offsetHeight",
			scroll: "scrollHeight",
			style: "height",
			pos: "top",
			cssname: "carousel-y",
			floatvalue: "none"
		}
	];
	
	var _state_translations = [];
	
	_state_translations[_states.FORWARD] = {
		backward: function(times) {
			this.sprites[this.incoming_sprite_index]
				.update(this.carousel_items.back(times).each(cp))
				.show();
			this.loadSubjects();
			this.sprites.invoke("updateSkipped");
			this.animator.reverse();
		},
		forward: function(times) {
			this.sprites.revolve(1);
			this.sprites[this.incoming_sprite_index]
				.update(this.carousel_items.next(times).each(cp))
				.show();
			this.loadSubjects();
			this.animator.play();
		}
	};
	_state_translations[_states.BACKWARD] = {
		backward: function(times) {
			this.sprites.revolve(1);
			this.sprites[this.incoming_sprite_index]
				.update(this.carousel_items.back(times).each(cp))
				.show();
			this.loadSubjects();
			this.sprites.invoke("updateSkipped");
			this.animator.reverse();
		},
		forward: function(times) {
			this.sprites[this.incoming_sprite_index]
				.update(this.carousel_items.next(times).each(cp))
				.show();
			this.loadSubjects();
			this.animator.play();
		}
	};
	_state_translations[_states.ANIMATING] = {
		backward: Prototype.emptyFunction,
		forward: Prototype.emptyFunction
	};
	

	function cp(el) {
		return el.cloneNode(true);
	}

	return {
/*------------------------------------------------------------------------------
	
	Method: initialize
	
	Parameters:
	container - element or id as string
	options - hash
	
	Options:
	axis - "x" or "y"
	clipping_mask - selector string
	duration - integer, default is 400
	oninitialized - Prototype.emptyFunction
	remember - boolean, default is false
	rotate - integer, default is 0
	select_button_back - selector string
	select_button_next - selector string
	select_item - selector string
	transition - Animator transition, default is Animator.tx.easeInOut
	visible - integer, default is 1
	offset - integer, default is 0
	
	Returned Value:
	None
	
	Example:
>	new adobe.Carousel("myCarousel")	
	
------------------------------------------------------------------------------*/
	initialize: function(container, options) {
			this.container = $(container);
			this.options = Object.extend({
				axis: "x",
				duration: 400, 
				offset: 0,
				remember: false,
				rotate: 0,
				select_mask: "",
				select_button_back: ".carousel-back",
				select_button_next: ".carousel-next",
				select_item: ".carousel-item",
				style_uninitialized: "",
				style_initialized: "",
				transition: Animator.tx.easeInOut,
				visible: 1
			}, options);
			
			this.currentstate = _states.FORWARD;
			this.outgoing_sprite_index = 1;
			this.incoming_sprite_index = 0;
			
/*---------------------------------------------------------------------------------------

	Layout

---------------------------------------------------------------------------------------*/
			
			var attributes = _attributes.clone();
			
			var orient_index = _orient[this.options.axis];
			
			if(orient_index > 0) {
				attributes.revolve(1);
			}
			
			var attributes1 = attributes[0];
			var attributes2 = attributes[1];
			
			this.clipping_mask = (this.options.select_mask) ? this.container.select(this.options.select_mask).first() : this.container;
		
			this.sprite_orient = attributes1.pos;
			var item_range = this.options.visible;
			this.sprite_size = this.clipping_mask[attributes1.offset] - (this.options.offset*2);
			var item_size = Math.floor(this.sprite_size/item_range);
			
			this.clipping_mask.addClassName(attributes1.cssname);
			
			var styles = {
				position: "absolute",
				zIndex: 0
			};
			styles[attributes1.style] = this.sprite_size.pixelate();
			
			this.sprites = [new adobe.CarouselSprite(1, styles), 
					new adobe.CarouselSprite(2, styles)];
			
			this.sprites.invoke("setParent", this.clipping_mask);
			
			var item_style = {};
			item_style[attributes1.style] = item_size.pixelate();
			item_style["cssFloat"] = attributes1.floatvalue;
			
			var items = this.clipping_mask.select(this.options.select_item);
			
			items = items.invoke("wrap", "div").invoke("setStyle", item_style);
			
			// this must follow setting the primiary size attributes
			
			this.clipping_mask.style[attributes2.style] = (items.inject(0, 
				function(accum, div) {
					return Math.max(accum, div[attributes2.scroll]);	
			})).pixelate();
			
			this.carousel_items = new adobe.DataWheel("test", {
				range: item_range
			});
			
			var removed = items.collect(function(div) {
				return this.clipping_mask.removeChild(div);
			}, this);
			
			this.carousel_items.add(removed);
			
			
			/* set initial state */
			
			var remember = Prototype.emptyFunction;
			var revolutions = this.options.rotate;
			
			if(this.options.remember) {
				adobe.use("adobe.Cookie");
				
				revolutions = parseInt(adobe.Cookie.get(this.container.identify()));

				// write cookies
				remember = (function() {
					adobe.Cookie.set(this.container.identify(), this.carousel_items.revolutions);
				}).bind(this);
			}
			
			if(revolutions) {
				var m = (revolutions > 0) ? "next" : "back";
				this.carousel_items[m](Math.abs(revolutions));
			}
			
			this.animator = new Animator({
				duration: this.options.duration,
				transition: this.options.transition,
				onComplete: (function() {
					this.sprites.invoke("updateSkipped");
					this.animator.clearSubjects();
					this.sprites[this.nextstate].hide();
					this.currentstate = this.nextstate;
					this.container.fire("carousel:endrevolve");
					remember(); // lexical reference so this must follow the state init
				}).bind(this)	
			});
			
			this.loadSubjects();
			this.animator.jumpTo(1);
			
			this.sprites[this.incoming_sprite_index]
				.update(this.carousel_items.read().collect(cp))
				.show();
				
			this.sprites[this.outgoing_sprite_index]
				.hide();
			
			this.container.select(this.options.select_button_next)
				.invoke("writeAttribute", "href", "#")
				.invoke("observe", "click", adobe.u.nonEvent)
				.invoke("observe", "click", this.next.bind(this, 1));
				
			this.container.select(this.options.select_button_back)
				.invoke("writeAttribute", "href", "#")
				.invoke("observe", "click", adobe.u.nonEvent)
				.invoke("observe", "click", this.back.bind(this, 1));
			
			
			if(this.options.style_initialized) {
				this.container.addClassName(this.options.style_initialized);
			}
			
			if(this.options.style_uninitialized && this.container.hasClassName(this.options.style_uninitialized)) {
				this.container.removeClassName(this.options.style_uninitialized);
			}
			
			this.container.fire("carousel:initialized");
		},
/*---------------------------------------------------------------------------------------

		Method: loadSubjects
		
		Returned Value:
		None

---------------------------------------------------------------------------------------*/
		loadSubjects: function() {
			this.animator.addSubject(new NumericalStyleSubject(this.sprites[0].element, 
									this.sprite_orient, 
									this.sprite_size+this.options.offset, 
									this.options.offset));
									
			this.animator.addSubject(new NumericalStyleSubject(this.sprites[1].element, 
									this.sprite_orient, 
									this.options.offset, 
									this.sprite_size*-1));
		},
/*---------------------------------------------------------------------------------------

		Method: next
		
		Parameters:
		times - Integer
		
		Events Fired:
		"carousel:startrevolve"
		
		Returned Value:
		None

---------------------------------------------------------------------------------------*/
		next: function(times) {
			this.container.fire("carousel:startrevolve");
			
			this.outgoing_sprite_index = 1;
			this.incoming_sprite_index = 0;
			
			var translate = this.currentstate;
			this.currentstate = _states.ANIMATING;
			this.nextstate = _states.FORWARD;
			_state_translations[translate].forward.call(this, times);					
		},
/*---------------------------------------------------------------------------------------

		Method: back
		
		Parameters:
		times - Integer
		
		Events Fired:
		"carousel:startrevolve"
		
		Returned Value:
		None

---------------------------------------------------------------------------------------*/
		back: function(times) {
			this.container.fire("carousel:startrevolve");
			
			this.outgoing_sprite_index = 0;
			this.incoming_sprite_index = 1;
			
			var translate = this.currentstate;
			this.currentstate = _states.ANIMATING; //prevent 
			this.nextstate = _states.BACKWARD;
			_state_translations[translate].backward.call(this, times);
		},
/*---------------------------------------------------------------------------------------

		Method: observe
		Attach a callback to an event.
		
		Observable Custom Events:
		carousel:startrevolve - fires before a carousel revolution
		carousel:endrevolve - fires after a carousel revolution
		
		Parameters:
		eventname - string
		func - function reference
		
		Returned Value:
		None

---------------------------------------------------------------------------------------*/
		observe: function(eventname, func) {
			this.container.observe(eventname, func);
		},
/*---------------------------------------------------------------------------------------

		Method: stopObserving
		Detach a callback from an event.
		
		Parameters:
		eventname - string
		func - function reference
		
		Returned Value:
		None

---------------------------------------------------------------------------------------*/
		stopObserving: function(eventname, func) {
			this.container.stopObserving(eventname, func);
		}
	};
})());

adobe.CarouselSprite = Class.create({
	initialize: function(oid, styles) {
		this.oid = oid;
		this.element = new Element("div");
		this.element.setStyle(styles);
		this.parent = null;
		this.skipped = [];
	},
	hide: function() {
		this.element.style.visibility = "hidden";
		return this;
	},
	show: function() {
		this.element.style.visibility = "visible";
		return this;
	},
	update: function(elements) {
		var n=0;
		while(n=this.element.childNodes[0]) {
			this.element.removeChild(n);	
		}
		for(var i=0;i<elements.length;i++) {
			var element = elements[i];
			if(element.getParent()) {
				this.skipped.push([element, i]);
				continue;
			}
			this.element.appendChild(element);
		}
		return this;
	},
	updateSkipped: function() {
		var s;
		while(s = this.skipped.shift()) {
			var skippednode = s[0],
			afternode = this.element.childNodes[s[1]];
			if(afternode) {
				this.element.insertBefore(skippednode,afternode);	
			} else {
				this.element.appendChild(skippednode);
			}	
		}
		return this;
	},
	setParent: function(parent) {
		this.parent = parent;
		parent.appendChild(this.element);
	}	
});

/*---------------------------------------------------------------------------------------

	Class: adobe.CarouselAutomator
	Provide transport controls for a carousel

---------------------------------------------------------------------------------------*/

adobe.CarouselAutomator = Class.create({
/*---------------------------------------------------------------------------------------

	Method: initialize
	
	Parameters:
	carousel - <Carousel> instance
	options - hash
	
	Options:
	interval - integer, default is 6000
	pauseOnHover - boolean, default is true
	stopOnFocus - boolean, default is true
	
	Returned Value:
	Class instance
	
	Example:
>	new adobe.CarouselAutomator(myCarousel)

---------------------------------------------------------------------------------------*/

	initialize: function(carousel, options) {
		
		this.carousel = carousel;
		this.options = Object.extend({
			interval: 6000,
			pauseOnHover: true,
			stopOnFocus: true
		}, options);
		this.interval;
		this.timer = null;
		this.playing = false;
		this.resetRotationInterval();
		
		if(this.options.pauseOnHover) {
			
			this.stopMethod = this.stop.bind(this);
			this.playMethod = this.play.bind(this);
			
			this.enableFocusPause();
			
			if(this.options.stopOnFocus) {
				
				var disableFocusHandler = this.tearDownAutomation.bind(this);
				
				this.carousel.observe("mousedown", disableFocusHandler);
				this.carousel.observe("keydown", disableFocusHandler);
				
			}
		}
	},
/*---------------------------------------------------------------------------------------

	Method: play
	Begin carousel automation.
	
	Returned Value:
	None

---------------------------------------------------------------------------------------*/
	play: function() {
		var _do = (function () {
			this.carousel.next();
		}).bind(this);
		this.timer = window.setInterval(_do, this.interval);
		this.playing = true;
	},
/*---------------------------------------------------------------------------------------

	Method: setRotationInterval
	
	Parameters:
	ms - Number
	
	Returned Value:
	None

---------------------------------------------------------------------------------------*/
	setRotationInterval: function(ms) {
		this.interval = parseInt(ms);
		if(this.playing) {
			this.stop();
			this.play();
		}
	},
/*---------------------------------------------------------------------------------------

	Method: resetRotationInterval
	Set rotation interval to the initial value
	
	Returned Value:
	None

---------------------------------------------------------------------------------------*/
	resetRotationInterval: function() {
		this.setRotationInterval(this.options.interval);
	},
/*---------------------------------------------------------------------------------------

	Method: stop
	Stop carousel automation.
	
	Returned Value:
	None

---------------------------------------------------------------------------------------*/
	stop: function() {
		window.clearInterval(this.timer);
		this.playing = false;
	},
/*---------------------------------------------------------------------------------------

	Method: tearDownAutomation
	stop auto-rotating and remove event listeners for pausing on focus
	
	Returned Value:
	None

---------------------------------------------------------------------------------------*/
	tearDownAutomation: function() {
		if(this.playing) { this.stop(); }
		this.disableFocusPause();
	},
/*---------------------------------------------------------------------------------------

	Method: disableFocusPause
	Removes event listeners attached by <enableFocusPause>. 
	
	Returned Value:
	Boolean indicating a successful event removal

---------------------------------------------------------------------------------------*/
	disableFocusPause : function() {
		if(!this.stopMethod || !this.playMethod) { return false; }
		this.carousel.stopObserving('mouseover', this.stopMethod);
		this.carousel.stopObserving('mouseout', this.playMethod);
		return true;
	},
	/*---------------------------------------------------------------------------------------

	Method: enableFocusPause
	On mouseover, the carousel automation will stop and on mouseout, it will resume. 
	
	Returned Value:
	Boolean indicating a successful event attachment

---------------------------------------------------------------------------------------*/
	enableFocusPause : function() {
		if(!this.stopMethod || !this.playMethod) {return false;}
		this.carousel.observe("mouseover", this.stopMethod);
		this.carousel.observe("mouseout", this.playMethod);
		return true;
	}
});

adobe.CarouselSafari2Fix = Class.create({
	initialize: function(carousel) {
		this.carousel = carousel;
		this.patch;
		this.boundfix = this.fix.bind(this);
	},
	fix: function(event) {
		container = event.element();
		if(this.patch) {
			this.patch = container.removeChild(this.patch);
		}
		this.patch = container.appendChild(document.createTextNode("."))
	},
	attach: function() {
		this.carousel.observe("carousel:endrevolve", this.boundfix);
	},
	remove: function() {
		this.carousel.stopObserving("carousel:endrevolve", this.boundfix);
	}
	
	
});