// jquery.jparallax.js
// 0.9.1
// Stephen Band
//
// Dependencies:
// jQuery 1.2.6 (jquery.com)
//
// Project and documentation site:
// http://webdev.stephband.info/parallax.html


// CLOSURE

(function(jQuery) {
	
	// PRIVATE FUNCTIONS
	
	function stripFiletype(ref) {
		var x=ref.replace('.html', '');
		return x.replace('#', '');
	}
	
	function initOrigin(l) {
		if (l.xorigin=='left') {
			l.xorigin=0;
		}
		else if (l.xorigin=='middle' || l.xorigin=='centre' || l.xorigin=='center')	{
			l.xorigin=0.5;
		}
		else if (l.xorigin=='right') {
			l.xorigin=1;
		}
	  	if (l.yorigin=='top') {
			l.yorigin=0;
		}
		else if (l.yorigin=='middle' || l.yorigin=='centre' || l.yorigin=='center')	{
			l.yorigin=0.5;
		}
		else if (l.yorigin=='bottom') {
			l.yorigin=1;
		}
	}
	
	function positionMouse(mouseport, localmouse, virtualmouse) {
	
		var difference = {x: 0, y: 0, sum: 0};
		
		// Set where the virtual mouse is, if not on target
		if (!mouseport.ontarget) {
		
			// Calculate difference
			difference.x    = virtualmouse.x - localmouse.x;
			difference.y    = virtualmouse.y - localmouse.y;
			difference.sum  = Math.sqrt(difference.x*difference.x + difference.y*difference.y);
			
			// Reset virtualmouse
			virtualmouse.x = localmouse.x + difference.x * mouseport.takeoverFactor;
			virtualmouse.y = localmouse.y + difference.y * mouseport.takeoverFactor;
			
			// If mouse is inside the takeoverThresh set ontarget to true
			if (difference.sum < mouseport.takeoverThresh && difference.sum > mouseport.takeoverThresh*-1) {
				mouseport.ontarget=true;
			}
		}
		// Set where the layer is if on target
		else {
			virtualmouse.x = localmouse.x;
			virtualmouse.y = localmouse.y;
		}
	}
	
	function setupPorts(viewport, mouseport) {
	
		var offset = mouseport.element.offset();
			
		jQuery.extend(viewport, {
			width: 	viewport.element.width(),
			height: viewport.element.height()
		});
		
		jQuery.extend(mouseport, {
			width:	mouseport.element.width(),
			height:	mouseport.element.height(),
			top:	offset.top,
			left:	offset.left
		});
	}
	
	function parseTravel(travel, origin, dimension) {
	  
		var offset;
		var cssPos;
		
		if (typeof(travel) === 'string') {
			if (travel.search(/^\d+\s?px$/) != -1) {
				travel = travel.replace('px', '');
				travel = parseInt(travel, 10);
				
				// Set offset constant used in moveLayers()
				offset = origin * (dimension-travel);
				
				// Set origin now because it won't get altered in moveLayers()
				cssPos = origin * 100 + '%';   
			  	return {travel: travel, travelpx: true, offset: offset, cssPos: cssPos};
			}
			else if (travel.search(/^\d+\s?%$/) != -1) {
			  	travel.replace('%', '');
				travel = parseInt(travel, 10) / 100;
			}
			else {
				travel=1;
			}
		}
		
		// Set offset constant used in moveLayers()
		offset = origin * (1 - travel);
		
		return {
			travel: travel, 
			travelpx: false, 
			offset: offset
		};
	}
	
	function setupLayer(layer, i, mouseport) {
	
		var xStuff;
		var yStuff;
		var cssObject = {};
		
		layer[i]=jQuery.extend({}, {
			width:		layer[i].element.width(),
			height:		layer[i].element.height()
		}, layer[i]);
		
		xStuff = parseTravel(layer[i].xtravel, layer[i].xorigin, layer[i].width);
		yStuff = parseTravel(layer[i].ytravel, layer[i].yorigin, layer[i].height);
		
		jQuery.extend(layer[i], {
			
			// Used in triggerResponse
			diffxrat:    mouseport.width / (layer[i].width - mouseport.width),
			diffyrat:    mouseport.height / (layer[i].height - mouseport.height),
			
			// Used in moveLayers
			xtravel:     xStuff.travel,
			ytravel:     yStuff.travel,
			xtravelpx:   xStuff.travelpx,
			ytravelpx:   yStuff.travelpx,
			xoffset:     xStuff.offset,
			yoffset:     yStuff.offset
		});
		
		// Set origin now if it won't be altered in moveLayers()
		if (xStuff.travelpx) {
			cssObject.left = xStuff.cssPos;
		}
		if (yStuff.travelpx) {
			cssObject.top = yStuff.cssPos;
		}
		if (xStuff.travelpx || yStuff.travelpx) {
			layer[i].element.css(cssObject);
		}
	}
	
	function setupLayerContents(layer, i, viewportOffset) {
	
		var contentOffset;
		
		// Give layer a content object
		jQuery.extend(layer[i], {content: []});
		
		// Layer content: get positions, dimensions and calculate element offsets for centering children of layers
		for (var n=0; n<layer[i].element.children().length; n++) {
		  
			if (!layer[i].content[n]) {
		  		layer[i].content[n] = {};
		  	}
		  	if (!layer[i].content[n].element) {
				layer[i].content[n]['element'] = layer[i].element.children().eq(n);
			}
		  
			// Store the anchor name if one has not already been specified.  You can specify anchors in Layer Options rather than html if you want.
			if(!layer[i].content[n].anchor && layer[i].content[n].element.children('a').attr('name')) {
				layer[i].content[n]['anchor'] = layer[i].content[n].element.children('a').attr('name');
			}
		  
			// Only bother to store child's dimensions if child has an anchor.  What's the point otherwise?
			if(layer[i].content[n].anchor) {
				contentOffset = layer[i].content[n].element.offset();
			  	jQuery.extend(layer[i].content[n], {
			  		width: 		layer[i].content[n].element.width(),
			  		height:		layer[i].content[n].element.height(),
			  		x:			  contentOffset.left - viewportOffset.left,
			  		y:			  contentOffset.top - viewportOffset.top
			  	});
			  	jQuery.extend(layer[i].content[n], { 
			  		posxrat:  (layer[i].content[n].x + layer[i].content[n].width/2) / layer[i].width,
			  	  	posyrat:  (layer[i].content[n].y + layer[i].content[n].height/2) / layer[i].height
			  	});
			}
		}
	}
	
	function moveLayers(layer, xratio, yratio) {
	
		var xpos;
		var ypos;
		var cssObject;
		var total = layer.length;
		
		for (var i=0; i<total; i++) {
			
		    // Calculate the moving factor
		  	xpos = layer[i].xtravel * xratio + layer[i].xoffset;
		    ypos = layer[i].ytravel * yratio + layer[i].yoffset;
			
		    cssObject = {};
		  	// Do the moving by pixels or by ratio depending on travelpx
		    if (layer[i].xparallax) {
				if (layer[i].xtravelpx) {
					cssObject.marginLeft = xpos * -1 + 'px';
				} 
				else {
					cssObject.left = xpos * 100 + '%';
					cssObject.marginLeft = xpos * layer[i].width *-1 + 'px';
				}
			}
			if (layer[i].yparallax) {
				if (layer[i].ytravelpx) {
					cssObject.marginTop = ypos * -1 + 'px';
				}
				else {
					cssObject.top = ypos * 100 + '%';
					cssObject.marginTop = ypos * layer[i].height * -1 + 'px';
				}
			}
			layer[i].element.css(cssObject);
		}
	}
	
	// PLUGIN DEFINITION **********************************************************************
	
	jQuery.fn.jparallax = function(options) {
		
		// Organise settings into objects (Is this a bit of a mess, or is it efficient?)
		var settings = jQuery().extend({}, jQuery.fn.jparallax.settings, options);
		
		var settingsLayer = {
			xparallax:	settings.xparallax,
			yparallax:	settings.yparallax,
			xorigin:	settings.xorigin,
			yorigin:	settings.yorigin,
			xtravel:    settings.xtravel,
			ytravel:    settings.ytravel
		};
		
		var settingsMouseport = {
			element:		settings.mouseport,
			takeoverFactor:	settings.takeoverFactor,
			takeoverThresh:	settings.takeoverThresh
		};
		if (settings.mouseport) {
			settingsMouseport['element'] = settings.mouseport;
		}
		
		// Populate layer array with default settings
		var layersettings = [];
		for(var a=1; a<arguments.length; a++) {
			layersettings.push( jQuery.extend( {}, settingsLayer, arguments[a]) );
		}
		
		// Iterate matched elements
		return this.each(function() {
	    	// VAR
			var localmouse = {
				x:	0.5,
				y:	0.5
			};
			
	    	var virtualmouse = {
				x:	0.5,
				y:	0.5
			};
			
			var timer = {
			  running: 	false,
			  frame:	settings.frameDuration,
			  fire:		function(x, y) {
			  	  			
							
							positionMouse(mouseport, localmouse, virtualmouse);
	                    	
							moveLayers(layer, virtualmouse.x, virtualmouse.y);
							
			  	  			this.running = setTimeout(function() {
			  	  				if ( localmouse.x!=x || localmouse.y!=y || !mouseport.ontarget ) {
			  	  					timer.fire(localmouse.x, localmouse.y);
								}
			  	  				else if (timer.running) {
			  	  					timer.running=false;
			  	  				}
			  	  			}, timer.frame);
							
			  	  		}
			};
	
			var viewport = { element: jQuery(this) };		
	
			var mouseport = jQuery.extend({}, {element: viewport.element}, settingsMouseport, {
			  	xinside:  false,		// is the mouse inside the mouseport's dimensions?
				yinside:  false,
				active:	  false,		// are the mouse coordinates still being read?
				ontarget: false			// is the top layer inside the takeoverThresh?
			});
	    
			var layer			= [];
	    
	    	// FUNCTIONS
	    
			function matrixSearch(layer, ref, callback) {
				for (var i=0; i<layer.length; i++) {
			    	var gotcha=false;
			    	for (var n=0; n<layer[i].content.length; n++) {
			      		if (layer[i].content[n].anchor==ref) {
			        		callback(i, n);
			        		return [i, n];
			      		}
			    	}
			  	}
			  	return false;
			}
	    
	    	// RUN
	    
	    	setupPorts(viewport, mouseport);
			
			// Cycle through and create layers
	    	for (var i=0; i<viewport.element.children().length; i++) {
				// Create layer from settings if it doesn't exist
				layer[i]=jQuery.extend({}, settingsLayer, layersettings[i], {
					element:	viewport.element.children('*:eq('+i+')')
				});
				
			  	setupLayer(layer, i, mouseport);
	      
	      		if (settings.triggerResponse) {
			    	setupLayerContents(layer, i, viewport.element.offset());
			  	}
			}
			
	    	// Set up layers CSS and initial position
	    	viewport.element.children().css('position', 'absolute');
			moveLayers(layer, 0.5, 0.5);
			
			// Mouse Response
			if (settings.mouseResponse) {
				jQuery(document).mousemove(function(mouse){
					
					// Is mouse inside?
					mouseport.xinside = (mouse.pageX >= mouseport.left && mouse.pageX < mouseport.width+mouseport.left) ? true : false;
					mouseport.yinside = (mouse.pageY >= mouseport.top  && mouse.pageY < mouseport.height+mouseport.top)  ? true : false;
					// Then switch active on.
					if (mouseport.xinside && mouseport.yinside && !mouseport.active) {
						mouseport.ontarget = false;
						mouseport.active = true;
					}
					// If active is on give localmouse coordinates
					if (mouseport.active) {
						if (mouseport.xinside) { 
							localmouse.x = (mouse.pageX - mouseport.left) / mouseport.width; 
						}
						else { 
							localmouse.x = (mouse.pageX < mouseport.left) ? 0 : 1; 
						}
						if (mouseport.yinside) { 
							localmouse.y = (mouse.pageY - mouseport.top) / mouseport.height; 
						} 
						else { 
							localmouse.y = (mouse.pageY < mouseport.top) ? 0 : 1; 
						}
					}
					
					// If mouse is inside, fire timer
					if (mouseport.xinside && mouseport.yinside)  { 
						if (!timer.running) {
							timer.fire(localmouse.x, localmouse.y);
						} 
					}
					else if (mouseport.active) { 
						mouseport.active = false; 
					}			
				});
			}
			
			// Trigger Response
			if (settings.triggerResponse) {
				viewport.element.bind("jparallax", function(event, ref) {
			    	ref = stripFiletype(ref);
			          
	        		matrixSearch(layer, ref, function(i, n) {
	          			localmouse.x = layer[i].content[n].posxrat * (layer[i].diffxrat + 1) - (0.5 * layer[i].diffxrat);
	          			localmouse.y = layer[i].content[n].posyrat * (layer[i].diffyrat + 1) - (0.5 * layer[i].diffyrat);
	  
	          			if (!settings.triggerExposesEdges) {
	            			if (localmouse.x < 0) {
								localmouse.x = 0;
							}
	            			if (localmouse.x > 1) {
								localmouse.x = 1;
							}
	            			if (localmouse.y < 0) {
								localmouse.y = 0;
							}
	            			if (localmouse.y > 1) {
								localmouse.y = 1;
							}
	          			}
	          
	          			mouseport.ontarget = false;
	          
	          			if (!timer.running) {
							timer.fire(localmouse.x, localmouse.y);
						}
	        		});
			  	});
			}
			
			// Window Resize Response
			jQuery(window).resize(function() {
				setupPorts(viewport, mouseport);
			  	for (var i=0; i<layer.length; i++) {
			    	setupLayer(layer, i, mouseport);
	      		}
			});
		});
	};
	
	// END OF PLUGIN DEFINITION **********************************************************************
	
	// PLUGIN DEFAULTS
	
	jQuery.fn.jparallax.settings = {
		mouseResponse:		    true,	// Sets mouse response
		mouseActiveOutside:		false,	// Makes mouse affect layers from outside of the mouseport. 
		triggerResponse:	    true,	// Sets trigger response
	    triggerExposesEdges:	false,  // Sets whether the trigger pulls layer edges into view in trying to centre layer content.
		xparallax:				true,	// Sets directions to move in
		yparallax:				true,	//
		xorigin:				0.5,	// Sets default alignment - only comes into play when travel is not 1
		yorigin:				0.5,	//
		xtravel:              	1,      // Factor by which travel is amplified
		ytravel:              	1,      //
		takeoverFactor:		    0.65,	// Sets rate of decay curve for catching up with target mouse position
		takeoverThresh:		    0.002,	// Sets the distance within which virtualmouse is considered to be on target, as a multiple of mouseport width.
		frameDuration:        	25		// In milliseconds
	};
	
	// RUN
	
	initOrigin(jQuery.fn.jparallax.settings);
	
	jQuery(function() {
		
	});
	
	
	// END CLOSURE

})(jQuery);
