// all possible sites that comes through
var SITES = "vw-golf,adrivelessordinary,newgolf,thenewgolf,newgenerationgolf,new-golf,thenew-golf,volkswagen-golf,volkswagennewgolf";

// display the error message not before 2 seconds
var ERROR_DISPLAY_DELAY = 2000;

// minimum distance for each request
var MIN_DISTANCE = 0;

// threshold for bearing to determine turn type, this threshold is not included left and right type
var BEARING_THRESHOLD =
[
	{ type: "t1", min: 135, max: 180 },
	{ type: "t2", min: 90, max: 135 },
	{ type: "t3", min: 0, max: 90 }
];

// threshold for turning, it determine what is the length modifier to take
// for straight line, the length is based on the real length of the trip
var TURN_THRESHOLD =
{
	s: {length: 0, speed: 4 },
	t1: {length: 0.1, speed: 3 },
	t2: {length: 0.2, speed: 2 },
	t3: {length: 0.3, speed: 1 }
};

// threshold for speed, it determine which speed all step belong to
var SPEED_THRESHOLD =
[
	{ speed: 1, min: 0, max: 0.4 },
	{ speed: 2, min: 0.4, max: 0.7 },
	{ speed: 3, min: 0.7, max: 1 }
];

// max point that should be in the data that key and sub points
var MAX_POINT = 100;

// road info request
var ROAD_INFO_THRESHOLD =
{
	// total road info we should get in each side
	GET_COUNT: 6,
	// total road info we should give in XML
	SHOW_COUNT: 3,
	// distance in between road info request
	DISTANCE: 100
};

// radius to search the dealer in meters
var RADIUS_PROXIMITY = 30000;

var gdir;
var gsv;
var gcg;
var request;

// document ready event handler
$(document).ready(function()
{
	// create request object
	request = new Object();

	// enable the error by period of time since we do not want to show the error panel straight away
	setTimeout(function()
	{
		var flashError = $("#flasherror");

		// check if the flash error panel is exist or not, since it may be replaced by swfobject
		if (flashError != null && flashError.length == 1)
		{
			// if still exist then it means the flash is not properly loaded then show the error message
			flashError.show();
		}
	}, ERROR_DISPLAY_DELAY);

	// create flash variable object for Flash
	var flashVar = new Object();

	// extracted start address
	var start = "";
	// extracted end address
	var end = "";
	// extracted host referral with default value is 'volkswagen-golf'
	var refer = "volkswagen-golf";

	// check for query string start and end in the request for Send A Friend feature
	if (location.search.length != 0)
	{
		// create an array
		var qs = location.search.substr(1).split("&");

		// iterate all the array item
		for (var i = 0; i < qs.length; i++)
		{
			var pair = qs[i].split("=");
			var name = pair[0];
			var value = pair[1];

			// check whether the site value is exist or not
			if (value != null)
			{
				// if the name in the query string is start
				if (name == "start")
				{
					start = value;
				}
				// if the name in the query string is end
				else if (name == "end")
				{
					end = value;
				}
				// if the name in the query string is r
				else if (name == "r")
				{
					// split the sites by character ','
					var sites = SITES.split(",");

					// browse the sites
					for (var j = 0; j < sites.length; j++)
					{
						// check if value the same as one of sites described
						if (value == sites[j])
						{
							// they are the same then copy it over
							refer = value;
						}
					}
				}
			}
		}
	}
	
	// set into flashVar
	flashVar.start = start;
	flashVar.end = end;
	flashVar.refer = refer;

	// set into request
	request.refer = refer;

	// check if tracker object is valid
	if (pageTracker != null)
	{
		// track the page
		pageTracker._trackPageview("/" + flashVar.refer + "/home");
	}

	// set the base path for flash to get all resources
	flashVar.basePath = "/flash/";
	//flashVar.basePath = "/volkswagen/golf6/flash/";

	// check flash
	if (swfobject.hasFlashPlayerVersion("9.0.0"))
	{
		// register the Golf6 flash
		//swfobject.embedSWF("flash/golf6.swf", "flashcontent", "550", "400", "9.0.0", "http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75", flashVar);
		swfobject.embedSWF("flash/assets/swf/index.swf", "flashcontent", "950", "650", "9.0.0", "http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75", flashVar);
	}

	// create GDirection instance
	gdir = new GDirections();
	// set load event handler for GDirection
	GEvent.addListener(gdir, "load", onGDirLoad);
	// set error event handler for GDirection
	GEvent.addListener(gdir, "error", onGDirError);
	// create GStreetviewClient instance
	gsv = new GStreetviewClient();
	// create GClientGeocoder instance
	gcg = new GClientGeocoder();
});

// window unload event handler
$(window).unload(function()
{
	// google function to scrap all unused memory to prevent memory leak
	GUnload();
});

// set copyright text
function setCopyright(/*string*/text)
{
	// get div object
	var googleFooter = $("#google_footer");
	// set visibility
	googleFooter.show();
	// get div object
	var copyright = $("#copyright");
	// set text
	copyright.html(text);
}

// get full address XML by specifying address
function getAddress(/*string*/address)
{
	request.address = address;

	// get location by specifying address
	gcg.getLocations(address, onGetAddressLoad);
}

// GClientGeocoder onLoad event handler for getAddress request
function onGetAddressLoad(data)
{
	// set root
	var response = new Object();
	var isError = false;

	// set address
	response.addresses = new Object();
	response.addresses.address = new Array();
	
	// check if we get valid response
	if (data != null && data.Status.code == 200)
	{
		// get list of response from GClientGeocoder
		var placemarks = data.Placemark;

		/* REMOVED DUE TO AUTO PICK THE FIRST ONE,
		   EVEN THOUGH WE HAVE MORE THAN ONE POSSIBILITIES THE VALID XML WILL ONLY PICK THE FIRST ONE
		
		// if we got ambiguity
		if (placemarks.length > 1)
		{
		// custom error for ambiguity
		data.Status.code = 800;
		isError = true;
		}
		
		*/
		
		for (var i = 0; i < placemarks.length; i++)
		{
			// create address object
			var address = new Object();
			// set root->addresses->address->location
			address.location = placemarks[i].address;

			// set root->addresses->address
			response.addresses.address.push(address);
		}
	}
	else
	{
		isError = true;
	}
	
	// if we got error
	if (isError)
	{
		// set root->errror
		response.error = new Object();
		// set root->error->code
		response.error.code = data.Status.code;
		// get error message for geocode
		response.error.message = getGeocodeErrorMessage(data.Status.code);
		// send invalid response
		sendInvalidAddressXML(response);
	}
	else
	{
		// send valid response
		sendValidAddressXML(response);
	}
}

// get drive XML by specifying start and end address
function getDrive(/*string*/start, /*string*/end)
{
	request.start = start;
	request.end = end;

	// request for drive direction with step and polyline response
	gdir.load("from: " + start + " to: " + end,
	{
		getPolyline: true,
		getSteps: true
	});
}

// GDirection load event handler
function onGDirLoad()
{
	// set root
	var response = new Object();
	// set root->drive
	response.drive = new Object();

	// get route of the direction
	var route = gdir.getRoute(0);
	// get polyline from direction
	var path = gdir.getPolyline();
	// get polygon rectangle bound from top left to bottom right
	var bound = path.getBounds();
	// get total distance of the trip
	var totalDistance = route.getDistance().meters;

/*
	console.debug("distance: " + totalDistance);
	var distance = bound.getSouthWest().distanceFrom(bound.getNorthEast());
	console.debug("bound length: " + distance);
	console.debug("bound span: " + bound.toSpan());
	console.debug("degree: " + Math.atan2(bound.toSpan().lat(), bound.toSpan().lng()) / Math.PI * 180);
	var rad2 = Math.atan2(bound.toSpan().lat(), bound.toSpan().lng());
	var width = Math.sin(rad2) * distance;
	console.debug("width: " + width);
	var height = Math.cos(rad2) * distance;
	console.debug("height: " + height);
	console.debug(Math.sqrt(width * width + height * height));
*/
	
	// check if minimum distance restriction is fulfilled
	if (totalDistance >= MIN_DISTANCE)
	{
		// set root->drive->start_address
		response.drive.start_address = route.getStartGeocode();

		// set root->drive->end_address
		response.drive.end_address = route.getEndGeocode();

		// set root->drive->distance in meter
		response.drive.distance = gdir.getDistance().meters;

		// set root->drive->drive_time in second
		response.drive.drive_time = 30;

		// set root->drive->map_zoom
		response.drive.map_zoom = 0; // getZoom2(bound, bound.getCenter(), request.width, request.height, request.padding);

		// set root->drive->bound_ne
		response.drive.bound_ne = bound.getNorthEast();

		// set root->drive->bound_sw
		response.drive.bound_sw = bound.getSouthWest();

		// set root->drive->map_centre
		response.drive.map_centre = bound.getCenter();

		// get date time now
		var now = new Date();
		// set root->drive->time
		response.drive.time = now.getHours() + ":" + now.getMinutes();

		// get path direction in polygon
		var polyline = gdir.getPolyline();
		// total distance that already gone through
		var distanceLapse = 0;
		// get step number
		var stepCount = route.getNumSteps();
		// total sub point in the direction
		var subPointCount = polyline.getVertexCount() - stepCount;
		// counter how many sub point we have gone through
		var counterSubPoint = 1;
		// total sub point we have in the map
		var totalSubPoint = 0;
		// get include sub point counter to aiming max point
		var includeSubPoint = subPointCount / MAX_POINT;

		// create object for street view request
		var ri = new Object();
		// set root->road_info->id counter
		ri.sID = 1;
		// set counter to determine when all point already visited (both panorama and street name)
		ri.count = 0;
		// set total request to 0
		ri.total = 0;
		// set root->map
		response.map = new Object();
		// set root->map->m
		response.map.m = new Array();
		// set root->road_info
		response.road_info = new Object();
		// set root->road_info->s
		response.road_info.sn = new Object();
		response.road_info.sn.start = new Array();
		response.road_info.sn.end = new Array();
		response.road_info.sv = new Object();
		response.road_info.sv.start = new Array();
		response.road_info.sv.end = new Array();

		// iterate them to get all step count
		for (var i = 0; i < stepCount; i++)
		{
			// get step
			var step = route.getStep(i);
			// get previous step
			var prevStep = null;

			// check whether current step is not the first one
			if (i > 0)
			{
				prevStep = route.getStep(i - 1);
			}

			// get next step
			var nextStep = null;

			// check whether current step is not the last one
			if (i < stepCount - 1)
			{
				nextStep = route.getStep(i + 1);
			}

			// get polyline index from the step
			var polylineIndex = step.getPolylineIndex();
			// get polyline index from the previous step
			var prevPolylineIndex = polylineIndex;

			// check if previous step is exist
			if (prevStep != null)
			{
				prevPolylineIndex = prevStep.getPolylineIndex();
			}

			// get polyline index from the next step
			var nextPolylineIndex = polylineIndex;

			// check if next step is exist
			if (nextStep != null)
			{
				nextPolylineIndex = nextStep.getPolylineIndex();
			}

			// get point according to vertex in the polyline
			var point = path.getVertex(polylineIndex);

			// get distane of this step
			var distance = step.getDistance().meters;

			// add how many length already gone through
			distanceLapse += distance;
			// ratio length to total length that can be represent to time ratio
			var timeRatio = distanceLapse / totalDistance;

			// set root->map->m
			var m = new Object();
			// set root->map->m->id
			m.id = i * 2;
			// get bearing of current vertex into next vertex
			var bearing = path.Bearing(polylineIndex);
			// TODO: set root->map->m->type
			m.type = "s";
			// set root->map->m->point
			m.point = point;
			// set root->map->m->length
			m.length = distance / totalDistance;
			// set root->map->m->direction
			m.direction = "180";
			// set root->map->m->speed
			m.speed = 0;
			// set root->map-m->sm
			m.sm = new Array();

			// go through all index between step point
			for (var j = polylineIndex + 1; j < nextPolylineIndex; j++)
			{
				if (Math.floor(counterSubPoint / includeSubPoint) >= totalSubPoint)
				{
					var sm = new Object();
					// get point according to vertex in the polyline
					sm.point = path.getVertex(j);
					// add into map
					m.sm.push(sm);
					// increase total sub point
					totalSubPoint++;
				}

				// add counter
				counterSubPoint++;
			}

			// add it into root->map->m
			response.map.m.push(m);

			// add for turn map
			// set root->map->m
			var m = new Object();
			// set root->map->m->id
			m.id = (i * 2) + 1;
			// get bearing of current vertex into next vertex
			var bearing = path.Bearing(polylineIndex);
			// set root->map->m->type
			m.type = getTurnType(bearing);
			// set root->map->m->length
			m.length = 0.02;
			// set root->map->m->direction
			m.direction = bearing;
			// add it into root->map->m
			response.map.m.push(m);
		}

		// iterate them to get all step count
		for (var i = 1; i < (stepCount * 2) - 1; i = i + 2)
		{
			// get m object that contain the turn type and length
			var m = response.map.m[i];
			// get previous m object that contain the straight line
			var prevM = response.map.m[i - 1];
			// get next m object that contain the straight line
			var nextM = response.map.m[i + 1];
			// get type of turn
			var type = m.type;
			// length of turn
			var length = 0;

			// determine the speed and length based on the turn both left and right
			switch (type)
			{
				case "lt1":
				case "rt1":
					// set the length of turn
					length = TURN_THRESHOLD["t1"].length;
					break;

				case "lt2":
				case "rt2":
					// set the length of turn
					length = TURN_THRESHOLD["t2"].length;
					break;

				case "lt3":
				case "rt3":
					// set the length of turn
					length = TURN_THRESHOLD["t3"].length;
					break;

				default:
					// set the length of turn
					length = TURN_THRESHOLD["s"].length;
			}

			// set root->map->m->length
			m.length = (prevM.length * length) + (nextM.length * length);
			// decrease the length by multiply current length with length modifier
			prevM.length = prevM.length * (1 - length);
			// decrease the length by multiply current length with length modifier
			nextM.length = nextM.length * (1 - length);
			// set root->map->m->speed
			m.speed = getSpeed(m.length / totalDistance);
		}

		var sortM = new Array();

		// iterate them to get all step count
		for (var i = 0; i < response.map.m.length - 1; i++)
		{
			// get m object
			var m = response.map.m[i];
			// create new m object to be sorted
			var newM = new Object();
			// set index of m
			newM.index = i;
			// set length of m
			newM.length = m.length;
			// insert into list
			sortM.push(newM);
		}

		// sort the list by length
		sortM.sort(function(a, b)
		{
			// we want to sort ascending based on the length
			return (a.length - b.length);
		});

		// iterate them to get all step count
		for (var i = 0; i < sortM.length; i++)
		{
			// get m object
			var m = response.map.m[sortM[i].index];
			// get ratio based on current index by total step
			var ratio = i / sortM.length;

			// set speed based on ratio of step distribution
			m.speed = getSpeed(ratio);
		}

		// get all road info in front side
		for (var i = 0; i < ROAD_INFO_THRESHOLD.GET_COUNT; i++)
		{
			// get point for every i-th distance
			var point = path.GetPointAtDistance(ROAD_INFO_THRESHOLD.DISTANCE * i);
			
			// check if the point is valid
			if (point != null)
			{
				// get request the road info based on the point
				requestRoadInfo(ri, response, point, "start");
			}
		}

		// get all road info in back side
		for (var i = ROAD_INFO_THRESHOLD.GET_COUNT - 1; i >= 0; i--)
		{
			// get point for every i-th distance in backward
			var point = path.GetPointAtDistance(totalDistance - (ROAD_INFO_THRESHOLD.DISTANCE * i));

			// check if the point is valid
			if (point != null)
			{
				// get request the road info based on the point
				requestRoadInfo(ri, response, point, "end");
			}
		}
	}
	else
	{
		// send error message because trip does not meet minimum distance
		composeDriveError(801);
	}
}

// send request to Google to get all panorama image
function requestRoadInfo(ri, response, point, type)
{
	// increase total of request by 2 (panorama and street name)
	ri.total += 2;
	
	// get panorama based on point
	gsv.getNearestPanorama(point, function(data)
	{
		// check if we get valid response
		if (data != null && data.code == 200)
		{
			// get response code
			var code = data.code;

			// handle the response based on the code
			switch (code)
			{
				// SUCCESS      
				case 200:
					// set root->road_info->s
					var s = new Object();
					// set root->road_info->s->id
					s.id = ri.sID;
					// set root->road_info->s->type
					s.type = "sv";
					// set root->road_info->s->asset
					s.asset = "http://cbk3.google.com/cbk?output=tile&panoid=" + data.location.panoId + "&zoom=3&x=2&y=1&cb_client=maps_sv,http://cbk3.google.com/cbk?output=tile&panoid=" + data.location.panoId + "&zoom=3&x=3&y=1&cb_client=maps_sv,http://cbk3.google.com/cbk?output=tile&panoid=" + data.location.panoId + "&zoom=3&x=4&y=1&cb_client=maps_sv";

					// determine the type
					if (type == "start")
					{
						// set root->road_info->s in start
						response.road_info.sv.start.push(s);
					}
					else if (type == "end")
					{
						// set root->road_info->s in end
						response.road_info.sv.end.push(s);
					}
					
					ri.sID++;
					break;
				// SERVER_ERROR       
				case 500:

					break;
				// NO_NEARBY_PANO       
				case 600:

					break;
			}
		}

		ri.count++;

		// check whether it is already finished or not
		if (ri.count >= ri.total)
		{
			// if yes, then we send request to get all dealer surrounding
			requestDealer(response);
		}
	});

	// get address from point
	gcg.getLocations(point, function(data)
	{
		// check if we get valid response
		if (data != null && data.Status.code == 200)
		{
			// set root->road_info->s
			var s = new Object();
			// set root->road_info->s->id
			s.id = ri.sID;
			// set root->road_info->s->type
			s.type = "sn";
			// set root->road_info->s->asset
			s.asset = extractAddress(data);

			// determine the type
			if (type == "start")
			{
				// set root->road_info->s in start
				response.road_info.sn.start.push(s);
			}
			else if (type == "end")
			{
				// set root->road_info->s in end
				response.road_info.sn.end.push(s);
			}
			
			ri.sID++;
		}

		ri.count++;

		// check whether it is already finished or not
		if (ri.count >= ri.total)
		{
			// if yes, then we send request to get all dealer surrounding
			requestDealer(response);
		}
	});
}

// send request to get all dealer in 
function requestDealer(response)
{
	/* We disabled the searching dealer surrounding
	
	// create request dealer
	var rDealer = new Object();
	// set up the list of point
	rDealer.points = new Array();
	// set radius proximity to search the dealer
	rDealer.radius = RADIUS_PROXIMITY;
	
	// iterate all point that defined in the list
	for (var i = 0; i < response.map.m.length - 1; i = i + 2)
	{
		// get the map for straight
		var m = response.map.m[i];
		// create point object
		var point = new Object();
		// set lat point
		point.lat = m.point.lat();
		// set lng point
		point.lng = m.point.lng();
		// insert into list
		rDealer.points.push(point);

		// browse all sub map in the straight step
		for (var j = 0; j < m.sm.length; j++)
		{
			// get the sub map for each straight step
			var sm = m.sm[j];
			// create point object
			var point = new Object();
			// set lat point
			point.lat = m.point.lat();
			// set lng point
			point.lng = m.point.lng();
			// insert into list
			rDealer.points.push(point);
		}
	}
	*/
	
	// get all dealer
	Golf6.Service.GetDealers(
	// function callback when request on complete
	function(data)
	{
		// set the vw_dealer
		response.vw_dealers = data;
		// we send the XML to flash
		sendValidDriveXML(response);
	},
	// function callback when request on error
	function(data)
	{
		// we send the XML to flash anyway
		sendValidDriveXML(response);
	});
}

// create error response back to Flash
function composeDriveError(code)
{
	// set root
	var response = new Object();
	// set root->errror
	response.error = new Object();
	// set root->error->code
	response.error.code = code;
	// set root->error->message
	response.error.message = getGeocodeErrorMessage(code);

	// send the XML to flash
	sendInvalidDriveXML(response);
}

// GDirection error event handler
function onGDirError()
{
	// create error message and send it to Flash
	composeDriveError(gdir.getStatus().code);
}

// compose XML and send to flash as invalid request
function sendInvalidAddressXML(response)
{
	// get flash object
	var flash = swfobject.getObjectById("flashcontent");
	var xml = "<?xml version=\"1.0\"?><root>";

	// write root->error
	xml = xml + "<error>";

	// write root->error->code
	xml = xml + "<code>" + response.error.code + "</code>";
	// write root->error->message
	xml = xml + "<message>" + response.error.message + "</message>";

	// write root->addresses
	xml = xml + "<addresses>";
	
	// write root->addresses->address
	for (var i = 0; i < response.addresses.address.length; i++)
	{
		// write root->addresses->address
		xml = xml + "<address><![CDATA[" + response.addresses.address[i].location + "]]></address>";
	}

	// write root->addresses close tag
	xml = xml + "</addresses>";

	// write root->error close tag
	xml = xml + "</error>";
	
	// write root close tag
	xml = xml + "</root>";

	// send invalid XML back to flash
	flash.onAddressComplete(xml);
}

// compose XML and send to flash as invalid request
function sendInvalidDriveXML(response)
{
	// get flash object
	var flash = swfobject.getObjectById("flashcontent");
	var xml = "<?xml version=\"1.0\"?><root>";

	// write root->error
	xml = xml + "<error>";

	// write root->error->code
	xml = xml + "<code>" + response.error.code + "</code>";
	// write root->error->message
	xml = xml + "<message>" + response.error.message + "</message>";

	// write root->error close tag
	xml = xml + "</error>";
	// write root close tag
	xml = xml + "</root>";

	// send invalid XML back to flash
	flash.onDriveComplete(xml);

	// check if the request is valid or not
	if (request != null)
	{
		// check if tracker object is valid
		if (pageTracker != null)
		{
			// track the page
			pageTracker._trackPageview("/" + request.refer + "/error?code=" + response.error.code);
		}
	}
}

// compose XML and send to flash as valid request
function sendValidAddressXML(response)
{
	var flash = swfobject.getObjectById("flashcontent");
	var xml = "<?xml version=\"1.0\"?><root>";

	// write root->drive
	xml = xml + "<drive>";

	// check the address to make sure no error
	if (response.addresses.address.length > 0)
	{
		// write root->drive->start_address
		xml = xml + "<d id=\"start_address\"><![CDATA[" + response.addresses.address[0].location + "]]></d>";
	}

	// write root->drive close tag
	xml = xml + "</drive>";
	// write root close tag
	xml = xml + "</root>";

	// send valid response
	flash.onAddressComplete(xml);
}

// compose XML and send to flash as valid request
function sendValidDriveXML(response)
{
	var flash = swfobject.getObjectById("flashcontent");
	var xml = "<?xml version=\"1.0\"?><root>";
	
	// write root->drive
	xml = xml + "<drive>";

	var start_address = response.drive.start_address.address.split(",");
	var start_address_output = "";
	
	for (var i = 0; i < start_address.length - 1; i++)
	{
		if (i != start_address.length - 2)
		{
			start_address_output = start_address_output + start_address[i] + ",";
		}
		else
		{
			start_address_output = start_address_output + start_address[i];
		}
	}

	var end_address = response.drive.end_address.address.split(",");
	var end_address_output = "";

	for (var i = 0; i < end_address.length - 1; i++)
	{
		if (i != end_address.length - 2)
		{
			end_address_output = end_address_output + end_address[i] + ",";
		}
		else
		{
			end_address_output = end_address_output + end_address[i];
		}
	}
	
	// write root->drive->start_address
	xml = xml + "<d id=\"start_address\"><![CDATA[" + start_address_output + "]]></d>";
	// write root->drive->end_address
	xml = xml + "<d id=\"end_address\"><![CDATA[" + end_address_output + "]]></d>";
	// write root->drive->distance
	xml = xml + "<d id=\"distance\"><![CDATA[" + response.drive.distance + "]]></d>";
	// write root->drive->drive_time
	xml = xml + "<d id=\"drive_time\"><![CDATA[" + response.drive.drive_time + "]]></d>";
	// write root->drive->map_zoom
	xml = xml + "<d id=\"map_zoom\"><![CDATA[" + response.drive.map_zoom + "]]></d>";
	// write root->drive->bound_ne
	xml = xml + "<d id=\"bound_ne\"><![CDATA[" + response.drive.bound_ne.lat() + ", " + response.drive.bound_ne.lng() + "]]></d>";
	// write root->drive->bound_sw
	xml = xml + "<d id=\"bound_sw\"><![CDATA[" + response.drive.bound_sw.lat() + ", " + response.drive.bound_sw.lng() + "]]></d>";
	// write root->drive->map_centre
	xml = xml + "<d id=\"map_centre\"><![CDATA[" + response.drive.map_centre.lat() + ", " + response.drive.map_centre.lng() + "]]></d>";
	// write root->drive->time
	xml = xml + "<d id=\"time\"><![CDATA[" + response.drive.time + "]]></d>";
	// write root->drive close tag
	xml = xml + "</drive>";
	
	// write root->map
	xml = xml + "<map>";
	
	// iterate all m nodes
	for (var i = 0; i < response.map.m.length; i = i + 2)
	{
		var m = response.map.m[i];

		// write root->map->m for straight line
		xml = xml + "<m id=\"" + m.id + "\" type=\"" + m.type + "\" point=\"" + m.point.lat() + ", " + m.point.lng() + "\" length=\"" + m.length + "\" direction=\"" + m.direction + "\" speed=\"" + m.speed + "\">";

		for (var j = 0; j < m.sm.length; j++)
		{
			var sm = m.sm[j];
			xml = xml + "<sm point=\"" + sm.point.lat() + ", " + sm.point.lng() + "\" />";
		}

		// write root->map->m close tag for straight line
		xml = xml + "</m>";

		// check to remove last turn in the list
		if (i + 1 < response.map.m.length - 1)
		{
			m = response.map.m[i + 1];

			// write root->map->m for turn line
			xml = xml + "<m id=\"" + m.id + "\" type=\"" + m.type + "\" length=\"" + m.length + "\" direction=\"" + m.direction + "\" />";
		}
	}

	// write root->map close tag
	xml = xml + "</map>";

	// write root->road_info
	xml = xml + "<road_info>";

	var sID = 1;
	
	// iterate all nodes
	for (var i = 0; i < ROAD_INFO_THRESHOLD.SHOW_COUNT; i++)
	{
		var sv = response.road_info.sv.start[i];
		var sn = response.road_info.sn.start[i];
		
		// check if not null
		if (sv != null)
		{
			// write root->map->m
			xml = xml + "<s id=\"" + sID + "\" type=\"" + sv.type + "\"><![CDATA[" + sv.asset + "]]></s>";
			sID++;
			
			// check if not null
			if (sn != null)
			{
				// write root->map->m
				xml = xml + "<s id=\"" + sID + "\" type=\"" + sn.type + "\"><![CDATA[" + sn.asset + "]]></s>";
				sID++;
			}
		}
	}

	var sv_index = response.road_info.sv.end.length - ROAD_INFO_THRESHOLD.SHOW_COUNT;

	if (sv_index < 0)
	{
		sv_index = 0;
	}
	
	// iterate all nodes
	for (var i = 0; i < ROAD_INFO_THRESHOLD.SHOW_COUNT; i++)
	{
		var sv = response.road_info.sv.end[i + sv_index];
		var sn = response.road_info.sn.end[i + sv_index];

		// check if not null
		if (sv != null)
		{
			// write root->map->m
			xml = xml + "<s id=\"" + sID + "\" type=\"" + sv.type + "\"><![CDATA[" + sv.asset + "]]></s>";
			sID++;
			
			// check if not null
			if (sn != null)
			{
				// write root->map->m
				xml = xml + "<s id=\"" + sID + "\" type=\"" + sn.type + "\"><![CDATA[" + sn.asset + "]]></s>";
				sID++;
			}
		}
	}
	
	// write root->road_info close tag
	xml = xml + "</road_info>";

	// write root->vw_dealers
	xml = xml + "<vw_dealers>";
	
	// iterate all d nodes
	for (var i = 0; i < response.vw_dealers.d.length; i++)
	{
		var d = response.vw_dealers.d[i];
		
		// write root->vw_dealers->d
		xml = xml + "<d id=\"" + d.id + "\" point=\"" + d.point.lat + ", " + d.point.lng + "\"><![CDATA[" + d.name + "]]></d>";
	}
	
	// write root->vw_dealers close tag
	xml = xml + "</vw_dealers>";

	// TODO: remove this static tag
	xml = xml + "<share><s id=\"stumble\"><i id=\"url\"><![CDATA[http://www.sumble.com/dsfsdfsf]]></i></s><s id=\"facebook\"><i id=\"url\"><![CDATA[http://www.facebook.com/eryujh]]></i></s><s id=\"email\"><i id=\"subject\"><![CDATA[Hey mate! Here is the VW Golf Drive.]]></i><i id=\"body\"><![CDATA[Here is the url to be clicked: www.vw.com.au.]]></i></s></share>";
	
	// write root close tag
	xml = xml + "</root>";
	
	// send valid response
	flash.onDriveComplete(xml);
}

// get geocode error message by specifying the error code
function getGeocodeErrorMessage(/*int*/code)
{
	var message = "";
	
	// determine the error message
	switch (code)
	{
		case G_GEO_BAD_REQUEST:
			message = "Your request is not valid, it could be empty address or too many waypoint specified.";
			break;
		case G_GEO_SERVER_ERROR:
			message = "Unknown error, it could be server timeout when expecting response.";
			break;
		case G_GEO_MISSING_QUERY:
		case G_GEO_MISSING_ADDRESS:
			message = "Your request is not valid, please insert non empty query direction.";
			break;
		case G_GEO_UNKNOWN_ADDRESS:
			message = "Your request is not valid, please insert valid 'from' and 'destination' address.";
			break;
		case G_GEO_UNAVAILABLE_ADDRESS:
			message = "Your request is not valid, the specified address is protected.";
			break;
		case G_GEO_UNKNOWN_DIRECTIONS:
			message = "Your request is not valid, it is not possible to create route direction based on your direction query.";
			break;
		case G_GEO_BAD_KEY:
			message = "Application error, the key is not valid to this domain.";
			break;
		case G_GEO_TOO_MANY_QUERIES:
			message = "Application error, request limit has been reached. Please try again later.";
			break;
		case 800:
			message = "Your request is not valid, please insert more detail address";
			break;
		case 801:
			message = "Your request is not valid, please select another driving trip that has more distance";
			break;
		default:
			message = "Unknown error code, please try again.";
	}

	return message;
}

// get turn type according to the bearing
function getTurnType(/*float*/bearing)
{
	// side status, left or right
	var side = "r";
	var type = "";
	
	// if bearing more than 180, then it is left
	if (bearing > 180)
	{
		side = "l";
		// we do not want to have both side, we want only 1 side, so for left side we convert it as right side
		bearing = 360 - bearing;
	}
	
	for (var i = 0; i < BEARING_THRESHOLD.length && type == ""; i++)
	{
		var threshold = BEARING_THRESHOLD[i];

		// if bearing in the range
		if (bearing >= threshold.min && bearing < threshold.max)
		{
			type = threshold.type;
		}
	}

	// check whether the turn type is valid or not
	if (type == "")
	{
		type = "s";
	}

	return side + type;
}

// get speed category based on the length in ratio compare to total length
function getSpeed(/*float*/length)
{
	// set 1 as default
	var speed = 1;

	for (var i = 0; i < SPEED_THRESHOLD.length; i++)
	{
		var threshold = SPEED_THRESHOLD[i];

		// if length in the range
		if (length >= threshold.min && length < threshold.max)
		{
			speed = threshold.speed;
		}
	}

	return speed;
}

// get format address that we want from the GClientGeocode response
function extractAddress(/*Object*/data)
{
	var result = "";
	var street = null;
	var suburb = null;
	
	try
	{
		// get street name from response from GClientGeocode
		street = data.Placemark[0].AddressDetails.Country.AdministrativeArea.Locality.Thoroughfare.ThoroughfareName;
		// get suburb name from response from GClientGeocode
		suburb = data.Placemark[0].AddressDetails.Country.AdministrativeArea.Locality.LocalityName;
	}
	catch(e)
	{
		// catch exception and be silent
	}
	
	// if street value is valid
	if (street != null)
	{
		// split it based on space
		var section = street.split(" ");
		var temp = "";

		// go through all section and get all string if first character is not number
		for (var i = 0; i < section.length; i++)
		{
			if (isNaN(section[i][0]))
			{
				temp = temp + section[i] + " ";
			}
		}

		// remove last character if space
		if (temp[temp.length - 1] == ' ')
		{
			temp = temp.substr(0, temp.length - 1);
		}
		
		// we set as result if the street name is more than 4 chars
		if (temp.length >= 4)
		{
			result = temp;
		}
	}

	// we use suburb name if result still not has been set
	if (suburb != null && result == "")
	{
		result = suburb;
	}
	
	return result;
}

function getZoom(/*GLatLngBound*/bound, /*int*/width, /*int*/height, /*int*/padding)
{
	// default zoom
	var zoom = 8;
	var dimension = [131072, 65536, 32768, 16384, 8192, 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8];
	// represent min lat and lng
	var ne = bound.getNorthEast();
	// represent max lat and lng
	var sw = bound.getSouthWest();
	// calculate the distance
	var distance = (6371 * Math.acos(Math.sin(ne.lat() / 57.2958) * Math.sin(sw.lat() / 57.2958) +
		(Math.cos(ne.lat() / 57.2958) * Math.cos(sw.lat() / 57.2958) * Math.cos(sw.lng() / 57.2958 - ne.lng() / 57.2958))));
	// diagonal in pixel
	var diagonal = Math.sqrt(width * width + height * height);
	// calculate what is the distance per pixel
	var ratio = distance / diagonal;
	// since it is both side
	padding = padding * 2;
	// get additional padding in distance
	var paddingDistance = Math.sqrt(padding * padding + padding * padding) * ratio;

	// recalculate distance with padding
	distance = distance + paddingDistance;

	// check the distance where is the category belong to
	for (var i = 0; i < dimension.length; i++)
	{
		if (distance < dimension[i])
		{
			zoom = i;
		}
	}

	return zoom;
}

function getZoom2(/*GLatLngBound*/bound,/*int*/width, /*int*/height,/*int*/padding)
{
	// default zoom
	var zoom = 8;
	var dimension = [24576, 12288, 6144, 3072, 1536, 768, 384, 192, 96, 48, 24, 11, 4.8, 3.2, 1.6, 0.8, 0.3];
	// represent min lat and lng
	var ne = bound.getNorthEast();
	// represent max lat and lng
	var sw = bound.getSouthWest();
	// calculate the distance
	var distance = (6371 * Math.acos(Math.sin(ne.lat() / 57.2958) * Math.sin(sw.lat() / 57.2958) +
		(Math.cos(ne.lat() / 57.2958) * Math.cos(sw.lat() / 57.2958) * Math.cos(sw.lng() / 57.2958 - ne.lng() / 57.2958))));
	// diagonal in pixel
	var diagonal = Math.sqrt(width * width + height * height);
	// calculate what is the distance per pixel
	var ratio = distance / diagonal;
	// since it is both side
	padding = padding * 2;
	// get additional padding in distance
	var paddingDistance = Math.sqrt(padding * padding + padding * padding) * ratio;

	// recalculate distance with padding
	//distance = distance + paddingDistance;
	
	// check the distance where is the category belong to
	for (var i = 0; i < dimension.length; i++)
	{
		if(distance < dimension[i])
		{
			zoom = i + 1;
		}
	}

	return zoom;
}

function getZoom3(/*GLatLngBound*/bound, /*GPoint*/centre, /*int*/width, /*int*/height,/*int*/padding)
{
	// represent min lat and lng
	var ne = bound.getNorthEast();
	// represent max lat and lng
	var sw = bound.getSouthWest();
	var minLat = ne.lat();
	var maxLat = sw.lat();
	var minLng = ne.lng();
	var maxLng = sw.lng();
	var ctrLat = centre.lat();
	var ctrLng = centre.lng();
	var mapdisplay = width;
	var interval = 0;

	if (height < mapdisplay)
	{
		mapdisplay = height;
	}
	
	if (Math.abs(maxLat - minLat) > Math.abs(maxLng - minLng))
	{
		interval = (maxLat - minLat) / 2;
		minLng = ctrLng - interval;
		maxLng = ctrLng + interval;
	}
	else
	{
		interval = (maxLng - minLng) / 2;
		minLat = ctrLat - interval;
		maxLat = ctrLat + interval;
	}

	var dist = (6371 * Math.acos(Math.sin(minLat / 57.2958) * Math.sin(maxLat / 57.2958) + (Math.cos(minLat / 57.2958) * Math.cos(maxLat / 57.2958) * Math.cos((maxLng / 57.2958) - (minLng / 57.2958)))));

	return Math.ceil(8 - Math.log(1.6446 * dist / Math.sqrt(2 * (mapdisplay * mapdisplay))) / Math.log(2));
}