var things = [];
var pois = [];//points of interest
var mapMarkers = [];//markers on the google map
c3dl.debug.setVisible(false);
c3dl.addModel("/wp-content/2.0Release/3DGoogleMaps/models/road.dae");

c3dl.addModel("/wp-content/2.0Release/3DGoogleMaps/models/compass.dae");
c3dl.addModel("/wp-content/2.0Release/3DGoogleMaps/models/tetrahedron.dae");
c3dl.addMainCallBack(load,"C3D");

var comp;//the compass

var map;//the map
var directions;//the mapDirections obtained from Google
var dir;//the GDirections obtained from Google
var steps;//The div on the html page showing the directions
var hiddenDiv;//A hidden dive on the html page for the search control (hidden because we want to control it all in here).
var points = new Array( );//The list of points obtained from Google
var pointNum = 0;//index used to step through Google's directions
var geocoder = new google.maps.ClientGeocoder();//Used for converting addresses into lat-long coordinates and vice-versa
var cams;//The camera
var defaultStart="70 The Pond Rd, Toronto, ON M3J, Canada";//Default start location
var defaultEnd="10 Allstate Pkwy, Markham, ON L3R 5Y1, Canada";//Default end location
var posit = ([0,0,0]);//The current position in 3D space
var scale = 100;//scale value for roads
var speed = 10;//speed at which to travel
var rotTime = 1;//speed at which to rotate
var dest;//current destination coordinates
var totalDist = 0;//Total distance from start to finish
var RoadIndex = 0;//tracks the end of the current set of roads (usually 10 ahead of current position)
var ang = 0;//The total amount to rotate through
var paused = true;//Whether the 3D map is paused or not
var destBearing = 0;//The angle to be facing after the turn
var traversed = 0;//The total amount that has been rotated for this turn


var currentRoad = 0; //the index of the road currently being driven on
var currentDistance = 0; //the length of the current road
var ll1;//this point in lat-long coordinates
var ll2;//the next point in lat-long coordinates
var deltaLat;//the difference in latitude between ll1 and ll2
var deltaLong;//the difference in longitude between ll1 and ll2
var startedAt;//The point this movement started at
var goingTo;//The point this movement will end at
var carMarker;//A map marker for the current position

var leads = new Array();//used for the Google map search
var leadMarkers = new Array();//used for the Google map search
var searchControl;//The control that manages the search
var ls;//The 'local' search
var searchTerm = "Coffee";//the thing to search for

function update(time)
{
	if(dest != undefined && paused == false)
	{
		paused = true;
		if(ang != 0)//if turning
		{
			yawDist = ang * time/1000 * rotTime;//calculates how far to turn based on the amount of time since the last update
			var curBear = currentBearing();//get the current bearing
			if(yawDist > 0)//turning left
			{
				if(traversed + yawDist >= ang)//if it will turn too far, cut it short
				{
					yawDist = ang - traversed;//calculate how much it traversed past where it should have
					ang = 0;//note to stop turning
					traversed = 0;//note to stop turning
				}
				else//if it hasn't finished turning yet, keep track of the distance it will turn
				{
					traversed += yawDist;
				}
			}
			else//turning right
			{
				if(traversed + yawDist <= ang)//if turning to far
				{
					yawDist = ang - traversed;//calculate how much it traversed past where it should have
					ang = 0;//note to stop turning
					traversed = 0;//note to stop turning
				}
				else////if it hasn't finished turning yet, keep track of the distance it will turn
				{
					traversed += yawDist;
				}
			}
			cams.yaw(yawDist);//yaw the determined distance
		}//endif turning
		else//moving forward
		{
			var pos = cams.getPosition();//get the current position of the camera
			var dir = cams.getDir();//get the direction the camera is pointing
			var move = speed * time/1000;//calculate how far to mave, based on how long since the last update
			var nextPos = [pos[0] + (dir[0] * move), pos[1] + (dir[1] * move), pos[2] + (dir[2] * move)]; //position to move to
			//if the camera passes the end of the road
			if((pos[0] < dest[0] && nextPos[0] >= dest[0]) || (pos[0] > dest[0] && nextPos[0] <= dest[0]) || (pos[2] < dest[2] && nextPos[2] >= dest[2]) || (pos[2] > dest[2] && nextPos[2] <= dest[2]))
			{
				nextDirections();//get the next step in the directions
			}
			else//still somewhere on the road, so just move forward
			{
				cams.setPosition(nextPos);//move the camera to the calculated next position
			}

			//hide points of interest that are far away
			for(var i = 0; i <  pois.length; i++)//go through the list of points of interest
			{
				var mark = pois[i].getPosition();//get the position of each point
				var dist = getDistanceCartesian(pos,mark);//get the distance between the camera and that point
				if(dist > 100)//if it is far away
				{
					pois[i].setVisible(false);//make it invisible
				}
				else//if it is close
				{
					pois[i].setVisible(true);//make sure it is visible
				}
			}
			
			//move the car marker on the Google map
			var travelled = cams.getPosition();//use the camera's position to calculate how far it has moved along the current road
			var percentDistance = getDistanceCartesian(travelled,startedAt)/getDistanceCartesian(goingTo,startedAt);//as a percent of the length of that road
			var newPosition = new GLatLng(ll1.lat() + deltaLat * percentDistance,ll1.lng() + deltaLong * percentDistance);//now use that percent to calculate a location between the lat-long coordinates on the map
			carMarker.setLatLng(newPosition);//move the car marker to the new position
			
			positionRose();//move the compass to catch up with the camera
		}//endif moving
		
		paused = false;//let the 3D map continue
	}
}

//load function called when the page loads
function load(canvasId)
{
	initialize();//set up some basic stuff for the Google map
	scns = new c3dl.Scene( );

	renderers = new c3dl.WebGL( );
	cams = new c3dl.FreeCamera( ); 

	scns.setCanvasTag( canvasId );
	// set the renderer for this scene
	scns.setRenderer( renderers );
	// set default camera
	scns.setCamera( cams );
	// initialize this scene;
	var rc= scns.init( );
	// check that the initialization succeeded
	if( rc )
	{
		scns.setAmbientLight([0.5,0.5,0.5]);
		scns.setUpdateCallback(update);
		scns.setPickingCallback(handler);

		//add a compass to the scene
		comp = new c3dl.Collada();
		comp.init("/wp-content/2.0Release/3DGoogleMaps/models/compass.dae");
		positionRose();//position the compass with the camera
		scns.addObjectToScene(comp);
		
		//add a light
		var sun = new c3dl.DirectionalLight();
		sun.setName('mr. sun');
		sun.setDirection([-5,-200,-1]);
		sun.setDiffuse([0.5,0.5,0.5]);
		sun.setOn(true);
		scns.addLight(sun);
		
		// start the scene
		scns.startScene();
	}
}

//initialization function for the Google map
function initialize()
{
	//set up the local search functionality
	google.load('search', '1');
	searchControl = new google.search.SearchControl();
	ls = new google.search.LocalSearch();
	searchControl.addSearcher(ls);
	searchControl.setSearchCompleteCallback(this,treasureHunt);
	searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
	
	//set up the map
	map = new GMap2(document.getElementById("map_canvas"));
	ls.setCenterPoint(map);
	directions = document.getElementById("map_directions");
	dir = new GDirections( map, directions );
	steps = document.getElementById("steps");
	hiddenDiv = document.getElementById("hidden");
	GEvent.addListener( dir, "load", getArrayOfPoints );
	GEvent.addListener( dir, "load", startDirections );
	GEvent.addListener( map, "moveend", move );
	getDirections(defaultStart,defaultEnd);
}

//Get the directions from the fromLoc to toLoc
function getDirections(fromLoc, toLoc)
{
	if (GBrowserIsCompatible())
	{
		paused = true;
		showAddress(fromLoc);
		dir.clear();
		points = new Array( );
		dir.load( "from: " + fromLoc + " to: " + toLoc);
		
	}
}

//Try to find the given address on the map
function showAddress(address)
{
	geocoder.getLatLng(address,
        function(point)
		{
			if (!point) {
				alert(address + " not found");
			}
			else
			{
				map.setCenter(point, 15);
			}
        }
	);
}

//Get the list of points obtained from Google
function getArrayOfPoints( )
{
	var polyline = dir.getPolyline( );
	var polyNum = polyline.getVertexCount( );
	var p;
	var prev;
	totalDist = dir.getDistance().meters;//get the new total distance
	//based on the total distance, figure out how to scale the roads
	if(totalDist < 1000)//less than 1km
	{
		scale = 1000;
	}
	else if(totalDist < 5000)//less than 50km
	{
		scale = 500;
	}
	else if(totalDist < 10000)//less than 10km
	{
		scale = 200;
	}
	else if(totalDist < 20000)//less than 20km
	{
		scale = 100;
	}
	else if(totalDist < 50000)//less than 50km
	{
		scale = 50;
	}
	else
	{
		scale = 10;//If it is really big, keep the roads fairly short
	}
	for ( var i = 0; i < polyNum; i++ )//get only non-duplicate points
	{
		p = polyline.getVertex( i );
		if(i == 0 || getDistance(p,prev) * scale > 2.5)
		{
			points.push( p );
			prev = p;
		}
	}
	map.setCenter( points[0], 15 );
	setTimeout( move, 2000 );
}

function move()//move the google map to the next point
{
	if ( pointNum < points.length - 1 )
	{
		do
		{
			var pointA = points[ pointNum++ ];
			var pointB = points[ pointNum ];
        }
		while ( pointB.distanceFrom( pointA ) == 0 );
        map.panTo( pointB, 15 );
        setTimeout( move, 2000 );
	}
}
    
function setMoveTimeout()
{      
	setTimeout( moveToPoint, 500 );
}

//Set up the framework for a new set of directions
function startDirections()
{
	paused = true;//pause the 3D map while this gets taken care of
	//reset some variables
	ang = 0;
	destBearing = 0;
	paused = true;
	var sp; //standpoint
	var fp; //forepoint
	roadIndex = 0;
	posit = [0,0,0];
	//totalDist = dir.getDistance().meters;//get the new total distance
	currentRoad = 0;
	
	while(things.length > 0)//clear out any old roads
	{
		road = things.pop();
		scns.removeObjectFromScene(road);
	}
	for(var i = pois.length; i > 0; i--)//clear the old points of interest
	{
		var poi = pois.pop();
		scns.removeObjectFromScene(poi);
	}
	for(var i = mapMarkers.length; i > 0; i--)//take off the old map markers
	{
		var rem = mapMarkers.pop();
		map.removeOverlay(rem.getMarker());
	}
	if(carMarker == undefined)//if the car marker has not been created yet, do so
	{
		// Create a marker icon for the car
		var carIcon = new GIcon(G_DEFAULT_ICON);
		carIcon.image = "/wp-content/2.0Release/3DGoogleMaps/images/carMarker.png";

		var markerOptions = { icon:carIcon };
		carMarker = new GMarker(new GLatLng(-90,-90), markerOptions); 
		map.addOverlay(carMarker);
	}
	else//if it has, just move it back to the origin
	{
		carMarker.setLatLng(points[0]);
	}

	polyNum = points.length;//get the number of points in the current set of directions
	
	//get the latitude and longitude values for this point and the next 
	ll1 = points[currentRoad];
	ll2 = points[currentRoad+1];
	//calculate the diffence in latitude and longitude
	deltaLat = ll2.lat() - ll1.lat();
	deltaLong = ll2.lng() - ll1.lng();
	
	//make sure the search control gets added to the html page
	searchControl.draw(hiddenDiv);
	
	//start off with a few roads
	for (roadIndex = 0; roadIndex < polyNum-1 && roadIndex < 10; roadIndex++)
	{
		sp = points[roadIndex];
		fp = points[roadIndex+1];
		roadCrew(sp,fp);
	}
	
	pog = things[0].getPosition();
	startedAt = pog;
	cams.setPosition([0,0.2,0]);//set the camera just above the originf
	positionRose();//position the compass with the camera
	
	if(things.length > 1)//if there is at least one road in the current set of directions
	{
		pog = things[1].getPosition();//track the next point
		cams.setLookAtPoint([pog[0],pog[1]+0.2,pog[2]]);//look at the next point
		dest = [pog[0],pog[1]+0.2,pog[2]];//set the camera destination to be just above the next point
	}
	else
	{
		cams.yaw(bear * -1);
	}
	
	goingTo = pog;//keep track of the point the camera is headed towards
	
	paused = false;//let the update function continue
}

//get the next road
function nextDirections()
{
	paused = true;//temporaily stop the update function
	currentRoad++;//move to the next road
	if(things.length > 0)//if there are more roads ahead
	{
		rd = things.shift();//shift the road that just got driven out of the array
		scns.removeObjectFromScene(rd);//remove that road from the scene
		if(things.length > 0)//if there are still roads
		{
			var polyNum = points.length;
			var sp;
			var fp;
			carMarker.setLatLng(points[currentRoad]);//set the map marker for the car to the current position
			if(roadIndex < polyNum -1)
			{
				sp = points[roadIndex];
				do//get the next point that is not colocated with this one
				{
					fp = points[roadIndex+1];
					roadIndex++;
				}
				while(roadIndex < polyNum && getDistance(sp,fp) * scale < 2.5);
				roadCrew(sp,fp);//add the next road onto the end of the array
				currentDistance = getDistance(sp,fp) * scale;//get the length of this road
			}
			pog = things[0].getPosition();//get the start position
			startedAt = pog;//keep track of the start position for later
			cams.setPosition( new Array(pog[0], pog[1]+0.2, pog[2]) );//set the camera just above the start of the road
			positionRose();//put the compass with the camera
			
			//get the camera's current bearing
			var theta1 = currentBearing();

			if(things.length > 1)//if there are more roads
			{
				pog = things[1].getPosition();//get the position of the next road
			}
			else//if this is the last road, calculate the position of the end point
			{
				var standPoint = points[0];
				var forePoint = points[polyNum-1];
				dist = getDistance(standPoint, forePoint);
				bear = getBearing(standPoint, forePoint);
				var opp = Math.sin(bear) * dist* scale;
				var adj = Math.cos(bear) * dist* scale;
				pog = [opp,0,-1* adj];
			}
			goingTo = pog;//keep track of the end of this road
			
			//get the latitude and longitude values for this point and the next 
			ll1 = points[currentRoad];
			ll2 = points[currentRoad+1];
			//and the difference in latitude and longitude between them
			deltaLat = ll2.lat() - ll1.lat();
			deltaLong = ll2.lng() - ll1.lng();
			
			//get the bearing from the camera to the next position
			theta2 = getBearingCartesian(cams.getPosition(),pog);
			destBearing = theta2;

			//get the difference in bearings
			var diffTheta = theta1 - theta2;

			//make sure the turn happens in the shorter direction
			if(diffTheta > toRad(180))
			{
				diffTheta -= toRad(360);
			}
			else if(diffTheta < toRad(-180))
			{
				diffTheta += toRad(360);
			}

			//keep the angle for the turn
			ang = diffTheta;

			//track the destination
			dest = [pog[0],pog[1]+0.2,pog[2]];
		}
	}
	paused = false;//let the update function happen
}

//set up one road, specified by the points sp and fp
function roadCrew(sp,fp)
{
	//search at the start point
	ls.setCenterPoint(fp);
	searchControl.execute(searchTerm);

	//make a new road
	road = new c3dl.Collada();
	road.init("/wp-content/2.0Release/3DGoogleMaps/models/road.dae");
        road.scale([0.0254,1,0.0254]);
	//use 'great circle' calculation to get distance in km
	dist = getDistance(sp,fp);
	//now get the bearing to angle the road
	bear = getBearing(sp,fp);

	//if this isn't the first road, move it so it connects to the end of the last road
	if(things.length > 0)
	{
		road.translate(posit);
	}
	//stretch the road out to reach the required distance
	//road.scale([1,1,dist*scale]);
        road.setHeight(1);
        road.setLength(1);
	road.setWidth(dist*scale);//The road seems to be a little confused as to Length vs Width...
	//rotate the road to point in the required direction
	road.yaw(bear * -1);//-1 because sin and the roll function consider positive to be in opposite directions
	things.push(road);//add the road into the array
	scns.addObjectToScene(road);//add the road to the scene

	//keep track of the end of this road
	var opp = Math.sin(bear) * dist * scale;
	var adj = Math.cos(bear) * dist * scale;
	posit=([posit[0]+opp,0,posit[2]-adj]);
}

//convert a value to radians
function toRad(deg)
{
	return deg * (Math.PI/180);
}

//convert a value to degrees
function toDeg(rad)
{
	return rad * (180/Math.PI);
}

//find gthe current bearing of the camera
function currentBearing()
{
	return getBearingCartesian([0,0,0],cams.getDir());
}

//this function is called anytime a search finishes
//because the searches are limited to 4 (now 8) results, this gets called often.
//if the result has not already been obtained it is added to both maps
//Unfortunately, results will tend to be clustered around the end of each road
function treasureHunt(control, searcher)
{
	var standPoint = points[0];
	var fpLat = 0;
	var fpLon = 0;
	var dontAdd = false;
	for(var i = 0; i < searcher.results.length; i++)//for each result returned
	{
		dontAdd = false;//assume it is unique and should be added
		fpLat = searcher.results[i].lat;//get the latitude
		fpLon = searcher.results[i].lng;//get the longitude
		var thisPoi = new c3dl.Collada();//create a new instance of the tetrahedron to mark this in 3D space
		thisPoi.init("/wp-content/2.0Release/3DGoogleMaps/models/tetrahedron.dae");
	
		//position this poi
		dist = getDistance(standPoint,new GLatLng(fpLat,fpLon));
		//now get the bearing from the startpoint to it
		bear = getBearing(standPoint,new GLatLng(fpLat,fpLon));
		var opp = Math.sin(bear) * dist* scale;
		var adj = Math.cos(bear) * dist* scale;
		thisPoi.translate([opp,0,-1 * adj]);
		
		//check that this point does not already exist
		for(var j = 0; j < pois.length; j++)
		{
			if(pois[j].getPosition()[0] == thisPoi.getPosition()[0] && pois[j].getPosition()[1] == thisPoi.getPosition()[1] && pois[j].getPosition()[2] == thisPoi.getPosition()[2])
			{
				dontAdd = true;//if it already exists, note that it shouldn't be added
				j = pois.length;//and exit the loop
			}
		}
		if(!dontAdd)//if this point is unique and should be added
		{
			thisPoi.setVisible(false);//start it off invisible
			pois.push(thisPoi);//add it to the array of points of interest
			thisPoi.setAngularVel([0,0.001,0]);//rotate very slowly
			scns.addObjectToScene(thisPoi);//add it to the scene
			//put it on the google map too
			mark = new GMarker(new GLatLng(fpLat,fpLon));
			map.addOverlay(mark);
			var rem = new resultMarker(mark,searcher.results[i]);
			mapMarkers.push(rem);
		}
	}
}

//This function is called when a new search is requested using the search button
//it removes all the old points of interest, chnages the term being searched for and
//searches up to the current road before allowing the car to continue moving
function searchFor(searchText)
{
	paused = true;
	searchTerm = searchText;
	searchesComplete = 0;
	for(var i = pois.length; i > 0; i--)//clear the old points of interest out of the 3D space
	{
		var poi = pois.pop();
		scns.removeObjectFromScene(poi);
	}
	for(var i = mapMarkers.length; i > 0; i--)//clear the markers off the Google map
	{
		var rem = mapMarkers.pop();
		map.removeOverlay(rem.getMarker());
	}
	for(var i = 0; i < roadIndex; i++)//search up to the current road
	{
		ls.setCenterPoint(points[i]);
		searchControl.execute(searchTerm);
	}
	paused = false;
}

// This function is called whenever an is clicked on.
//If the object is a point of interest, information about it will be displayed
function handler(result)
{
	var buttonUsed = result.getButtonUsed();
	var objectsPicked = result.getObjects();
	if(objectsPicked != undefined)
	{
		// a left mouse click will equal 1;
		// at present that is the only mouse event implemented
		if (buttonUsed == 1)
		{
			// loop through the objects
			for(var i = 0 ; i < objectsPicked.length; i++)
			{
				// get the object that was picked
				obj = objectsPicked[i];
				for(var j = 0; j < pois.length; j++)
				{
					if(obj == pois[j])//if the object picked is this point of interest
					{
						//get info about this poi
						var rem = mapMarkers[j];
						var message = rem.getResult().titleNoFormatting;
						var phon = rem.getResult().phoneNumbers;
						//put that info together
						for(var lin = 0; lin < phon.length; lin++)
						{
							if(phon[lin].type == "main" || phon[lin].type == "")
							{
								message+="\n" + phon[lin].number;
								lin = phon.length;
							}
						}
						var addr = rem.getResult().addressLines;
						for(var lin = 0; lin < addr.length; lin++)
						{
							message+="\n" + addr[lin];
						}
						//display that information
						alert(message);
						//exit the loop
						j = pois.length;
					}
				}
			}
		}
	}
}

//speed up the movement
function speedUp()
{
	speed *= 1.2;
	rotTime *= 1.2;
}

//slwo down the movement
function speedDown()
{
	speed /= 1.2;
	rotTime /= 1.2;
}

//An object that holds a search result and its corresponding map marker
resultMarker = function(mark, res)
{
	var marker = mark;
	var result = res;
	
	this.getMarker = function()//the map marker
	{
		return marker;
	}
	
	this.getResult = function()//the search result
	{
		return result;
	}
}

//get the distance (in km) between the standpoint (sp) and forepoint(fp)
function getDistance(sp,fp)
{
	var dl = fp.lng() - sp.lng();
	spr = toRad(sp.lat());
	fpr = toRad(fp.lat());
	dlr = toRad(dl);
	//use 'great circle' calculation to get distance in km
	return Math.atan(Math.sqrt(Math.pow(Math.cos(fpr)*Math.sin(dlr),2) + Math.pow(Math.cos(spr)*Math.sin(fpr) - Math.sin(spr)*Math.cos(fpr)*Math.cos(dlr),2)) / (Math.sin(spr)*Math.sin(fpr) + Math.cos(spr)*Math.cos(fpr)*Math.cos(dlr)) ) * 6371.01;
}

//get the distance between two cartesian coordinates
function getDistanceCartesian(sp,fp)
{
	return Math.sqrt(((sp[0] - fp[0]) * (sp[0] - fp[0])) + ((sp[2] - fp[2]) * (sp[2] - fp[2])));
}

//get the bearing from the standpoint (sp) to the forepoint(fp) using latitude and longitude
function getBearing(sp,fp)
{
	var dl = fp.lng() - sp.lng();
	spr = toRad(sp.lat());
	fpr = toRad(fp.lat());
	dlr = toRad(dl);
	return (Math.atan2(Math.sin(dlr) * Math.cos(fpr),Math.cos(spr)*Math.sin(fpr)-Math.sin(spr)*Math.cos(fpr)*Math.cos(dlr)) + toRad(360)) % toRad(360);
}

//get bearing using two sets of cartesian coordinates
function getBearingCartesian(sp,fp)
{
	var xdiff = fp[0] - sp[0];
	var zdiff = fp[2] - sp[2];
	var lindiff = Math.sqrt(xdiff*xdiff + zdiff*zdiff);
	var sinTheta = zdiff/lindiff;
	var cosTheta = xdiff/lindiff;
			
	var theta = Math.asin(sinTheta);
	
	if(sinTheta > 0 && cosTheta <= 0)
		{
			theta += 2 * (toRad(90) - theta);
		}
		else if(sinTheta < 0 && cosTheta < 0)
		{
			theta -= 2 * (theta - toRad(270));
		}
		else if(sinTheta == 0 && cosTheta == -1)
		{
			theta += toRad(180);
		}
	theta = (theta + toRad(360)) % toRad(360);
	return theta;
}

//position the compass rose in the correct spot
function positionRose()
{
	var posit = cams.getPosition()
	comp.setPosition([posit[0],posit[1]+0.6,posit[2]]);
}

