OV.GetDefaultCamera = function (direction)
{
	if (direction === OV.Direction.X) {
		return new OV.Camera (
			new OV.Coord3D (2.0, -3.0, 1.5),
			new OV.Coord3D (0.0, 0.0, 0.0),
			new OV.Coord3D (1.0, 0.0, 0.0)
		);	
	} else if (direction === OV.Direction.Y) {
		return new OV.Camera (
			new OV.Coord3D (-1.5, 2.0, 3.0),
			new OV.Coord3D (0.0, 0.0, 0.0),
			new OV.Coord3D (0.0, 1.0, 0.0)
		);	
	} else if (direction === OV.Direction.Z) {
		return new OV.Camera (
			new OV.Coord3D (-1.5, -3.0, 2.0),
			new OV.Coord3D (0.0, 0.0, 0.0),
			new OV.Coord3D (0.0, 0.0, 1.0)
		);
	}
	return null;
};

OV.UpVector = class
{
	constructor ()
	{
		this.direction = OV.Direction.Z;
		this.isFixed = true;
		this.isFlipped = false;
	}

	SetDirection (newDirection, oldCamera)
	{
		this.direction = newDirection;
		this.isFlipped = false;

		let defaultCamera = OV.GetDefaultCamera (this.direction);
		let defaultDir = OV.SubCoord3D (defaultCamera.eye, defaultCamera.center);

		let distance = OV.CoordDistance3D (oldCamera.center, oldCamera.eye);
		let newEye = oldCamera.center.Clone ().Offset (defaultDir, distance);

		let newCamera = oldCamera.Clone ();
		if (this.direction === OV.Direction.Y) {
			newCamera.up = new OV.Coord3D (0.0, 1.0, 0.0);
			newCamera.eye = newEye;
		} else if (this.direction === OV.Direction.Z) {
			newCamera.up = new OV.Coord3D (0.0, 0.0, 1.0);
			newCamera.eye = newEye;
		}
		return newCamera;
	}

	SetFixed (isFixed, oldCamera)
	{
		this.isFixed = isFixed;
		if (this.isFixed) {
			return this.SetDirection (this.direction, oldCamera);
		}
		return null;
	}

	Flip (oldCamera)
	{
		this.isFlipped = !this.isFlipped;
		let newCamera = oldCamera.Clone ();
		newCamera.up.MultiplyScalar (-1.0);
		return newCamera;
	}
};

OV.Viewer = class
{
	constructor ()
	{
		this.canvas = null;
		this.renderer = null;
		this.scene = null;
		this.camera = null;
		this.light = null;
		this.navigation = null;
		this.upVector = null;
		this.settings = {
			animationSteps : 40
		};
	}
	
	Init (canvas)
	{
		this.canvas = canvas;
		this.canvas.id = 'viewer';
		this.canvas.addEventListener ('contenxtmenu', function (ev) {
			ev.preventDefault ();
		});

		let parameters = {
			canvas : this.canvas,
			antialias : true
		};
		
		this.renderer = new THREE.WebGLRenderer (parameters);
		this.renderer.setClearColor ('#ffffff', 1);
		this.renderer.setSize (this.canvas.width, this.canvas.height);
		
		this.scene = new THREE.Scene ();

		this.InitCamera ();
		this.InitLights ();

		this.Render ();
	}

	SetClickHandler (onClick)
	{
		this.navigation.SetClickHandler (onClick);
	}

	GetCamera ()
	{
		return this.navigation.GetCamera ();
	}
	
	SetCamera (camera)
	{
		this.navigation.SetCamera (camera);
		this.Render ();
	}

	Resize (width, height)
	{
		let innerSize = OV.GetInnerDimensions (this.canvas, width, height);
		this.camera.aspect = innerSize.width / innerSize.height;
		this.camera.updateProjectionMatrix ();
		this.renderer.setSize (innerSize.width, innerSize.height);	
		this.Render ();
	}

	FitToWindow (boundingSphere, animation)
	{
		if (boundingSphere === null) {
			return;
		}
		let center = new OV.Coord3D (boundingSphere.center.x, boundingSphere.center.y, boundingSphere.center.z);
		let radius = boundingSphere.radius;
		let fov = this.camera.fov;
		if (animation) {
			let newCamera = this.navigation.GetFitToSphereCamera (center, radius, fov);
			this.navigation.MoveCamera (newCamera, this.settings.animationSteps);
		} else {
			this.navigation.FitToSphere (center, radius, fov);
		}
	}

	AdjustClippingPlanes (boundingSphere)
	{
		if (boundingSphere === null) {
			return;
		}		
		if (boundingSphere.radius < 10.0) {
			this.camera.near = 0.01;
			this.camera.far = 100.0;
		} else if (boundingSphere.radius < 100.0) {
			this.camera.near = 0.1;
			this.camera.far = 1000.0;
		} else if (boundingSphere.radius < 1000.0) {
			this.camera.near = 10.0;
			this.camera.far = 10000.0;			
		} else {
			this.camera.near = 100.0;
			this.camera.far = 1000000.0;
		}
		this.camera.updateProjectionMatrix ();
		this.Render ();
	}

	IsFixUpVector ()
	{
		return this.navigation.IsFixUpVector ();
	}	

	SetFixUpVector (fixUpVector)
	{
		let oldCamera = this.navigation.GetCamera ();
		let newCamera = this.upVector.SetFixed (fixUpVector, oldCamera);
		this.navigation.SetFixUpVector (fixUpVector);
		if (newCamera !== null) {
			this.navigation.MoveCamera (newCamera, this.settings.animationSteps);
		}
		this.Render ();
	}

	SetUpVector (upDirection, animate)
	{
		let oldCamera = this.navigation.GetCamera ();
		let newCamera = this.upVector.SetDirection (upDirection, oldCamera);
		let animationSteps = animate ? this.settings.animationSteps : 0;
		this.navigation.MoveCamera (newCamera, animationSteps);
		this.Render ();
	}

	FlipUpVector ()
	{
		let oldCamera = this.navigation.GetCamera ();
		let newCamera = this.upVector.Flip (oldCamera);
		this.navigation.MoveCamera (newCamera, 0);
		this.Render ();
	}

	Render ()
	{
		let navigationCamera = this.navigation.GetCamera ();
		this.camera.position.set (navigationCamera.eye.x, navigationCamera.eye.y, navigationCamera.eye.z);
		this.camera.up.set (navigationCamera.up.x, navigationCamera.up.y, navigationCamera.up.z);
		this.camera.lookAt (new THREE.Vector3 (navigationCamera.center.x, navigationCamera.center.y, navigationCamera.center.z));
	
		let lightDir = OV.SubCoord3D (navigationCamera.eye, navigationCamera.center);
		this.light.position.set (lightDir.x, lightDir.y, lightDir.z);	
		this.renderer.render (this.scene, this.camera);
	}

	AddMeshes (meshes)
	{
		for (let i = 0; i < meshes.length; i++) {
			let mesh = meshes[i];
			this.scene.add (mesh);
		}
		this.Render ();
	}

	Clear ()
	{
		this.ClearMeshes ();
		this.Render ();
	}

	SetMeshesVisibility (isVisible)
	{
		this.EnumerateMeshes (function (mesh) {
			let visible = isVisible (mesh.userData);
			if (mesh.visible !== visible) {
				mesh.visible = visible;
			}
		});
		this.Render ();
	}

	SetMeshesHighlight (highlightMaterial, isHighlighted)
	{
		function CreateHighlightMaterials (originalMaterials, highlightMaterial)
		{
			let highlightMaterials = [];
			for (let i = 0; i < originalMaterials.length; i++) {
				highlightMaterials.push (highlightMaterial);
			}
			return highlightMaterials;
		}

		this.EnumerateMeshes (function (mesh) {
			let highlighted = isHighlighted (mesh.userData);
			if (highlighted) {
				if (mesh.userData.threeMaterials === null) {
					mesh.userData.threeMaterials = mesh.material;
					mesh.material = CreateHighlightMaterials (mesh.material, highlightMaterial);
				}
			} else {
				if (mesh.userData.threeMaterials !== null) {
					mesh.material = mesh.userData.threeMaterials;
					mesh.userData.threeMaterials = null;
				}
			}
		});
		this.Render ();
	}	

	GetMeshUnderMouse (mouseCoords)
	{
		let raycaster = new THREE.Raycaster ();
		let mousePos = new THREE.Vector2 ();
		mousePos.x = (mouseCoords.x / this.canvas.width) * 2 - 1;
		mousePos.y = -(mouseCoords.y / this.canvas.height) * 2 + 1;
		raycaster.setFromCamera (mousePos, this.camera);
		let iSectObjects = raycaster.intersectObjects (this.scene.children);
		for (let i = 0; i < iSectObjects.length; i++) {
			let iSectObject = iSectObjects[i];
			if (iSectObject.object.type === 'Mesh' && iSectObject.object.visible) {
				return iSectObject.object.userData;
			}
		}
		return null;
	}

	GetBoundingBox (needToProcess)
	{
		let hasMesh = false;
		let boundingBox = new THREE.Box3 ();
		this.EnumerateMeshes (function (mesh) {
			if (needToProcess (mesh.userData)) {
				boundingBox.union (new THREE.Box3 ().setFromObject (mesh));
				hasMesh = true;
			}
		});

		if (!hasMesh) {
			return null;
		}
		return boundingBox;
	}

	GetBoundingSphere (needToProcess)
	{
		let boundingBox = this.GetBoundingBox (needToProcess);
		if (boundingBox === null) {
			return null;
		}

		let boundingSphere = new THREE.Sphere ();
		boundingBox.getBoundingSphere (boundingSphere);
		return boundingSphere;
	}

	EnumerateMeshesUserData (enumerator)
	{
		this.EnumerateMeshes (function (mesh) {
			enumerator (mesh.userData);
		});
	}

	EnumerateMeshes (enumerator)
	{
		this.scene.traverse (function (object) {
			if (object.isMesh) {
				enumerator (object);
			}
		});
	}

	ClearMeshes ()
	{
		for (let i = this.scene.children.length - 1; i >= 0; i--) {
			let object = this.scene.children[i];
			if (object.isMesh) {
				object.geometry.dispose ();
				this.scene.remove (object);
			}
		}
	}

	InitCamera ()
	{
		this.camera = new THREE.PerspectiveCamera (45.0, this.canvas.width / this.canvas.height, 0.1, 1000.0);
		this.scene.add (this.camera);

		let canvasElem = this.renderer.domElement;
		let camera = OV.GetDefaultCamera (OV.Direction.Z);

		let obj = this;
		this.navigation = new OV.Navigation (canvasElem, camera);
		this.navigation.SetUpdateHandler (function () {
			obj.Render ();
		});

		this.upVector = new OV.UpVector ();
	}

	InitLights  ()
	{
		let ambientLight = new THREE.AmbientLight (0x888888);
		this.scene.add (ambientLight);
	
		this.light = new THREE.DirectionalLight (0x888888);
		this.scene.add (this.light);
	}
};
