// © Copyright Kenny Grant 2008
// Released under an MIT licence

// Slides should be in a div with class 'slideflow'
// this div should contain a list of links with class 'slide', containing images
// and another element with class 'caption_container'
// may also include 'slide_nav' links for navigation



var Slideflow = {
	version				: '1.0',
	
	delay				: 30, 
	execution_time 		: 0,
	
	container			: null,
	slides				: null,
	navs				: null,
	caption_container	: null,
	
	current_index		: 1, // slides use 1 based index
	starting_index		: 0, // redefine this in another js file if you want another value
	
	
	slide_width			: 200,
	x_space_modifier 	: 1.15,
	y_space_modifier	: 5,
	
	// Set up the slideshow elements and listen to events
	// run on window load by function call at end of this file
	// 
	init : function () {
		// If no slideflow, abort init
		if (!document.getElementsByClassName('slideflow')[0])
			return;
		
		//override hook
		Slideflow.setup();
		
		// test execution time for dom access and setup
		var start = new Date(); 
		
	
		
		// We assume this exists
		Slideflow.container = document.getElementsByClassName('slideflow')[0];
		
		
		// We assume this exists
		Slideflow.caption_container = document.getElementsByClassName('caption_container')[0];
		
		// Load the slideflow elements and observe click events
		Slideflow.load_slides();
		
		// listen to key events for document
		Event.observe(document,'keydown',Slideflow.respond_keys.bindAsEventListener(Slideflow));
		

		
		// Listen to the window for certain events
		Event.observe(window, 'resize', Slideflow.respond_resize.bindAsEventListener(Slideflow));
		
		Event.observe(Slideflow.container, 'mousewheel', Slideflow.respond_mousewheel.bindAsEventListener(Slideflow));
		
		
		// Select the first slide (defaults to half way)
		if (Slideflow.starting_index > 0)
			Slideflow.select_slide_index(Slideflow.starting_index);
		else
			Slideflow.select_slide_index(Slideflow.slide_count()/2 );
			
			
		var finish = new Date(); 
		Slideflow.execution_time = finish.getTime() - start.getTime();	
		Slideflow.endtime = finish.getTime();
		//debug_log(Slideflow.execution_time)
		//debug_log(navigator.appName);	
			
			
	},
	
	// Override hook, define this in another file to override setup of variables 
	//
	// 
	setup : function () {
		
	},
	
	// Load the slides - all links under element 'slideflow'
	//
	//
	load_slides: function(){
	
		
		Slideflow.slides = $A(Slideflow.container.getElementsByClassName('slide'));
		
		Slideflow.navs = $A(Slideflow.container.getElementsByClassName('slide_nav'));
		
		
		if (Slideflow.slide_count() == 0)
			return;
			
			
		Slideflow.slides.each(function (slide,index) {
			
			
			// attach to these slides
			slide.observe('mouseover',Slideflow.respond_mouseover.bindAsEventListener(Slideflow));
			slide.observe('click',Slideflow.respond_click.bindAsEventListener(Slideflow));	
			slide.setAttribute('slide_index',index+1); // use 1 based index
			//  now disable links
			slide.observe('click', function(e) {e.preventDefault();});
	
			// also ensure it is absolutely positioned
			slide.absolutize();
			
			//Now store some slide children to avoid dom access while animating
			slide.flow_slide_image = slide.down('img');

			if (slide.getElementsByClassName('reflected') && slide.down('div'))
				{
				// if reflection and border, need to resize reflection slightly wider
				slide.hasBorder = slide.down('div').hasClassName('border');
				slide.flow_reflect_container  = slide.down('div');
				slide.flow_slide_reflection  = slide.down('canvas');
				}
	
			
		});
		
			Slideflow.navs.each(function (nav,index) {
				nav.observe('click',Slideflow.respond_click_nav.bindAsEventListener(Slideflow));	
				nav.observe('click', function(e) {e.preventDefault();});
				nav.setAttribute('slide_index',index+1); // use 1 based index
				
			});
		
		
	},
	
	
	
	
// Events
	
	// Respond to a click on a nav element
	//
	//
	respond_click_nav: function(event){
		slide_nav = event.element();
		index = slide_nav.getAttribute('slide_index');
		// respond to click by selecting slide	
		Slideflow.select_slide_index(index);
	},
	


	// Respond to a click on a slide
	//
	//
	respond_click: function(event){
		slide = event.element().up('a');
		index = slide.getAttribute('slide_index');
		
		if (index == Slideflow.current_index)
			{
			// respond to click by opening link
			window.location.href = slide.href;	
			}
		else
			{
			// respond to click by selecting slide	
			Slideflow.select_slide_index(index);	
			}
	},
	
	
	// Respond to the end of a click - currently disabled
	//
	//
	respond_unclick: function(event){
//		slide = event.element().up('a');
//		slide.dragging = false;
	},
	
	
	
	// Respond to a hover over a slide
	//
	//
	respond_mouseover: function(event){
		// Perhaps show caption in lightly
	},

	// Respond to a scroll event
	//
	//
	respond_mousewheel: function(event) {
		var delta = event.detail ? event.detail * -1 : event.wheelDelta / 40;
		
		// scroll a set number of slides depending on delta
		
		if (delta > 0)
			{
			Slideflow.select_slide_index(Slideflow.current_index -1);	
			}
		else if (delta < 0)
			{
			Slideflow.select_slide_index(Slideflow.current_index +1);	
			}

		event.preventDefault();
	},
	
	// Respond to a resize event
	//
	//
	respond_resize: function(event) {
		Slideflow.execution_time = 1000;// force immediate move
		Slideflow.position_slides();
		Slideflow.execution_time = 0;
	},


	/*	// Respond to a click
	//
	//
	respond_click: function(event){
	alert(event.which);
	
	// Use this to calculate position and  allow drag to scroll
		
	},*/


	// Respond to key events we're interested in
	//
	//
	respond_keys: function(event) {
		var key = event.which || event.keyCode;	
		switch(key)
			{
		    case Event.KEY_LEFT:
				Slideflow.select_slide_index(Slideflow.current_index -1);
				Event.stop(event);	
			break;
			
		    case Event.KEY_RIGHT:
				Slideflow.select_slide_index(Slideflow.current_index +1);
				Event.stop(event);	
			break;	

			case Event.KEY_HOME:
				Slideflow.select_slide_index(1);
				Event.stop(event);	
			break;

			case Event.KEY_END:
				Slideflow.select_slide_index(Slideflow.slide_count());
				Event.stop(event);		
			break;

			case Event.KEY_ESC:
				Event.stop(event);
			break;	

			default:
			break;
			}

	},

// Slides

	// Select the given slide index
	//
	//
	select_slide_index: function(index){
		
		
		// Convert to int just in case
		index = Math.round(index);
		
		// bounds check - wrap around if out of bounds
		// index = index > Slideflow.slide_count() ? 1 : index ;
		// index = index < 1 ? Slideflow.slide_count() : index ;
		
		// bounds check - don't move if requested index out of bounds
		if (index > Slideflow.slide_count() || index < 1)
			return; // could beep for feedback
		
		
		
		// Set our internal variable
		Slideflow.current_index = index;
		
		// Replace caption with new one
		Slideflow.update_caption();
		
		Slideflow.update_nav();
		
		// Now animate to this desired state
		Slideflow.position_slides();
		
	},
	
	
	// Update the caption to reflect slide alt attribute
	//
	//
	update_caption: function(){
		slide = Slideflow.slide_for_index(Slideflow.current_index);
		
		if (slide && Slideflow.caption_container)
			{
			var	caption = slide.getAttribute('title');
			Slideflow.caption_container.update(caption);	
			}
		
	},

	
	// Update the navigation if any
	//
	//
	update_nav: function(){
		var index_match = Slideflow.current_index -1;//0 based
		
		Slideflow.navs.each(function(nav, index){
			if (index == index_match)
				nav.addClassName('selected');	
			else
				nav.removeClassName('selected');	
			
		});	
				
	},


	// Position the slides given the current index
	//
	//
	position_slides: function(){
		Slideflow.update_slide_zindex();
		Slideflow.update_slide_positions();
		Slideflow.animate_slides();
	},
	

	
	// Update the zindex of all slides
	//
	//
	update_slide_zindex: function(){
		// start at 100 to ensure we're above everything else
		var zIndex = 100;
		Slideflow.slides.each(function(slide, index){
        
			slide_index = index + 1;
			
			if (slide_index > Slideflow.current_index)
				zIndex = zIndex - 1;
			else
				zIndex = zIndex + 1;
        
			slide.style.zIndex = zIndex;
		});
		
	},


	// Run through our slides and calculate desired positions
	// based on current_index and desired gap
	// we store those desired positions in attributes of slides themselves
	//
	//
	update_slide_positions: function(){
		
		// store dimensions of container
		var container_width = Element.getWidth(Slideflow.container);  
	/*	var slides_width 	= Slideflow.slide_count() * Slideflow.slide_width;
		// Now we trim the nominal container width, if we have not enough slides to fill it
		// so that they bunch in the middle
		if (slides_width < container_width)
			container_width = slides_width;*/
		
		      
		var container_height = Element.getHeight(Slideflow.container);
		var container_midpoint = container_width * 0.5;
	

		Slideflow.x_spacing = (container_width / (Slideflow.slide_count()*Slideflow.x_space_modifier));
		Slideflow.y_spacing = (container_height / (Slideflow.slide_count()*Slideflow.y_space_modifier));
		
		
		// Walk through slides setting desired position and size
		// for animation routine to aim towards
		Slideflow.slides.each(function(slide, index){
			
			var slide_index = index + 1;// note index is zero based, we want 1 based
			
		
			// work out the offset from the selected slide (- is before + after)
			var slide_distance_from_current = slide_index - Slideflow.current_index;

			
			var slide_xoffset = Slideflow.x_spacing * slide_distance_from_current;
			var slide_yoffset = Slideflow.y_spacing * slide_distance_from_current;
			// for y we always want distance to be negative
			slide_yoffset = Math.abs(slide_yoffset);
			
			var desired_height = Math.round(Slideflow.slide_width - slide_yoffset*1.3);
			
			
			// round off to the nearest multiple of two
			desired_height = desired_height - (desired_height % 2);
			
			// Round up to Slideflow.slide_width if very close
			if (desired_height > (Slideflow.slide_width - 2))
				desired_height = Slideflow.slide_width;
			
			slide.setAttribute("desired_height",desired_height);
			
			
			// here we really need the *target* width 
			// - assuming slides are square we use height, as that defines width for us
			//var slide_width = slide.getWidth();
			var slide_width = desired_height;	
			
			
			var desired_left = Math.round(container_midpoint - (slide_width * 0.5) + slide_xoffset);
			var desired_top  = Math.round(-slide_yoffset + 50);
	
			
			slide.setAttribute("desired_left",desired_left);
			slide.setAttribute("desired_top",desired_top);
			
		});
	},
	

	// Animate the slides, moving toward their desired positions
	//
	//
	animate_slides: function(){
			// Walk through slides stepping toward desired positions
			Slideflow.slides.each(function(slide, index){
				Slideflow.step_slide(slide.getAttribute('slide_index'));
			});
	},
	
	// Step a slide closer to its goal
	//
	//
	step_slide: function(slide_index){
	
		var slide = Slideflow.slide_for_index(slide_index);
		
		var current_left 		= parseFloat(slide.getStyle('left'));
		var current_top 		= parseFloat(slide.getStyle('top'));
		var current_height 		= parseFloat(slide.flow_slide_image.getStyle('height'));
		
		var desired_left 		= parseFloat(slide.getAttribute("desired_left"));
		var desired_top 		= parseFloat(slide.getAttribute("desired_top"));
		var desired_height 		= parseFloat(slide.getAttribute("desired_height"));

		// If this client is too slow, just flip to slide positions
		// a slow G4 with Safari 1.2 will take ~ 15 milliseconds
		if (Slideflow.execution_time > 60)
			{
			// if it was too slow to animate last time, just jump to desired state
			Slideflow.resize_slide(slide,desired_left,desired_top,desired_height);	
			}
		else
			{
			// if not at desired state, move a step closer, then recurse
			if ( (Math.abs(desired_left - current_left) > 3) || 
				(Math.abs(desired_top - current_top) > 3) ||
				(Math.abs(desired_height - current_height) > 2) )
				{
			
				var new_left = current_left + (desired_left - current_left)*0.5;
				var new_top = current_top + (desired_top - current_top)*0.5;
				var new_height = current_height + (desired_height - current_height)*0.6;

				

				// Round up to Slideflow.slide_width if very close
				if (new_height > (Slideflow.slide_width - 1))
					new_height = Slideflow.slide_width;

				Slideflow.resize_slide(slide,new_left,new_top,new_height);

				window.setTimeout("Slideflow.step_slide("+slide.getAttribute('slide_index')+")", Slideflow.delay);
			
				
				
				}
	
			}	
			
				
	},




	// Resize a slide, given desired attributes attached to it
	//
	//
	resize_slide: function(slide,desired_left,desired_top,desired_height){

		// set slide position
		slide.setStyle({
				left: desired_left + "px",
	            top:  desired_top + "px"  
			});	

		// now set size of image (and reflection if we have one)

		slide.flow_slide_image.setStyle({
			height:desired_height + "px",
			width:desired_height + "px" // be explicit in assumption that they are square
		});

		// we need to also adjust the reflection height, if there is one
		if (slide.flow_slide_reflection)
			{
			// note the reflection width must have 2px knocked off for borders if we have them
			var reflection_width = desired_height;
			if(slide.hasBorder)
				reflection_width += 2;

			// First set actual reflection canvas element width
			slide.flow_slide_reflection.setStyle({
				width:reflection_width + "px"// NB this assumes the width is same as height
			});

			slide.flow_reflect_container.setStyle({
				width:reflection_width + "px"// NB this assumes the width is same as height
			});
			}
	},


// Accessors

	
	// Returns the slide object for a given index int (with bounds checking)
	//
	//
	slide_for_index: function(requested_index){
		var index = parseInt(requested_index) - 1;
		
		if (index > -1 && index < Slideflow.slides.size())
			return Slideflow.slides[index];
		else
			return Slideflow.slides[1];
	},
	
	
	// Return the number of slides
	//
	//
	slide_count: function(){
		return Slideflow.slides.size();
	}
	
};


Event.observe(window,'load',Slideflow.init,false);


		
