﻿/*
	carousel.js : general carousel functionality.
*/

// -----------------------------------------------------------------------------
// CAROUSEL FUNCTIONS
// -----------------------------------------------------------------------------

//! Initialize a carousel.
/*!	
	@param crs	Carousel object to initialize.
	@param opts	Carousel options:
		- $me			jquery object for carousel;
		- speed			Time for full 360 revolution [ms];
		- radius		carousel radius;
		- itemWidth		Carousel item width;
		- itemHeight	Carousem item height;
	@todo	Generalize more components.
*/
function carouselInit(crs, opts)
{
	opts= $.extend({
		speed: 4000,
		radius: 400,
		focus: 0.3, 
		onSelect: function(crs, i, dt)	{ }, 
		onClick: function(crs, i, e)	{	carouselSelect(crs, i, crs.speed);	}
	}, opts);

	crs.$me= opts.$me;
	crs.$view= crs.$me.find('.view');
	crs.$list= crs.$view.find('.item');
	crs.$pager= crs.$me.find('.pager li');
	crs.$title= crs.$me.find('.title');

	crs.onClick= opts.onClick; 
	crs.onSelect= opts.onSelect;
	crs.speed= opts.speed;

	crs.count= crs.$list.length;
	crs.width= crs.$view.width();
	crs.height= crs.$view.height();
	crs.itemWidth= opts.itemWidth;
	crs.itemHeight= opts.itemHeight;
	
	crs.x0= (opts.x0 ? opts.x0 : crs.width/2);
	crs.y0= (opts.y0 ? opts.y0 : crs.itemHeight*0.4);
	crs.radius= opts.radius;			// Radius of carousel
	crs.sel= -1;
	crs.phi= 0;
	crs.dphi= 2*Math.PI/crs.count;		// angular distance between items.
	crs.focus= opts.focus;				// mid/front scaling ratio.
	
	crs.$me.find('.nav-left').click(function(e) {
		crs.onClick(crs, (crs.sel-1+crs.count)%crs.count, e);
	});
	
	crs.$me.find('.nav-right').click(function(e) {
		crs.onClick(crs, (crs.sel+1+crs.count)%crs.count, e);
	});
	
	crs.$pager.click(function(e) {
		crs.onClick(crs, $(this).index(), e);
	});
	
	if(crs.$me.find('.imap').length > 0)
	{
		crs.$imap= crs.$me.find('.imap');
		crs.imapCoords= $.parseJSON(crs.$imap.find('._opts').text()).map;
		
		crs.$imap.find('area').click(function(e) {
			crs.onClick(crs, $(this).attr('alt'), e);
			e.preventDefault();
		});
	}
	else
	{
		crs.$list.click(function(e) {
			crs.onClick(crs, $(this).index(), e);
		});
	}
	
	crs.i2id= function(i) {
		return this.$list.eq(i).attr('id').substr(4);
	}
	crs.id2i= function(id) {
		//log(this.$list);//.find("#crs-" + id));
		return this.$list.filter("#crs-" + id).index();
	}

	carouselSelect(crs, Math.max(crs.count/2-1, 0), 0);
}

//! Select an item in a carousel.
/*!
	@param crs	Carousel object.
	@param i	New selection index.
	@param dt	if >0: animation duration [ms].
*/
function carouselSelect(crs, i, dt)
{
	i= parseInt(i);
	
	// --- Nothing to do ---
	if(crs.sel == i)
		return;

	// --- Highlight button ---
	$me= crs.$pager.eq(i);
	$sel= $('li.active', $me.parent());
	$sel.removeClass('active');
	$me.addClass('active');

	// --- Turn the carousel ---
	crs.sel= i;
	crs.$title.text($('img', crs.$list.eq(i)).attr("alt"));

	if(dt>0)
	{
		$(crs).stop();
		var dphi= wrapAngle(i*crs.dphi - crs.phi);
		
		$(crs).animate( {phi: crs.phi + dphi}, { 
			duration: dt*Math.abs(dphi)/(2*Math.PI), 
			step: function(val, opts) {
				carouselPlaceAngle(this, val);
			}
		});
	}
	else
		carouselPlaceIndex(crs);
	
	// --- Extra handler ---
	crs.onSelect(crs, i, dt);

}

//! Place a carousel by angle.
function carouselPlaceAngle(crs, phi0)
{
	var items= [];
	
	for(i=0; i<crs.count; i++)
	{
		// --- Circlish distribution ---
		/* NOTE: we've chosen for a fixed distance between items, not 
			equidistant distribution. The laater would be awkward for small 
			numbers.
		  NOTE: the scalings have been tweeked slightly for the non-front 
		  items to match the design.
		*/
		var pi= Math.PI;
		var phi= wrapAngle(i*crs.dphi - phi0);
		
		var xf= Math.sin(phi), zf= 1-Math.cos(phi);
		var scale= 1/(1+zf*(1/crs.focus-1));

		items[i]= {
			i:		i, 
			$me:	crs.$list.eq(i),
			x:		crs.x0 + crs.radius*xf - scale*crs.itemWidth/2, 
			y: 		crs.y0 - scale*crs.itemHeight*0.4 ,
			z: 		zf, 
			scale:	scale
		};

		var css={
			'left' : 	items[i].x, 
			'top' : 	items[i].y, 
			'width':	scale*crs.itemWidth
		};

		items[i].$me.css(css);
	}
	
	// --- Z-sort ---
	items.sort(function(a, b) { return (a.z>b.z ? -1 : (a.z<b.z ? 1 : 0));  });
	
	for(i=0; i<crs.count; i++)
		items[i].$me.css('z-index', i+2);
		
	// --- Image map ---
	if(crs.$imap)
	{
		var $areas= crs.$imap.find('area');
		var shape= crs.imapCoords;

		for(i=0; i<crs.count; i++)
		{
			var coords= [];
			var item= items[i];
			for(j=0; j<shape.length/2; j++)
			{
				coords[2*j+0]= item.scale*shape[2*j+0] + item.x;
				coords[2*j+1]= item.scale*shape[2*j+1] + item.y;
			}
			coords= coords.join();
			
			var $area= $areas.eq(crs.count-i-1);
			$area.attr('coords', coords);
			$area.attr('alt', item.i);
			$area.attr('href', "#" + item.$me.attr('id'));
		}
	}

	crs.phi= phi0;
}

//! Place carousel by index.
function carouselPlaceIndex(crs)
{
	carouselPlaceAngle(crs, crs.sel*crs.dphi);
}

//! Wrap angle to range (-pi, pi].
function wrapAngle(alpha)
{
	var pi= Math.PI;
	return (alpha > pi ? alpha-2*pi : (alpha <= -pi ? alpha+2*pi : alpha));
}

// EOF

