/**
 * @author mrdoob / http://mrdoob.com/
 * @author alteredq / http://alteredqualia.com/
 * @author paulirish / http://paulirish.com/
 * @author Lukas Goldschmidt lukas.goldschmidt@googlemail.com
 */
/**
 * @namespace LG
 * @desc Namespace of Lukas Goldschmidt
 */

/**
 * @namespace LG.THREE
 * @desc Some extensions to MrDoobs great Three.js
 */
Namespace.Manager.Register("LG.THREE");

/*
* parameters = {
*  fov: <float>,
*  aspect: <float>,
*  near: <float>,
*  far: <float>,
*  target: <THREE.Object3D>,

*  movementSpeed: <float>,
*  lookSpeed: <float>,

*  noFly: <bool>,
*  lookVertical: <bool>,
*  autoForward: <bool>,

*  constrainVertical: <bool>,
*  verticalMin: <float>,
*  verticalMax: <float>,

*  heightSpeed: <bool>,
*  heightCoef: <float>,
*  heightMin: <float>,
*  heightMax: <float>,

*  domElement: <HTMLElement>,
* }
*/

/**
 * @class LG.THREE.FirstPersonCamera
 * @desc A remake of mrdoobs Quakecam that behaves the way i need it, including keypresses etc...
 * @param {Object} parameters
 * parameters = {
 *  fov: <float>,
 *  aspect: <float>,
 *  near: <float>,
 *  far: <float>,
 *  target: <THREE.Object3D>,

 *  movementSpeed: <float>,
 *  lookSpeed: <float>,

 *  noFly: <bool>,
 *  lookVertical: <bool>,
 *  autoForward: <bool>,

 *  constrainVertical: <bool>,
 *  verticalMin: <float>,
 *  verticalMax: <float>,

 *  heightSpeed: <bool>,
 *  heightCoef: <float>,
 *  heightMin: <float>,
 *  heightMax: <float>,

 *  domElement: <HTMLElement>,
 * }
 * @type THREE.Camera

 * @constructor
 */
LG.THREE.FirstPersonCamera = function(parameters) {
	/** @augments THREE.Camera */
	THREE.Camera.call(this, parameters.fov, parameters.aspect, parameters.near, parameters.far, parameters.target);
	/** A reference to the Browser object containing all the logic and references */
	this.Browser = new Object();
	this.overObject = null;
	this.movementSpeed = 1.0;
	this.lookSpeed = 0.005;

	this.noFly = false;
	this.lookVertical = true;
	this.autoForward = false;

	this.activeLook = true;

	this.heightSpeed = false;
	this.heightCoef = 1.0;
	this.heightMin = 0.0;

	this.constrainVertical = false;
	this.verticalMin = 0;
	this.verticalMax = 3.14;

	this.domElement = document;

	this.lastUpdate = new Date().getTime();
	this.tdiff = 0;

	this.isShiftDown = false;
	this.oldMouseX = 0;
	this.oldMouseY = 0;
	this.onMouseDownPosition = new THREE.Vector2();

	this.dest = this.position.clone();
	this.drift = new THREE.Vector3(0, 0, 0);
	this.destlat = 0;
	this.panning = false;
	if(parameters) {

		if(parameters.movementSpeed !== undefined)
			this.movementSpeed = parameters.movementSpeed;
		if(parameters.lookSpeed !== undefined)
			this.lookSpeed = parameters.lookSpeed;
		if(parameters.noFly !== undefined)
			this.noFly = parameters.noFly;
		if(parameters.lookVertical !== undefined)
			this.lookVertical = parameters.lookVertical;

		if(parameters.autoForward !== undefined)
			this.autoForward = parameters.autoForward;

		if(parameters.activeLook !== undefined)
			this.activeLook = parameters.activeLook;

		if(parameters.heightSpeed !== undefined)
			this.heightSpeed = parameters.heightSpeed;
		if(parameters.heightCoef !== undefined)
			this.heightCoef = parameters.heightCoef;
		if(parameters.heightMin !== undefined)
			this.heightMin = parameters.heightMin;
		if(parameters.heightMax !== undefined)
			this.heightMax = parameters.heightMax;

		if(parameters.constrainVertical !== undefined)
			this.constrainVertical = parameters.constrainVertical;
		if(parameters.verticalMin !== undefined)
			this.verticalMin = parameters.verticalMin;
		if(parameters.verticalMax !== undefined)
			this.verticalMax = parameters.verticalMax;

		if(parameters.domElement !== undefined)
			this.domElement = parameters.domElement;

	}

	this.autoSpeedFactor = 0.0;

	this.mouseX = 0;
	this.mouseY = 0;

	this.lat = 0;
	this.lon = 0;
	this.phi = 0;
	this.theta = 0;

	this.moveForward = false;
	this.moveBackward = false;
	this.moveLeft = false;
	this.moveRight = false;
	this.freeze = false;

	this.mouseDragOn = false;

	/**
	 * @param {Object} event A mouse Event
	 */
	this.onMouseDown = function(event) {

		//		event.preventDefault();
		//		event.stopPropagation();
		if(this.Browser.dialogOpen) {
			return false
		};
		if(event.pageX > this.Browser.offset.left && event.pageX < this.Browser.offset.left + this.Browser.width) {
			if(event.pageY > this.Browser.offset.top && event.pageY < this.Browser.offset.top + this.Browser.height) {
				this.onMouseDownPosition.x = event.pageX - this.Browser.offset.left;
				this.onMouseDownPosition.y = event.pageY - this.Browser.offset.top;
				window.focus();
				this.panning = false;
				this.mouseDragOn = true;
				event.preventDefault();
			} else {
				return;
			}
		} else {
			return;
		}
	};
	/**
	 * @param {Object} event A mouse Event
	 *
	 */
	this.onMouseUp = function(event) {
		if(this.Browser.dialogOpen) {
			return false
		};

		//		event.preventDefault();
		//		event.stopPropagation();
		//		var top = (this.Browser.offset.top - $("html").scrollTop() > 0) ? this.Browser.offset.top - $("html").scrollTop() : -$("html").scrollTop() + this.Browser.offset.top;

		this.mouseDragOn = false;

		this.onMouseDownPosition.x = event.pageX - this.Browser.offset.left - this.onMouseDownPosition.x;
		this.onMouseDownPosition.y = event.pageY - this.Browser.offset.top - this.onMouseDownPosition.y;

		if(this.onMouseDownPosition.length() > 5) {
			return false;
		}
		if(this.isMouseOver && !this.Browser.dialogOpen) {
			var vector = new THREE.Vector3(((event.pageX - this.Browser.offset.left) / this.Browser.width ) * 2 - 1, -((event.pageY - this.Browser.offset.top) / this.Browser.height ) * 2 + 1, 0.5);
			this.Browser.projector.unprojectVector(vector, this);
			var ray = new THREE.Ray(this.position, vector.subSelf(this.position).normalize());
			var intersects = ray.intersectObjects(this.Browser.click_items);

			if(intersects.length > 0) {
				intersects[0].object.clicked();
			}
		}

	};
	/**
	 * @param {Object} event A mouse Event
	 *
	 */
	this.onMouseMove = function(event) {
		if(this.Browser.dialogOpen) {
			return false
		};

		if(event.pageX > this.Browser.offset.left && event.pageX < this.Browser.offset.left + this.Browser.width) {
			if(event.pageY > this.Browser.offset.top && event.pageY < this.Browser.offset.top + this.Browser.height) {
				this.mouseX = event.pageX - this.Browser.offset.left - this.Browser.width / 2;
				this.mouseY = event.pageY - this.Browser.offset.top - this.Browser.height / 2;

				this.isMouseOver = true;

				if(this.mouseDragOn) {

				} else {
					var vector = new THREE.Vector3(((event.pageX - this.Browser.offset.left) / this.Browser.width ) * 2 - 1, -((event.pageY - this.Browser.offset.top) / this.Browser.height ) * 2 + 1, 0.5);
					this.Browser.projector.unprojectVector(vector, this);
					var ray = new THREE.Ray(this.position, vector.subSelf(this.position).normalize());
					var intersects = ray.intersectObjects(this.Browser.click_items);

					if(intersects.length > 0) {
						if(this.overObject == null) {
							this.overObject = intersects[0].object;
							this.overObject.mouseIn();
						} else if(this.overObject !== intersects[0].object) {

							this.overObject.mouseOut();
							this.overObject = intersects[0].object;
							this.overObject.mouseIn();

						} else {
							// object is the same
						}

					} else {
						if(this.overObject !== null) {
							this.overObject.mouseOut();
						}
						this.overObject = null;

					}

				}

			} else {
				this.isMouseOver = false;
				return;
			}
		} else {
			this.isMouseOver = false;
			return;
		}

	};
	/**
	 * @param {Object} event A keyboard Event
	 *
	 */
	this.onKeyDown = function(event) {
		if(!this.isMouseOver) {
			return;
		};
		event.preventDefault();
		event.stopPropagation();
		switch( event.keyCode ) {
			case 16:
				this.isShiftDown = true;
				break;
			case 17:
				this.isControlDown = true;
				break;
			case 32:
				this.panning = false;
				this.Browser.overView();
				break;	
			case 38:
			/*up*/
			case 87:
				/*W*/
				this.moveForward = true;
				this.panning = false;
				break;

			case 37:
			/*left*/
			case 65:
				/*A*/
				this.moveLeft = true;
				this.panning = false;
				break;

			case 40:
			/*down*/
			case 83:
				/*S*/
				this.moveBackward = true;
				this.panning = false;
				break;

			case 39:
			/*right*/
			case 68:
				/*D*/
				this.moveRight = true;
				this.panning = false;
				break;

			case 82:
				/*R*/
				this.moveUp = true;
				this.panning = false;
				break;
			case 70:
				/*F*/
				this.moveDown = true;
				this.panning = false;
				break;
			case 33:
				/*page UP*/
				this.panning = false;
				this.Browser.toNext();
				break;
			case 34:
				/*page DOWN*/
				this.panning = false;
				this.Browser.toPrev();
				break;


			//			case 81:
			//				this.freeze = !this.freeze;
			//				break;

		}

	};
	/**
	 * @param {Object} event A keyboard Event
	 *
	 */
	this.onKeyUp = function(event) {

		switch( event.keyCode ) {
			case 16:
				this.isShiftDown = false;
				break;
			case 17:
				this.isControlDown = false;
				break;
			case 38:
			/*up*/
			case 87:
				/*W*/
				this.moveForward = false;
				break;

			case 37:
			/*left*/
			case 65:
				/*A*/
				this.moveLeft = false;
				break;

			case 40:
			/*down*/
			case 83:
				/*S*/
				this.moveBackward = false;
				break;

			case 39:
			/*right*/
			case 68:
				/*D*/
				this.moveRight = false;
				break;

			case 82:
				/*R*/
				this.moveUp = false;
				break;
			case 70:
				/*F*/
				this.moveDown = false;
				break;

		}

	};
	/**
	 * Move and look to , at a certain Item
	 * @param (THREE.Vector3) dest the coordinates of the item to look at
	 */
	this.panTo = function(dest, lon, lat) {
		var dest = dest;
		this.dest = dest;
		this.lon_dest = lon;
		this.lat_dest = lat;

		this.panning = true;
	};
	/**
	 * Updates the camera coordinates etc
	 */
	this.update = function() {
		this.activeLook = this.mouseDragOn;
		var now = new Date().getTime();
		this.tdiff = (now - this.lastUpdate ) / 1000;
		this.lastUpdate = now;
		if(this.panning) {

			var drift = new THREE.Vector3(this.dest.x - this.position.x, this.dest.y - this.position.y, this.dest.z - this.position.z);
			// drift = this.Browser.projector.unprojectVector(drift,this);
			if(drift.length() > 1 || Math.abs(this.lon_dest - this.lon) > 1 || Math.abs(this.lat_dest - this.lat) > 1) {
				if(!this.mouseDragOn){
					this.lon = this.lon + (this.lon_dest - this.lon) / 5;
					this.lat = this.lat + (this.lat_dest - this.lat) / 5;
				}
				this.position.set(this.position.x + drift.x / 6, this.position.y + drift.y / 6, this.position.z + drift.z / 6);
			} else {
				this.panning = false;
			}
		} else {
			//
		}

		if(!this.freeze) {

			if(this.heightSpeed) {

				var y = clamp(this.position.y, this.heightMin, this.heightMax), delta = y - this.heightMin;

				this.autoSpeedFactor = this.tdiff * (delta * this.heightCoef );

			} else {

				this.autoSpeedFactor = 0.0;

			}
			if(this.isMouseOver) {
				var actualMoveSpeed = this.tdiff * this.movementSpeed;

			} else {
				var actualMoveSpeed = 0;

			}
			if(this.moveForward || (this.autoForward && !this.moveBackward ))
				this.translateZ(-(actualMoveSpeed + this.autoSpeedFactor ));
			if(this.moveBackward)
				this.translateZ(actualMoveSpeed);

			if(this.moveLeft)
				this.translateX(-actualMoveSpeed);
			if(this.moveRight)
				this.translateX(actualMoveSpeed);

			if(this.moveUp)
				this.translateY(actualMoveSpeed);
			if(this.moveDown)
				this.translateY(-actualMoveSpeed);

			var actualLookSpeed = this.tdiff * this.lookSpeed;

			if(!this.activeLook) {
				actualLookSpeed = 0;

			}

			this.lon += (this.mouseX - this.oldMouseX) * actualLookSpeed;
			if(this.lookVertical)
				this.lat -= (this.mouseY - this.oldMouseY) * actualLookSpeed;

			this.lat = Math.max(-85, Math.min(85, this.lat));
			this.phi = (90 - this.lat ) * Math.PI / 180;
			this.theta = this.lon * Math.PI / 180;

			var targetPosition = this.target.position, position = this.position;

			targetPosition.x = position.x + 100 * Math.sin(this.phi) * Math.cos(this.theta);
			targetPosition.y = position.y + 100 * Math.cos(this.phi);
			targetPosition.z = position.z + 100 * Math.sin(this.phi) * Math.sin(this.theta);

		}

		var verticalLookRatio = 1;

		if(this.constrainVertical) {
			verticalLookRatio = 3.14 / (this.verticalMax - this.verticalMin );

		}

		this.lon += (this.mouseX - this.oldMouseX) * actualLookSpeed;
		if(this.lookVertical)
			this.lat -= (this.mouseY - this.oldMouseY) * actualLookSpeed;
		this.oldMouseX = this.mouseX;
		this.oldMouseY = this.mouseY;

		this.lat = Math.max(-85, Math.min(85, this.lat));
		this.phi = (90 - this.lat ) * Math.PI / 180;

		this.theta = this.lon * Math.PI / 180;

		if(this.constrainVertical) {

			this.phi = map_linear(this.phi, 0, 3.14, this.verticalMin, this.verticalMax);

		}

		var targetPosition = this.target.position, position = this.position;

		targetPosition.x = position.x + 100 * Math.sin(this.phi) * Math.cos(this.theta);
		targetPosition.y = position.y + 100 * Math.cos(this.phi);
		targetPosition.z = position.z + 100 * Math.sin(this.phi) * Math.sin(this.theta);

		this.supr.update.call(this);

	};

	this.domElement.addEventListener('contextmenu', function(event) {
		event.preventDefault();
	}, false);

	this.domElement.addEventListener('mousemove', bind(this, this.onMouseMove), false);
	this.domElement.addEventListener('mousedown', bind(this, this.onMouseDown), false);
	this.domElement.addEventListener('mouseup', bind(this, this.onMouseUp), false);
	this.domElement.addEventListener('keydown', bind(this, this.onKeyDown), false);
	this.domElement.addEventListener('keyup', bind(this, this.onKeyUp), false);

	/** Binds a context to a function
	 * @param {Object} scope The Object we want to apply as "this"
	 * @param {Function} fn the function we want this to be bound to
	 */
	function bind(scope, fn) {

		return function() {

			fn.apply(scope, arguments);

		};
	};

	function map_linear(x, sa, sb, ea, eb) {

		return (x - sa ) * (eb - ea ) / (sb - sa ) + ea;

	};

	function clamp_bottom(x, a) {

		return x < a ? a : x;

	};

	function clamp(x, a, b) {

		return x < a ? a : (x > b ? b : x );

	};

};

LG.THREE.FirstPersonCamera.prototype = new THREE.Camera();
LG.THREE.FirstPersonCamera.prototype.constructor = LG.THREE.FirstPersonCamera;
LG.THREE.FirstPersonCamera.prototype.supr = THREE.Camera.prototype;

LG.THREE.FirstPersonCamera.prototype.translate = function(distance, axis) {

	this.matrix.rotateAxis(axis);

	if(this.noFly && !this.moveUp && !this.moveDown) {

		axis.y = 0;

	}

	this.position.addSelf(axis.multiplyScalar(distance));
	this.target.position.addSelf(axis.multiplyScalar(distance));

};
