/* 
RecPath distance mapping software
Copyright (C) 2005  Andy Allen

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

You can contact the author at andy@recpath.com
*/

var R2D = 180/Math.PI;		

function RPRoute() {
	this.Points = new Array();
	this._history = new Array();
	
	// path manipulation
	this.AddPointAt = RP_addPointAt;
	this.AddPointToEnd = RP_addPointToEnd;
	this.AddPointToMiddle = RP_addPointToMiddle;
	this.RemovePointFromEnd = RP_removePointFromEnd;
	this.RemovePointAt = RP_removePointAt;
	this.RemoveAllPoints = RP_removeAllPoints;
	
	// path computation
	this.ComputeLength = RP_computeLength;
	this.FindNearestLine = RP_findNearestLine;
	this.FindNearestPoint = RP_findNearestPoint;
	this.MilesToLine = RP_milesToLine;
	this.MilesBetween = RP_milesBetween;
	this.Bearing = RP_bearing;
	this.TurnIsNegligible = RP_turnIsNegligible;
	this.EstimatePackedRouteSize = RP_estPackedSize;
	
	// route saving/loading
	this.MakeSmallUrl = RP_makeSmallUrl;
	this.ReadSmallUrl = RP_readSmallUrl;
	this.MakePackedRoute = RP_makePackedRoute;
	this.ReadPackedRoute = RP_readPackedRoute;
	
	// history
	this.Undo = RP_undo;
	this._addToHistory = RP_addToHistory;
	this._removeFromHistory = RP_removeFromHistory;
	this._viewLastHistory = RP_viewLastHistory;
}

function _printHist(list) {
	var out = ""; 
	for (var i = 0; i < list.length; i++) {
		var item = list[i];
		out += item['type'] + " " + item['index'] + "\n";
	}
	return out;
}

function RP_addPointAt(point, ix, noHistory) {
	this.Points.splice(ix, 0, point);
	if (!(noHistory)) {
		this._addToHistory('add', ix);
	}
	return this.Points.length - 1;
	
}

function RP_addPointToEnd(point) {
	if((!(this.Points)) || this.Points.length == 0) {
		this.Points = new Array(point);
	}
	else {
		this.AddPointAt(point, this.Points.length);
	}
	return this.Points.length - 1;
}

function RP_addPointToMiddle(point) {
	var nearestLine = this.FindNearestLine(point);
	this.AddPointAt(point, (nearestLine + 1));
	return(nearestLine + 1);
}

function RP_removePointFromEnd() { 
	var ix = this.Points.length - 1;
	this._addToHistory('remove', ix, this.Points[ix]);
	return this.RemovePointAt(ix);
}

function RP_removePointAt(ix, noHistory) {
	var removed = this.Points.splice(ix, 1);
	if (!(noHistory)) this._addToHistory('remove', ix, removed[0]);	
	return this.Points.length - 1;
}


function RP_removeAllPoints() {
	for (var i = this.Points.length - 1; i >= 0; i--) {
		this._addToHistory('removeMult', i, this.Points[i]);
	}
	this.Points = new Array();
}

function RP_undo() {
	var h = this._removeFromHistory();
	if (h) {
		switch (h['type']) {
			case 'add':
				var addedPointIx = h['index'];
				this.RemovePointAt(addedPointIx, true);
				break;
			case 'remove':
				var removedPoint = h['point'];
				var removedPointIx = h['index'];
				this.AddPointAt(removedPoint, removedPointIx, true);
				break;
			case 'removeMult':
				while(h['type'] == 'removeMult') {
					var removedPoint = h['point'];
					var removedPointIx = h['index'];
					this.AddPointAt(removedPoint, removedPointIx, true);
					h = this._removeFromHistory();
				}
				this._addToHistory(h['type'], h['index'], h['point']); //oops, put it back
				break;
		}
	}
	return this.Points.length - 1;
}

function RP_addToHistory(evt, ix, pt) {
	var historyEvent = new Object();
	historyEvent['type'] = evt;
	historyEvent['index'] = ix;
	historyEvent['point'] = pt;
	this._history.push(historyEvent);
}

function RP_removeFromHistory() {
	var historyEvent = this._history.pop();
	return historyEvent;
}

function RP_viewLastHistory() {
	var historyEvent;
	historyEvent = this._history[this._history.length - 1];
	return historyEvent;
}

function RP_computeLength() {
	var dist = 0;
	if (this.Points.length >= 2) {
		for (var i = 1; i < this.Points.length; i++) {
			dist += parseFloat(this.MilesBetween(this.Points[i-1], this.Points[i]));
		}
	}
	return(dist);

}

function RP_findNearestLine(c) {
	if (this.Points.length < 2) return Infinity;
	
	var minD = Infinity;
	var n = -1;
	for (var ix = 0; ix < this.Points.length - 1; ix++) {
		var d = this.MilesToLine(c, ix);
		if (d < minD) {
			minD = d;
			n = ix;
		}
	}
	return n;
}

function RP_findNearestPoint(c) {
	if(this.Points.length < 1) return 0;
	
	var minD = Infinity;
	var nearestIx = -1;
	for (var ix = this.Points.length - 1; ix >= 0; ix--) {
		var distanceToPoint = this.MilesBetween(this.Points[ix], c);
		if(distanceToPoint < minD) {
			minD = distanceToPoint;
			nearestIx = ix;
		}
	}
	return nearestIx;
}

function RP_milesToLine(c, lineIx) {
	var d;
	if (this.Points.length < 2) return Infinity;
	
	var A = this.Points[lineIx];
	var B = this.Points[lineIx + 1];
	var P, r;
	var aX = A.x;
	var aY = A.y;
	var bX = B.x;
	var bY = B.y;
	var cX = c.x;
	var cY = c.y;
	var L = Math.sqrt(Math.pow((aX - bX), 2) + Math.pow((aY - bY), 2));
	r =((aY - cY) * (aY - bY) - (aX - cX) * (bX - aX)) / Math.pow(L,2);
	P = new GPoint(parseFloat(aX) + parseFloat(r * (bX - aX)), parseFloat(aY) + parseFloat(r * (bY - aY)));
	if (r > 1 || r < 0) {
		if (this.MilesBetween(c, A) <= this.MilesBetween(c, B))
			P = A;
		else
			P = B;
	}
	d = this.MilesBetween(c, P);
	return d;
}

function RP_milesBetween(a, b) { 
	try { 
		var radiusE = 3963;
		return ( radiusE * Math.acos(Math.sin(a.y/R2D) * Math.sin(b.y/R2D) + Math.cos(a.y/R2D) * Math.cos(b.y/R2D) *  Math.cos(b.x/R2D - a.x/R2D)) );
	}
	catch(e) { return 0; }
}

function RP_makeSmallUrl(center, zoom, maptype, color, weight, opacity, prec, neg) {
	var url = window.location.protocol + "//" + window.location.host + window.location.pathname;
	url += "?v=2";
	url += "&r=" + this.MakePackedRoute(center, zoom, maptype, color, weight, opacity, prec, neg);
	return url;
}


function RP_readSmallUrl(searchStr) {
	// searchStr = window.location.search.substr(1)
	var cNz = new Array();
	if (searchStr.indexOf("&r=") > -1) {
		var packedRoute; 
		var routeBegin = searchStr.indexOf("&r=") + 3;
		var routeEnd = searchStr.indexOf("&", searchStr.indexOf("&r=") + 4);
		if (routeEnd > -1) 
			packedRoute = searchStr.substring(routeBegin, routeEnd);
		else
			packedRoute = searchStr.substr(routeBegin);
		
		cNz = this.ReadPackedRoute(packedRoute);
	}
	return cNz
}

function fixDecimal(number, scale) {
	var fixedNum = truncDecimal(number, scale);
	if (fixedNum.lastIndexOf('.') == -1) fixedNum += "."; // append a decimal if the number lacks one
	while (fixedNum.length - fixedNum.lastIndexOf('.') < (scale + 1)) fixedNum += "0"; // append 0's if the number needs them
	return fixedNum;
}

function truncDecimal(number, scale) {
	var factor = Math.pow(10,scale);
	var truncNum = Math.round(factor * number) / factor + "";
	return truncNum;
}

function RP_bearing(a, b, c) {
	var la = this.MilesBetween(a, b);
	var lb = this.MilesBetween(b, c);
	var lc = this.MilesBetween(c, a);
	var C = Math.abs(Math.acos((Math.pow(la, 2) + Math.pow(lb, 2) - Math.pow(lc, 2)) / (2 * la * lb)) * R2D - 180);
	
		
	return C;		
}

function RP_turnIsNegligible(ix, negligible) {
	var isNegligible = false;
	if (ix > 1 && ix < (this.Points.length - 1)) {
		var a = this.Points[ix - 1];
		var b = this.Points[ix];
		var c = this.Points[ix + 1];
		if (this.Bearing(a, b, c) <= negligible) {
			isNegligible = true;
		}
	}
	return isNegligible;
}

