// 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
//
// Edited by Matthijs Kuiper

// 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;

	for (var i=0; i<layer.length; 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()
	{
		// VARS

		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().mousemove(function(mouse) {
			$(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);
