/*
**	Simple '360-degree productrotator with hotspots' Javascript class
**	version 1.0
**
**	(c) 2010 Mikoon Webservices <http://www.mikoon.nl>
**	
**	These classses are released under the MIT License <http://www.opensource.org/licenses/mit-license.php> 
*/

/*
**	Example usage:
**
**	var myproductrotator = new productrotator('container-element-id', {options});
**
*/

var productrotator = new Class({
	
	Implements: Options,
	
	is_userdrag: 			false,
	mouse_start_x: 			0,
	drag_start_offset: 		0,
	current_slide_nr: 		0,
	loopstep: 				1,
	loopfps: 				50,
	autoloop_interval: 		0,
	introloop_interval: 	0,
	perspectivecorrection: 	30,
	
	hotspots: 				new Array(),
	
	options: {
		
		namespace: 'rotator_',		//	namepace prefix for generated elements
		
		direction:	1,				//	loop direction, 1 or -1
		
		no_slides: 72,				//	total nr of slide images
		
		slidesfolder: 'images/',	//	(relative or absolute) path to slide images
		slidesprefix: 'image_',		//	prefix of slide image filename
		slidespad: 3,				//	0-character digit padding e.g. slide_001.jpg
		slidespostfix: '.jpg',		//	postfix of slide image filename
		slidesclass: 'rotateimg',	//	selector & styling class for rotator images
		slidesheight: 400,			//	slide image height
		slideswidth:  400,			//	slide image width
		
		autoloop: true,				//	auto start loop animation
		loopduration: 4000,			//	loop animation in msec
		fps: 50,					//	loop animation frames per second
		autoloop_delay:	1000,		//	delay after a 'user drag' before re-start of autoloop
		
		introslidestart: 0,			//	intro animation start slide nr
		introduration: 500,			//	intro animation in msec
		
		onComplete: function(){},	//	function to call on preload complete 
		onProgress: function(){},	//	function to call on preload progress
		
		dragsensitivity: 0.20,		//	mouse rotate sensitivity
		has_userdrag: false			//	setup userdrag events	
		
    },
	
	initialize: function(in_container, options){
		
		//	set options
		this.setOptions(options);

		//	assign local variables
		this.container 	= 	$(in_container);

		//	calculate & create local variables
		this.draglock 	= 	$('draglock');
		this.loopstep 	= 	Math.max(1, this.options.no_slides/(this.options.loopduration/this.options.fps));
		this.loopfps	= 	Math.max(this.options.fps, Math.round(this.options.loopduration/this.options.no_slides));

		//	setup events
		this.setup();
		
		// preload images
		this.preload();

	},
	
	preload: function(){
		//	preload all slides and inject into container element
		var THIS = this;
		var loaded = 0;
		var imgs = new Array();
		
		//	load all slides, track progress
		for(i=0;i<this.options.no_slides;i++){
			
			//	create image object for each slide image
			imgs[i] = new Image();
			imgs[i].src = this.options.slidesfolder+this.options.slidesprefix+this.pad((i+1),this.options.slidespad)+this.options.slidespostfix;
			imgs[i].onload = (function(nr){ 
				
				//	create div element with loaded image as background
				var div = new Element('div', {id: THIS.options.namespace+'img'+nr, 'class': THIS.options.slidesclass});
				div.setStyle('background-image', 'url('+imgs[nr].src+')');
				div.inject(THIS.container, 'bottom');
				loaded++;
				
				//	track progress of preload
				THIS.progress(loaded);
				if(loaded == THIS.options.no_slides) THIS.intro();
				
			}).pass(i);	
		}
	},
	
	progress: function(in_progress){
		//	loaded
		this.current_progress = Math.round(in_progress / this.options.no_slides * 100);
		//	call user function
		myfunction = this.options.onProgress.bind(this); myfunction();
	},
		
	pad: function(in_number, in_length) {
		//	(left)pad integer with [length] [number]'s
    	var str = '' + in_number;
    	while (str.length < in_length) str = '0' + str;   
    	return str;
	},
	
	setup: function(){
		//	setup events
		var THIS = this;
		
		if(this.options.has_userdrag){
			
			//	mousedown (start drag) event
			this.container.addEvent('mousedown', (function(e){ 
				var e = new Event(e); 
				this.is_userdrag = true;
				this.autoloop_stop();
				this.mouse_start_x = e.page.x;
				this.drag_start_offset = this.current_slide_nr;
			}).bind(this));
			
			//	mousemove (drag) event
			this.container.addEvent('mousemove', (function(e){  
				if(this.is_userdrag){
					var e = new Event(e);  
					this.drag(e.page.x);
				}
			}).bind(this));
			
			//	mousedown (stop drag) event
			this.container.addEvent('mouseup', (function(e){ 
				this.is_userdrag = false;  
				//	re-start autoloop after optional delay
				(function(){ this.autoloop_start(); }).bind(THIS).delay(this.options.autoloop_delay);
			}).bind(this));
			
			//	mouseleave (stop drag) event
			this.container.addEvent('mouseleave', (function(e){ 
				this.is_userdrag = false; 
				//	re-start autoloop after optional delay
				(function(){ this.autoloop_start(); }).bind(THIS).delay(this.options.autoloop_delay);
			}).bind(this));
			
		}
	},
	
	intro: function(){
		//	all slides loaded, call user function
		myfunction = this.options.onComplete.bind(this); myfunction();
		
		//	set start position to intro slide start nr
		this.to(this.options.introslidestart);
		
		//	start intro animation
		this.intro_interval = (function(){
			if(this.current_slide_nr == 0){
				//	end of intro animation
				clearInterval(this.intro_interval);
				//	start autoloop
				if(this.options.autoloop){
					(function(){ this.autoloop_start(); }).bind(this).delay(this.options.autoloop_delay);
				}	
			} else {
				//	next slide
				this.to(Math.max(0, this.current_slide_nr - Math.round(this.options.introslidestart/(this.options.introduration/this.options.fps))));
			}
		}).periodical(this.options.fps, this);
	},
	
	autoloop_start: function(){
		//	stop current autoloop animation, if any
		this.autoloop_stop();
		//	run autoloop animation
		if(this.options.autoloop){
			this.autoloop_interval = (function(){
				var nr = Math.floor((this.current_slide_nr - this.loopstep)%this.options.no_slides);
				if(nr<0) nr = this.options.no_slides + nr;
				this.to(nr);
			}).periodical(this.loopfps, this);
		}
	},
		
	autoloop_stop: function(){
		//	stop autoloop animation
		clearInterval(this.autoloop_interval);
	},
	
	drag: function(in_x){
		//	user drags mouse
		var nr = Math.floor((((this.mouse_start_x - in_x)*this.options.dragsensitivity) + this.drag_start_offset)%this.options.no_slides);
		if(nr<0) nr = this.options.no_slides + nr;
		if(nr != this.current_slide_nr){
			this.to(nr);
		}
	},
	
	to: function(in_nr){
		//	show slide [nr] 
		$(this.options.namespace+'img'+this.current_slide_nr).setStyle('display', 'none');
		$(this.options.namespace+'img'+in_nr).setStyle('display', 'block');
		this.current_degrees = (360/this.options.no_slides) * in_nr;
		this.current_slide_nr = in_nr;
		if(this.hotspots.length) this.hotspot();
	},
	
	set: function(in_degrees){
		//	set to slide [degrees], 360 is full rotation
		if(this.options.direction == -1) in_degrees = 360-in_degrees;
		var degrees = Math.abs(in_degrees%360);
		var nr = Math.round(degrees * (this.options.no_slides/360));
		$(this.options.namespace+'img'+this.current_slide_nr).setStyle('display', 'none');
		$(this.options.namespace+'img'+nr).setStyle('display', 'block');
		this.current_degrees = degrees;
		this.current_slide_nr = nr;
		if(this.hotspots.length) this.hotspot();
	},
	
	add_hotspot: function(in_object){
		//	add a new hotspot to rotator
		this.hotspots[this.hotspots.length] = in_object;
	},
	
	hotspot: function(){
		//	track movement of hotspots
		var THIS = this;
		
		//	calculate x,y,z, & alpha and display for each hotspot
		this.hotspots.each(function(el,i){
			//	hotspot opacity
			var o = Math.cos(((THIS.current_degrees / 360) + (el.x_degrees/360)) * THIS.options.direction *  2 * Math.PI);
			if(o>0){
				//	hotspot cylindrical x position
				var x = Math.sin(((THIS.current_degrees / 360)  + (el.x_degrees/360)) * THIS.options.direction *  2 * Math.PI) * (THIS.options.slideswidth/2) * (el.z_offset/THIS.options.slideswidth);
				//	hotspot offset x position
				var x_offset = 1 - Math.cos(((THIS.current_degrees / 360)  + (el.x_degrees/360)) * THIS.options.direction *  2 * Math.PI) * el.x_offset;
				//	hotspot y perspective correction
				var y = Math.sin(((THIS.current_degrees / 360) + (el.x_degrees/360)) * THIS.options.direction *  2 * Math.PI);
				var x_correction = el.x_offset / (THIS.options.slideswidth/2);
				var y_correction = (el.y_offset - (THIS.options.slidesheight/2)) / (THIS.options.slidesheight/2);
				var perspective_correction = y * x_correction * y_correction * THIS.perspectivecorrection;
				//	hotspot place hotspot
				el.element.setStyles({'display': 'block', 'opacity': o, 'margin-left': x-x_offset+'px', 'margin-top': el.y_offset - perspective_correction +'px'});
			} else {
				//	hide hotspot
				el.element.setStyle('display', 'none');
			}
		});
	}
	
});

/*
**	Example usage:
**
**	myhotspot = new productrotatorhotspot(rotator_object, 'hotspot-element-id', in_x_degrees, in_x_offset, in_y_offset, in_z_offset, {options});
*/

var productrotatorhotspot = new Class({
	
	Implements: Options,
	
	options: {
		onEnter: function(){ this.rotator.autoloop_stop(); },
		onLeave: function(){ this.rotator.autoloop_start(); },
		onClick: function(){ alert('clicked: '+this.element.id); }
    },
	
	rotator: {},		//	container rotator object
	element: {},		//	the hotspot element
	x_degrees: 0,		//	x degrees in cylindrical shape
	x_offset: 0,		//	offset on x axis
	y_offset: 0,		//	offset on y axis
	z_offset: 0,		//	offset on z axis
	
	initialize: function(in_rotator, in_element, in_x_degrees, in_x_offset, in_y_offset, in_z_offset, options){
		
		//	set options
		this.setOptions(options);

		//	assign local variables
		this.rotator = in_rotator;
		this.element = $(in_element);
		this.x_degrees = in_x_degrees;
		this.x_offset = in_x_offset;
		this.y_offset = in_y_offset;
		this.z_offset = in_z_offset;
		
		//	setup events
		this.setup();	
		
		//	add to rotator
		this.rotator.add_hotspot(this);
		
	},
	
	setup: function(){
		this.element.addEvent('focus', function(){ this.blur(); });
		this.element.addEvent('click', this.options.onClick.bind(this));
		this.element.addEvent('mouseenter', this.options.onEnter.bind(this));
		this.element.addEvent('mouseleave', this.options.onLeave.bind(this));
	}
	
});
