function dbg() {
	if (typeof(console) != 'undefined') {
		console.log.apply(console, arguments);
	} else {
		var args = $A(arguments);
		args.each(function(arg) {
			if (typeof(arg) == 'object') {
				var props = [];
				for (var prop in arg) {
					props.push(prop);
				}
				alert(props.join(', '));
			} else {
				alert(arg);
			}
		});
	}
}

var EzMapHelper = {
	stampShapes: {},
	arrRegisteredMapZoomCallbackMethods:{},

	init: function(EzMapData, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames) {
		if (typeof(ezMapsController) == 'undefined') {
			throw 'Unable to start ezMaps';
		}
		
		if(EzMapData['strMapMode']=="route"){
			var defObj = {
				srid: EzMapData['srid'],
				centerPoint: centerPoint,
				zoomLevel: zoomLevel,
				mapCacheUrl: EzMapData['mapCacheUrl'],
				sessionId: EzMapData['sessionId'],
				mapLayerServerUrl: EzMapData['mapLayerServerUrl'],
				mapStacks: EzMapData['mapStacks'], // Map stacks now being sent into ezmaps rather than defined internally.
				mapDetails: EzMapData['mapDetails'],
				layout: null,
				module: 'ytn',
				mapChangeCallback: this.mapChangeCallback.bind(this),
				expiredMapCallback: this.expiredMapCallback.bind(this),
				mapMoveCallback: this.mapMoveCallback.bind(this)
			};
		}
		else{
			var defObj = {
				srid: EzMapData['srid'],
				centerPoint: centerPoint,
				zoomLevel: zoomLevel,
				mapCacheUrl: EzMapData['mapCacheUrl'],
				sessionId: EzMapData['sessionId'],
				mapLayerServerUrl: EzMapData['mapLayerServerUrl'],
				mapStacks: EzMapData['mapStacks'], // Map stacks now being sent into ezmaps rather than defined internally.
				mapDetails: EzMapData['mapDetails'],
				layout: 'layer',
				module: 'ytn',
				mapChangeCallback: this.mapChangeCallback.bind(this),
				expiredMapCallback: this.expiredMapCallback.bind(this),
				mapMoveCallback: this.mapMoveCallback.bind(this)
			};			
			
		}

		// The main ezmaps controller object. Instantiate a new ezmaps instance.

		this.ezMapsController = new ezMapsController('ezMaps', defObj);		

		// This is the list of active map layers. Add in any default map layers now.
		var mapChanges = [];
		for(var i = 0; i <EzMapData['mapLayers'].length; i++)
		{
			// Note we create each map as a seperate layer.
			var layer = this.ezMapsController.makeNewLayer(EzMapData['mapLayers'][i]);
			// The layers get stored in ezmaps in reverse order, so we alter the layerNum to match
			mapChanges.push({layerNum: EzMapData['mapLayers'].length-i, mapId: EzMapData['mapLayers'][i]});
		}
		this.mapChangeCallback(mapChanges);

		this.geometriesLayer = this.ezMapsController.makeNewLayerServerLayer();

		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// Now add all of the interaction widgets.

		this.widgets = $H();
		this.widgets["mapClick"] = this.ezMapsController.addWidget('mapClick');
		this.widgets["mouse"] = this.ezMapsController.addWidget('mouse');
		this.widgets["zoomDisplay"] = this.ezMapsController.addWidget('zoomDisplay');
		this.widgets["compass"] = this.ezMapsController.addWidget('compass');
		this.widgets["zoomSlider"] = this.ezMapsController.addWidget('zoomSlider');
		this.widgets["progressBarWidget"] = this.ezMapsController.addWidget('progressBarWidget');
		this.widgets["scribble"] = this.ezMapsController.addWidget('scribble');	

		// The A-B map crossfader		
		this.widgets["transparencySlider"] = this.ezMapsController.addWidget('transparencySlider');
		
		this.arrRegisteredMapZoomCallbackMethods = $A();
	},

	getZoomAmount: function() {
		var ezMapsController = this.ezMapsController;
		var zoomAmount = ezMapsController.mapStatus.getZoomAmount();
		return zoomAmount;
	},

	getZoomLevel: function() {
		var ezMapsController = this.ezMapsController;
		var zoomLevel = ezMapsController.mapStatus.getZoomLevel();
		return zoomLevel;
	},	

	getGroundScale: function()
	{
		var ezMapsController = this.ezMapsController;
		var pixelPerMeter = ezMapsController.mapStatus.getGroundScale();
		return pixelPerMeter;
	},

	mapChangeCallback: function(mapChanges)
	{
		/*
		this.notify('recordMapChange', {changes: mapChanges.toJSON()});
		
		//dbg('Map change callback!');
		// If map changed, also refresh any geometry layers...
		this.updateGeometriesLayer();
		*/
	},
	
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// When ezmaps sees an expired map (when user clicks on menu), it will call this rather than do anything itself.
	expiredMapCallback: function()
	{		
		Shell.launchNewLightboxWindow(this.expiredMapCallbackLightboxContent.bind(this), 500, 150);
	},	
	
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// When ezmaps notices the map has moved and the mouse button released, it will call this after doing its own stuff.
	mapMoveCallback: function()
	{
		// Loop over any registered methods and call them...
		this.arrRegisteredMapZoomCallbackMethods.each(function(objCallbackMethod)
		{
			// Execute the callback.
			objCallbackMethod();
		}.bind(this));	
		
		// Loop over any registered methods and call them...
		/*
		this.arrRegisteredMapMoveCallbackMethods.each(function(objCallbackMethod, intKey)
		{
			// Execute the callback.
			objCallbackMethod();
		}.bind(this));
		*/
	},

	registerMapClickCallback: function(callBackFunction) {
		if(this.widgets["mapClick"]) {
			this.widgets["mapClick"].registerCallback(callBackFunction);
		}
	},

	registerMapZoomCallback: function(callBackFunction) {
		this.arrRegisteredMapZoomCallbackMethods.push(callBackFunction);
	},	

	////////////////
	//
	// Thematic layers
	//

	// Returns a new server-drawn layer which displays the given thematic_rule nodes
	addThematicNodes: function(thematicNodeIds) {
		this.widgets['transparencyLayersSlider'] = this.ezMapsController.addWidget('transparencyLayersSlider');
		this.thematicLayer = this.ezMapsController.makeNewLayerServerLayer();

		for(var i = 0; i < thematicNodeIds.length; i++) {
			this.thematicLayer.addNode(thematicNodeIds[i], {mode: 'thematic'});
		}
		return this.thematicLayer;
	},

	refreshThematicLayer: function() {
		this.thematicLayer.updateLayerCache();
	},

	////////////////
	//
	// Setting extents
	//

	jumpToPoint: function(point, zoom) {
		this.ezMapsController.jumpToPoint(point, zoom);
	},

	jumpToPoints: function(points) {
		this.ezMapsController.jumpToPoints(points);
	},

	jumpToLayer: function(layerId) {
		this.thematicLayer.jumpToNodeExt(layerId);
	},

	////////////////
	//
	// Stamps
	//

	createSchoolStamp: function(point) {
		return this.createStampFromImage(point, 'school', '/images/icons/schoolSurveys.gif', 24, 24);
	},

	createHomeStamp: function(point) {
		return this.createStampFromImage(point, 'home', '/images/icons/home.gif', 24, 24);
	},

	createPoiStamp: function(point, iconType) {
		return this.createStampFromImage(point, 'poi:' + iconType, '/images/icons/mapping/poi/' + iconType + '.png', 24, 24);
	},

	createStampFromImage: function(point, shapeId, url, width, height) {
		this.__assertDomStamp(shapeId, url, width, height);
		return this.__addStamp(point, shapeId);
	},

	createCircleStamp: function(point, radius, strColour)
	{
		var shapeId = 'circle' + radius + 'm' + strColour;
		this.__assertShapeStamp(shapeId, 'circle', new Point(radius, radius, 'meters'), strColour);
		return this.__addStamp(point, shapeId);
	},

	removeStamp: function(stamp) {
		var stampWidget = this.__getStampWidget();
		stampWidget.removePoint(stamp);
	},

	makeStampDraggable: function(stamp, callback) {
		stamp.makeDraggable(this.__getDropWidget(), callback);
	},

	registerDropCallback: function(callback) {
		var dropWidget = this.__getDropWidget();
		dropWidget.registerCallback(callback);
	},

	////////////////
	//
	// Lines and shapes
	//

	createLine: function(points, bEdit) {
		//var scribbleWidget = this.__getScribbleWidget();
		var scribbleWidget = this.widgets["scribble"];
		if (bEdit) {
			var shapeId = scribbleWidget.editPoints('polyline', points);
		} else {
			var shapeId = scribbleWidget.displayPoints('polyline', points);
		}
		return shapeId;
	},

	deleteShape: function(shapeId) {
		var scribbleWidget = this.__getScribbleWidget();
		scribbleWidget.removeShape(shapeId);
	},

	getShapePoints: function(shapeId) {
		var scribbleWidget = this.__getScribbleWidget();
		var points = scribbleWidget.getShapePoints(shapeId);
		return points;
	},

	setShapeEditMode: function(shapeId, bEdit) {
		var scribbleWidget = this.__getScribbleWidget();
		if (bEdit) {
			scribbleWidget.setEditMode(shapeId);
		} else {
			scribbleWidget.setDisplayMode(shapeId);
		}
	},

	setShapeStrokeStyle: function(shapeId, strokeStyle) {
		var scribbleWidget = this.__getScribbleWidget();
		scribbleWidget.setStroke(shapeId, strokeStyle);
	},

	moveShapeToFront: function(shapeId) {
		var scribbleWidget = this.__getScribbleWidget();
		scribbleWidget.moveShapeToFront(shapeId);
	},

	registerScribbleCallback: function(callback) {
		var scribbleWidget = this.__getScribbleWidget();
		scribbleWidget.registerCallback(callback);
	},

	////////////////
	//
	// Popups
	//

	showPopup: function(popupDef) {
		var popupWidget = this.__getPopupWidget();
		popupWidget.show(popupDef);
	},

	hidePopup: function() {
		var popupWidget = this.__getPopupWidget();
		popupWidget.hide();
	},

	movePopup: function(point) {
		var popupWidget = this.__getPopupWidget();
		popupWidget.setPosition(point);
		popupWidget.moveMapForPopup();
	},

	////////////////
	//
	// Private
	//

	__getWidget: function(name) {

		if (!this.widgets[name]) {
			this.widgets[name] = this.ezMapsController.addWidget(name);
		}
		return this.widgets[name];

	},

	__getPopupWidget: function() {
		return this.__getWidget('popUp');
	},

	__getDropWidget: function() {
		return this.__getWidget('droppableGeocode');
	},

	__getScribbleWidget: function() {
		return this.__getWidget('scribble');
	},

	__getStampWidget: function() {
		return this.__getWidget('stampDisplay');
	},

	__assertDomStamp: function(id, src, width, height) {
		// Check for an existing, matching stamp shape
		if (this.stampShapes[id])
			return;

		// Make a new stamp shape
		var img = new Element('img');
		img.src = src;
		
		var stampShape = new Stamp.Dom(img, {size:new Point(width, height, 'pixel')});
		var stampWidget = this.__getStampWidget();
		stampWidget.addStamp(stampShape, id);
		this.stampShapes[id] = stampShape;		
	},

	__assertShapeStamp: function(id, type, size, strColour)
	{
		// Check for an existing, matching stamp shape
		if (this.stampShapes[id])
			return;

		// Define stamp colours to choose from:
		var objStampColours = 
		{
			yellow:	{r:240,g:245,b:44},
			red:	{r:255,g:0,b:0},
			green:	{r:0,g:128,b:0},
			blue:	{r:27,g:127,b:147}
		}

		var strSelectedColour = (typeof(strColour) != "undefined") ? strColour : 'blue' ;

		// Make a new stamp shape
		var options = {
			size: size,
			fill: {r:0, g:0, b:0, a:1, w:0},			
			stroke: {r:objStampColours[strSelectedColour].r, g:objStampColours[strSelectedColour].g, b:objStampColours[strSelectedColour].b, a:195, w: 3}
		};

		var stampShape = new Stamp.Shape(type, options);
		var stampWidget = this.__getStampWidget();
		stampWidget.addStamp(stampShape, id);
		this.stampShapes[id] = stampShape;
	},

	__addStamp: function(point, shapeId) {
		var stampWidget = this.__getStampWidget();
		var stamp = stampWidget.addPoint(point, shapeId);
		return stamp;
	},

	expandMap: function()
	{
		// Detect the available browser width and height.
		this.calculateBrowserViewportDimensions();

		// Show and hide the various elements required.
		$('headerContainer').setStyle({display:'none'});
		$('footerContainer').setStyle({display:'none'});
		$('expandMapButtons').setStyle({display:'none'});
		$('collapseMapButtons').setStyle({display:''});
		$('leftColumnMapStuff').setStyle({display: 'none'});

		// Alter the size of the masterLayoutTable elements.
		var intNewWidth = this.intBrowserViewportWidth - 50;
		var intNewMapHeight = this.intBrowserViewportHeight - 150; // leave space for key underneath and bar at top.

		$('masterLayoutTable').setStyle({width: intNewWidth + 'px'});
		$('masterLayoutTableColumn').setStyle({width: intNewWidth + 'px'});

		// Store the previous height of the map so that it can be returned to the correct height on collapse.
		this.intEzmapsHeight = parseInt($('ezMaps').getStyle('height'));

		// Alter the size/position of the map.
		$('ezMaps').setStyle(
		{
			width: '100%',
			height: intNewMapHeight + 'px'
		});
	},

	collapseMap: function()
	{
		// Show and hide the various elements required.
		$('headerContainer').setStyle({display:''});
		$('footerContainer').setStyle({display:''});
		$('expandMapButtons').setStyle({display:''});
		$('collapseMapButtons').setStyle({display:'none'});
		$('leftColumnMapStuff').setStyle({display: ''});

		// Alter the size of the masterLayoutTable elements.
		$('masterLayoutTable').setStyle({width: 770 + 'px'});
		$('masterLayoutTableColumn').setStyle({width: 770 + 'px'});

		// Alter the size/position of the map.
		$('ezMaps').setStyle(
		{
			height: this.intEzmapsHeight + 'px'
		});
	},

/*
	expandMap: function()
	{
		// Detect the available browser width and height.
		this.calculateBrowserViewportDimensions();
		
		// Show and hide the various elements required.
		$('headerContainer').setStyle({display:'none'});	
		$('footerContainer').setStyle({display:'none'});
		$('expandMapButtons').setStyle({display:'none'});
		$('collapseMapButtons').setStyle({display:''});	
				
		// Alter the size of the masterLayoutTable elements.
		var intNewWidth = this.intBrowserViewportWidth - 50;
		var intNewMapHeight = this.intBrowserViewportHeight - 300; // leave space for key underneath and bar at top.
		
		$('masterLayoutTable').setStyle({width:intNewWidth+'px'});
		$('masterLayoutTableColumn').setStyle({width:intNewWidth+'px'});
		
		// Alter the size/position of the map.		
		$('rightColumnMapStuff').setStyle({position:'absolute',top:'100px',left:'10px',width:intNewWidth+'px',height:intNewMapHeight+'px'});
		$('ezMaps').setStyle({width:'100%',height:intNewMapHeight+'px'});	
		
		// Attempt to move this below the map when in full screen mode.
		var intKeyTopOffset = this.intBrowserViewportHeight - 150;
		$('leftColumnMapStuff').setStyle({position:'absolute',top:intKeyTopOffset+'px',left:'10px',width:intNewWidth+'px',height:'100px'});
		
		// Arrange key the elements so that they wrap horizontally!
		$('leftColumMapStuffScope').setStyle({position:'absolute',top:'0px',left:'10px',width:'100px',height:'100px'});
		$('leftColumMapStuffKeyContainer').setStyle({position:'absolute',top:'0px',left:'150px',width:(intNewWidth - 150)+'px',height:'100px'});
		$('leftColumMapStuffKeyTitle').setStyle({cssFloat:'left'});
		$('leftColumMapStuffKey').setStyle({cssFloat:'left'});
						
		// Get all divs within the key and float them too!
		var arrChildKeyElements = $$('#leftColumMapStuffKey div.keyItem');
		arrChildKeyElements.each(function(objElement, intKey)
		{
			objElement.setStyle({cssFloat:'left'});
		});	
	},
	
	collapseMap: function()
	{
		// Detect the available browser width and height.
		this.calculateBrowserViewportDimensions();
		
		// Show and hide the various elements required.
		$('headerContainer').setStyle({display:''});
		$('footerContainer').setStyle({display:''});
		$('collapseMapButtons').setStyle({display:'none'});
		$('expandMapButtons').setStyle({display:''});
		
		// Alter the size of the masterLayoutTable elements.
		$('masterLayoutTable').setStyle({width:770+'px'});
		$('masterLayoutTableColumn').setStyle({width:770+'px'});
						
		// Attempt to move this to left of the map when in standard collapsed mode.
		$('leftColumnMapStuff').setStyle({position:'relative',top:'',left:'',width:'200px',height:''});
						
		// Alter the size/position of the map.	
		$('rightColumnMapStuff').setStyle({position:'relative',top:'',left:'',width:'600px',height:''});	
		$('ezMaps').setStyle({height:'400px'});
		
		// Return key elements back to original style.
		$('leftColumMapStuffScope').setStyle({position:'relative',top:'',left:'',width:'',height:''});
		$('leftColumMapStuffKeyContainer').setStyle({position:'relative',top:'',left:'',width:'',height:'',overflow:'hidden',border:'0px solid red'});
		$('leftColumMapStuffKeyTitle').setStyle({cssFloat:'none'});
		$('leftColumMapStuffKey').setStyle({cssFloat:'none'});
		
		// Get all divs within the key and remove floats too!
		var arrChildKeyElements = $$('#leftColumMapStuffKey div.keyItem');
		arrChildKeyElements.each(function(objElement, intKey)
		{
			objElement.setStyle({cssFloat:'none'});
		});
	},
*/
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	// Calcualte the browser viewport dimensions.
	calculateBrowserViewportDimensions: function()
	{
		intBrowserViewportWidth = 0;
		intBrowserViewportHeight = 0;
		
		// Calculate the browser viewport dimensions.
		// NB. In order to take into account the appearance of scrollbars we need
		// to use the clientWidth/Height properties (innerWidth/Height ignore scrollbars).
		if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) )
		{
			// FF & IE 6+ in 'standards compliant mode'
			var intBrowserViewportWidth = document.documentElement.clientWidth;
			var intBrowserViewportHeight = document.documentElement.clientHeight;
		} 
		else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) )
		{
			// IE 4 compatible
			var intBrowserViewportWidth = document.body.clientWidth;
			var intBrowserViewportHeight = document.body.clientHeight;
		}

		// Store the browser viewport dimensions.
		this.intBrowserViewportWidth = intBrowserViewportWidth;
		this.intBrowserViewportHeight = intBrowserViewportHeight;
	}
};

var RoutesAuthorise = {
	__getRoutesUrl: '/routes/getUnauthorisedRoutes',
	__updateRouteUrl: '/routes/updateRoute',

	// <geom_id>: {lineId: <lineId>, homeStamp: <homeStamp>, points: <points>}
	__currentRoutes: new Hash(),
	__paging: false,
	__currentRouteId: undefined,

	__strokeStyles: new Hash(),

	selectedRoutes: [],
	allSelected: false,

	init: function(EzMapData, mapServerUrl, layerServerUrl, sessionId) {
		centerPoint = new Point(300000, 500000, 27700);
		zoomLevel = 13;

		var visibleMapNames = ['aerial', 'OS'];
		EzMapHelper.init(EzMapData, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames);

		this.offset = 0;
		this.setOffsetCompleteCallback = this.setOffsetComplete.bind(this);

		this.highlighting = false;

		EzMapHelper.registerScribbleCallback(this.scribbleCallback.bind(this));
	},
	
	setModeColours: function(modeColours) {
		var normalAlpha = 200;
		var normalWidth = 3;

		var highlightAlpha = 255;
		var highlightWidth = 4;

		var strokeStyles = new Hash();
		modeColours.each(function(pair) {
			var id = pair.key;
			var col = pair.value;
			strokeStyles[id] = {
				normal: {r: col.red, g: col.green, b: col.blue, a: normalAlpha, w: normalWidth},
				highlight: {r: col.red, g: col.green, b: col.blue, a: highlightAlpha, w: highlightWidth}
			};
		});
		this.__strokeStyles = strokeStyles;
	},

	scribbleCallback: function(shapeId, eventType) {
		if (this.__currentRouteId && eventType == 'shapeUpdated') {
			var currentRoute = this.__currentRoutes[this.__currentRouteId];
			if (shapeId == currentRoute.lineId) {
				Element.show('routeSave' + this.__currentRouteId);
			}
		}
	},

	setSchoolLocation: function(easting, northing) {
		var schoolPoint = new Point(easting, northing, 27700);
		var schoolStamp = EzMapHelper.createSchoolStamp(schoolPoint);
	},

	// routes is a list of routes, each route is an object with an ID, mode, and a list of [x,y] pairs
	// ie. jsonRoutes = [{id: id, mode: mode, points: [[x,y], [x,y], ...]}, {id: id2, mode: mode, points: [[x,y], [x,y], ...]}, ...]
	setVisibleRoutes: function(routes) {
		// Delete any old routes
		this.__currentRoutes.values().each(function(route) {
			EzMapHelper.deleteShape(route.lineId);
			EzMapHelper.removeStamp(route.homeStamp);
		});

		// Build up the new list of routes
		var routesHash = new Hash();
		var allPoints = [];
		var strokeStyles = this.__strokeStyles;
		routes.each(function(route) {
			// Convert from simple [x,y] pairs into Point objects
			var points = route.points.map(function(point) {
				point = new Point(point[0], point[1], 27700);
				allPoints.push(point);
				return point;
			});
			var lineId = EzMapHelper.createLine(points);
			EzMapHelper.setShapeStrokeStyle(lineId, strokeStyles[route.mode].normal);
			var homePoint = points[points.length - 1];
			var homeStamp = EzMapHelper.createHomeStamp(homePoint);
			routesHash[route.id] = {lineId: lineId, homeStamp: homeStamp, points: points, mode: route.mode};
		});
		this.__currentRoutes = routesHash;

		EzMapHelper.jumpToPoints(allPoints);
	},

	highlightRoute: function(routeId) {
		// Ignore if there's no change
		if (routeId == this.__currentRouteId)
			return;

		// Get rid of any previous highlighted route
		this.__removeHighlight();

		// Remember the new highlight route
		this.__currentRouteId = routeId;

		// Switch the new route to edit mode and set its stroke style to highlight
		var route = this.__currentRoutes[routeId];
		EzMapHelper.setShapeEditMode(route.lineId, true);
		EzMapHelper.setShapeStrokeStyle(route.lineId, this.__strokeStyles[route.mode].highlight);
		EzMapHelper.moveShapeToFront(route.lineId);

		// Highlight the route in the detail listing
		Element.addClassName('route' + this.__currentRouteId, 'routeHighlight');
	},

	deleteRoute: function(routeId) {
		if (this.__paging)
			return;

		if (!confirm("Are you sure you want to delete this route?"))
			return;

		this.setOffset(this.offset, routeId);
	},

	saveRoute: function(routeId) {
		var img = $('routeMode' + routeId);
		if (img.oldSrc)
			return;

		// Hide the save button
		Element.hide('routeSave' + routeId);

		img.oldSrc = img.src;
		img.src = '/images/slices/activityIndicator.gif';

		// Extract the current state of the line ...
		var route = this.__currentRoutes[routeId];
		var points = EzMapHelper.getShapePoints(route.lineId);

		// ... simplify it ...
		points = points.map(function(point) {
			return [point.x, point.y];
		});

		// ... and send it to the server
		var parameters = {routeId: routeId, points: points.toJSON()};
		var options = {
			parameters: parameters,
			onComplete: function() {
				img.src = img.oldSrc;
				delete img.oldSrc;
			}.bind(this)
		};
		new Ajax.Request(this.__updateRouteUrl, options);
	},

	toggleRoute: function(checkbox) {
		var id = checkbox.value;

		if (this.selectedRoutes.indexOf(id) == -1) {
			this.selectedRoutes.push(id);
		} else {
			this.selectedRoutes = this.selectedRoutes.without(id);
		}
	},

	toggleAll: function() {
		this.allSelected = !this.allSelected;

		var routeCheckboxes = $('routes').getElementsBySelector('input');
		var allSelected = this.allSelected;
		routeCheckboxes.each(function(checkbox) {
			checkbox.disabled = allSelected;
		});
	},

	setOffset: function(offset, deleteRouteId) {
		// Prevent multiple requests from being triggered
		// concurrently - we can't guarantee what order they'll be
		// processed on the server.
		if (this.__paging)
			return;

		this.__removeHighlight();

		this.offset = offset;
		var parameters = {offset: offset, 'selected[]': this.selectedRoutes};
		if (this.allSelected)
			parameters['allSelected'] = 1;

		if (deleteRouteId)
			parameters['deleteRouteId'] = deleteRouteId;

		var options = {
			parameters: parameters,
			evalScripts: true,
			//onSuccess: this.searchSuccessCallback,
			//onFailure: this.searchFailureCallback,
			onComplete: this.setOffsetCompleteCallback
		};
		this.__paging = true;
		Element.show('loading');
		new Ajax.Updater('routes', this.__getRoutesUrl, options);
	},

	setOffsetComplete: function() {
		this.__paging = false;
		Element.hide('loading');
	},

	authorise: function() {
		if (!this.allSelected && this.selectedRoutes.length == 0) {
			alert('Please select one or more routes to authorise.');
			return;
		}

		var label = 'Are you sure you want to authorise this route?';
		if (this.allSelected) {
			label = 'Are you sure you want to authorise all the routes?';
		} else if (this.selectedRoutes.length > 1) {
			label = 'Are you sure you want to authorise these routes?';
		}

		if (confirm(label)) {
			var args;
			if (this.allSelected) {
				args = ['allSelected=1'];
			} else {
				args = this.selectedRoutes.map(function(id) {
					return 'selected[]=' + id;
				});
			}
			
			var url = 'authoriseRoutes?' + args.join('&');

			window.location = url;
		}
	},

	cancel: function() {
		window.location = './';
	},

	__removeHighlight: function() {
		if (!this.__currentRouteId)
			return;

		// Switch the highlight route back to read mode and reset its stroke style
		var route = this.__currentRoutes[this.__currentRouteId];
		EzMapHelper.setShapeEditMode(route.lineId, false);
		EzMapHelper.setShapeStrokeStyle(route.lineId, this.__strokeStyles[route.mode].normal);

		// Remove the highlight from the route in the detail listing
		Element.removeClassName('route' + this.__currentRouteId, 'routeHighlight');

		// Forget the highlight route
		this.__currentRouteId = undefined;
	}
};

/////////////////////////////////////////////////////////////////////////////
//
// Used by the page for plotting points of interest
//
var RoutesPoi = {
	__createRouteUrl: 'createRoute',
	__drawRouteUrl: 'plotRoute',
	__getPopupContentsUrl: 'poiEditPopup',
	
	__uploadFormId: 'routePoiUploadForm',

	__uploading: false,

	__iconTypes: ['traffic', 'crossing', 'scary', 'litter', 'other'],

	// The stamp ID of the POI that's currently showing in the pop-up, or
	// undefined if the pop-up is not open.
	__activeStampId: undefined,

	__pois: new Hash(),

	init: function(EzMapData, mapServerUrl, layerServerUrl, sessionId, schoolLocation, homeLocation, route) {
		centerPoint = new Point(300000, 500000, 27700);
		zoomLevel = 13;

		route = route.map(function(point) {
			return new Point(point[0], point[1], 27700);
		});

		var visibleMapNames = ['aerial', 'OS'];
		EzMapHelper.init(EzMapData, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames);
		
		EzMapHelper.jumpToPoints(route);

		// Show the route plus the home and school locations
		var schoolPoint = new Point(schoolLocation.easting, schoolLocation.northing, 27700);
		var homePoint = new Point(homeLocation.easting, homeLocation.northing, 27700);
		var schoolStamp = EzMapHelper.createSchoolStamp(schoolPoint);
		var homeStamp = EzMapHelper.createHomeStamp(homePoint);
		this.lineId = EzMapHelper.createLine(route);

		// Make the icons draggable
		this.__iconTypes.each(this.makeStdIconDraggable.bind(this));

		// Register a callback for things being dropped
		EzMapHelper.registerDropCallback(this.poiDropCallback.bind(this));
	},

/*
	init: function(EzMapData, mapServerUrl, layerServerUrl, sessionId, schoolLocation, homeLocation, route) {
		centerPoint = new Point(300000, 500000, 27700);
		zoomLevel = 13;

		route = route.map(function(point) {
			return new Point(point[0], point[1], 27700);
		});

		var visibleMapNames = ['aerial', 'OS'];
		EzMapHelper.init(EzMapData, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames);
		
		EzMapHelper.jumpToPoints(route);

		// Show the route plus the home and school locations
		var schoolPoint = new Point(schoolLocation.easting, schoolLocation.northing, 27700);
		var homePoint = new Point(homeLocation.easting, homeLocation.northing, 27700);
		var schoolStamp = EzMapHelper.createSchoolStamp(schoolPoint);
		var homeStamp = EzMapHelper.createHomeStamp(homePoint);
		this.lineId = EzMapHelper.createLine(route);

		// Make the icons draggable
		this.__iconTypes.each(this.makeStdIconDraggable.bind(this));

		// Register a callback for things being dropped
		EzMapHelper.registerDropCallback(this.poiDropCallback.bind(this));
	},
*/ 

	back: function() {
		window.location = this.__drawRouteUrl;
	},

	finish: function() {
		// Make sure the pop-up is closed so we have recorded the latest state
		if (this.__activeStampId)
			this.closePoi();

		var poiValues = this.__pois.values().collect(function(poi) {
			var point = poi.stamp.worldPoint;
			var poiValue = {
				point: [point.x, point.y],
				description: poi.description,
				iconType: poi.iconType
			};
			if (poi.imageId) {
				poiValue.imageId = poi.imageId;
			}
			return poiValue;
		});
		$('finishedState').value = poiValues.toJSON();
		$('finishedForm').submit();
	},

	////////////////
	//
	// Pop-up handling
	//

	poiDropCallback: function(result) {
		var point = result.pointOnMap;

		// User has dropped one of the standard icons
		var match = /^poiIcon:(\w+)$/.exec(result.activeElement.id);
		if (match) {
			var iconType = match[1];
			if (this.__iconTypes.indexOf(iconType) >= 0) {
				// Since we're creating a new POI we need to close any old pop-up
				if (this.__activeStampId) {
					EzMapHelper.hidePopup();
				}
				this.makePoiStamp(result.pointOnMap, iconType);
			}

		// User has dropped a stamp
		} else {
			// User has moved the stamp associated with the currently active pop-up
			if (result.activeElement.stampId == this.__activeStampId) {
				EzMapHelper.movePopup(point);
			}
		}
	},

	makePoiStamp: function(point, iconType) {
		// Create a new POI
		var stamp = EzMapHelper.createPoiStamp(point, iconType);
		var poi = {stamp: stamp, iconType: iconType};
		EzMapHelper.makeStampDraggable(stamp);
		this.__pois[stamp.stampId] = poi;

		// Listen for events on the stamp
		stamp.setCallback(this.stampCallback.bind(this));

		this.createPopup(poi);
	},

	stampCallback: function(event, eventName, stampId) {
		if (eventName == 'click') {
			if (stampId != this.__activeStampId) {
				// Close the old pop-up ...
				this.closePoi();

				// ... and open the new one
				var poi = this.__pois[stampId];
				this.createPopup(poi);
			}
		}
	},

	createPopup: function(poi) {
		this.__activeStampId = poi.stamp.stampId;

		var popupDef = {
			width: 300,
			height: 180,
			pointOnMap: poi.stamp.worldPoint,
			innerHtml: '<div id="popupContents">Loading ...</div>',
			onClose: this.popupCloseCallback.bind(this)
		};
		EzMapHelper.showPopup(popupDef);

		// Now get the proper contents of the pop-up
		var parameters = {};
		if (poi.description)
			parameters['description'] = poi.description;
		if (poi.imageId) {
			parameters['imageId'] = poi.imageId;
		}
		var options = {
			parameters: parameters,
			evalScripts: true
			// XXX
			//onSuccess: this.searchSuccessCallback,
			//onFailure: this.searchFailureCallback,
			//onComplete: this.searchCompleteCallback
		};
		new Ajax.Updater('popupContents', this.__getPopupContentsUrl, options);
	},

	deletePoi: function() {
		// Delete the stamp
		var poi = this.__pois[this.__activeStampId];
		EzMapHelper.removeStamp(poi.stamp);

		// Delete the POI
		this.__pois.remove(this.__activeStampId);
		this.__activeStampId = undefined;

		this.closePoi();
	},

	closePoi: function() {
		EzMapHelper.hidePopup();
	},

	// Called by EzMaps when the pop-up is about to close
	popupCloseCallback: function() {
		if (this.__activeStampId) {
			// Save the state of the POI
			this.__pois[this.__activeStampId].description = $F('poiDescription');
			this.__activeStampId = undefined;
		}

		// Always allow the pop-up to close
		return true;
	},

	removeImage: function() {
		if (confirm('Are you sure you want to remove this image?')) {
			// Unregister the image from the POI
			delete this.__pois[this.__activeStampId].imageId;

			// Alter the pop-up to hide the image
			$('poiDescription').className = 'generalDescription';
			var thumbnail = Element.down('popupContents', 'img.thumbnail');
			var thumbnailDelete = Element.down('popupContents', 'img.thumbnailDelete');
			var uploadIcon = Element.down('popupContents', 'img.upload');
			thumbnail.hide();
			thumbnailDelete.hide();
			uploadIcon.show();
		}
	},

	////////////////
	//
	// File upload
	//

	showUpload: function() {
		var style = {
			'top': '50%',
			'marginTop': '-75px',
			'height': '150px',

			'left': '50%',
			'marginLeft': '-150px',
			'width': '300px'
		};
		LightboxHelper.showCallback(this.__getLightboxContents.bind(this), style);
	},

	cancelUpload: function() {
		// Ignore cancellation while an upload is in progress
		// XXX ... could change this to quietly 'dispose' of the upload
		if (this.__uploading)
			return;

		LightboxHelper.close();
	},

	upload: function() {
		// Don't allow another upload while one is in progress
		if (this.__uploading)
			return;
		this.__uploading = true;

		// Show the activity indicator
		var uploadForm = $(this.__uploadFormId);
		Element.down(uploadForm, 'img').show();

		// Send the file off to the server
		uploadForm.submit();
	},

	// Callback - called by inline JavaScript in the IFRAME which was
	// populated by the form upload.
	addImage: function(id, url) {
		// Register the new image against the POI
		this.__pois[this.__activeStampId].imageId = id;

		// Alter the pop-up to show the image
		$('poiDescription').className = 'imageDescription';
		var thumbnail = Element.down('popupContents', 'img.thumbnail');
		var thumbnailDelete = Element.down('popupContents', 'img.thumbnailDelete');
		var uploadIcon = Element.down('popupContents', 'img.upload');
		thumbnail.src = url;
		uploadIcon.hide();
		thumbnail.show();
		thumbnailDelete.show();

		LightboxHelper.close();

		this.__uploading = false;
	},

	// Callback - called by inline JavaScript in the IFRAME which was
	// populated by the form upload.
	noImage: function() {
		// Hide the activity indicator
		var uploadForm = $(this.__uploadFormId);
		Element.down(uploadForm, 'img').hide();

		this.__uploading = false;
	},

	__getLightboxContents: function() {
		var formStart = '<form id="' + this.__uploadFormId + '" target="uploadIframe" action="upload" method="post" enctype="multipart/form-data">';
		var formContents = $('formContents').innerHTML;
		var formEnd = '</form>';
		return formStart + formContents + formEnd;
	},

	////////////////
	//
	// Misc
	//

	makeStdIconDraggable: function(type) {
		this.makeElementDraggable('poiIcon:' + type);
	},

	makeElementDraggable: function(id) {
		var options = {revert: true};
		var draggable = new Draggable(id, options);

		// Add the 'ezmapDraggables' CSS class so that EzMaps pays attention to
		// drop events from this element
		Element.addClassName(id, 'ezmapDraggables');
	}
};

/////////////////////////////////////////////////////////////////////////////
//
// Used by the page for plotting a route to school
//
var RoutesPlot = {
	

	init: function(EzMapData, mapServerUrl, layerServerUrl, sessionId, schoolLocation, homeLocation) {
		
		centerPoint = new Point(300000, 500000, 27700);
		zoomLevel = 12;

		var visibleMapNames = ['aerial', 'OS'];
		EzMapHelper.init(EzMapData, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames);	

		var schoolPoint = new Point(schoolLocation.easting, schoolLocation.northing, 27700);
		var schoolStamp = EzMapHelper.createSchoolStamp(schoolPoint);
		
		var homePoint = new Point(homeLocation.easting, homeLocation.northing, 27700);
		var homeStamp = EzMapHelper.createHomeStamp(homePoint);
		
		var points = [schoolPoint, homePoint];
		
		EzMapHelper.jumpToPoints(points);

		this.lineId = EzMapHelper.createLine(points, true);
	},	
	/*
	init: function(simpleMapStacks, mapServerUrl, layerServerUrl, sessionId, schoolLocation, homeLocation) {
		centerPoint = new Point(300000, 500000, 27700);
		zoomLevel = 13;

		var visibleMapNames = ['aerial', 'OS'];
		EzMapHelper.init(simpleMapStacks, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames);

		var schoolPoint = new Point(schoolLocation.easting, schoolLocation.northing, 27700);
		var homePoint = new Point(homeLocation.easting, homeLocation.northing, 27700);
		var schoolStamp = EzMapHelper.createSchoolStamp(schoolPoint);
		var homeStamp = EzMapHelper.createHomeStamp(homePoint);
		var points = [schoolPoint, homePoint];
		EzMapHelper.jumpToPoints(points);
		this.lineId = EzMapHelper.createLine(points, true);
	},
	*/

	save: function() {
		// Check if user has changed the straight line route
		// ie. Have they added one or more points
		var line = EzMapHelper.getShapePoints(this.lineId);
		if (line.length == 2) {
			alert('Please click and drag the line to mark your route.');
			return;
		}
		if (!$F('nickname')) {
			alert('Please enter a nickname before saving your route.');
			return;
		}
		var duration = $F('duration');
		var durationMins = parseFloat(duration, 10);
		if (!durationMins || (duration != durationMins)) {
			alert('Please enter the number of minutes your route takes before saving.');
			return;
		}
		if (confirm('Are you sure you want to save your route?')) {
			var locations = line.map(function(point) {
				return [point.x, point.y];
			});
			$('route').value = locations.toJSON();
			$('routeForm').submit();
		}
	},

	cancel: function() {
		window.location = './';
	}
};

/////////////////////////////////////////////////////////////////////////////
//
// Used by the page for setting home locations
//
var RoutesHome = {
	__getPostcodeLocationUrl: '/routes/getPostcodeLocation',
	__updateHomeLocationUrl: 'updateHomeLocation',

	__detailZoom: 3,


	init: function(EzMapData, mapServerUrl, layerServerUrl, sessionId, easting, northing, schoolEasting, schoolNorthing) {
		
		this.getPostcodeLocationSuccessCallback = this.getPostcodeLocationSuccess.bind(this);
		this.getPostcodeLocationFailureCallback = this.getPostcodeLocationFailure.bind(this);
		this.getPostcodeLocationCompleteCallback = this.getPostcodeLocationComplete.bind(this);

		this.status = 'waiting';

		var schoolPoint = new Point(schoolEasting, schoolNorthing, 27700); // x, y, SRID

		if (easting) {
			this.easting = easting;
			this.northing = northing;
			centerPoint = new Point(easting, northing, 27700); // x, y, SRID
		} else {
			centerPoint = schoolPoint;
		}
		zoomLevel = this.__detailZoom;
		var visibleMapNames = ['aerial', 'OS'];
		
		EzMapHelper.init(EzMapData, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames);		

		if (easting) {
			this.makeHomeStamp(centerPoint);
		} else {
			// Make the home icon draggable
			var options = {revert: true};
			this.homeDraggable = new Draggable('homeIcon', options);

			// Add the 'ezmapDraggables' CSS class so that EzMaps pays attention to
			// drop events from the home icon
			$('homeIcon').addClassName('ezmapDraggables');
		}

		this.schoolStamp = EzMapHelper.createSchoolStamp(schoolPoint);

		// And register a callback for when it's dropped
		EzMapHelper.registerDropCallback(this.homeDropCallback.bind(this));
		
	},

/*
	init: function(simpleMapStacks, mapServerUrl, layerServerUrl, sessionId, easting, northing, schoolEasting, schoolNorthing) {
		this.getPostcodeLocationSuccessCallback = this.getPostcodeLocationSuccess.bind(this);
		this.getPostcodeLocationFailureCallback = this.getPostcodeLocationFailure.bind(this);
		this.getPostcodeLocationCompleteCallback = this.getPostcodeLocationComplete.bind(this);

		this.status = 'waiting';

		var schoolPoint = new Point(schoolEasting, schoolNorthing, 27700); // x, y, SRID

		if (easting) {
			this.easting = easting;
			this.northing = northing;
			centerPoint = new Point(easting, northing, 27700); // x, y, SRID
		} else {
			centerPoint = schoolPoint;
		}
		zoomLevel = this.__detailZoom;
		var visibleMapNames = ['aerial', 'OS'];
		EzMapHelper.init(simpleMapStacks, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames);

		if (easting) {
			this.makeHomeStamp(centerPoint);
		} else {
			// Make the home icon draggable
			var options = {revert: true};
			this.homeDraggable = new Draggable('homeIcon', options);

			// Add the 'ezmapDraggables' CSS class so that EzMaps pays attention to
			// drop events from the home icon
			$('homeIcon').addClassName('ezmapDraggables');
		}

		this.schoolStamp = EzMapHelper.createSchoolStamp(schoolPoint);

		// And register a callback for when it's dropped
		EzMapHelper.registerDropCallback(this.homeDropCallback.bind(this));
	},
*/

	makeHomeStamp: function(point) {
		this.homeStamp = EzMapHelper.createHomeStamp(point);
		EzMapHelper.makeStampDraggable(this.homeStamp);
	},

	homeDropCallback: function(result) {
		// Check whether this event comes from the home icon or the home stamp
		if (result.activeElement.id == 'homeIcon') {
			if (this.homeStamp) {
				this.homeStamp.moveTo(result.pointOnMap);
			} else {
				this.makeHomeStamp(result.pointOnMap);
			}
		}

		this.easting = result.pointOnMap.x;
		this.northing = result.pointOnMap.y;
	},

	save: function() {
		if (!this.easting) {
			alert('Please drag the home icon on to the map before saving.');
			return;
		}
		if (confirm('Are you sure you want to save this as your home location?')) {
			url = this.__updateHomeLocationUrl + '?easting=' + this.easting + '&northing=' + this.northing;
			window.location = url;
		}
	},

	cancel: function() {
		window.location = './';
	},

	getPostcodeLocation: function() {
		// Ignore empty-postcode searches
		var postcode = $F('postcode').strip();
		if (!postcode)
			return;

		// Prevent multiple simultaneous searches
		if (this.status == 'searching')
			return;
		this.status = 'searching';

		// Provide visual feedback that the search is underway
		this.updateVisualStatus();

		// Kick off the actual search on the server
		var options = {
			parameters: {postcode: $F('postcode')},
			onSuccess: this.getPostcodeLocationSuccessCallback,
			onFailure: this.getPostcodeLocationFailureCallback,
			onComplete: this.getPostcodeLocationCompleteCallback
		};
		new Ajax.Request(this.__getPostcodeLocationUrl, options);
	},

	getPostcodeLocationSuccess: function(transport) {
		// Extract the easting & northing from the server's response
		var postcodeLocation = eval('(' + transport.responseText + ')');
		var easting = postcodeLocation['easting'];
		var northing = postcodeLocation['northing'];

		if (easting && northing) {
			// Centre the map on the postcode
			point = new Point(easting, northing, 27700); // x, y, SRID
			EzMapHelper.jumpToPoint(point, this.__detailZoom);

			this.status = 'success';
		} else {
			this.getPostcodeLocationFailure();
		}
	},

	getPostcodeLocationFailure: function() {
		$('error').update('Sorry, we were unable to search for that postcode. Please check your text and try again.').style.display = '';
		this.status = 'error';
	},

	getPostcodeLocationComplete: function() {
		// Remove the visual feedback
		this.updateVisualStatus();
	},

	updateVisualStatus: function() {
		$('searching').style.display = (this.status == 'searching') ? '' : 'none';
		$('error').style.display = (this.status == 'error') ? '' : 'none';
	}
};

/////////////////////////////////////////////////////////////////////////////
//
// Used by the page for setting school locations
//
var RoutesLocation = {
	__searchUrl: '/routes/searchPostcode',
	__updateSchoolLocationUrl: 'updateSchoolLocation',

	init: function(EzMapData, mapServerUrl, layerServerUrl, sessionId, easting, northing) {
		
		this.searchSuccessCallback = this.searchSuccess.bind(this);
		this.searchFailureCallback = this.searchFailure.bind(this);
		this.searchCompleteCallback = this.searchComplete.bind(this);

		this.status = 'waiting';

		if (easting) {
			var centerPoint = new Point(easting, northing, EzMapData['srid']); // x, y, SRID
			zoomLevel = 4;
		} else {
			var centerPoint = new Point(337000, 500000, EzMapData['srid']); // x, y, SRID
			zoomLevel = 13;
		}
		
		var visibleMapNames = ['aerial', 'OS'];
		EzMapHelper.init(EzMapData, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames);

		if (easting) {
			var schoolStamp = EzMapHelper.createSchoolStamp(centerPoint);
			var schoolDropCallback = this.schoolDropCallback.bind(this);
			EzMapHelper.makeStampDraggable(schoolStamp, schoolDropCallback);
		}
	},

	schoolDropCallback: function(result) {
		if(typeof(result.pointOnMap)!='undefined'){
			this.newEasting = result.pointOnMap.x;
			this.newNorthing = result.pointOnMap.y;
		}
	},

	save: function() {
		if (confirm('Are you sure you want to save your establishment location? This cannot be undone once the first journey has been plotted.')) {
			var url = './';
			
			/*
			dbg(this.newEasting);
			dbg(this.newNorthing);
			dbg(this.__updateSchoolLocationUrl);
			*/
			
			if (this.newEasting) {
				url = this.__updateSchoolLocationUrl + '?easting=' + this.newEasting + '&northing=' + this.newNorthing;
			}
			window.location = url;
		}
	},

	cancel: function() {
		window.location = './';
	},

	search: function() {
		// Ignore empty-postcode searches
		var postcode = $F('postcode').strip();
		if (!postcode)
			return;

		// Prevent multiple simultaneous searches
		if (this.status == 'searching')
			return;
		this.status = 'searching';

		// Provide visual feedback that the search is underway
		this.updateVisualStatus();

		// Kick off the actual search on the server
		var options = {
			parameters: {postcode: $F('postcode')},
			onSuccess: this.searchSuccessCallback,
			onFailure: this.searchFailureCallback,
			onComplete: this.searchCompleteCallback
		};
		new Ajax.Updater('results', this.__searchUrl, options);
	},

	searchSuccess: function() {
		this.status = 'success';
	},

	searchFailure: function() {
		$('error').update('Sorry, we were unable to search for that postcode. Please check your text and try again.').style.display = '';
		this.status = 'error';
	},

	searchComplete: function() {
		// Remove the visual feedback
		this.updateVisualStatus();
	},

	updateVisualStatus: function() {
		$('searching').style.display = (this.status == 'searching') ? '' : 'none';
		$('results').style.display = (this.status == 'success') ? '' : 'none';
		$('error').style.display = (this.status == 'error') ? '' : 'none';
	}
};

/////////////////////////////////////////////////////////////////////////////
//
// Used by the route viewing page
//
var Routes = {
	__selectSchoolsLightboxUrl: '/routes/selectSchools',
	__searchSchoolsLightboxUrl: '/routes/searchSchools',
	__getRouteUrl: '/routes/popUpRoute',
	__updateStateUrl: '/routes/updateState',
	//__getSchoolLocationsUrl: '/somerset/censusMaps/getSchoolLocations',

	init: function(EzMapData, mapServerUrl, layerServerUrl, sessionId, allModes, thematicRulesNodeIds, activeSchools, schoolCount) {
		var modes = new Hash();
		allModes.each(function(mode) {
			modes[mode] = true;
			$('mode' + mode).checked = true;
		});
		this.modes = modes;
		this.activeSchools = new Hash(activeSchools);
		this.schoolCount = schoolCount;
		this.refreshSuccessCallback = this.refreshSuccess.bind(this);
		this.refreshCompleteCallback = this.refreshComplete.bind(this);
		this.searchCompleteCallback = this.searchComplete.bind(this);
		this.schoolLocationsSuccessCallback = this.schoolLocationsSuccess.bind(this);
		this.loading = 0;
		this.searching = false;
		// This center point and zoom level are just used as the default settings
		// until the "jumpToLayer" method has retrieved the layer's extents.
		var centerPoint = new Point(332000, 125000, 27700);

		var zoomLevel = 11;
		var visibleMapNames = ['aerial', 'OS'];		
		EzMapHelper.init(EzMapData, mapServerUrl, layerServerUrl, sessionId, centerPoint, zoomLevel, visibleMapNames);
		EzMapHelper.addThematicNodes(thematicRulesNodeIds);
		
		this.routesLayerId = thematicRulesNodeIds[0];
		EzMapHelper.jumpToLayer(this.routesLayerId);
		EzMapHelper.registerMapClickCallback(this.mapClick.bind(this));

	},
	mapClick: function(returnedObject, worldPoint) {
		// Show a pop-up with placeholder content
		var popupDef = {
			width: 270,
			height: 130,
			pointOnMap: worldPoint,
			innerHtml: '<div id="popUpContents">Loading</div>'
		};
		EzMapHelper.showPopup(popupDef);

		// Now get the proper contents of the pop-up
		//var url = this.__getRouteUrl + '?geomObject=' + returnedObject.toJSON();

		var url = this.__getRouteUrl;
		
		//new Ajax.Updater('popUpContents', url, {method: 'post'});
		
		new Ajax.Updater('popUpContents', url, {
				method: 'post',
				parameters: 'geomObject=' + returnedObject.toJSON()
			}		
		);
		
	},

	///////////////////////////////
	//
	//  Functions for handling school selection, including the lightbox
	//

	// Pop up the school selection lightbox
	selectSchools: function() {
		// Figure out the URL for the contents of the lightbox
		var url = this.__selectSchoolsLightboxUrl + '?' + this.getActiveSchoolParameters();

		// Backup the current list of active schools so we can restore it
		// if the user cancels the lightbox
		this.activeSchoolsBackup = new Hash(this.activeSchools);

		// Fire up the lightbox
		LightboxHelper.show(url);
	},

	// onKeyDown event handler for lightbox's "searchText" field
	searchKeyDown: function(event) {
		if (event.keyCode == 13) {
			this.search();
		}
	},

	// Called by the lightbox's "Search" button
	search: function() {
		if (this.searching)
			return;

		var searchText = $('searchText').value.strip();
		if (searchText == '')
			return;
		var searchType = $('searchType').value;

		// Flag the search as in-progress
		this.searching = true;
		$('routesSearchActivity').style.display = 'inline';

		// Do an AJAX call to update the list of search results
		var url = this.__searchSchoolsLightboxUrl + '?search=' + encodeURIComponent(searchText);
		url += '&type=' + searchType;
		url += '&' + this.getActiveSchoolParameters();
		var options = {
			onComplete: this.searchCompleteCallback,
			onFailure: function() {alert('Failure')},
			onException: function(request, exception) {alert(exception)}
		};
		new Ajax.Updater('routesSelectSchoolsPage', url, options);
	},

	// onComplete handler for search's AJAX request
	searchComplete: function() {
		$('routesSearchActivity').style.display = 'none';
		this.searching = false;
	},

	// Called by the lightbox's "Cancel" button
	cancelSearch: function() {
		this.activeSchools = this.activeSchoolsBackup;
		LightboxHelper.close();
	},

	// Called by the main page's "All schools" button
	// Clears the set of active schools to signal that all school results
	// should be shown.
	allSchools: function() {
		this.activeSchools = new Hash();
		this.updateSearch();
	},

	// Called by the lightbox's "Select" button
	updateSearch: function() {
		this.updateSchoolStatus();
		this.refresh();

		LightboxHelper.close();
	},

	///////////////////////////////
	//
	//  Functions for ... ?
	//

	getActiveSchoolParameters: function() {
		var parameters = this.activeSchools.keys().map(function(item) {
			return 'id[]=' + item;
		});
		return parameters.join('&');
	},

	updateLoadingStatus: function() {
		var loadingImg = $('loading');
		if (this.loading > 0) {
			loadingImg.style.display = 'inline';
		} else {
			loadingImg.style.display = 'none';
		}
	},

	toggleSchool: function(checkbox) {
		var id = checkbox.value;

		if (this.activeSchools[id]) {
			delete this.activeSchools[id];
		} else {
			var name = $('name' + id).innerHTML;
			this.activeSchools[id] = name;
		}
	},

	updateSchoolStatus: function() {
		var schoolDetails = '';
		var activeSchoolCount = this.activeSchools.keys().length;
		if (activeSchoolCount == 0) {
			schoolDetails = this.schoolCount + ' schools';
		} else if (activeSchoolCount == 1) {
			schoolDetails = this.activeSchools.values().join();
		} else {
			schoolDetails = activeSchoolCount + ' schools';
		}
		$('schoolDetails').innerHTML = schoolDetails;
	},

	toggleMode: function(id) {
		this.modes[id] = !this.modes[id];
		$('mode' + id).checked = this.modes[id];
	},

	getActiveModes: function() {
		var activeModes = [];
		this.modes.each(function(keyValue) {
			var mode = keyValue[0];
			var status = keyValue[1];
			if (status) {
				activeModes.push(mode);
			}
		});
		return activeModes;
	},

	addOptionParameters: function(parameters, controlIds) {
		controlIds.each(function(id) {
			if ($(id)&& $F(id)) {
				parameters[id] = true;
			}
		});
	},

	toggleDetails: function() {
		var state = $F('details');
		if (state) {
			Element.show('orderBy');
		} else {
			Element.hide('orderBy');
		}
	},

	// Get the server to re-write the thematic rules to match the new state
	refresh: function() {
		this.loading += 1;
		this.updateLoadingStatus();

		var parameters = {
			'mode[]': this.getActiveModes(),
			'id[]': this.activeSchools.keys()
		};
		this.addOptionParameters(parameters, ['dirto', 'dirfrom', 'unauth', 'labels', 'poi', 'details', 'distance', 'time', 'nickname']);

		var options = {
			parameters: parameters,
			onSuccess: this.refreshSuccessCallback,
			onComplete: this.refreshCompleteCallback
		};
		new Ajax.Request(this.__updateStateUrl, options);
	},

	refreshComplete: function() {
		this.loading -= 1;
		this.updateLoadingStatus();
		EzMapHelper.jumpToLayer(this.routesLayerId);
	},

	refreshSuccess: function(transport) {
		// XXX Check if the response matches the current mode state or use an
		// incrementing counter to track each request

		EzMapHelper.refreshThematicLayer();

		// XXX Enable this if we're drawing circles around schools
		//this.schoolLocationsSuccess(transport);
	},

	// Use this to get the school locations for drawing circles around schools
	getSchoolLocations: function() {
		var url = this.__getSchoolLocationsUrl;
		var options = {
			parameters: this.getActiveSchoolParameters(),
			onSuccess: this.schoolLocationsSuccessCallback
		};
		// XXX new Ajax.Request(url, options);
	},

	schoolLocationsSuccess: function(transport) {
		var locations = eval(transport.responseText);
	}
};

var CensusMaps =
{
	init: function(EzMapData, mapServerUrl, layerServerUrl, sessionId, modes, thematicRulesNodeIds, activeSchools, schoolCount, schoolsLayerId, userLA)
	{
		// Set paths.
		this.__selectSchoolsLightboxUrl = '/'+userLA+'/censusMaps/selectSchools';
		this.__searchSchoolsLightboxUrl = '/'+userLA+'/censusMaps/searchSchools';

		this.__updateModesUrl = '/'+userLA+'/censusMaps/updateModes';
		this.__getSchoolLocationsUrl = '/'+userLA+'/censusMaps/getSchoolLocations';

		this.__getSchoolPopupContentUrl = '/'+userLA+'/censusMaps/getSchoolPopupContent';
		this.__getPupilPopupContentUrl = '/'+userLA+'/censusMaps/getPupilPopupContent';

		this.defaultWalkPrimaryRadii = 800; // in metres
		this.defaultWalkSecondaryRadii = 2000; // in metres

		var activeModes = new Hash();
		modes.each(function(mode)
		{
			activeModes[mode] = true;
			$('mode' + mode).checked = true;
		});

		this.activeModes = activeModes;

		this.activeSchools = new Hash(activeSchools);
		this.schoolCount = schoolCount;

		this.checkForRefreshCallback = this.checkForRefresh.bind(this);
		this.refreshSuccessCallback = this.refreshSuccess.bind(this);
		this.refreshCompleteCallback = this.refreshComplete.bind(this);

		this.searchCompleteCallback = this.searchComplete.bind(this);

		this.schoolLocationsSuccessCallback = this.schoolLocationsSuccess.bind(this);

		this.loading = 0;
		this.searching = false;

		// Create the EzMap instance
		this.centerPoint = new Point(337000, 128000, 27700);
		this.zoomAmount = 10;
		var visibleMapNames = ['aerial', 'OS'];
		EzMapHelper.init(EzMapData, mapServerUrl, layerServerUrl, sessionId, this.centerPoint, this.zoomAmount, visibleMapNames);

		// Set up thematic layers
		EzMapHelper.addThematicNodes(thematicRulesNodeIds);
		this.__schoolsLayerId = schoolsLayerId;

		EzMapHelper.registerMapClickCallback(this.mapClick.bind(this));

		// Setup our map zoom callback.
		this.zoomLevel = EzMapHelper.getZoomLevel();
		this.zoomAmount = EzMapHelper.getZoomAmount();
		EzMapHelper.registerMapZoomCallback(this.mapZoom.bind(this));

		// Setup the primary and secondary school walk radii.
		this.pixelPerMeter = EzMapHelper.getGroundScale();

		this.walkPrimaryRadii = this.pixelPerMeter * this.defaultWalkPrimaryRadii;
		this.walkSecondaryRadii = this.pixelPerMeter * this.defaultWalkSecondaryRadii;

		// Get the school locations so we can draw circles round them
		this.getSchoolLocations();

		this.allSchools();
	},

	mapClick: function(geoms, worldPoint) {
		// Show a pop-up with placeholder content
		var popupDef = {
			width: 350,
			height: 200,
			pointOnMap: worldPoint,
			innerHtml: '<div id="popUpContents">Loading</div>'
		};
		EzMapHelper.showPopup(popupDef);

		// Now get the proper contents of the pop-up

		var schoolLayerId = this.__schoolsLayerId;
		var schools = geoms.findAll(function(geom) {
			return geom.container_node_id == schoolLayerId;
		});
		if (schools.length > 0) {
			var url = this.__getSchoolPopupContentUrl;
			geoms = schools;
		} else {
			var url = this.__getPupilPopupContentUrl;
		}
		var parameters = {'geomIds[]': geoms.pluck('id')};
		var options = {
			parameters: parameters
		};
		new Ajax.Updater('popUpContents', url, options);
	},

	mapZoom: function() {
		if(typeof(this.zoomAmount)!='undefined'){
			var zoomAmount = EzMapHelper.getZoomAmount();		
			if(this.zoomAmount!=zoomAmount){
				this.updateWalkRadii();
			}
		}
	},	

	searchKeyDown: function(event) {
		if (event.keyCode == 13) {
			this.search();
		}
	},

	search: function() {
		if (this.searching)
			return;

		var searchText = $('searchText').value.strip();
		if (searchText == '')
			return;
		var searchType = $('searchType').value;

		// Flag the search as in-progress
		this.searching = true;
		$('censusMapsSearchActivity').style.display = 'inline';

		// Do an AJAX call to update the list of search results
		var url = this.__searchSchoolsLightboxUrl + '?search=' + encodeURIComponent(searchText);
		url += '&type=' + searchType;
		url += '&' + this.getActiveSchoolParameters();

		var options = {
			onComplete: this.searchCompleteCallback,
			onFailure: function() {alert('Failure')},
			onException: function(request, exception) {alert(exception)}
		};
		new Ajax.Updater('censusMapsSelectSchoolsPage', url, options);
	},

	searchComplete: function() {
		$('censusMapsSearchActivity').style.display = 'none';
		this.searching = false;
	},

	cancelSearch: function() {
		this.activeSchools = this.activeSchoolsBackup;
		LightboxHelper.close();
	},

	allSchools: function() {
		this.activeSchools = new Hash();
		this.updateSearch();
	},

	updateSearch: function() {
		this.updateSchoolStatus();
		this.refresh();

		// Reset the extents to the default
		EzMapHelper.jumpToPoint(this.centerPoint, this.zoomAmount);

		LightboxHelper.close();
	},

	selectSchools: function() {
		// Figure out the URL for the contents of the lightbox
		var url = this.__selectSchoolsLightboxUrl + '?' + this.getActiveSchoolParameters();
		this.activeSchoolsBackup = new Hash(this.activeSchools);

		// Fire up the lightbox
		LightboxHelper.show(url);
	},

	getActiveSchoolParameters: function() {
		var parameters = this.activeSchools.keys().map(function(item) {
			return 'id[]=' + item;
		});
		return parameters.join('&');
	},

	updateLoadingStatus: function() {
		var loadingImg = $('loading');
		if (this.loading > 0) {
			loadingImg.style.display = 'inline';
		} else {
			loadingImg.style.display = 'none';
		}
	},

	toggleSchool: function(checkbox) {
		var id = checkbox.value;

		if (this.activeSchools[id]) {
			delete this.activeSchools[id];
		} else {
			var name = $('name' + id).innerHTML;
			this.activeSchools[id] = name;
		}
	},

	updateSchoolStatus: function() {
		var schoolDetails = '';
		var activeSchoolCount = this.activeSchools.keys().length;
		
		if (activeSchoolCount == 0) {
			schoolDetails = this.schoolCount + ' schools';
		} else if (activeSchoolCount == 1) {
			schoolDetails = this.activeSchools.values().join();
		} else {
			schoolDetails = activeSchoolCount + ' schools';
		}
		$('schoolDetails').innerHTML = schoolDetails;
		
	},

	toggleMode: function(id) {
		this.activeModes[id] = !this.activeModes[id];
		$('mode' + id).checked = this.activeModes[id];
		//this.requestRefresh();
	},

	// Flag that we'd like to do a refresh
	// If no other requests occur in the meantime, then this will actually happen in a second or so
	requestRefresh: function() {
		this.lastChange = new Date();
		this.checkForRefreshCallback.delay(1);
	},

	checkForRefresh: function() {
		// If there's a refresh-request pending ...
		if (this.lastChange) {
			// See how long it's been since the last request
			var now = new Date();
			var delta = now.getTime() - this.lastChange.getTime();
			if (delta < 1000) {
				// Too soon, try again in a while
				this.checkForRefreshCallback.delay(1);
			} else {
				// OK, it's been long enough. Time for action!
				this.lastChange = undefined;
				this.refresh();
			}
		}
	},

	refresh: function() {

		this.loading += 1;
		this.updateLoadingStatus();

		var parameters = [];

		this.activeModes.each(function(keyValue) {
			var mode = keyValue[0];
			var status = keyValue[1];
			if (status) {
				parameters.push('mode[]=' + mode);
			}
		});

		var options = {
			parameters: parameters.join('&') + '&' + this.getActiveSchoolParameters(),
			onSuccess: this.refreshSuccessCallback,
			onComplete: this.refreshCompleteCallback
		};

		new Ajax.Request(this.__updateModesUrl, options);
		
	},

	refreshComplete: function() {
		this.loading -= 1;
		this.updateLoadingStatus();
	},

	refreshSuccess: function(transport) {
		
		//dbg(transport);
		
		// XXX Check if the response matches the current mode state or use an
		// incrementing counter to track each request
		EzMapHelper.refreshThematicLayer();

		this.schoolLocationsSuccess(transport);
	},

	getSchoolLocations: function()
	{
		var url = this.__getSchoolLocationsUrl;
		var options = 
		{
			parameters: this.getActiveSchoolParameters(),
			onSuccess: this.schoolLocationsSuccessCallback
		};

		new Ajax.Request(url, options);
	},

	removeSchoolCircles: function()
	{
		if (this.__schoolCircles)
		{
			this.__schoolCircles.each(function(circle)
			{
				EzMapHelper.removeStamp(circle);
			});
		}

		this.__schoolCircles = undefined;
	},

	updateWalkRadii: function()
	{
		if(typeof(this.__schoolCircles)!='undefined')
		{
			if(this.__schoolCircles.length > 0)
			{
				this.pixelPerMeter = EzMapHelper.getGroundScale();

				this.walkPrimaryRadii = this.pixelPerMeter * this.defaultWalkPrimaryRadii;
				this.walkSecondaryRadii = this.pixelPerMeter * this.defaultWalkSecondaryRadii;
/*
				this.zoomAmount = EzMapHelper.getZoomAmount();
				var ratio = (this.zoomLevel/EzMapHelper.getZoomLevel());

				if(ratio > 0)
				{
					this.walkSecondaryRadii = this.defaultWalkSecondaryRadii * ratio;
					this.walkPrimaryRadii = this.defaultWalkPrimaryRadii * ratio;
				}
				else
				{
					this.walkSecondaryRadii = this.defaultWalkSecondaryRadii * 10;
					this.walkPrimaryRadii = this.defaultWalkPrimaryRadii * 10;
				}
*/
				this.removeSchoolCircles();
				this.getSchoolLocations();
			}
		}
	},

	schoolLocationsSuccess: function(transport)
	{
		this.removeSchoolCircles();
		var locations = eval(transport.responseText);

		var circles = [];		
		var walkSecondaryRadii = this.walkSecondaryRadii;
		var walkPrimaryRadii = this.walkPrimaryRadii;

		//dbg(locations);

		locations.each(function(point)
		{
			var type = point[2];

			//dbg(type);

			switch(type)
			{
				// For these types show 800m.
				case 'first':
				case 'infant':
				case 'junior':
				case 'primary':
					var strRequiredColour = 'yellow';
					var circle = EzMapHelper.createCircleStamp(new Point(point[0], point[1], 27700), walkPrimaryRadii, strRequiredColour);
					circles.push(circle);
				break;
				
				// For these types show 2km.
				case 'secondary':
				case 'upper':
					var strRequiredColour = 'red';
					var circle = EzMapHelper.createCircleStamp(new Point(point[0], point[1], 27700), walkSecondaryRadii, strRequiredColour);	
					circles.push(circle);
				break;
				
				// For these types show both (but in different colours).
				case 'middle':					
					var strRequiredColour = 'green';					
					var circle1 = EzMapHelper.createCircleStamp(new Point(point[0], point[1], 27700), walkPrimaryRadii, strRequiredColour);
					var circle2 = EzMapHelper.createCircleStamp(new Point(point[0], point[1], 27700), walkSecondaryRadii, strRequiredColour);
					circles.push(circle1);
					circles.push(circle2);
				break;							
				case 'special':					
					var strRequiredColour = 'blue';					
					var circle1 = EzMapHelper.createCircleStamp(new Point(point[0], point[1], 27700), walkPrimaryRadii, strRequiredColour);
					var circle2 = EzMapHelper.createCircleStamp(new Point(point[0], point[1], 27700), walkSecondaryRadii, strRequiredColour);
					circles.push(circle1);
					circles.push(circle2);
				break;
				
				// For every other type of school do not show anything!
				default:
				alert('Unknown school type:"' + type + '" was enabled. Cannot draw radius circle!');
			}
		});
		this.__schoolCircles = circles;
	}
};

var Admin = {
	__downloadCsvUrl: 'download.csv',

	search: function() {
		$('filters').submit();
	},

	reset: function() {
		window.location = 'reports';
	},

	printPage: function() {
		this.todo();
	},

	downloadPage: function() {
		var queryString = Form.serialize('filters');
		window.location = this.__downloadCsvUrl + '?' + queryString;
	},

	downloadAll: function() {
		var elements = Form.getElements('filters');

		// Serialise the form values, but use our own value for 'limit'
		var serialised = elements.map(function(element) {
			if (element.name == 'limit') {
				return 'limit=';
			} else {
				return Form.Element.serialize(element);
			}
		});

		// Remove any form values will an empty serialisation
		serialised = serialised.findAll(function(value) {
			return value;
		});

		var queryString = serialised.join('&');
		window.location = this.__downloadCsvUrl + '?' + queryString;
	},

	setOffset: function(offset) {
		$('offset').value = offset;
		this.search();
	},

	sort: function(column) {
		var orderByField = $('orderBy');
		var orderAscField = $('orderAsc');

		if (orderByField.value == column && orderAscField.value == '1') {
			orderAscField.value = '';
		} else {
			orderAscField.value = '1';
		}
		orderByField.value = column;

		// Now reset the offset and do the search
		this.setOffset(0);
	},

	todo: function() {
		alert('Coming soon');
	},

	saveUser: function() {
		// Validate user form
		var formId = 'adminUserForm';
		if (false) {
		if (!validateMandatoryFields(formId))
			return;

		if ($F('password') && $F('password') != $F('confirmPassword')) {
			$('password').value = '';
			$('confirmPassword').value = '';
			alert('Passwords don\'t match. Please try again.');
			return;
		}
		}

		// Submit the form
		$('action').value = 'update';
		$(formId).submit();
	},

	deleteUser: function(userId) {
		if (confirm('Are you sure want to delete this user? This can not be undone.')) {
			window.location = 'deleteUser?userId=' + userId;
		}
	}
};

var SurveyResults = {
	__ajaxResultsUpdateUrl: 'resultsUpdate',
	__ajaxSingleResultUpdateUrl: 'resultQuestionUpdate',
	__lightboxFilterUrl: 'filters',
	__lightboxTextResponsesUrl: 'textResponses',

	__style: 'graph',
	__filters: [],
	__mapQuestions: [],

	//////////////////////////////////////
	//
	// Public methods
	//

	init: function(surveyId) {
		this.__surveyId = surveyId;
	},

	updateResults: function() {
		this.__updateResults();
	},

	onChangeLaId: function() {
		$('scopeLa').checked = true;
		this.updateResults();
	},

	setViewStyle: function(style) {
		this.__style = style;

		if (style == 'text') {
			ButtonHelper.setButtonClass('textSurveyResultsStyleButton', 'surveyResultsActiveButtonBackground');
			ButtonHelper.setButtonClass('graphSurveyResultsStyleButton', 'surveyResultsInfoButtonBackground');
		} else {
			ButtonHelper.setButtonClass('textSurveyResultsStyleButton', 'surveyResultsInfoButtonBackground');
			ButtonHelper.setButtonClass('graphSurveyResultsStyleButton', 'surveyResultsActiveButtonBackground');
		}
		this.__updateResults();
	},

	showTextResponses: function(questionTypeId) {
		// Figure out the URL for the contents of the lightbox
		var params = ['surveyId=' + this.__surveyId, 'questionTypeId=' + questionTypeId];
		var filterParams = this.__getFilterParams();
		params = params.concat(this.__getFilterParams(), this.__getScopeParams());		
		var url = this.__lightboxTextResponsesUrl + '?' + params.join('&');
		// Fire up the lightbox
		LightboxHelper.show(url);
	},
	
	showTextResponsesPage: function(url,questionTypeId) {
		// Figure out the URL for the contents of the lightbox
		var params = ['surveyId=' + this.__surveyId, 'questionTypeId=' + questionTypeId];
		var filterParams = this.__getFilterParams();
		params = params.concat(this.__getFilterParams(), this.__getScopeParams());
		var url = this.__lightboxTextResponsesUrl + url + '&' + params.join('&');

		new Ajax.Request(url, {
			method: 'get',
			onSuccess: function(transport) {
				$('lbContent').update(transport.responseText);
			}		  
		});

	},	
	
	closeLightBox: function(){
 		$('overlay').remove();
		$('lightbox').remove();		
	},

	updateLightBox: function(response){
		//dbg(response);
		$('lbContent').update(response.responseText);
		
	},

	// Display the filter selection lightbox
	changeFilters: function() {
		// Figure out the URL for the contents of the lightbox
		var params = ['surveyId=' + this.__surveyId];
		var filterParams = this.__getFilterParams();
		params = params.concat(this.__getFilterParams());
		var url = this.__lightboxFilterUrl + '?' + params.join('&');

		// Fire up the lightbox
		LightboxHelper.show(url);
	},

	removeAllFilters: function() {
		this.__removeFilters();

		// Update the visual description of the active filters
		this.__setDescription();

		// Request the new results set
		this.__updateResults();
	},

	// Update the filter description and results to reflect the new filter values
	updateFilters: function() {
		// Clear out and then accumulate a list of all the active filters
		this.__removeFilters();

		// Accumulate the question filters ...
		var controls = $A(Form.getInputs('filters'));
		var names = controls.pluck('name').uniq();
		names.each(function(name) {
			var values = controls.findAll(function(control) {return control.name == name && control.checked;}).pluck('value');
			if (values.length > 0) {
				var labels = values.collect(function(value) {
					return $(name + '_' + value).innerHTML;
				});
				var description = $(name + '_label').innerHTML + ' - ' + labels.join(', ');
				SurveyResults.__addFilter(name, values, description);
			}
		});

		// Update the visual description of the active filters
		this.__setDescription();

		// Request the new results set
		this.__updateResults();
	},

	printable: function() {
		var params = [
			'surveyId=' + this.__surveyId,
			'style=' + this.__style
		];
		params = params.concat(this.__getFilterParams(), this.__getScopeParams(), this.__getQuestionStyleParams());
		params = params.join('&');
		window.location = 'printableResults?' + params;
	},

	setQuestionViewStyle: function(questionTypeId, style, altRow) {
		// Always start off by making sure this questionTypeId isn't in the list of map questions
		this.__mapQuestions = this.__mapQuestions.reject(function(id) {
			return id == questionTypeId;
		});
		var mapButtonId = 'mapSurveyResultsStyleButton' + questionTypeId;
		var graphButtonId = 'graphSurveyResultsStyleButton' + questionTypeId;
		if (style == 'map') {
			this.__mapQuestions.push(questionTypeId);
			ButtonHelper.setButtonClass(mapButtonId, 'surveyResultsActiveButtonBackground');
			ButtonHelper.setButtonClass(graphButtonId, 'surveyResultsInfoButtonBackground');
		} else {
			ButtonHelper.setButtonClass(mapButtonId, 'surveyResultsInfoButtonBackground');
			ButtonHelper.setButtonClass(graphButtonId, 'surveyResultsActiveButtonBackground');
		}
		this.__updateSingleResult(questionTypeId, altRow);
	},

	//////////////////////////////////////
	//
	// Private & callback methods
	//

	__getScopeParams: function() {
		if ($('scopeId')) {
			var params = ['scope=School'];
			params.push('scopeId=' + $F('scopeId'));
		} else {
			var controls = $A(Form.getInputs('scope', 'radio', 'scope'));
			var values = controls.findAll(function(control) {return control.checked;}).pluck('value');
			var scope = values[0];
			var params = ['scope=' + scope];
			if (scope == 'LA') {
				params.push('scopeId=' + $F('laId'));
			}
		}
		return params;
	},

	__getFilterParams: function() {
		var params = [];
		this.__filters.each(function(filter) {
			params.push(filter.param + '=' + encodeURIComponent(filter.value));
		});
		return params;
	},

	__getQuestionStyleParams: function() {
		var params = [];
		if (this.__style == 'graph') {
			this.__mapQuestions.each(function(id) {
				params.push('mapQuestions[]=' + id);
			});
		}
		return params;
	},

	__setDescription: function() {
		if (this.__filters.length > 0) {
			Element.hide('noFilters');
			var descriptions = [];
			this.__filters.each(function(filter) {
				descriptions.push(filter.description);
			});
			var html = descriptions.join('<br/>');
			var filterDesc = $('filterDescriptions');
			filterDesc.innerHTML = html;
			filterDesc.show();
			$('filterWarning').show();
		} else {
			Element.hide('filterDescriptions');
			$('filterWarning').hide();
			Element.show('noFilters');
		}
	},

	__removeFilters: function() {
		this.__filters = [];
	},

	__addFilter: function(param, value, description) {
		this.__filters.push({
			param: param,
			value: value,
			description: description
		});
	},

	__updateResults: function() {
		Element.hide('surveyResults');
		Element.show('surveyResultsLoading');

		var params = [
			'surveyId=' + this.__surveyId,
			'style=' + this.__style
		];
		params = params.concat(this.__getFilterParams(), this.__getScopeParams(), this.__getQuestionStyleParams());
		params = params.join('&');

		var successFunc = function(request) {
			Element.hide('surveyResultsLoading');
			Element.show('surveyResults');
		};

		var updater = new Ajax.Updater('surveyResults', this.__ajaxResultsUpdateUrl, {
			method:'get', parameters:params, onSuccess:successFunc
			,onFailure:function(){alert('Failure')}
			,onException:function(request, exception){alert(exception)}
			,evalScripts: true
		});
	},

	__updateSingleResult: function(questionTypeId, altRow) {
		//Element.hide('surveyResults');
		//Element.show('surveyResultsLoading');

		var params = [
			'surveyId=' + this.__surveyId,
			'questionTypeId=' + questionTypeId,
			'altRow=' + altRow
		];
		params = params.concat(this.__getFilterParams(), this.__getScopeParams(), this.__getQuestionStyleParams());
		params = params.join('&');

		var successFunc = function(request) {
			//Element.hide('surveyResultsLoading');
			//Element.show('surveyResults');
		};

		var updater = new Ajax.Updater('graphAndKeyContainer' + questionTypeId, this.__ajaxSingleResultUpdateUrl, {
			method:'get', parameters:params, onSuccess:successFunc
			,onFailure:function(){alert('Failure')}
			,onException:function(request, exception){alert(exception)}
			,evalScripts: true
		});
	}
};

var ButtonHelper = {
	setButtonClass: function(buttonId, className) {
		var topSpan = $(buttonId);
		var backgroundSpan = topSpan.down('.buttonBackground');
		backgroundSpan.className = 'buttonBackground ' + className;
	}
};

function selectPortal() {
	var portal = $('portal');
	var portalIndex = portal.selectedIndex;
	if (portalIndex) {
		var portalTag = portal.options[portalIndex].value;
		window.location = '/' + portalTag + '/';
	}
}

var STP = {
	__ajaxSurveyQuestionsUpdateUrl: 'surveyQuestionsUpdate',
	__ajaxGraphUpdateUrl: 'graphUpdate',
	__lightboxQuestionPreviewUrl: 'surveyQuestionPreview',

	//////////////////////////////////////
	//
	// Public methods
	//

	jumpTo: function(planId) {
		var jumpTo = $('jumpTo');
		var jumpToIndex = jumpTo.selectedIndex;
		window.location = 'viewSection?planId=' + planId + '&sectionId=' + jumpTo.options[jumpToIndex].value;
	},

	elementJumpTo: function(planId, sectionId) {
		var subJumpTo = $('subJumpTo');
		var subJumpToIndex = subJumpTo.selectedIndex;
		window.location = 'editElement?planId=' + planId + '&sectionId=' + sectionId + '&elementId=' + subJumpTo.options[subJumpToIndex].value;
	},

	selectSurvey: function() {
		Element.hide('surveyQuestions');
		Element.show('surveyQuestionsLoading');

		Element.hide('graphAndKeyContainer');
		Element.hide('graphActions');
		Element.hide('graphLoading');
		Element.show('graphEmpty');

		var survey = $('survey');
		var surveyIndex = survey.selectedIndex;

		var params = [
			'surveyId=' + survey.options[surveyIndex].value
		];
		params = params.join('&');

		var successFunc = function(request) {
			Element.hide('surveyQuestionsLoading');
			Element.show('surveyQuestions');
		};
		var failureFunc = function(request, exception) {
			Element.hide('surveyQuestionsLoading');
			if (exception) {
				alert(exception);
			} else {
				alert('Unable to retrieve list of questions');
			}
		};

		var updater = new Ajax.Updater('surveyQuestions', this.__ajaxSurveyQuestionsUpdateUrl, {
			method:'get', parameters:params, onSuccess:successFunc
			,onFailure:failureFunc, onException:failureFunc
		});
	},

	updateGraph: function() {
		this.__updateGraph();
	},

	setViewStyle: function(style) {
		this.__style = style;

		if (style == 'map') {
			ButtonHelper.setButtonClass('mapSchoolTravelPlanStyleButton', 'schoolTravelPlanActiveButtonBackground');
			ButtonHelper.setButtonClass('graphSchoolTravelPlanStyleButton', 'schoolTravelPlanOtherButtonBackground');
		} else {
			ButtonHelper.setButtonClass('mapSchoolTravelPlanStyleButton', 'schoolTravelPlanOtherButtonBackground');
			ButtonHelper.setButtonClass('graphSchoolTravelPlanStyleButton', 'schoolTravelPlanActiveButtonBackground');
		}
		$('style').value = style;
		this.__updateGraph();
	},

	savePrev: function() {
		this.__save('prev');
	},

	saveNext: function() {
		this.__save('next');
	},

	saveFinish: function() {
		this.__save('finish');
	},

	deleteGraph: function(planId, sectionId, graphId) {
		if (confirm('Are you sure want to delete this graph?')) {
			window.location = 'deleteGraph?planId=' + planId + '&sectionId=' + sectionId + '&graphId=' + graphId;
		}
	},

	deleteMap: function(planId, sectionId, graphId) {
		if (confirm('Are you sure want to delete this map?')) {
			window.location = 'deleteGraph?planId=' + planId + '&sectionId=' + sectionId + '&graphId=' + graphId;
		}
	},

	deleteObjective: function(planId, sectionId, objectiveId) {
		if (confirm('Are you sure want to delete this objective?\nNB. This will also delete any targets and actions attached to this objective.')) {
			window.location = 'deleteObjective?planId=' + planId + '&sectionId=' + sectionId + '&objectiveId=' + objectiveId;
		}
	},

	deleteTarget: function(planId, sectionId, objectiveId, targetId) {
		if (confirm('Are you sure want to delete this target?\nNB. This will also delete any actions attached to this target.')) {
			window.location = 'deleteTarget?planId=' + planId + '&sectionId=' + sectionId + '&objectiveId=' + objectiveId + '&targetId=' + targetId;
		}
	},

	deleteAction: function(planId, sectionId, objectiveId, targetId, actionId) {
		if (confirm('Are you sure want to delete this action?')) {
			window.location = 'deleteAction?planId=' + planId + '&sectionId=' + sectionId + '&objectiveId=' + objectiveId + '&targetId=' + targetId + '&actionId=' + actionId;
		}
	},

	deleteConsultation: function(planId, sectionId, consultationId) {
		if (confirm('Are you sure want to delete this consultation?')) {
			window.location = 'deleteConsultation?planId=' + planId + '&sectionId=' + sectionId + '&consultationId=' + consultationId;
		}
	},

	deleteSignature: function(planId, sectionId, signatureId) {
		if (confirm('Are you sure want to delete this signature?')) {
			window.location = 'deleteSignature?planId=' + planId + '&sectionId=' + sectionId + '&signatureId=' + signatureId;
		}
	},

	//////////////////////////////////////
	//
	// Private & callback methods
	//

	__getSelectValue: function(selectId) {
		var value = undefined;
		var select = $(selectId);
		if (select) {
			value = select.options[select.selectedIndex].value;
		}
		return value;
	},

	__save: function(action) {
		$('action').value = action;
		$('editForm').submit();
	},

	__updateGraph: function() {
		var surveyId = this.__getSelectValue('survey');
		var questionId = this.__getSelectValue('question');

		if (questionId) {
			Element.hide('graphAndKeyContainer');
			Element.hide('graphActions');
			Element.show('graphLoading');
			Element.hide('graphEmpty');

			var params = ['surveyId=' + surveyId, 'questionId=' + questionId, 'style=' + $F('style')];
			params = params.join('&');

			var successFunc = function(request) {
				Element.hide('graphLoading');
				Element.show('graphAndKeyContainer');
				Element.show('graphActions');
			};

			var updater = new Ajax.Updater('graphAndKeyContainer', this.__ajaxGraphUpdateUrl, {
				method:'get', parameters:params, onSuccess:successFunc
				,onFailure: function(){alert('Failure')}
				,onException: function(request, exception){alert(exception)}
				,evalScripts: true
			});
		} else {
			Element.hide('graphAndKeyContainer');
			Element.hide('graphActions');
			Element.hide('graphLoading');
			Element.show('graphEmpty');
		}
	}
};

function resetPassword(username) {
	return confirm("Are you sure you want to reset the password for '" + username + "'?");
}

function updateResponseCount() {
	$('responseCount').value = $F('count');
}

function validateSearchSubmit() {
	return $F('search') != '';
}

function searchSubmit() {
	if (validateSearchSubmit()) {
		$('searchForm').submit();
	}
}

function validateRegistrationSubmit() {
	return $F('laId') != '';
}

function registrationSubmit() {
	if (validateRegistrationSubmit()) {
		$('registrationForm').submit();
	}
}

function onKeyDownRegistrationSubmit(event) {
	if (event.keyCode == 13) {
		registrationSubmit();
	}
}

function validateMandatoryFields(formId) {
	arrMandatoryFields = document.getElementsByClassName("mandatory", formId);
	blnIncompleteMandatoryFields = false;
	firstFieldFocused = false;

	// Loop through the list of elements and check that they have been completed...
	for(count = 0; count < arrMandatoryFields.length; count++)
	{
		if (!Field.present($(arrMandatoryFields[count])))
		{
			blnIncompleteMandatoryFields = true;
			new Effect.Shake($(arrMandatoryFields[count]));

			// Focus the field if required...
			if (!firstFieldFocused)
			{
				new Field.focus($(arrMandatoryFields[count]));
				firstFieldFocused = true;
			}
		}
	}

	// Check the status of blnIncompleteMandatoryFields...
	if (blnIncompleteMandatoryFields)
	{
		alert("Please complete all mandatory fields");
		return false;
	}
	return true;
}

function validateRegistrationDetailsSubmit(bIncludeLicensing)
{
	// Only do these checks if it's the full form ...
	if ($('emailAddress'))
	{	
		if (bIncludeLicensing) {
			// Check that a licence type has been selected...
			if ($("type1YearPrimary").checked == false && $("type1YearSecondary").checked == false && $("type2YearSecondary").checked == false && $("type2YearPrimary").checked == false && $("type3YearSecondary").checked == false && $("type3YearPrimary").checked == false&&$("AuthorityLicence").checked == false) {
				new Effect.Shake('licenceOptions');
				alert("Please select a licence type...");
				new Field.focus('type1YearPrimary');
				return false;
			}
		}
	}
	
	// Remove the invoice fields if not required.
	if(typeof(blnRegistrationAuthorityLicence) != 'undefined')
	{
		if(blnRegistrationAuthorityLicence)
		{
			$('registrationInvoiceFields').remove();			
		}
	}	
	
	if (!validateMandatoryFields('registrationDetailsForm'))
		return false;

	return true;
}

function hideRegistrationInvoiceFields()
{
	$('registrationInvoiceFields').hide();	
	blnRegistrationAuthorityLicence = true;
}
function showRegistrationInvoiceFields()
{
	$('registrationInvoiceFields').show();	
	blnRegistrationAuthorityLicence = false;
}

function registrationDetailsSubmit(bIncludeLicensing) {
	if (validateRegistrationDetailsSubmit(bIncludeLicensing)) {
		$('registrationDetailsForm').submit();
	}
}

function registrationDetailsReset() {
	$('registrationDetailsForm').reset();
}

function showLogin() {
	var login = $('login');
	Element.hide('loginLoading');
	login.style.display = 'block';
}

function checkForLogin() {
	var login = $('login');
	if (login) {
		var loginSrc = $('loginSrc');
		loginSrc = loginSrc.innerHTML;
		loginSrc = loginSrc.replace('&amp;', '&', 'g');
		Event.observe(login, 'load', showLogin, false);
		login.src = loginSrc;
	}
}


function getQuestionRow(details) {
	var questionTypeId = details.questionTypeId;
	var options = details.options;
	if (options) {
		var option = options[options.length-1];
		var id = questionTypeId + '_' + option;
		var field = $(id);
		var row = field.up('table').up('tr');
	} else {
		var id = questionTypeId;
		var field = $(id);
		var row = field.up('tr');
	}
	return row;
}

function getRowPieces(row) {
	var pieces = {};

	pieces.topLeft = row.down('div');

	var lastCell = row.cells.item(row.cells.length - 1);
	pieces.topRight = Element.down(lastCell, 'div');

	pieces.arrowImage = row.down('img');

	var table = row.up('table');
	var nextRow = table.rows.item(row.rowIndex + 1);
	pieces.nextRow = nextRow;

	pieces.bottomLeft = Element.down(nextRow, 'div');

	pieces.bottomRight = Element.down(nextRow, 'div', 1);

	var mandatoryCell = row.cells.item(4);
	pieces.mandatoryImage = Element.down(mandatoryCell, 'img');

	return pieces;
}

function replaceClassName(element, className) {
	element.oldClass = element.className;
	element.className = className;
}

function restoreClassName(element) {
	element.className = element.oldClass;
	element.oldClass = undefined;
}

function highlightQuestion(details) {
	var row = getQuestionRow(details);
	if (row.oldClass) {
		return;
	}

	replaceClassName(row, 'questionRowMandatory')

	var pieces = getRowPieces(row);
	replaceClassName(pieces.topLeft, 'corner topLeftMMRBPB');
	replaceClassName(pieces.topRight, 'corner topRightMMRBPB');
	replaceClassName(pieces.nextRow, 'questionRowMandatory')
	replaceClassName(pieces.bottomLeft, 'corner bottomLeftMMRBPB');
	replaceClassName(pieces.bottomRight, 'corner bottomRightMMRBPB');

	var src = pieces.arrowImage.oldSrc = pieces.arrowImage.src;
	pieces.arrowImage.src = src.sub(/arrows\/.*/, 'sadFace.png');

	if (pieces.mandatoryImage) {
		var src = pieces.mandatoryImage.oldSrc = pieces.mandatoryImage.src;
		pieces.mandatoryImage.src = src.sub(/mandatory-\d/, 'mandatory-m');
	}
}

function restoreQuestion(details) {
	var row = getQuestionRow(details);
	if (!row.oldClass) {
		return;
	}

	restoreClassName(row);

	var pieces = getRowPieces(row);
	restoreClassName(pieces.topLeft);
	restoreClassName(pieces.topRight);
	restoreClassName(pieces.nextRow);
	restoreClassName(pieces.bottomLeft);
	restoreClassName(pieces.bottomRight);

	pieces.arrowImage.src = pieces.arrowImage.oldSrc;
	pieces.arrowImage.oldSrc = undefined;

	if (pieces.mandatoryImage) {
		pieces.mandatoryImage.src = pieces.mandatoryImage.oldSrc;
		pieces.mandatoryImage.oldSrc = undefined;
	}
}

function responseValidate() {
	var ok = true;
	textQuestions.each(function(details) {
		if (details.mandatory) {
			if (!$F(details.questionTypeId)) {
				highlightQuestion(details);
				ok = false;
			} else {
				restoreQuestion(details);
			}
		}
	});
	numberQuestions.each(function(details) {
		if (details.mandatory) {
			var v = $F(details.questionTypeId);
			if (typeof(v) == 'undefined' || isNaN(parseInt(v))) {
				highlightQuestion(details);
				ok = false;
			} else {
				restoreQuestion(details);
			}
		}
	});
	singleChoiceQuestions.each(function(details) {
		if (details.mandatory) {
			var questionTypeId = details.questionTypeId;
			var optionSelected = false;
			var options = details.options;
			for(var i = 0; i < options.length; i++) {
				if (options[i]) {
					var id = questionTypeId + '_' + options[i];
					if ($(id).checked) {
						optionSelected = true;
						break;
					}
				}
			}
			if (!optionSelected) {
				highlightQuestion(details);
				ok = false;
			} else {
				restoreQuestion(details);
			}
		}
	});
	multiChoiceQuestions.each(function(details) {
		if (details.mandatory) {
			var questionTypeId = details.questionTypeId;
			var optionSelected = false;
			var options = details.options;
			for(var i = 0; i < options.length; i++) {
				if (options[i]) {
					var id = questionTypeId + '_' + options[i];
					if ($(id).checked) {
						optionSelected = true;
						break;
					}
				}
			}
			if (!optionSelected) {
				highlightQuestion(details);
				ok = false;
			} else {
				restoreQuestion(details);
			}
		}
	});
	if (!ok) {
		alert('Please ensure you have answered all mandatory questions');
	}
	return ok;
}

function bulkResponseValidate() {
	var singleOk = true;
	var max = $F('responseCount');
	if (max) {
		max = parseInt(max);
	} else {
		return true;
	}
	singleChoiceQuestions.each(function(details) {
		var questionTypeId = details.questionTypeId;
		var options = details.options;
		var mandatory = details.mandatory;
		var total = 0;
		for(var i = 0; i < options.length; i++) {
			if (options[i]) {
				var id = questionTypeId + '_' + options[i];
				var v = $F(id);
				if (v) {
					v = parseInt(v);
					if (v) {
						total += v;
					}
				}
			}
		}
		if ((mandatory && total != max) || (!mandatory && total > max)) {
			highlightQuestion(details);
			singleOk = false;
		} else {
			restoreQuestion(details);
		}
	});
	var multiOk = true;
	multiChoiceQuestions.each(function(details) {
		var questionTypeId = details.questionTypeId;
		var options = details.options;
		var questionOk = true;
		for(var i = 0; i < options.length; i++) {
			if (options[i]) {
				var id = questionTypeId + '_' + options[i];
				var v = $F(id);
				if (v) {
					v = parseInt(v);
					if (v) {
						if (v > max) {
							multiOk = questionOk = false;
						}
					}
				}
			}
		}
		if (questionOk) {
			restoreQuestion(details);
		} else {
			highlightQuestion(details);
		}
	});
	if (!singleOk && !multiOk) {
		alert('Please ensure you have entered the correct number of responses for all single-choice questions, and no more than ' + max + ' in any multiple-choice response');
		return false;
	}
	if (!singleOk) {
		alert('Please ensure you have entered the correct number of responses for all single-choice questions');
		return false;
	}
	if (!multiOk) {
		alert('Please ensure you have entered no more than ' + max + ' in any multiple-choice response');
		return false;
	}
	return true;
}

var validateFunc = undefined;
var bigButtonInProgress = false;
function bigButton(label) {
	if (bigButtonInProgress)
		return;

	if (validateFunc) {
		if (!validateFunc()) {
			return;
		}
	}

	bigButtonInProgress = true;

	var field = $('bigButton'+label);
	field.value = label;
	field.form.submit();
}

function addFolder(folderId) {
	var td = document.getElementById('newFolder' + folderId);
	if (td) {
		td.style.display = 'block';
	}
}

function deleteFolder(folderId, folderName) {
	if (confirm('Are you sure want to delete the "' + folderName + '" folder?')) {
		window.location = window.location + '?deleteFolder=' + folderId;
	}
}

function deleteContent(contentId, contentName, contentType) {
	if (confirm('Are you sure want to delete the ' + contentType + ' "' + contentName + '"?')) {
		window.location = window.location + '?deleteContent=' + contentId;
	}
}

function renameFolder(folderId) {
	var td = document.getElementById('name' + folderId);
	if (td) {
		td.style.display = 'none';
	}
	td = document.getElementById('renameFolder' + folderId);
	if (td) {
		td.style.display = 'block';
	}
}

function hideSection(id) {
	var div = document.getElementById(id);
	div.style.display = 'none';
	var hideLink = document.getElementById(id + 'Hide');
	hideLink.style.display = 'none';
	var showLink = document.getElementById(id + 'Show');
	showLink.style.display = 'inline';
}

function showSection(id) {
	var div = document.getElementById(id);
	div.style.display = 'block';
	var hideLink = document.getElementById(id + 'Hide');
	hideLink.style.display = 'inline';
	var showLink = document.getElementById(id + 'Show');
	showLink.style.display = 'none';
}

var popupId;
var popupFirstClick;

function hidePopup() {
	if (popupFirstClick) {
		popupFirstClick = false;
		return;
	}
	var popup = document.getElementById(popupId);
	popup.style.display = 'none';
	if (document.detachEvent) {
		document.onclick = null;
	} else {
		window.removeEventListener('click', hidePopup, false);
	}
}

function colourPicker(id) {
	if (popupId && popupId != id) {
		hidePopup();
	}

	var src = document.getElementById('colourPickerSrc');
	popupId = 'colourPicker' + id;
	popupFirstClick = true;
	var dest = document.getElementById(popupId);
	dest.innerHTML = src.innerHTML;
	dest.style.backgroundColor = '#ffffff';
	dest.style.width = '100%';
	dest.style.display = 'block';
	if (document.attachEvent) {
		document.onclick = hidePopup;
	} else {
		window.addEventListener('click', hidePopup, false);
	}
}

function updateColourSample(id, value)
{
	var sample = document.getElementById('colourSample' + id);
	if (value.match(/^[0-9a-f]{3}$|^[0-9a-f]{6}$/))
	{
		sample.style.backgroundColor = '#' + value;
		sample.className = 'colourSample';
	}
	else
	{
		sample.className = 'colourSampleError';
	}
}

var ImagePicker = {
	inProgress: false,

	selectedAnchor: undefined,
	selectedId: undefined,
	selectedUrl: undefined,

	__init: function()
	{
		this.selectedAnchor = undefined;
		this.selectedId = undefined;
		this.selectedUrl;
	},

	show: function(containerId, hiddenId, imageId, width, height)
	{
		if (this.inProgress)
			return;
		this.inProgress = true;

		this.container = $(containerId);
		this.hidden = $(hiddenId);
		this.image = $(imageId);

		this.__init();

		var url = '/libraryImages.php?id=';
		url += this.hidden.value;
		url += '&width=';
		url += width;
		url += '&height=';
		url += height;

		var w = new Window('imagePicker', {
			className:'mac_os_x', title:'Images', width:550, height:400, url: url,
			resizable: true, closable: false, minimizable: false, maximizable: false,
			showEffect: Element.show, hideEffect: Element.hide
		});
		this.w = w;
		w.setDestroyOnClose();
		w.showCenter(false, 100);
	},

	ok: function()
	{
		this.w.destroy();

		if (this.selectedId)
		{
			this.hidden.value = this.selectedId;
			this.image.src = this.selectedUrl;
			if (this.selectedUrl)
			{
				Element.show(this.container);
			}
		}

		this.inProgress = false;
	},

	cancel: function()
	{
		this.w.destroy();
		this.inProgress = false;
	},

	selectImage: function(doc, id, url)
	{
		if (this.selectedAnchor)
		{
			this.selectedAnchor.className = undefined;
		}
		anchor = doc.getElementById('img' + id).parentNode;
		anchor.className = 'selected';
		this.selectedAnchor = anchor;
		this.selectedId = id;
		this.selectedUrl = url;
	},

	clear: function(containerId, hiddenId, imageId)
	{
		if (this.inProgress)
			return;

		container = $(containerId);
		hidden = $(hiddenId);

		Element.hide(container);
		hidden.value = '';
	},

	navigate: function(srcWin, queryString)
	{
		this.selectedAnchor = undefined;
		this.selectedId = undefined;
		this.selectedUrl = undefined;

		srcWin.location.replace('/libraryImages.php' + queryString);
	},

	addImage: function(newImageValue)
	{
		var bits = newImageValue.split('/');
		var filename = bits.pop();
		var ext = filename.split('.').pop();
		var allowedExts = new Array('gif', 'png', 'jpg', 'jpeg');
		var allowed = allowedExts.indexOf(ext) >= 0;
		if (!allowed)
		{
			alert('Please select an image file and try again');
		}
		return allowed;
	}
}

var OptionSetPicker = {
	inProgress: false,

	show: function(optionSetHidden, optionSetContents)
	{
		if (this.inProgress)
			return;
		this.inProgress = true;

		this.optionSetHidden = optionSetHidden;
		this.optionSetContents = optionSetContents;

		var w = new Window('optionSetPicker', {
			className:'mac_os_x', title:'Option Sets', width:535, height:320,
			resizable: false, closable: false, minimizable: false, maximizable: false,
			showEffect: Element.show, hideEffect: Element.hide
		});
		this.w = w;
		w.setDestroyOnClose();
		w.getContent().innerHTML = 'Loading ...';
		var url = '/optionSets.php?id=' + optionSetHidden.value;
		w.setAjaxContent(url);
		w.showCenter(false, 100);
	},

	setHiddenValue: function(optionSetId)
	{
		this.optionSetHidden.value = optionSetId;
	},

	setType: function(type)
	{
		if (type == 'existing')
		{
			$('optionSetTypeExisting').checked = true;
		}
		else
		{
			$('optionSetTypeNew').checked = true;
		}
	},

	updateOptions: function()
	{
		this.setType('existing');

		// Send a request for the contents of the option set
		new Ajax.Updater('optionSetOptions', '/optionSets.php', {
				method: 'post',
				parameters: 'mode=existing&id=' + $F('optionSetExisting'),
				evalScripts: true
			}
		);
	},

	ok: function()
	{
		if ($F('optionSetTypeNew'))
		{
			var name = $F('optionSetNewName');
			var options = $F('optionSetNewOptions');

			options = options.split(/\r?\n/);

			// Assign a default name if none given
			if (name == '')
			{
				name = options.join(', ').substr(0, 10);
			}
			name = encodeURIComponent(name);
			options = options.map(encodeURIComponent);
			// Send a request to update the database
			new Ajax.Updater(this.optionSetContents, '/optionSets.php', {
					method: 'post',
					parameters: 'mode=new&name=' + name + '&options[]=' + options.join('&options[]='),
					evalScripts: true
				}
			);
		}
		else
		{
			// Set the relevant hidden 'optionSet_<n>' field to the selected option set
			this.optionSetHidden.value = $F('optionSetExisting');

			// Send a request for the contents of the option set
			new Ajax.Updater(this.optionSetContents, '/optionSets.php', {
					method: 'post',
					parameters: 'mode=existing&id=' + $F('optionSetExisting'),
					evalScripts: true
				}
			);
		}
		this.w.destroy();
		this.inProgress = false;
	},

	cancel: function()
	{
		this.w.destroy();
		this.inProgress = false;
	}
}

var Questions = {
	init: function()
	{
		Sortable.create('questionContainer', {tag:'div', handle:'handle', 'scroll':window});
	},

	updateType: function(select, ignorePicker)
	{
		// XXX Need a better way to do this ...
		// ... [update] should now be able to use IDs?
		var nextTd = select.parentNode.nextSibling;
		if (!nextTd.tagName) {
			nextTd = nextTd.nextSibling;
		}
		var options = nextTd.firstChild;
		if (!options.tagName) {
			options = options.nextSibling;
		}

		var previous = select.previousSibling;
		if (!previous.tagName) {
			previous = previous.previousSibling;
		}
		if (select.value == 'single' || select.value == 'multi')
		{
			var previousValue = previous.value;
			if (previousValue != 'single' && previousValue != 'multi')
			{
				Element.show(options);
				if (!ignorePicker)
					this._showOptionSetPicker(options);
			}
		}
		else
		{
			Element.hide(options);
		}
		previous.value = select.value;

		var id = parseInt(select.id.split('_')[1]);
		var filter = $('filter_' + id);
		if (select.value == 'text' || select.value == 'number') {
			filter.disabled = 'disabled';
		}
		else {
			filter.disabled = '';
		}
	},

	_showOptionSetPicker: function(optionsAncestor)
	{
		var inputs = optionsAncestor.getElementsByTagName('input');
		var optionSetHidden = inputs.item(0);
		var optionListContents = document.getElementsByClassName('optionListContents', optionsAncestor)[0];
		OptionSetPicker.show(optionSetHidden, optionListContents);
	},

	chooseOptionSet: function(anchor)
	{
		var optionList = anchor.parentNode.parentNode;
		this._showOptionSetPicker(optionList);
		return false;
	},

	observePlaceholder: function()
	{
		var placeholder = $('questionDefinition_placeholder');
		var inputs = placeholder.getElementsByTagName('input');
		var text = inputs.item(0);
		Event.observe(text, 'focus', Questions.addNew, false);
	},

	addNew: function()
	{
		// Get rid of the sortable before we clone the last question
		// otherwise we end up cloning some of the attributes set by sortable.
		Sortable.destroy('questionContainer');

		// Make a copy of the placeholder question definition
		var newQuestion = $('questionDefinition_placeholder').cloneNode(true);

		// Set the ID of the new question to one greater than the last
		// question which already exists.
		var questions = $('questionContainer').childNodes;
		var lastId = 0;
		if (questions.length > 0)
		{
			var lastQuestion = questions.item(questions.length-1);
			lastId = parseInt(lastQuestion.id.split('_')[1]);
		}
		var newId = lastId + 1;
		newQuestion.id = 'questionDefinition_' + newId;

		// Update the field names to match the new ID
		// e.g. mandatory_12 => mandatory_13
		var inputs = newQuestion.getElementsByTagName('input');
		Questions._updateFieldNames(inputs, newId);
		Questions._updateFieldNames(newQuestion.getElementsByTagName('select'), newId);

		// Update the title
		var handle = document.getElementsByClassName('handle', newQuestion)[0];
		Element.update(handle, '<a href="#" onClick="return Questions.remove(this)">X</a><span>Click-and-drag to re-order</span>');
		//var span = handle.getElementsByTagName('span').item(0);
		//Element.update(span, 'Click-and-drag to re-order');

		// Add the new question to the sortable list
		$('questionContainer').appendChild(newQuestion);
		Questions.init();

		// Clear out the question and set the focus
		// NB. For some reason, the contents of <inputs> are removed by the "appendChild" method
		var questionInput = $('question_' + newId);
		questionInput.value = '';
		Event.stopObserving(questionInput, 'focus', Questions.addNew, false);
		questionInput.focus();

		if (lastId > 0)
		{
			// Copy the 'mandatory', 'type' and 'optionSet' fields from the previous question
			$('mandatory_' + newId).checked = $('mandatory_' + lastId).checked;
			$('type_' + newId).value = $('type_' + lastId).value;
			$('optionSet_' + newId).value = $('optionSet_' + lastId).value;
			var lastOptionsDiv = document.getElementsByClassName('optionListContents', lastQuestion)[0];
			var newOptionsDiv = document.getElementsByClassName('optionListContents', newQuestion)[0];
			Element.update(newOptionsDiv, lastOptionsDiv.innerHTML);
			Questions.updateType($('type_' + newId), true);
		}
	},

	remove: function(anchor)
	{
		var element = anchor;
		do
		{
			element = element.parentNode;
		} while(!Element.hasClassName(element, 'questionDefinition'));
		Element.remove(anchor);
		Effect.BlindUp(element, {'afterFinish':function(){Element.remove(element)}});

		// XXX ... does this need a call-back or other setting to delete the
		// question at the end of the effect?
		// Yes!!

		return false;
	},

	serialiseOrder: function()
	{
		var questions = $('questionContainer').childNodes;
		var order = new Array();
		for(var i = 0; i < questions.length; i++)
		{
			var question = questions.item(i);
			var id = question.id.split('_')[1];
			order.push(id);
		}
		$('questionOrder').value = order.join(',');
		return true;
	},

	_updateFieldNames: function(fields, newId)
	{
		for(var i = 0; i < fields.length; i++)
		{
			var field = fields.item(i);
			field.name = field.name.split('_')[0] + '_' + newId;
			field.id = field.id.split('_')[0] + '_' + newId;
		}
	}
}

var SummarySchool =
{

	toggleSections: function(id)
	{
		Element.toggle('closePlan'+id);
		Element.toggle('openPlan'+id);
		Element.toggle('sections'+id);
		return false;
	},
	toggleShowPlan: function(id)
	{
		Element.toggle('bodyPlan'+id);
		Element.toggle('buttonOpen'+id);
		Element.toggle('buttonClose'+id);

		//Element.toggle('openPlan'+id);
		//Element.toggle('sections'+id);
		return false;
	}

};

var Sections = {
	///////////////////
	//
	// Configuration
	//
	__ajaxGetSectionUrl: 'sectionsList',

	///////////////////
	//
	// Public functions
	//
	init: function(planId) {
		this.__planId = planId;

		this.__state = {};
	},

	expand: function(sectionId) {
		this.tog[sectionId] = true;
		if (this.__state[sectionId] == 'active')
			return;

		Element.hide('expand' + sectionId);

		if (this.__state[sectionId] == 'done') {
			Element.show('collapse' + sectionId);
			Element.show('helpText'+sectionId);
			return;
		}

		Element.show('active' + sectionId);


		var params = 'planId=' + this.__planId + '&sectionId=' + sectionId;

		var successFunc = function(request) {
			Sections._setSectionContents(sectionId);
			Element.show('helpText' + sectionId);

		};

		var updater = new Ajax.Updater('helpText'+sectionId,this.__ajaxGetSectionUrl, {
			method:'get', parameters:params, onSuccess:successFunc
			,onFailure:function(){alert('Failure')}
			,onException:function(request, exception){alert(exception)},evalScripts: true
		});
	},

	collapse: function(sectionId) {
		Element.hide('collapse' + sectionId);
		Element.show('expand' + sectionId);
		Element.hide('helpText'+sectionId);
		this.tog[sectionId] = false;

	},
		tog:[],

	toggle:function(sectionId)
	{
		if(!this.tog[sectionId])
		{
			this.expand(sectionId);
		}
		else
		{
			this.collapse(sectionId);
		}


	},

	///////////////////
	//
	// Call-back functions
	//
	_setSectionContents: function(sectionId) {

		Element.hide('active' + sectionId);
		Element.show('collapse' + sectionId);

		this.__state[sectionId] = 'done';
	}
}
	function resetTinyMC(sectionId)
	{
		if(window["section"+sectionId])
		{
			window["section"+sectionId].makeAllGroupsUnEditable();
		}
	}
/**
 * Object: sectionWizard
 * This object represents functionality related to section wizards
 *
 * Its main responsibiity is initiation of the section wizard and making calls to the associated url handler
 * which in turn instigates functionality to retrieve the associated data.
 *
 * During init method call the url to pass to the handler is specified using the 'type' parameter.
 * Using a standard identifier of 'sectionname' + 'Wizard' e.g 'questionWizard' we standardize
 * parameters (in this case the url) for all sections. These relate to identifiers in the associated url handler
 *
 * Using the prototype library Ajax Updater the returned results are placed into the relevant section container
 * specified here as the node parameter to the updateNodeRequest method
 */
var sectionWizard = {
	/**
	 * Initiate the question wizard
	 * @param {integer} planId the travel plan identifier
	 * @param {integer} sectionId the identifier of the section to retireve question(s) for
	 * @param {string} type the identifier for the section type e.g 'questions'
	 */
	init: function initSectionWizard(planId, sectionId, type) {

			resetTinyMC(sectionId);
			var params = 'sectionId=' + sectionId + '&planId='+ planId;
			var updater = new Ajax.Updater(
				$('sectionContainer'),
				type+'Wizard', // create our url using 'sectionName'+'Wizard' (see TravelPlanUrlHandler.class.php)
				{
					method:'get',
					parameters:params,
					onSuccess: function() {
						//alert('Call OK - Retrieving data for: sectionId:'+sectionId+' planId:'+planId+' section type:'+type);
					},
					onFailure:function(){
						alert('Failure')
					},
					onException:function(request, exception){
						alert(exception)
					},
					evalScripts: true
			});
		},

	addNew: function addNewSectionWizard(planId, sectionId, type) {


			resetTinyMC(sectionId);
			var params = 'sectionId=' + sectionId + '&planId='+ planId + '&type='+ type ;

			var updater = new Ajax.Updater(
				$('sectionContainer'),
				'addNodeToWizard',
				{
					method:'get',
					parameters:params,
					onSuccess: function() {

					},
					onFailure:function(){
						alert('Failure')
					},
					onException:function(request, exception){
						alert(exception)
					},
					evalScripts: true
			});
		},



	/**
	 * Initiate the update environment - set the receiving node and make the first call to retrieve
	 * data for the next step
	 * @param {Object} updateNodeName the node that will be updated with the results of the call
	 * @param {Object} urlString the url to call in order to retireve the required data
	 * @param {Object} planId the current plan identifier
	 */
	initUpdateEnvironment: function initUpdateEnvironment(updateNodeName, urlString, planId, sectionId, wizardContext) {
		resetTinyMC(sectionId);
		this.setWizardContext(wizardContext);
		this.setSectionId(sectionId);
		this.setUpdateNode(updateNodeName);
		/**
		 * updateNodeRequest needs sectionId to be set for it to work
		 */
		this.updateNodeRequest(urlString, planId);
		this.setHttpMethod('get'); // default http method
	},
	/**
	 * this method makes a request and updates the argument 'node' from the page with
	 * the results of the call
	 * @param {Object} urlString the url to call
	 * @param {Object} node the xhtml node to update
	 * @param {Object} planId the current plan identifier
	 * @param {boolean} flag to specify whether or not to use insert parameter to Ajax.Updater
	 * 								  we need to be able to turn this on and off to allow for saves
	 */
	updateNodeRequest: function updateNodeRequest(urlString, planId) {
			/**
			 * Assign the update node section
			 */
			var ajaxUpdater = new Ajax.Updater(
			this.getUpdateNode(),
			urlString,
			{
				method: this.getHttpMethod(),
				parameters: 'planId='+planId,
				onFailure: function() { alert('Failed')},
				evalScripts: true
			});
	},
	/**
	 * make the request to load in the question associated with a survey Id and the node to update
	 * @param {Object} nodeElement a select list - the selected node having the value of the survey id
	 * @param {integer} planId the current plan id
	 */
	getQuestionList: function getQuestionsList(nodeElement, planId) {
		var options = nodeElement.options;
		var index = (nodeElement.selectedIndex>-1)?nodeElement.selectedIndex:0;
		var surveyId = options[index].id;
		this.updateNodeRequest('loadQuestionSelector?surveyId='+surveyId, planId);
	},
	/**
	 * save all selected checkbox elements on the form
	 * @param {Object} graphContainerForm the form that contains all the graphs
	 * @param {integer} surveyId the identifier of the survey associated with the data used to derive the graphs
	 * @param {string} updateNode the xhtml node that will be updated with the results of the save graphs call
	 */
	saveCheckedGraphs: function saveCheckedGraphs(graphContainerForm, surveyId, planId) {
		/**
		 * We are potentially modifying data on the server so set the method to post
		 */
		this.setHttpMethod('post');
		/**
		 * Checked count - incremented every time a checked box is encountered
		 */
		var checkCount = 0;

		/**
		 * the list of input based tags in the form
		 */
		var options = graphContainerForm.getElementsByTagName('input');
		var currentId = null;
		var currentSectionId = this.getSectionId();
		var wizardContext = this.getWizardContext();
		/**
		 * 	this object will hold our list of graphs to be saved and the
		 *	associated information
		 */
		var graphNodeObject = {
			surveyId: surveyId,
			count: 0 ,
			graphList:[]
		}
		/**
		 * Loop through the form elements
		 */
		for(var ix = 0; ix < options.length; ix++) {
			var element = options[ix];
			/**
			 * this object represents and individual graph from the list
			 * we will insert this into the graphNodeObject.graphs list
			 */
			var aGraph = {
						graphName: '',
						notes: '',
						questionTypeId: null
			}
			/**
			 * Here we match on checked boxes using the value as the id to select the
			 * other fields relevant to the graph in question. The other fields have the following naming convention '<string><integer>'
			 */
			switch(element.type) {
				case 'checkbox':
					if(element.checked) {
						checkCount++;
						currentId = element.value;
						aGraph.graphName = $F('name'+currentId);
						aGraph.notes = $F('notes'+currentId);
						aGraph.questionTypeId  = currentId;
						graphNodeObject.graphList.push(aGraph);
						graphNodeObject.count++;
					}
				break;
			}
		}
		/**
		 * loop ends - if we have at least one checked item then save
		 * else complain to the user
		 */
		var url = "saveGraph?planId="+planId;
		url += "&sectionId="+currentSectionId;
		url += "&wizardContext="+this.getWizardContext();


		if(checkCount > 0)
		{
			url += "&jsonObj="+Object.toJSON(graphNodeObject);
		}
		this.updateNodeRequest(url, planId);
	},
	/**
	 * Show the link text to the user for the survey they have selected
	 * @param {Object} selectBox
	 */
	showLoadLink: function showLoadLink(selectBox) {
		var options = selectBox.options;
		var index = selectBox.selectedIndex;
		var newText = options[index].value;
		var loadLink = $$("#loadGraphsAnchor a")[0] ;
		loadLink.innerHTML = "Click here to load Graphs for: <b>"+options[index].text+"</b>";
	},
	/**
	 * Set the xhtml node that will receive all the updates.
	 * @param {Object} updateNode
	 */
	setUpdateNode: function setUpdateNode(updateNode) {
		this.updateNode = updateNode;
	},
	/**
	 * Retrieve the current xhtml node that will receieve the updates
	 * @return {String} the name of the xhtml node
	 */
	getUpdateNode: function getUpdateNode() {
		return this.updateNode;
	},
	/**
	 * Set the current section id
	 * @param {integer} sectionId
	 */
	setSectionId: function setSectionId(sectionID) {
		this.sectionId = sectionID;
	},
	/**
	 * retrieve the current section id
	 * @return {integer} the current section id
	 */
	getSectionId: function getSectionId() {
		return this.sectionId;
	},
	/**
	 * Set the http method type to use in requests
	 * @param {Object} httpMethod the type either 'get' or 'post'
	 */
	setHttpMethod: function setHttpMethod(httpMethod) {
		this.httpMethod = httpMethod;
	},
	/**
	 * Retrieve the current http method
	 * @return {string} the current http method
	 */
	getHttpMethod: function getHttpMethod() {
		return this.httpMethod;
	},
	/**
	 * Assign the wizard context flag to a state of true if were being called from within a section wizard context
	 * or false if not
	 * @param {boolean} inContext true if in wizard context, false if not
	 */
	setWizardContext: function setWizardContext(inContext) {
		this.wizardContext = inContext;
	},
	/**
	 * Get the current context setting
	 * @return {boolean} true if in wizard context, false if not
	 */
	getWizardContext: function getWizardContext() {
		return this.wizardContext;
	}
}

