/* 
	BaseFlow: Base class for a flow-like object
	Copyright (C) 2008 Best Foot Forward

	Based on protoflow: http://www.deensoft.com/lab/protoflow/

	This script requires prototype.js and reflection.js if reflection is enabled.
*/

/* fixme: This is a hack to get mouse wheel scrolling across browsers */
Object.extend (Event, {
	wheel : function(event) {
		var delta = 0;
		if (!event) event = window.event;

		if (event.wheelDelta) {
			delta = event.wheelDelta/120;
			if (window.opera) delta = -delta;
		} 
		else if (event.detail) {
			delta = -event.detail/3;
		}
		return Math.round(delta); //Safari Round
	}
});

BaseFlow = Class.create (
{
	initialize : function (elem, opts)
	{
		var baseopts = {
			'reflection' : true,  /* Use reflection effect on elements */
			'start' : 1,          /* Focus on page load (before initial movement) */
			'current' : 1,        /* Focus after page load */
			'ticklength' : 50,    /* Time to wait between frames (msec) */
			'clickfocus' : true,  /* Clicking on an item focuses it */
			'keyboard' : true,    /* Move focus on arrow presses */
			'scroll' : true		  /* Move focus on mouse wheel scroll */
		}

		Object.extend (baseopts, opts);

		this.elem = $(elem);
		if (this.elem == null)
			throw "BaseFlow element not specified";
		/* Save the width and height of the container:
		   fixme: Handle resizing */
		this.width = this.elem.getWidth ();
		this.height = this.elem.getHeight ();

		/* Get thie list of our children */
		this.stack = this.elem.childElements ();

		/* reflection.js messes with the images, so if reflection is desired
           apply it now and reload the stack */
		if (baseopts.reflection) {
			this.stack.each (function (item) {
				Reflection.add (item, { opacity: 2 / 3 });
			});
			this.stack = this.elem.childElements ();
		}

		/* Perform tasks on each element */
		for (var i = 0; i < this.stack.length; ++i) {
			var item = this.stack [i];

			/* Cache the width of the item */
			item.width = item.getWidth ();
			
			/* Add click event handler */
			if (baseopts.clickfocus) {
				item.observe ('click', function (index) {
					this.elem.fire ("baseflow:click", { 'index' : index });
					this.focus (index);
				}.bind (this, i));
			}
		}	

		this.ticklength = baseopts.ticklength;

		/* Listen for key events */
		if (baseopts.keyboard) {
			document.observe ('keydown', function(e) {
				var code = e.keyCode;
				if (code == 37)
					this.previous ();
				if (code == 39)
					this.next ();
			}.bind (this));
		}

		/* Listen to mouse wheel events
		   fixme: I hate special casing browsers. Investigate prototype.js to help us out here */
		if (baseopts.scroll) {
			var eventType = Prototype.Browser.Gecko ? "DOMMouseScroll" : "mousewheel";
			Event.observe (this.elem, eventType, (function (e) {
				if (Event.wheel (e) < 0)
					this.next()
				else
					this.previous();
				/* Eat up the event */
				Event.stop (e);

			}).bind (this));
		}

		/* Move focus as specified; queued to allow base classes to finish construction */
		this.current = baseopts.start;
		window.setTimeout (this.focus.bind (this, baseopts.current), this.ticklength);
	},

	focus : function (index)
	{
		var anim = { 'ticks' : 0,
					 'start' : this.current,
					 'target' : index,
					 'run' : true };

		/* Change the focus and signal */
		this.current = index;
		this.elem.fire ("baseflow:focus-change", { 'index' : index, 'last' : anim.start });

		/* Start the animation; stopping any old ones */
		if (this.anim)
			this.anim.run = false;
		
		this.anim = anim;
		this.step (anim);
	},

	step : function (anim)
	{
		/* Check if the animation has been aborted */
		if (!anim.run)
			return;

		/* Position our children */
		var carryon = this.position (anim);

		anim.ticks ++;

		/* Reschedule if animation should continue */
		if (carryon) 
			window.setTimeout (this.step.bind (this, anim), this.ticklength);
	},

	/* Position all of this.stack based on the animation anim. Return true to be
	   scheduled again */
	position : function (anim)
	{
		/* Position every child */
		for (var i = 0; i < this.stack.length; ++i) {

			var pos = this.stack [i].positionedOffset ();
			this.positionItem (anim, i, pos);

			/* Update the position based on pos */
			this.stack [i].setStyle ({
                left: pos.x + "px",
                top: pos.y + "px",
				zIndex : pos.z
            });
		}

		/* Default logic is not an animation */
		return false;
	},

	positionItem : function (anim, index, pos)
	{
		pos.x = 0;
		pos.y = 0;
		pos.z = 0;
	},

	next : function ()
	{
		this.focus (Math.min (this.current + 1, this.stack.length - 1));
	},

	previous : function ()
	{
		this.focus (Math.max (this.current - 1, 0));
	},

	start : function ()
	{
		this.focus (0);
	},

	end : function ()
	{
		this.focus (this.stack.length - 1);
	}
});

