HW.Carousel = function(id,opts) {
	
	// create an options object based on default values and user inputs
	this.opts = HW.extendObject(HW.Carousel.Options,opts);
	
	// launch the rotator
	this.init(id,'phRotatingElement');
}

HW.Carousel.prototype = {
	// set the starting theta value to bring the first image to the centre
	theta:3*Math.PI/2,
	// tracks the current focussed image
	current:0,
	// tracks the previously focussed image
	previous:0,
	// stores the angles needed to bring each image to the centre
	offsets:[],
	// flag if animation is happening
	active:true,
	/*
	* init(id,cls)
	* id - id of the container element
	* cls - class of the elements to rotate
	*/
	init:function(id,cls) {
		var obj = this;
		
		// find the container element
		this.container = $(id);
		// if the container does not exists then exit
		if(!this.container) {return;}
		
		// set the container width
		HW.setStyle(this.container,{width:this.opts.width+'px'});
		
		// create a wrapper div for our images to get around some css issues
		this.wrapper = HW.createNode('div',this.container,'',{className:'phRotatorWrapper'});
		
		// adjust the wrapper to fit our content
		this.findBounds();
		
		// find the elements to rotate
		this.images = _$('#'+id+' .'+cls);
		
		for(var i=0,j=this.images.length;i<j;i++) {
			HW.addClass(this.images[i],'phPhoto');
			// place the image in the wrapper element
			this.wrapper.appendChild(this.images[i]);
			//populate the offsets array
			this.offsets[i] = this.theta + i*2*Math.PI/j;
			// add an event handler to bring content to centre
			// put in closure to allow asynchronous access to 'i'
			(function(){
				var n = obj.images.length - i;
				HW.attachEvent(obj.images[i],'click',function(){obj.goTo(n);})
			})()
		}
		// add the left and right links
		this.drawButtons();
		// render the content
		this.draw();
	},
	/*
	* findBounds()
	* sets the position and dimensions of teh wrapper depending on the options defined by the user
	*/
	findBounds:function() {
		// ensure the images fill the space allowed by calculating the width of an image at the extremities
		var dw = this.opts.image_width*this.opts.image_scale;
		this.wrapperWidth = this.opts.width-dw;
		HW.setStyle(this.wrapper,{position:'relative',left:dw/2+'px',width:this.opts.width-dw+'px',height:this.opts.height+'px'});
	},
	/*
	* drawButtons()
	* create the footer section with left and right links
	*/
	drawButtons:function() {
		var obj = this;
		// create a footer wrapper to contain the links
		this.footer = HW.createNode('div',this.container,'',{className:'phRotatorButtons'});
		
		//create the left link and bind event handler
		var left = HW.createNode('a',this.footer,this.opts.leftLink,{className:'p_leftButton p_Button',href:'#'});
		HW.attachEvent(left,'click',function(e){HW.preventDefault(e);obj.go(1);});
		//create the right link and bind event handler
		var right = HW.createNode('a',this.footer,this.opts.rightLink,{className:'p_rightButton p_Button',href:'#'});
		HW.attachEvent(right,'click',function(e){HW.preventDefault(e);obj.go(-1);});
	},
	/*
	* go(d)
	* rotate the content
	* d - direction to rotate (1: left, -1: right)
	*/
	go:function(d) {
		// if no current animation
		if(this.active) {
			// set the previous and current elements
			this.previous = this.current;
			this.current = this.current + d;
			this.current = (this.current+this.images.length)%this.images.length;
			
			// set active flag
			this.active = false;
			
			// start animation
			this.animate();
		}
	},
	/*
	* goTo(n)
	* rotate the content to a specific element
	* n - index of the element to bring to center
	*/
	goTo:function(n) {
		// if no current animation
		if(this.active) {
			// ensure n is within bounds
			n = (n + this.images.length)%this.images.length;
			// if n is the current element then fire the onclickmain event and exit
			if(n == this.current) {
				this.clickMain();
				return;
			}
			// set the previous and current elements
			this.previous = this.current;
			this.current = n;
			
			// set active flag
			this.active = false;
			
			// start animation
			this.animate();
		}
	},
	/*
	* draw()
	* position the elements
	*/
	draw:function() {
		for(var i=0,j=this.images.length;i<j;i++) {
			// get the angle between the current element and element 0
			var offset = i*2*Math.PI/this.images.length;
			
			// calculate the z position
			// the is roughly equivalent to z-index, but is also used to position in vertical axis
			var z = 1 + Math.sin(this.theta + offset);
			
			// calculate the x position
			var x = this.wrapperWidth/2 + this.wrapperWidth*Math.cos(this.theta + offset)/2;
			
			// calculate the y position from the z value and the tilt option
			var y = -z * this.opts.height/2 * Math.sin(this.opts.tilt);
			
			// scale the content depending on z value and the image_scale option
			var scale = (2 - (z*2*(1-this.opts.image_scale)))/(this.opts.height/2)*(this.opts.height/4);
			
			// finally set the left/top positions and content sizes
			var _top = y + this.opts.height/2 - this.opts.image_height*scale/2 + 'px';
			var _left = x - this.opts.image_width*scale/2 + 'px';
			var _height = Math.max(this.opts.image_height*scale,0) + 'px';
			var _width = Math.max(this.opts.image_width*scale,0) + 'px';
			HW.setStyle(this.images[i],{top:_top,left:_left,width:_width,height:_height});
			
			// define the elements z value
			this.images[i].z = z;
		}
		// sort the elements by z value and apply z-indexes to allow layering
		this.sortZ();
	},
	/*
	* sortZ()
	* sort the elements in z value order in the z-index
	*/
	sortZ:function() {
		// create a temp copy of this.images
		var t = [];
		for(var i=0,j=this.images.length;i<j;i++) {
			t[i] = this.images[i];
		}
		// sort by the z value
		t.sort(function(a,b){
			return b.z - a.z;
		});
		// set the z-indexes in order
		for(var i=0,j=t.length;i<j;i++) {
			t[i].style.zIndex = i;
		}
	},
	/*
	* animate()
	* animate the rotator
	*/
	animate:function() {
		var obj = this;
		// set the current and final theta values
		var v1 = this.theta
		var v2 = this.offsets[this.current];
		
		// need to make sure we always go the shortest way round from one item to the next
		// if were more than half a revolution (Math.PI) apart then increment the values by 2*Math.PI (i.e. one full revolution)
		var dt = Math.abs(v2-v1);
		while(dt > Math.PI) {
			if(v1>v2) {
				v2 += 2*Math.PI;
			}
			else {
				v1 += 2*Math.PI;
			}
			dt = Math.abs(v2-v1);
		}
		
		// create an instance of the animator object to perform animation over 500ms
		// pass setter function as this.setTheta
		// pass callback funtion as this.callback
		new HW.Animator(this,v1,v2,function(o,v){obj.setTheta(v);},500,function(){obj.callback();});
	},
	/*
	* setTheta(v)
	* move the rotator to an angle of v
	* v - the angle to move to
	*/
	setTheta:function(v) {
		this.theta = v;
		// keep theta within sensible bounds
		while(this.theta > 2*Math.PI) {this.theta -= 2*Math.PI;}
		// render the content
		this.draw();
	},
	/*
	* callback()
	* finish animation
	*/
	callback:function() {
		// reset active flag to enable links
		this.active = true;
		// if onchange is defined then call it with argument of current index
		if(typeof this.opts.onchange == 'function') {
			this.opts.onchange(this.current);
		}
	},
	/*
	* clickMain()
	* handle click on main content
	*/
	clickMain:function() {
		// if onclickmain is defined then call it with argument of current index
		if(typeof this.opts.onclickmain == 'function') {
			this.opts.onclickmain(this.current);
		}
	}
}

// define the default options for our rotator
HW.Carousel.Options = {
	// dimensions of the images
	image_width:100,
	image_height:100,
	
	// dimensions of the container
	width:900,
	height:300,
	
	// angle to view rotator at
	// values >0 will view from above
	// values <0 will view from below
	tilt:0,
	
	// set the extent to which images shoudl scale
	image_scale:0.707,
	
	// text/innerHTML of left/right links
	leftLink:'prev',
	rightLink:'next',
	
	// event handler functions
	onchange:function(n){},
	onclickmain:function(n){}
}