﻿/*
	railroutes.js
	(c) 2008 Logogriph Ltd
	http://www.logogriph.com
*/



/*
	RRDirections class constructor, analogous to GDirections
	
	PUBLIC FUNCTIONS
	
	map: reference to the GMap to display route line
	panel: element id or DOM element of html element to display results which will replace innerHTML of element
*/
function RRDirections(map, panel) {
	this.map = arguments[0] || null;
	this.panel = arguments[1] || null;
	this.overlays = [];
	this.clear();
}

/*
	load a new set of directions based on a query string
	
	query: is a string like "london paddington to bristol temple meads" 
		the three letter station codes can be used instead of station names
		using less specific names like "London to Bristol" will work because the station names contain the place name, but obviously which station is chosen if multiples match may vary
		if you have lat/lng coordinates it may be better to use loadFromWaypoints(waypoints, queryOpts)
	queryOpts: {locale:"en_GB", getPolyline:true, getSteps:true, preserveViewport:true}
	//add event listenders onload, onaddoverlay, onerror as queryOpts values and NOT via GEvent.addListener()
*/
RRDirections.prototype.load = function(query, queryOpts) {
	this.send_request('&q='+query, queryOpts);	
}

/*
	waypoints is an array of strings or GLatLng elements
*/
RRDirections.prototype.loadFromWaypoints = function(waypoints, queryOpts) {
	if (!(waypoints instanceof Array)) {
		this.clear(400, 'loadFromWaypoints: waypoints is not an array');
	} else {
		var query = '';
		for(var i=0; i<waypoints.length; i++) {
			if ((typeof waypoints[i] == 'object') && (typeof waypoints[i].lat == 'function')) {	//(waypoints[i] instanceof GLatLng) {
				query += '&w'+i+'='+escape(waypoints[i].lat()+','+waypoints[i].lng());
			} else {
				query += '&w'+i+'='+escape(waypoints[i]);
			}
		}
		this.send_request(query, queryOpts);
	}
}

RRDirections.prototype.clear = function() {
	this.status = arguments[0] || 0;
	this.error = arguments[1] || '';
	this.info = false;	
	this.options = false;	
	if (this.map && (this.overlays.length)) {
		for(var i=0; i<this.overlays.length; i++) {
			map.removeOverlay(this.overlays[i]);
		}
	}
	this.overlays = [];
	if (this.panel) this.panel.innerHTML = '';
}

/*
	returns a GGeoStatusCode value
*/
RRDirections.prototype.getStatus = function() {
	return {code:this.status, request:this.error};
}

/*
	returns a GLatLngBounds object
*/

RRDirections.prototype.getBounds = function() {
	if (!this.info || !this.info.Bounds) return false;
	return new GLatLngBounds( new GLatLng(this.info.Bounds.s, this.info.Bounds.w), new GLatLng(this.info.Bounds.n, this.info.Bounds.e));
}

RRDirections.prototype.getNumRoutes = function() {
	if (!this.info) return false;
	return this.info.NumRoutes;
}

RRDirections.prototype.getRoute = function(i) {
	if (!this.info || !this.info.Routes || !this.info.Routes.length || (i >= this.info.Routes.length)) return false;
	return this.info.Routes[i];
}

RRDirections.prototype.getNumGeocodes = function() {
	if (!this.info) return false;
	return this.info.NumGeocodes;
}

RRDirections.prototype.getGeocode = function(i) {
	if (!this.info || !this.info.Markers || !this.info.Markers.length || (i >= this.info.Markers.length)) return false;
	return new GLatLng(this.info.Markers[i].lat, this.info.Markers[i].lng);
}

RRDirections.prototype.getSummaryHtml = function() {
	if (!this.info || !this.info.SummaryHtml) return false;
	return this.info.SummaryHtml;
}

RRDirections.prototype.getDistance = function() {
	if (!this.info || (typeof this.info.Distance != 'object')) return false;
	return this.info.Distance;	
}

RRDirections.prototype.getDuration = function() {
	if (!this.info) return false;
	return this.info.Duration;
}

RRDirections.prototype.getPolyline = function() {
	if (!this.info || (typeof this.info.Polyline == 'undefined')) return false;
	return this.info.Polyline;
}

RRDirections.prototype.getMarker = function(i) {
	if (!this.overlays || !this.overlays.length || (i >= this.info.NumGeocodes)) return false;
	return this.overlays[i];
}


/*
	PRIVATE FUNCTIONS for RRDirections
*/

RRDirections.prototype.send_request = function (query, queryOpts) {
	var options = queryOpts || {};
	
	this.preserveViewport = (('preserveViewport' in options) && options.preserveViewport);

	this.onload = false;
	if (('onload' in options) && (typeof options.onload == 'function')) this.onload = options.onload.RRDbind(this);
	this.onaddoverlay = false;
	if (('onaddoverlay' in options) && (typeof options.onaddoverlay == 'function')) this.onaddoverlay = options.onaddoverlay.RRDbind(this);
	this.onerror = false;
	if (('onerror' in options) && (typeof options.onerror == 'function')) this.onerror = options.onerror.RRDbind(this);

	if ('locale' in options) query += '&loc='+escape(options.locale);
	
	//polyline done by default but if no map and no option requesting it then turn it off
	if (!this.map || (('getPolyline' in options) && !options.getPolyline)) query += '&gp=0';
	
	//steps done by default but if no panel and no option requesting it then turn it off
	if (!this.panel || (('getSteps' in options) && !options.getSteps)) query += '&gs=0';
	
	if ('color' in options) query += '&c='+escape(options.color);
	if ('stroke' in options) query += '&k='+escape(options.stroke);
	if ('opacity' in options) query += '&t='+escape(options.opacity);
	if ('units' in options) query += '&u='+escape(options.units);
	if ('opto' in options) query += '&o='+escape(options.opto);	//dist|changes|stops|time
	if ('modes' in options) query += '&m='+escape(options.modes);	//tube|rail|tram|all
	
	var url = '';
	var scripts = $A(document.getElementsByTagName('script'));
	for(var i=0;i<scripts.length;i++) if (scripts[i].src && scripts[i].src.match(/railroute\.js(\?.*)?$/)) {
		url = scripts[i].src.replace(/railroute\.js(\?.*)?$/,'railservice.php');
		break;
	}

	JSONRequest.make(url+'?'+query.slice(1), this);

}


/*
	Note that the load() method initiates a new query, which in turn
	triggers a "load" event once the query has finished loading. The "load"
	event is triggered before any overlay elements are added to the
	map/panel. "addoverlay": This event is triggered after the polyline
	and/or textual directions components are added to the map and/or DIV
	elements. Note that the "addoverlay" event is not triggered if neither
	of these elements are attached to a GDirections object. "error": This
	event is triggered if a directions request results in an error. Callers
	can use GDirections.getStatus() to get more information about the error.
	When an "error" event occurs, no "load" or "addoverlay" events will be
	triggered. (Since 2.81)
*/


function RRDirections_cb(sid, rdata) {
	
	var inst = JSONRequest.get_baggage(sid);

	if ((typeof inst != 'undefined') && (inst != null)) {
		inst.clear.call(inst);
		
		if ((typeof rdata != 'object') || !('error' in rdata)) {
			inst.status = 500;
			inst.error = 'no data returned from web server';
			
		} else {
			inst.info = rdata;
			inst.status = rdata.error || 0;
			inst.error = rdata.error_str || '';
			
			if (inst.status != 200) {
				if (typeof inst.onerror == 'function') inst.onerror(inst.info.status, inst.info.error);
				
			} else {
				if (typeof inst.onload == 'function') inst.onload();
				
				if (inst.map) {
				
					if ((typeof inst.info.Markers != 'undefined') && inst.info.Markers) {
						for(var i=0; i<inst.info.Markers.length; i++) {
							var marker = inst.info.Markers[i];
							if (typeof marker == 'object') {
								mobject = add_waypoint(inst.map, marker.lat, marker.lng, marker.url);
								mobject.info = marker;
								inst.overlays[i] = mobject;
							}
						}
					}
					
					if ((typeof inst.info.Polyline != 'undefined') && inst.info.Polyline) {
						if (!this.preserveViewport && (typeof inst.info.Bounds != 'undefined')) {
							var pt1 = new GLatLng(inst.info.Bounds.s, inst.info.Bounds.w);
							var pt2 = new GLatLng(inst.info.Bounds.n, inst.info.Bounds.e);
							var rout_bounds = new GLatLngBounds(pt1, pt2);
							var new_zoom = inst.map.getBoundsZoomLevel(rout_bounds);
							inst.map.setCenter(new GLatLng((inst.info.Bounds.s+inst.info.Bounds.n)/2, (inst.info.Bounds.w+inst.info.Bounds.e)/2), new_zoom);
						}
						
						//Polyline could be a single line or an array of lines
						if (inst.info.Polyline instanceof Array) {
							var plines = inst.info.Polyline
						} else {
							var plines = new Array();
							plines.push({ident:'singlepath', pline:inst.info.Polyline});

						}
						
						for(var i = 0; i < plines.length; i++) {
							var encodedPolyline = new GPolyline.fromEncoded(plines[i].pline, {clickable:false});
							inst.map.addOverlay(encodedPolyline);
							inst.overlays.push(encodedPolyline);
						}
						
	
						if (typeof inst.onaddoverlay == 'function') inst.onaddoverlay();
					}
						
				}

				if (inst.panel && (typeof inst.info.html != 'undefined') && inst.info.html) {
					inst.panel.innerHTML = inst.info.html;
				}
			}
		}
	}
	JSONRequest.cleanup(sid);
}

/*
	UTILITY FUNCTIONS
*/

function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

Function.prototype.RRDbind = function() {
	if (arguments.length < 2 && arguments[0] === undefined) return this;
	var __method = this, args = $A(arguments), object = args.shift();
	return function() {
		return __method.apply(object, args.concat($A(arguments)));
	}
}

var JSONRequest = {

	scriptCounter: 1,
	baggage: {},

	make: function (url, baggage) {
		var script_id = 'JSONreq' + this.scriptCounter++;
		url += ((url.indexOf('?') >= 0) ? '&' : '?')+'sid='+script_id; //add the script id to the parameters so the call back can clean up the script object

		var scriptObj = document.createElement("script");
		
		// Add script object attributes
		scriptObj.setAttribute("type", "text/javascript");
		scriptObj.setAttribute("src", url);
		scriptObj.setAttribute("id", script_id);
		var headLoc = document.getElementsByTagName("head").item(0);

		if (typeof baggage != 'undefined') this.baggage[script_id] = baggage;

		headLoc.appendChild(scriptObj);
		
		return script_id;
	},
	
	get_baggage: function (script_id) {
		if (script_id in this.baggage) return this.baggage[script_id];
		return null;
	},
	
	cleanup: function (script_id) {
		if (script_id in this.baggage) delete this.baggage[script_id];
		var headLoc = document.getElementsByTagName("head").item(0);
		var scriptObj = document.getElementById(script_id);
		if (scriptObj && headLoc) headLoc.removeChild(scriptObj);
	}
}

function add_waypoint(map, lat, lng, path) {
	var icon = new GIcon();
	icon.image = path;
	var parts = path.split('/');
	parts.pop();
	path = parts.join('/');
	icon.shadow = path+'/shadow_icon_green.png';
	icon.iconSize = new GSize(24.0, 38.0);
	icon.shadowSize = new GSize(44.0, 38.0);
	icon.iconAnchor = new GPoint(12.0, 38.0);
	icon.infoWindowAnchor = new GPoint(12.0, 19.0);
	
	var point = new GLatLng(lat, lng);
	var marker = new GMarker(point, icon);
	map.addOverlay(marker);
	return marker;
}
