/** package control */
BRdCJS_Package =
{	version: '1.1',
	lastupdate: 'June 2008',
	googlemaps_api_specification: 'v2.117 17th June 2008'
}

/** Needed javascript header files (no useful include control in javascript)

<!-- Google Maps API v2.115+ (fill in key value) -->
<script src="http://maps.google.com/maps?file=api&v=2&key=" type="text/javascript"></script>

<!-- Open source MarkerManger (used instead of GMarkerManger) -->
<script src="http://gmaps-utility-library.googlecode.com/svn/trunk/markermanager/release/src/markermanager.js" type="text/javascript"></script>

<!-- Open source LabeledMarker package (used to provide text to labels) -->
<script src="http://gmaps-utility-library.googlecode.com/svn/trunk/labeledmarker/release/src/labeledmarker.js" type="text/javascript"></script>

*/

/** Current Settings as an easy metric to be set by the Server side, can keep
	values such as whether the map display should be in metric or imperial units
	-- standard values: 	measure	=		"metric" || "imperial"

*/
var BRdCSettings = {	};
function SetBRdCSetting(key,value)
{	BRdCSettings[key]=value;
}
function CheckBRdCSetting(key,cmp)
{	if (typeof BRdCSettings[key]==typeof cmp)
		return BRdCSettings[key]==cmp;
	return false;
}

/** List and Array Manipulation Functions */
CollectionFunctions = 
{	/**	returns the index of the entry (or -1 if not in the list)
		[starts at startpt or first entry if not set]						*/
	search:		
		function(needle,haystack,startpt)
		{	if (typeof startpt=='undefined')
				startpt = 0;
			for (var i=startpt;i<haystack.length;i++)
				if (haystack[i]==needle)
					return i;
			return -1;
		},
	/**	like search but starts at the endpt (or the end of the list if not set)
		and searchs to the front, good for finding a recently add()ed entry	*/
	rsearch:
		function(needle,haystack,endpt)
		{	if (typeof endpt=='undefined')
				endpt = haystack.length;
			for (var i=endpt-1;i>=0;--i)
				if (haystack[i]==needle)
					return i;
			return -1;
		},
	/**	If not in existent already in the list add this element to the list
		@return {Boolean} Whether it was added to the list (false=in list)	*/
	add:
		function(elem,array)			
		{	if (CollectionFunctions.search(elem,array)!=-1)
				return false;	//already in list
			array.push(elem);
			return true;
		},
	/**	Remove element from a list (if there were multiple copies in the array
		only the first is removed)
		@return {Boolean} Whether the element was found (and removed) so it is
						  a simple loop (while !remove(..)) to remove all	*/
	remove:
		function(elem,array)
		{	return CollectFunctions.removeidx(array, ListFunctions.search(elem,array) );
		},
	/**	Add a single element to the array at a certain adjusting the other
		elements following in the array to make space for it.				*/
	insert:
		function(elem, array, idx)
		{	for (var i=array.length+1;i>idx;--i)
				array[i] = array[i-1];
			array[idx] = elem;
		},
	/** Remove element at index idx from array, adjusting the later indices
		@return {Boolean} false if not a valid index (true: item removed)	*/
	removeidx:
		function (array, idx)
		{	if (idx<0 || idx>=array.length)
				return false;
			if (idx==0)
				array.shift();
			else
			{	while (idx<array.length-1)
				{	array[idx] = array[idx+1];
					idx++;
				}
				array.pop();
			}
			return true;
		}
}

/** Conversion Functions */
ConversionFunctions =
{	mileInMeters: 1609.344,
	mileInYards: 1760,
	mileInFeet: 5280,
	yardInMeters: 0.9144,
	yardInFeet: 3,
	feetInMeters: 0.3048,
	kilometerInMeters: 1000,
	
	metersToMiles:
		function(meters)
		{	return (meters/1609.344);
		},
	metersToYards:
		function(meters)
		{	return (meters/0.9144);
		},
	metersToKilometers:
		function(meters)
		{	return (meters/1000.0);
		},
	distanceImperial:
		function(meters)
		{	if (meters>=BRdCConv.mileInMeters/5)
				return (meters/BRdCConv.mileInMeters).toFixed(2) + 'mi';
			else
				return (meters/BRdCConv.feetInMeters).toFixed(0) + 'ft';
		},
	distanceMetric:
		function(meters)
		{	if (meters>=1000)
				return (meters/1000).toFixed(2) + 'km';
			else if (meters<1)
				return (meters).toFixed(1) + 'm';
			else
				return (meters).toFixed(0) + 'm';
		},
	distanceString:
		function(meters)
		{	return (CheckBRdCSetting('measure','metric')) ?
				distanceMetric(meters)	:	distanceImperial(meters);
		}
}



/** BRdC_Marker 
	--	ptid	int			*ID number for retrieval from the database
	--	coord	GLatLng		*Coordinates of this Marker
	
*/
function BRdCMarker(ptcoord,ptID)
{	this.coord = ptcoord;
	this.ptid = (typeof ptID!='undefined') ? ptID : null;
	this.poitype = 0;
	this.inSteps = new Array();
}
BRdCMarker.prototype.MarkerSeries = function()
{	if (typeof this.gmarkerseries=='undefined')
	{	var poi = (typeof this.poitype!='number') ? 0 : this.poitype;
		var poiFSeries = getPOI_IconSeries(poi);
		if (!poiFSeries)
			poiFSeries = getPOI_IconSeries(0);
		this.gmarkerseries = new Array();
		var minZoom=0;
		var zoomSteps = [ 6,  9, 11, 13, 15, 17 ];
		var sizeSteps = [ 7, 13, 25, 33, 49, 65 ];
		var lastdef;
		var curmarker;
		for (var i in zoomSteps)
		{	var cmarkerdef = poiFSeries.closest(sizeSteps[i]);
			if (cmarkerdef)
			{	if ((lastdef) && (cmarkerdef==lastdef))
					this.gmarkerseries[this.gmarkerseries.length-1].BRdCZmMax = zoomSteps[i];
				else
				{	curmarker = new GMarker( this.coord, {clickable:true, icon:cmarkerdef} );
					curmarker.BRdCZmMin = minZoom;
					curmarker.BRdCZmMax = zoomSteps[i];
					this.gmarkerseries.push( curmarker );
				}
				minZoom = zoomSteps[i]+1;
			}
		}
	}
	return this.gmarkerseries;
}
/**	@param {Number} poitype		The POI enum for this marker
*/
BRdCMarker.prototype.setPOI = function( poitype )
{	this.poitype = poitype;
}
/** @param {String} markername	The name for this marker
*/
BRdCMarker.prototype.setName = function( markername )
{	this.markername = name;
}
/**	@param {String} htmlinfo	The html formatted information to display
*/
BRdCMarker.prototype.setInfo = function( htmlinfo )
{	this.htmlinfo = htmlinfo;
}
/** @param {BRdCRoute_Step} inStep	The Route Step including this point
	@return {Number}	The index of the route step (ordered by rtname, stpidx)
*/
BRdCMarker.prototype.stepEndpoint = function( inStep )
{	if (CollectionFunctions.search(inStep, this.inSteps)==-1)
	{	for (var i in this.inSteps)
		{	if (this.inSteps[i].route == inStep.route)
			{	if (this.inSteps[i].stpidx > inStep.stpidx)
				{	CollectionFunctions.insert(inStep, this.inSteps, i);
					return i;
				}
			}	else
				if (this.inSteps[i].route.rtname >= inStep.route.rtname)
				{	CollectionFunctions.insert(inStep, this.inSteps, i);
					return i;
				}
		}
	}
	this.inSteps.push(inStep);
	return this.inSteps.length-1;
}


/** BRdC_Route
	--	rtid		int				*ID number for retrieval from the database
	--	rtname		string			*Route Name
	--	rtperm		string			*Permission flag string
	--	rtownid		int				*Route Owner's ID number in database
	--	rtowner		string			*Route Owner's name
	--	rtdsc		string			*Route Description
	--	rtbox		GLatLngBounds	*Box that encompasses the route
	--	marks		array			*array of marked points that have info for the route
	--	startinfo	string			*Optional Instruction for the beginning of the route
	--	steps		array			*array of step data that keep the info for the steps

	--	distance	float			*length of route in meters

	--	inDisplays	array			*array of displays that show the route
	--	dirtyFlag	boolean			*Route has been changed
	@constructor
	@param {String} rtdata	An optional string generated by the server database to create
							the route from (if not supplied an empty route is created).
*/

function BRdCRoute(rtdata)
{	this.dirtyFlag	= false;
	this.inDisplays	=new Array();
	if (typeof rtdata=='undefined')
	{	this.steps	= new Array();
		this.marks	= new Array();
		this.rtbox	= new GLatLngBounds();
		this.rtname = "(Untitled Route)";
		this.rtdsc	= '';
		this.distance	= 0.0;
		this.permission = '';
	}	else
	{	//load it from the route Data Obj see BRdC_loadRoute.php for a description
		var rtparse = rtdata.split('\n');
		var markcnt = 0;
		for (var ln in rtparse)
		{	var line = rtparse[ln];
			var cmd = line.split(":");
			switch (cmd[0])
			{	case 'RTBGN':
					if (cmd.length==1)
						this.editsess = cmd[1];
					break;
				case 'RTI':
					this.rtid = parseInt(cmd[1]);
					this.permission = cmd[2];
					this.rtname = (cmd.length>4) ? cmd.slice(3).join(':') : cmd[3];
					break;
				case 'RTO':
					this.rtownid = parseInt(cmd[1]);
					this.rtowner = (cmd.length>3) ? cmd.slice(2).join(':') : cmd[2];
					break;
				case 'RTT':
					this.created = new Date(parseInt(cmd[1])*1000);
					this.lastsave= new Date(parseInt(cmd[2])*1000);
					break;
				case 'RTD':
					this.marks = new Array(parseInt(cmd[1]));
					this.rtdsc = (cmd.length>3) ? cmd.slice(2).join(':') : cmd[2];
					break;
				case 'RTB':
					this.rtbox = new GLatLngBounds(new GLatLng(parseFloat(cmd[3]),parseFloat(cmd[2])),new GLatLng(parseFloat(cmd[1]),parseFloat(cmd[4])) );
					break;
				case 'RTS':
					this.steps = new Array(cmd[1]);
					this.distance = parseFloat(cmd[2]);
					this.startinfo = (cmd.length>4) ? cmd.slice(3).join(':') : cmd[3];
					break;
				case 'MRK':
					cmd.shift();
					this.marks[markcnt] = new BRdCMarker(new GLatLng(parseFloat(cmd[1]),parseFloat(cmd[2])),parseInt(cmd[0]));
					if (cmd.length>3)
					{	this.marks[markcnt].setPOI( parseInt(cmd[3]) );
						this.marks[markcnt].setName(cmd[4]);
						this.marks[markcnt].setInfo( (cmd.length>6) ? cmd.slice(5).join(':') : cmd[5]);
					}
					markcnt++;
					break;
				case 'STP':
					cmd.shift();
					var mk_ptA, mk_ptB;
					var ptAid = parseInt(cmd[2]);
					var ptBid = parseInt(cmd[3]);
					for (mk_ptA = markcnt-1;mk_ptA>=0;--mk_ptA)
						if (this.marks[mk_ptA].ptid == ptAid)
							break;
					for (mk_ptB = markcnt-1;mk_ptB>=0;--mk_ptB)
						if (this.marks[mk_ptB].ptid == ptBid)
							break;
					this.steps[cmd[0]] = new BRdCRoute_Step(this, cmd[0], this.marks[mk_ptA], this.marks[mk_ptB], parseFloat(cmd[1]), (cmd.length>7) ? cmd.slice(6).join(':') : cmd[6]);
					this.steps[cmd[0]].setEncoded( cmd[4], cmd[5] );
					break;
				case 'RTEND':
					return this;
				default:
					//bad.
			}	//end switch
		}	//end for
	}
}
BRdCRoute.prototype.linkDisplay = function(display)
{	if (CollectionFunctions.add(display, this.inDisplays))
	{	for (var step in this.steps)
		{	this.steps[step].addToDisplay( display );
		}
		for (var mrk in this.marks)
		{	display.addMark( this.marks[mrk] );
		}
	}
}
BRdCRoute.prototype.unlinkDisplay = function(display)
{	var idx = CollectionFunctions.rsearch(display, this.inDisplays);
	if (idx!=-1)
	{	for (var step in this.steps)
			this.steps[step].removeFromDisplay(display);
		for (var mrk in this.marks)
			display.removeMark( this.marks[mrk] );
		CollectionFunctions.removeidx(this.inDisplays, idx);
	}
}
/**	BRdCRoute_Step
	--	route		BRdCRoute			*The route that this step is a part of
	--	stpidx		integer				*The index of this step in route.steps[]
	--	A			BRdCMarker			*Marker representing the start of this step
	--	B			BRdCMarker			*Marker representing the end of this step
	--	distance	float				*The distance of this step (in meters)
	--	instruct	string				*Optional Instruction for this step
	--	polyline	GPolyline			*Polyline for the step
	@constructor
	@param {BRdCRoute} inRoute	Route this is a step in
	@param {Number} stepidx		Index of this step in said route
	@param {BRdCMarker} mrkA	Marker for start of this step's path
	@param {BRdCMarker} mrkB	Marker for end of the step's path
	@param {Number} dist		Distance of this step's path
	@param {String} instr		Instruction string to display for this step
*/

function BRdCRoute_Step( inRoute, stepidx, mrkA, mrkB, dist, instr )
{	this.route = inRoute;
	this.stpidx = stepidx;
	this.A = mrkA;
	this.B = mrkB;
	this.distance = dist;
	this.instruct = instr;
	this.polyline = null;
	mrkA.stepEndpoint( this );
	mrkB.stepEndpoint( this );
}
BRdCRoute_Step.prototype.setEncoded = function( encline, enclvls )
{	this.polyline = new GPolyline.fromEncoded(	{	zoomFactor: 2,
													numLevels: 18,
													points: encline,
													levels: enclvls,
													color: "#3300AA",
													weight: 3,
													opacity: 0.8
												}	);
}
BRdCRoute_Step.prototype.addToDisplay = function( display )
{	display.map.addOverlay( this.polyline );
}
BRdCRoute_Step.prototype.removeFromDisplay = function( display )
{	display.map.removeOverlay( this.polyline );
}



/**	RouteDisplay
	--	map			GMap2			*The google maps object for this display
	--	markerbox	MarkerManager	*The Marker Manager for the GMap
*/
function RouteDisplay(map)
{	if (typeof map=='object')
	{	this.map = map;
		this.centerUSA();
		this.map.savePosition();
		this.map.enableScrollWheelZoom();
		new GKeyboardHandler(this.map);
		this.map.addControl( new GSmallMapControl() );
		this.map.removeMapType( G_SATELLITE_MAP );
		this.map.addControl( new GMapTypeControl() );
		this.map.addControl( new GScaleControl() );
		//I think we still prefer the open source MarkerManager to google's GMarkerManager 
		//if only because it has the stated capability to REMOVE markers once added.
		this.markerbox = new MarkerManager( this.map, {trackMarkers:true, borderPadding:40} );

	}
}
RouteDisplay.prototype.centerUSA = function()
{	var initialPosSW = new GLatLng(10.9027, -107.4);
	var initialPosNE = new GLatLng(67.1527, -85.6);
	var initialBounds = new GLatLngBounds(initialPosSW,initialPosNE);
	this.map.setCenter( initialBounds.getCenter(), this.map.getBoundsZoomLevel(initialBounds) );
}
RouteDisplay.prototype.centerRoute = function(route)
{	this.map.setCenter( route.rtbox.getCenter(), this.map.getBoundsZoomLevel(route.rtbox) );
}
RouteDisplay.prototype.displayRoute = function(route)
{	route.linkDisplay( this );
}
RouteDisplay.prototype.removeRoute = function(route)
{	route.unlinkDisplay( this );
}
RouteDisplay.prototype.addMark = function(mark)
{	var markSeries = mark.MarkerSeries();
	for (var i in markSeries)
		this.markerbox.addMarker( markSeries[i], markSeries[i].BRdCZmMin, markSeries[i].BRdCZmMax );
	this.markerbox.refresh();
}
RouteDisplay.prototype.removeMark = function(mark)
{	var markSeries = mark.MarkerSeries();
	for (var i in markSeries)
		this.markerbox.removeMarker( markSeries[i] );
	this.markerbox.refresh();
}
