/*
 * Image preloader and magnifier
 * Copyright (C) 2010 H. Nelson
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// Main class to be called on each object you want
// to click on.
// The image located at "imageHref" will be displayed.
function hideandseek (nodeToClick, imageHref, strToDisplay) {
	
	// Number of iteration per animation
	this.animationValue = 30;
	
	// Refresh rate
	this.timeValue = 15;
	
	// Between 0. and infinity
	this.opacityFactor = 1;

	// Path to the "close by clicking" image
	// If none, set to null.
	this.closeImageSrc = '/style/images/close.png';
	
	///////////////////////////
	// NEXT INS'T FOR EDITING
	///////////////////////////
	
	// Set the new coordinates and dimensions to reach
	this.focus = function () {
		bridge.focused = true;

		// Destination to reach
		// We want to *center* the container
		bridge.dox = (window.innerWidth  - bridge.img.width)  / 2 + window.pageXOffset;
		bridge.doy = (window.innerHeight - bridge.img.height) / 2 + window.pageYOffset;
		
		// Dimensions to reach are the image's one
		bridge.dow = bridge.fullWidth;
		bridge.doh = bridge.fullHeight;

		bridge.computeIncrements();
		
		// Set Z-index
		bridge.cnt.style.zIndex = '200';
		
		// We want the first step not to increment the coordinates
		// or the dimensions
		bridge.increment = false;

		// Display the close image while zooming
		if (bridge.closeImage)
			bridge.closeImage.style.visibility = 'visible';
		
		// Update the value of the text node
		if (bridge.textBox) {
			if (bridge.textBox.firstChild)
				bridge.textBox.removeChild(bridge.textBox.firstChild);
			
			bridge.textBox.appendChild(document.createTextNode(strToDisplay));
		}
		
		// Start a new animation if necessary
		if (!bridge.interval)		
			bridge.interval = setInterval(bridge.animate, bridge.timeValue);
		
		// Do not follow the link
		return false;
	};
	
	// This function is called when the user clicks on the page
	this.onclickAnyWhere = function (e) {
		// Hide the ocused element
		// Initialize the reversed animation
		if (bridge.focused) {
			// Take back the focus
			bridge.focused = false;
			
			// Compute the clickable node's coordinates
			bridge.dox = 0;
			bridge.doy = 0;
			
			var el = nodeToClick;
			
			while (el.offsetParent && el.offsetParent.tagName != 'HTML') {
				bridge.dox += el.offsetLeft;
				bridge.doy += el.offsetTop;
				el = el.offsetParent;
			}
			
			// Final dimensions of the preview
			// are the clickable element's ones
			bridge.dow = nodeToClick.offsetWidth;
			bridge.doh = nodeToClick.offsetHeight;

			bridge.computeIncrements();

			// Set Z-index
			bridge.cnt.style.zIndex = '100';
			
			// We want the first step not to be incremented
			// only displayed
			bridge.increment = false;

			// Hide the close image while minimizing
			bridge.closeImage.style.visibility = 'hidden';

			// Init a new animation if necessary
			if (!bridge.interval)
				bridge.interval = setInterval(bridge.animate, bridge.timeValue);

			// Don't propagate the event to avoid a re-zoom.
			if (e.target == nodeToClick) {
				e.preventDefault();
				e.stopPropagation();
			}		
		}

		return 0;
	};
	
	// Perform one step of animation
	this.animate = function () {
		// First call, only displays, do not increment
		if (!bridge.increment) {
			// Hide the shadow while minimizing because of performance
			// issues.
			bridge.cnt.style.boxShadow       = 'none';
			bridge.cnt.style.WebkitBoxShadow = 'none';
			bridge.cnt.style.MozBoxShadow    = 'none';

			bridge.cnt.style.display = 'block';
			
			bridge.originalDistance = Math.sqrt(
				Math.pow(bridge.dox - bridge.cox, 2) +
				Math.pow(bridge.doy - bridge.coy, 2)
			);
			
			// Will animate on the next iteration
			bridge.increment = true;
		}
		// We can increments values now
		else {
			// X axis
			// The destination is on the right
			if ((bridge.cox <= bridge.dox &&
			    // We exceed the coordinates
			    bridge.cox + bridge.incx >= bridge.dox) ||
			// The destination is on the left
			    (bridge.cox >= bridge.dox &&
			    // We exceed the coordinates
			     bridge.cox + bridge.incx <= bridge.dox)) {
			    // We directly set the final coordinates
				bridge.cox = bridge.dox;
			}
			// No problem else, we just increment
			else
				bridge.cox += bridge.incx;

			// Y axis
			// The destination is under
			if ((bridge.coy <= bridge.doy &&
			    // We exceed the coordinates
			    bridge.coy + bridge.incy >= bridge.doy) ||
			// The destination is above
			    (bridge.coy >= bridge.doy &&
			    // We exceed the coordinates
			     bridge.coy + bridge.incy <= bridge.doy)) {
				// We directly set the final coordinates
				bridge.coy = bridge.doy;
			}
			// No problem else, we just increment
			else
				bridge.coy += bridge.incy;
			
			// Width
			// The destination is bigger
			if ((bridge.cow <= bridge.dow &&
			    // We exceed the width
			    bridge.cow + bridge.incw >= bridge.dow) ||
			// The destination is smaller
			    (bridge.cow >= bridge.dow &&
			    // We exceed the width
			     bridge.cow + bridge.incw <= bridge.dow)) {
				// We directly set the final coordinates
				bridge.cow = bridge.dow;
			}
			// No problem else, we just increment
			else
				bridge.cow += bridge.incw;
			
			// Height
			// The destination is bigger
			if ((bridge.coh <= bridge.doh &&
			    // We exceed the height
			    bridge.coh + bridge.inch >= bridge.doh) ||
			// The destination is smaller
			    (bridge.coh >= bridge.doh &&
			    // We exceed the height
			     bridge.coh + bridge.inch <= bridge.doh)) {
				// We directly set the final coordinates
				bridge.coh = bridge.doh;
			}
			// No problem else, we just increment
			else
				bridge.coh += bridge.inch;
		}

		// All destination offsets/dimensions have been reached
		if (bridge.cox == bridge.dox &&
		    bridge.coy == bridge.doy &&
		    bridge.cow == bridge.dow &&
		    bridge.coh == bridge.doh)
		{
		   	 // We can clear the interval
			clearInterval(bridge.interval);
			bridge.interval = null;
			
			// Display the shadow
			if (bridge.focused) {
				bridge.cnt.style.boxShadow       = '0 10px 30px rgba(0,0,0,.5)';
				bridge.cnt.style.WebkitBoxShadow = '0 10px 30px rgba(0,0,0,.5)';
				bridge.cnt.style.MozBoxShadow    = '0 10px 30px rgba(0,0,0,.5)';
			}
			
			// Remove the container from the DOM flow
			else
				bridge.cnt.style.display = 'none';
		}
		
		// Render positions/dimensions thanks to CSS
		bridge.cnt.style.top  = Math.round(bridge.coy) + 'px';
		bridge.cnt.style.left = Math.round(bridge.cox) + 'px';
		
		bridge.cnt.style.height = Math.round(bridge.coh) + 'px';
		bridge.cnt.style.width  = Math.round(bridge.cow) + 'px';
		
		// Compute the current distance between the current position and
		// the destination (Pythagore)
		var distance = Math.sqrt(
			Math.pow(bridge.dox - bridge.cox, 2) +
			Math.pow(bridge.doy - bridge.coy, 2)
		);
		
		// The alpha factor is function of this distance
		// But we can't divide by zero without destroying all the earth
		if (bridge.originalDistance > 0.) {
			// We want the ratio of advancement of the animation
			var ratio = Math.round(1000 * distance / bridge.originalDistance) / 1000;

			// If we are unfocusing we want the inverse the ratio
			if (bridge.focused)
				ratio = 1 - ratio;
			
			// Set alpha
			bridge.cnt.style.opacity = Math.round(1000 * Math.pow(ratio, bridge.opacityFactor)) / 1000;
			
			// Set font size
			if (bridge.textBox)
				bridge.textBox.style.fontSize = Math.round(1000 * Math.pow(ratio, bridge.opacityFactor)) / 1000 + 'em';
		}

		return 0;
	}
	
	// Compute the increments values for the current set of coordinates
	// and dimensions
	this.computeIncrements = function () {
		this.incx = (this.dox - this.cox) / this.animationValue;
		this.incy = (this.doy - this.coy) / this.animationValue;
		this.incw = (this.dow - this.cow) / this.animationValue;
		this.inch = (this.doh - this.coh) / this.animationValue;
		return 0;
	};
	
	// Fix the coordinates of the containers while resizing
	this.fixResizing = function () {
		// Compute the new coordinates of the clickable node
		bridge.nodeX = 0;
		bridge.nodeY = 0;
		
		var el = nodeToClick;
		
		while (el.offsetParent && el.offsetParent.tagName != 'HTML') {
			bridge.nodeX += el.offsetLeft;
			bridge.nodeY += el.offsetTop;
			el = el.offsetParent;
		}
			
		// The container is focused/focusing
		if (bridge.focused) {
			// Fix the coordinates of the center of the view
			// Destination to reach
			// We want to *center* the container horizontaly
			bridge.dox = (window.innerWidth  - bridge.img.width) / 2 + window.pageXOffset;
			bridge.cox = bridge.dox;
			// Render new position
			if (!bridge.interval) {
				bridge.increment = false;
				bridge.animate();
			}
		}
		// The container is minimized/minimizing
		else {			
			// Set the new destination
			bridge.cox = bridge.nodeX;
			bridge.coy = bridge.nodeY;
		}
		
		return 0;
	};
	
	// Internal variables
	//
	this.interval = null;
	this.el = nodeToClick;
	
	var bridge = this;

	// Define the coordinates & dimensions
	this.nodeX = 0;
	this.nodeY = 0;
	
	var el = nodeToClick;
	while (el.offsetParent && el.offsetParent.tagName != 'HTML') {
		this.nodeX += el.offsetLeft;
		this.nodeY += el.offsetTop;
		el = el.offsetParent;
	}
	
	// Coordinates
	this.cox = this.nodeX;
	this.coy = this.nodeY;
	
	// ... dimensions
	this.cow = nodeToClick.offsetWidth;
	this.coh = nodeToClick.offsetHeight;
	
	// Destination is still unknown
	this.dox = 0;
	this.doy = 0;
	this.dow = 0;
	this.doh = 0;
	
	// increments are useless for the moment
	this.incx = 0;
	this.incy = 0;
	this.incw = 0;
	this.inch = 0;

	// None instance is focused by default
	this.focused = false;
	
	// Create the DIV container
	this.cnt = document.createElement('DIV');
	
	this.cnt.style.position = 'absolute';
	this.cnt.style.display = 'none';
	this.cnt.style.textAlign = 'center';
	this.cnt.style.backgroundColor = 'transparent';
	
	// Append the container to the body
	document.body.appendChild(this.cnt);
	
	// Create the image that will be resized by the browser
	this.img = document.createElement('IMG');
	
	// We wait for the image to be fully loaded before
	// we grab its real dimensions
	this.fullWidth = 0;
	this.fullHeight = 0;

	this.img.onload = function () {
		// Set constants
		bridge.fullWidth  = this.width;
		bridge.fullHeight = this.height;

		// Bind methods to events
		nodeToClick.onclick = bridge.focus;
		document.body.addEventListener('click', bridge.onclickAnyWhere, true);
		window.addEventListener('resize', bridge.fixResizing, true);
		
		return 0;
	};

	// Load the image
	this.img.src = imageHref;
	
	// Auto resize it
	this.img.style.width  = '100%';
	this.img.style.height = '100%';
	
	this.closeImage = null;
	
	// Little close box (optional)
	if (this.closeImageSrc) {
		this.closeImage = document.createElement('IMG');
		
		this.closeImage.style.visibiliy = 'hidden';
		this.closeImage.style.position = 'absolute';
		
		this.closeImage.src = this.closeImageSrc;
		
		// Center back the image when it's loaded
		this.closeImage.onload = function () {
			this.style.left = '-' + Math.round(this.width  / 2) + 'px';
			this.style.top  = '-' + Math.round(this.height / 2) + 'px';
			this.style.visibiliy = 'visible';
			return 0;
		}
		
		// Append the button to the container
		this.cnt.appendChild(this.closeImage);
	}
	
	// Append the image to the container
	// and start the downloading
	this.cnt.appendChild(this.img);
	
	// Create the text box if necessary
	this.textBox = null;
	
	if (strToDisplay) {
		this.textBox = document.createElement('SPAN');
		this.textBox.style.display = 'inline-block';
		this.textBox.style.padding = '3px 20px';
		this.textBox.style.marginTop = '15px';
		this.textBox.style.color = 'white';
		this.textBox.style.backgroundColor = 'rgba(0,0,0,.7)';
		
		this.textBox.style.borderRadius = '10px';
		this.textBox.style.MozBorderRadius = '10px';
		this.textBox.style.WebkitBorderRadius = '10px';
		
		this.cnt.appendChild(this.textBox);
	}
	
	return 0;
};
