/* Constants */
var OP_ADD = 1;
var OP_ADD_KEEP_TO_ADDRESS = 2;
var OP_ADD_KEEP_FROM_ADDRESS = 3;
var OP_REPLACE_GEO = 4;
var OP_REPLACE_GEO_KEEP_TO_ADDRESS = 5;
var OP_REPLACE_GEO_KEEP_FROM_ADDRESS = 6;
var OP_REPLACE = 7;

var GEO_DELAY = 500;
var MESSAGE_URL = "/message?key=";
var GEO_MESSAGE_KEY_PREFIX = "js_geo_error_";
var LESS_ACCURACY = GEO_MESSAGE_KEY_PREFIX + "less_accuracy";
var MAPS_LINK_ERROR = "js_cabrioRoute_upload_maps_link_error"; 
var SUGGESTION_TITLE = "js_suggestion_title";

var SIMPLE_DIRECTION_REQUEST_COLLECTOR_SIZE = 1;
var DEFERRED_DIRECTION_REQUEST_COLLECTOR_SIZE = 2;

var DRAGDIR_REDIRECT_URL = "/dragdir";

var geocoder = new GClientGeocoder();

function initGeocoder(countryCode) {
	geocoder.setBaseCountryCode(countryCode);
}

var direction;
var callback;

/**************************************************************************
New Class: StringBuffer
***************************************************************************/	
function StringBuffer() {
   this.buffer = [];
 }

StringBuffer.prototype.append = function append(string) {
   this.buffer.push(string);
   return this;
 };

StringBuffer.prototype.toString = function toString() {
   return this.buffer.join("");
 };

 /**************************************************************************
 New Class: PartRouteUpdate
 ***************************************************************************/

 function PartRouteUpdate() {
	 this.position;
	 this.partRoute;
	 this.operation;
	 this.isStation = false;
	 this.toAddress;
	 this.fromAddress;
	 this.driveDuration;
	 this.length;
 }
 
 /**************************************************************************
 New Class: PartRouteCollecter
 ***************************************************************************/
 
function PartRouteCollector(full, sendElements) {
	this.full = full;
	this.elements = new Array();
	
	this.addElement = function(element) {
		this.elements.push(element)
		if (this.isFull()) {
			// Send part routes to the server, the server saves the part routes,
			// calls the search along the route and delivers the inserted part
			// routes and the found cabrio routes along the plannd route.
			sendElements(Object.toJSON(this.elements));
		}
	}
	
	this.isFull = function() {
		return (this.full == this.elements.length);
	}
}

 /**************************************************************************
 New Class: Direction
 ***************************************************************************/ 

function Direction(operation, errorContainer) {
	this.operation = operation;
	this.errorContainer = errorContainer;
	this.isStation;
	this.elementToUpdate;
	this.fromAddress;
	this.toAddress;
	this.gdirections = new GDirections();
	// Add the direction object to the GDirection obj to have it available in
	// onDirectionLoad
	this.gdirections.directionObj = this;
	GEvent.addListener(this.gdirections, "load", Direction.prototype.onDirectionLoad);
	GEvent.addListener(this.gdirections, "error", Direction.prototype.onDirectionError);
	var thisObject = this;
	
	this.computeDirection = function (fromAddress, toAddress) {
		if (fromAddress && toAddress) {
			disableMapSearchMode();
			thisObject.fromAddress = fromAddress;
			thisObject.toAddress = toAddress;
			thisObject.hasError = false;
			thisObject.errorMessage = "";
			thisObject.gdirections.load("from: " + thisObject.fromAddress + " to: " + thisObject.toAddress,
		              { getPolyline:true, getSteps:false });
		}
	}
	this.computeRequest = function (request) {
		thisObject.gdirections.load(request,
	              { getPolyline:true, getSteps:false });
	}
	
	
	this.addFromPlacemark = function(fromPlacemark) {
		thisObject.addFromAddress(getAddressString(extractAddress(fromPlacemark)));
	}
	
	this.addToPlacemark = function(toPlacemark) {
		thisObject.addToAddress(getAddressString(extractAddress(toPlacemark)));
	}
	
	this.addFromAddress = function(fromAddress) {
		thisObject.fromAddress = fromAddress;
		thisObject.computeDirection(thisObject.fromAddress, thisObject.toAddress);
	}
	
	this.addToAddress = function(toAddress) {
		thisObject.toAddress = toAddress;
		thisObject.computeDirection(thisObject.fromAddress, thisObject.toAddress);
	}
}

Direction.prototype.onDirectionLoad = function() {
	// send the route to the server
	var operation = this.directionObj.operation;
	var position = this.directionObj.elementToUpdate;
	var isStation = this.directionObj.isStation;
	var driveDuration = this.getDuration().seconds; //in seconds
	var element = new PartRouteUpdate();
	element.position = position;
	element.partRoute = convertToRouteString(this.getPolyline());
	element.operation = operation;
	element.isStation = isStation;
	element.fromAddress = extractAddress(this.getRoute(0).getStartGeocode());
	element.toAddress = extractAddress(this.getRoute(0).getEndGeocode());
	element.driveDuration = driveDuration;
	element.length = this.getPolyline().getLength();
	partRouteCollector.addElement(element);
}

Direction.prototype.onDirectionError = function () {
	addError(this.directionObj.errorContainer, this.getStatus().code, false);	
}

var partRouteCollector;

function createRouteCollector(size) {
	partRouteCollector = new PartRouteCollector(size, sendPartRoutesAndPartUpdate);
}

/*
* This function is called when a MapUpdateRequest is received.
* It starts the computation of the PartRoute
*/
function computeUpdateDirection(elementToUpdate, addresses) {
	direction = new Direction(OP_REPLACE_GEO);
	direction.elementToUpdate = elementToUpdate;
	direction.computeDirection(addresses.from, addresses.to);
}

/*
* This function is called for the calculation of the first route
*/
function computeDirection(fromInput, fromContainer, toInput, toContainer) {
	partRouteCollector = new PartRouteCollector(SIMPLE_DIRECTION_REQUEST_COLLECTOR_SIZE, sendPartRoutesAndUpdate);
	//save the current method call with current parameters
	callback = GEvent.callbackArgs(this,computeDirection,fromInput,fromContainer,toInput,toContainer);
	computeDirectionVerifyBothAddress(OP_ADD,fromInput, fromContainer, toInput, toContainer);
}

/*
 * This function is called, when a station is inserted
 */
function computeStationDirection(stationInputID, containerID, toAddress, position) {
	partRouteCollector = new PartRouteCollector(SIMPLE_DIRECTION_REQUEST_COLLECTOR_SIZE, sendPartRoutesAndUpdate);
	//save the current method call with current parameters
	callback = GEvent.callbackArgs(this,computeStationDirection,stationInputID,containerID,toAddress, position);
	computeDirectionVerifyFromAddress(OP_ADD, stationInputID, containerID, toAddress, position, true);
}

function computeEmbeddedDirection(fromInput, fromContainer, toAddress, toInput, toContainer, fromAddress) {
	partRouteCollector = new PartRouteCollector(DEFERRED_DIRECTION_REQUEST_COLLECTOR_SIZE, sendPartRoutesAndUpdate);
	
	//save the current method call with current parameters
	callback = GEvent.callbackArgs(this,computeEmbeddedDirection,fromInput,fromContainer,toAddress,toInput,toContainer,fromAddress);
	
	computeDirectionVerifyFromAddress(OP_ADD_KEEP_TO_ADDRESS, fromInput, fromContainer, toAddress, 0, false);
	computeDirectionVerifyToAddress(OP_ADD_KEEP_FROM_ADDRESS, toInput, toContainer, fromAddress, 1, false); //1: just greater than 0
}

function computeEditAddress(fromInput, fromContainer, toAddress, position) {
	partRouteCollector = new PartRouteCollector(SIMPLE_DIRECTION_REQUEST_COLLECTOR_SIZE, sendPartRoutesAndUpdate);
	//save the current method call with current parameters
	callback = GEvent.callbackArgs(this,computeEditAddress,fromInput,fromContainer,toAddress,position);
	computeDirectionVerifyFromAddress(OP_REPLACE_GEO_KEEP_TO_ADDRESS, fromInput, fromContainer, toAddress, position, false);
}

function computeEditAddressLast(toInput, toContainer, fromAddress, position) {
	partRouteCollector = new PartRouteCollector(SIMPLE_DIRECTION_REQUEST_COLLECTOR_SIZE, sendPartRoutesAndUpdate);
	//save the current method call with current parameters
	callback = GEvent.callbackArgs(this,computeEditAddressLast,toInput,toContainer,fromAddress,position);
	computeDirectionVerifyToAddress(OP_REPLACE_GEO_KEEP_FROM_ADDRESS, toInput, toContainer, fromAddress, position, false);
}

function computeDirectionVerifyToAddress(operation, toInput, toContainer, fromAddress, position, isStation) {
	var direction = createDirectionObject(operation, toContainer, position, isStation);
	verifyAddress(toInput, toContainer, direction.addToPlacemark);
	//wait until the toAddress is verified
	setTimeout(function() {direction.addFromAddress(fromAddress);},GEO_DELAY);
}

function computeDirectionVerifyFromAddress(operation, fromInput, fromContainer, toAddress, position, isStation) {
	var direction = createDirectionObject(operation, fromContainer, position, isStation);
	verifyAddress(fromInput, fromContainer, direction.addFromPlacemark);
	//wait until the fromAddress is verified
	setTimeout(function() {direction.addToAddress(toAddress);},GEO_DELAY);
}

function computeDirectionVerifyBothAddress(operation, fromInput, fromContainer, toInput, toContainer) {
	$(fromContainer).update();
	$(toContainer).update();
	var direction = createDirectionObject(operation, toContainer, 0, false);
	verifyAddress(fromInput, fromContainer, direction.addFromPlacemark);
	//wait with the second georequest to avoid the too many requests error
	setTimeout(function() {verifyAddress(toInput, toContainer, direction.addToPlacemark)},GEO_DELAY);
}

function createDirectionObject(operation, errorContainer, position, isStation) {
	$(errorContainer).update();
	direction = new Direction(operation, errorContainer);
	direction.elementToUpdate = position;
	direction.isStation = isStation;
	return direction;
}

function requestGoogleDragDir(errorContainer) {
	GDownloadUrl(DRAGDIR_REDIRECT_URL+'?cid='+conversationId, function(dragDirResponse) { 
		try {
			var dragDirObj = eval('(' + dragDirResponse + ')');
			if (dragDirObj.polylines) {
				var cabrioRoutePart = new Array();
				for (var i = 0; i < dragDirObj.polylines.length; i++) {
					cabrioRoutePart = cabrioRoutePart
							.concat(getArrayOfVertexes(GPolyline
									.fromEncoded(dragDirObj.polylines[i])));
				}
				var driveDuration = getDriveDuration(dragDirObj.tooltipHtml);
				sendDriveDuration(driveDuration.hours, driveDuration.minutes);
				var cabrioRoute = new GPolyline(cabrioRoutePart);
				sendCabrioRoute(convertToRouteString(cabrioRoute));
			} else {
				addError(errorContainer, MAPS_LINK_ERROR, true);
			}
		} catch (error) {
			addError(errorContainer, MAPS_LINK_ERROR, true);
		}
	}); 
}

function getDriveDuration(durationToExtract) {
	var driveDuration = new Object();
	/*
	 * Values the RegExp must match:
	 	(119\x26#160;km / 1 Stunde, 17 Minuten)
		(119\x26#160;km / 1 heure 17 minutes)
		(119\x26#160;km / 1 ora 17 min)
		(35,7\x26#160;km / 41 min)
		(23,0\x26#160;km / 29 Minuten)
		(119\x26#160;km / 1 hour 17 mins)
		(23.0\x26#160;km / 29 mins)
	 */
	var re = new RegExp("/ (\\d*)(\\s\\S*\\s*)(\\d*)");
	var m = re.exec(durationToExtract);
	if (m[3] != "") {
		// hour and minute
		driveDuration.hours = m[1];
		driveDuration.minutes = m[3];
	} else {
		// only minute
		driveDuration.hours = 0;
		driveDuration.minutes = m[1];
	}
	return driveDuration;
}

function verifyAddress(inputID, containerID, onUnique) {
	geocoder.getLocations($(inputID).value,  function(response) {
		if (response.Placemark) {
			var placemarks = response.Placemark;
			if (placemarks.length == 1) {
				//address is unique
				var address = extractAddress(placemarks[0]);
				if (address.accuracy >= 4) {
					$(inputID).value = getAddressString(extractAddress(placemarks[0]));
					onUnique(placemarks[0]);
				} else {
					//imprecise, at least city
					addError(containerID, LESS_ACCURACY, true);
				}
			} else {
				//address is not unique -> show possibilities to user
				addAddressOptions(placemarks, inputID, containerID);
			}
		} else {
			addError(containerID, response.Status.code, false);
		}
	});
}

function addAddressOptions(placemarks, inputID, containerID) {
	
	var buffer = new StringBuffer();
	buffer.append("<ul type=\"none\">");
	buffer.append("<li class=\"title\">");
	buffer.append(i8nMessages[SUGGESTION_TITLE]);
	buffer.append(":</li>");
	for (var i = 0; i < placemarks.length; i++) {
		var address = extractAddress(placemarks[i]);
		if (address["city"] != "") {
			var addressString = getAddressString(address);
			buffer.append('<li><a href="javascript:setAddressAndCallback(');
			buffer.append("'" + inputID + "'");
			buffer.append(",");
			buffer.append("'" + addressString + "'");
			buffer.append(');">');
			buffer.append(addressString);
			buffer.append("</a></li>")
		}
	}
	buffer.append("</ul>");
	
	Element.insert(containerID, {bottom: buffer.toString()});
}

/**
 * Adds an error message to the container
 * @param containerID id of the DOM-Element where the error will be shown
 * @param code errorCode
 * @param isKey true if code is the message key
 * @return
 */
function addError(containerID, code, isKey) {
	var modCode;
	var messageKey;
	if (!isKey) {
		if (code == G_GEO_UNKNOWN_ADDRESS) {
			modCode = code;
		} else if (code == G_GEO_MISSING_QUERY) {
			modCode = code;
		} else if (code == G_GEO_UNKNOWN_DIRECTIONS) {
			modCode = code;
		} else {
			modCode = "unknown";
		}
		messageKey = GEO_MESSAGE_KEY_PREFIX + modCode;
	} else {
		messageKey = code;
	}
	
	var buffer = new StringBuffer();
	buffer.append("<ul type=\"none\">");
	buffer.append("<li class=\"message\">");
	buffer.append(i8nMessages[messageKey]);
	buffer.append("</li>");
	buffer.append("</ul>");

	Element.insert(containerID, {bottom: buffer.toString()}); 
}

/*
 * Add the text to the input field and start a new direction request
 */
function setAddressAndCallback(input, address) {
	$(input).value = address;
	callback();
}

function getPxWidthForKm(km) {
	var center = map.getCenter();
	var nextPxLatLon = getNextLatLngX(center, 1);
	var dPx = nextPxLatLon.distanceFrom(center);
	return pxWidth = Math.ceil(km * 1000 / dPx) * 2;	
}

/**
 * 
 * @return a GLatLng object, which is pixel pixel to the right of fromLatLng
 */
function getNextLatLngX(fromLatLng, pixel) {
	var zoom = map.getZoom();
	var centerPx = map.getCurrentMapType().getProjection().fromLatLngToPixel(fromLatLng, zoom);
	return map.getCurrentMapType().getProjection().fromPixelToLatLng(new GPoint(centerPx.x+pixel,centerPx.y), zoom);
}

/**
 * 
 * @return a GPolyline which is quit short to show the search area as Point
 */
function getSmallPolylineFromStart(start) {
	var zoom = 10;
	var startPx = map.getCurrentMapType().getProjection().fromLatLngToPixel(start, zoom);
	var end = map.getCurrentMapType().getProjection().fromPixelToLatLng(new GPoint(startPx.x+5,startPx.y), zoom);
	return new GPolyline([start,end]);
}


/*
 * Converts the route coordinates into a String which can be used
 * to transfer the route to the server and store in the db
 */
function convertToRouteString(polyline) {
	var buffer = new StringBuffer();
	for ( var i = 0; i < polyline.getVertexCount(); i++) {
		if (i != 0)
			buffer.append(" , ");
		// currentVertex is of type GLatLng
		var currentVertex = polyline.getVertex(i)
		buffer.append(currentVertex.lat());
		buffer.append(" ");
		buffer.append(currentVertex.lng());
		// this is the altitude which is not received by google and
		// therefore set to 0.0
		buffer.append(" 0.0")
	}
	if (polyline.getVertexCount() == 1) {
		var oneCoord = buffer.toString();
		buffer.append(" , ");
		buffer.append(oneCoord);
	}
	return buffer.toString();
}

/*
 * Converts an object with attribute "address", "plz", "city", "countryCode"
 * to String
 */

function getAddressString(address) {
	var buffer = new StringBuffer();
	if (address["address"]) {
		buffer.append(address["address"]);
		buffer.append(" ");
	}
	if (address["plz"]) {
		buffer.append(address["plz"]);
		buffer.append(" ");
	}
	if (address["city"]) {
		buffer.append(address["city"]);
		buffer.append(" ");
	}
	if (address["areaName"]) {
		buffer.append(address["areaName"]);
		buffer.append(" ");
	}
	buffer.append(address["countryCode"]);
	return buffer.toString();
}


