// jquery.jparallax.js
// 0.6
// Stephen Band
//
// Dependencies:
// jquery.js
// jquery.dimensions.js
//
// Project and documentation site:
// http://webdev.stephband.info/parallax.html


// CLOSURE

(function(jQuery) {


// PRIVATE VARIABLES

var mouse = {
  		x:0, 
  		y:0, 
  		setxy: 		function(m){
  								mouse.x = m.pageX;
  								mouse.y = m.pageY;
  								// console.log(mouse.x+', '+mouse.y);
  							}
};

// PRIVATE FUNCTIONS

function pxToInt(n) {
	return parseInt(n.replace('px', ''), 10);
}

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 centreLayers(layer, viewport) {
	for (var i=0; i<layer.length; i++) {
		if (layer[i].xparallax) layer[i].x = layer[i].xoffset - (0.5*viewport.width) * layer[i].xdynamic;
		if (layer[i].yparallax) layer[i].y = layer[i].yoffset - (0.5*viewport.height) * layer[i].ydynamic;
	}
	moveLayers(layer);
}

function positionLayers(mouseport, layer, localmouse) {
	
	// Set where the layer SHOULD be and store in target
	for (var i=0; i<layer.length; i++) {
	  layer[i].xtarget = layer[i].xoffset - localmouse.x * layer[i].xdynamic;
	  layer[i].ytarget = layer[i].yoffset - localmouse.y * layer[i].ydynamic;
	}	
	// Set where the layer is going to be
  if (!mouseport.ontarget) {
    for (i=0; i<layer.length; i++) {
    	layer[i].x = layer[i].xtarget+(layer[i].x-layer[i].xtarget)*mouseport.takeoverFactor;
    	layer[i].y = layer[i].ytarget+(layer[i].y-layer[i].ytarget)*mouseport.takeoverFactor;		
    }
    var difference = Math.floor((layer[layer.length-1].x-layer[layer.length-1].xtarget)+(layer[layer.length-1].y-layer[layer.length-1].ytarget));
    
    // If the layer is inside the softlimit set ontarget to true
    if (difference < mouseport.takeoverThresh && difference > mouseport.takeoverThresh*-1) {
    	mouseport.ontarget=true;
    }
    else {
    	// console.log(difference+' '+mouseport.ontarget)
    }
  }
  // Set where the layer is if on target
  else {
    for (i=0; i<layer.length; i++) {
    	layer[i].x = layer[i].xtarget;
    	layer[i].y = layer[i].ytarget;
    }
  }
  moveLayers(layer);
}

function moveLayers(layer) {
	for (var i=0; i<layer.length; i++) {
	  if (layer[i].xparallax) layer[i].element.css("left", layer[i].x);
	  if (layer[i].yparallax) layer[i].element.css("top", layer[i].y);
	}
}

// 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
  		};
  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 localmouse = {
					x:				0,
					y:				0
		};
		
		var element = jQuery(this);
		
		var timer = {
		  running:		false,
		  frame:			settings.frameDuration,
		  fire:				function(x, y) {
		  	  				  // console.log('timer.fire! ');
		  	  				  positionLayers(mouseport, layer, localmouse);
		  	  				  this.running = setTimeout(function() {
		  	  				  	// console.log('x: '+localmouse.x+' '+x+' | y: '+localmouse.y+' '+y+' | ontarget: '+mouseport.ontarget);
		  	  				  	if ( localmouse.x!=x || localmouse.y!=y || !mouseport.ontarget ) {
		  	  				  		timer.fire(localmouse.x, localmouse.y);
		  	  				  	}
		  	  				  	else if (timer.running) {
		  	  				  		timer.running=false;
		  	  				  		// console.log('timer.running: '+timer.running);
		  	  				  	}
		  	  				  }, timer.frame);
		  	  				}
		};
		
		// Ports
		var viewport	=	{
			element: 	element,		
			width: 		element.width(),
			height: 	element.height()
		};
		var mouseport = jQuery.extend({}, {element: viewport.element}, settingsMouseport);
		jQuery.extend(mouseport, {
			width:		mouseport.element.width(),
			height:		mouseport.element.height(),
			top:			mouseport.element.offset().top,
			left:			mouseport.element.offset().left,
			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?
		});

		// Layers
		var layer			= [];
		element.children().css('position', 'absolute');
		for (var i=0; i<element.children().length; i++) {

			// Create layer from settings if it doesn't exist
			layer[i]=jQuery.extend({}, settingsLayer, layersettings[i], {
				element:	element.children('*:eq('+i+')')
			});
			
			layer[i]=jQuery.extend({}, {
				width:		pxToInt(layer[i].element.css("width")),
				height:		pxToInt(layer[i].element.css("height")),
				xtravel:	pxToInt(layer[i].element.css("width")) - viewport.width,
				ytravel:	pxToInt(layer[i].element.css("height")) - viewport.height
			}, layer[i]);
			
			jQuery.extend(layer[i], {	
				xoffset: 	(viewport.width - layer[i].width + layer[i].xtravel) * layer[i].xorigin,
				yoffset:	(viewport.height - layer[i].height + layer[i].ytravel) * layer[i].yorigin,
				xdynamic:	layer[i].xtravel / viewport.width,
				ydynamic:	layer[i].ytravel / viewport.height,
				xtarget:	0,
				ytarget:	0,
				x:				0,
				y:				0
			});

			if (settings.triggerResponse) {
        // 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) {
						jQuery.extend(layer[i].content[n], {
							width: 		pxToInt(layer[i].content[n].element.css("width")),
							height:		pxToInt(layer[i].content[n].element.css("height")),
							x:			  pxToInt(layer[i].content[n].element.css("left")),
							y:			  pxToInt(layer[i].content[n].element.css("top"))
						});
						jQuery.extend(layer[i].content[n], {
							xoffset:	(viewport.width/2) -(layer[i].content[n].x+(layer[i].content[n].width/2)),
							yoffset:	(viewport.height/2)-(layer[i].content[n].y+(layer[i].content[n].height/2))
						});
					}
					// console.log(layer[i].content[n].element.children('a').attr('name')+' x: '+layer[i].content[n].x+' y: '+layer[i].content[n].y+' xoffset: '+layer[i].content[n].xoffset+' yoffset: '+layer[i].content[n].yoffset);
		    }
	    }
		//  debug	
		//	console.log('width: '+layer[i].width+' height: '+layer[i].height+'\n'+
		//	  					'xtravel: '+layer[i].xtravel+' ytravel: '+layer[i].ytravel+'\n'+
		//	  					'xorigin: '+layer[i].xorigin+' yorigin: '+layer[i].yorigin+'\n'+
		//	  					'xoffset: '+layer[i].xoffset+' yoffset: '+layer[i].yoffset+'\n'+
		//	  					'xdynamic: '+layer[i].xdynamic+' ydynamic: '+layer[i].ydynamic);
		}

		centreLayers(layer, viewport);
		
		// Mouse Response
		if (settings.mouseResponse) {
			jQuery().mousemove(function(){
				// Is mouse inside?
				mouseport.xinside = (mouse.x >= mouseport.left && mouse.x<mouseport.width+mouseport.left) ? true : false;
				mouseport.yinside = (mouse.y >= mouseport.top && mouse.y<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.x-mouseport.left; }
					else { localmouse.x = (mouse.x < mouseport.left) ? 0 : mouseport.width; }
					if (mouseport.yinside) { localmouse.y = mouse.y-mouseport.top; } 
					else { localmouse.y = (mouse.y < mouseport.top) ? 0 : mouseport.height; }
				}
				// console.log(localmouse.x+', '+localmouse.y+' | '+mouseport.active);
				
				// 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);
		    
		    // console.log(ref);
		    
		    // find content item with anchor ref - there's got to be a neater way of writing an find matrix...
        for (i=0; i<layer.length; i++) {
          var gotcha=false;
          for (n=0; n<layer[i].content.length; n++) {
            if (layer[i].content[n].anchor==ref) {
              gotcha=true;
              break;
            }
          }
          if (gotcha) break;
        }
        
        // console.log('found in layer '+i+' element '+n);
        
        // Note: Should I include the following calcs in Layer[i].content[n]. properties?
        
        var posx = layer[i].content[n].x + layer[i].content[n].width;
        var r = layer[i].width;
        var d = viewport.width;
        var s = d/(r-d);
        
        var posy = layer[i].content[n].y + layer[i].content[n].height;
        var u = layer[i].height;
        var b = viewport.height;
        var z = b/(u-b);
        
        localmouse.x = d * ( (posx*(1+s))/r - 0.5*s );
        localmouse.y = b * ( (posy*(1+z))/u - 0.5*z );

        if (!settings.triggerExposesEdges) {
          if (localmouse.x < 0) localmouse.x = 0;
          if (localmouse.x > d) localmouse.x = d;
          if (localmouse.y < 0) localmouse.y = 0;
          if (localmouse.y > b) localmouse.y = b;
        }
        
        mouseport.ontarget = false;
        
        if (!timer.running) timer.fire(localmouse.x, localmouse.y);  
		  
		  });
		}
		
	});
};

// END OF PLUGIN DEFINITION **********************************************************************

// PLUGIN DEFAULTS

jQuery.fn.jparallax.settings = {
	mouseResponse:		    true,						// Sets mouse response
	triggerResponse:	    true,					  // Sets trigger response
	xparallax:				    true,						// Sets directions to move in
	yparallax:				    true,						//
	xorigin:					    'centre',				// Sets default alignment - only comes into play when travel is specified in layer options
	yorigin:					    'centre',				// 
	takeoverFactor:		    0.75,						// Sets rate of decay curve for catching up with target mouse position
	takeoverThresh:		    0,							// Sets the distance within which the layer position is considered to be on target   
	frameDuration:        40,							// In milliseconds
  triggerExposesEdges:  false           // Sets whether the trigger pulls layer edges into view in trying to centre layer content.
};


// RUN

initOrigin(jQuery.fn.jparallax.settings);

jQuery(function() {

	jQuery().mousemove(mouse.setxy);
	
});


// END CLOSURE

})(jQuery);
