/**
 * $Workfile: atUI.js $
 * $Revision: 13 $
 *  $Modtime: 21/10/09 12:25 $
 *   $Author: Aamir.afridi $ 
 * Class: Astun.JS.UI
 * Container for UI classes and functions.  
 */
if( !Prototype ) throw new Error( 'Prototype library not found' );  // Needs to be nicer, possibly iSharemaps-wide library error?
if( !Fx ) throw new Error( 'MooFX library not found' );  // Needs to be nicer, possibly iSharemaps-wide library error?

if( !Astun ) {
	var Astun = {};
}
if( !Astun.JS ) {
	Astun.JS = {};
}
if( !Astun.JS.UI ) {
	Astun.JS.UI = {};
}

Astun.JS.UI.Panel = Class.create( 
	{
	/**
		* Constructor: Astun.JS.UI.Panel
		* Create a new iShareMaps panel with header-content-footer structure.
		* Example: 
		*		<div class="atPanel">
		*			<div class="atPanelHeader">
		*				<h3>Panel header</h3>
		*			</div>
		*			<div class="atPanelContent"></div>
		*			<div class="atPanelFooter"></div>
		*		</div>
		* Parameters:
		* options - {object} container for:
		*		title - {string} header text for panel
		*		relative - {string | HTMLElement} element relative to which 
		*			this panel will be inserted.
		*		position - {string} position relative to parent( if specified )
		*			allowed: 'top', 'bottom', 'before' or 'after'.
		*			See: http://www.prototypejs.org/api/element/insert
		*		names - {object} classes and IDs of various elements in the panel.
		*				If absent no additional classes or ids will be added.
		*		names.panel.classes - {array}
		*		names.panel.id - {string}
		*		names.header.classes - {array}
		*		names.header.id - {string}
		*		names.content.classes - {array}
		*		names.content.id - {string}
		*		names.footer.classes - {array}
		*		names.footer.id - {string}
		* 
		* Returns:
		* new instance of class.
		*/
		'initialize': function( options ) {
			var panelType = this.CLASS.replace( /Astun\.JS\.UI\./, 'at' ).replace( /\./, '-' ).camelize();
			var childElements = [ 'header', 'content', 'footer' ] ;
			var addNames = function( element, id, classes ) {
				if( !!classes ) {
					if( classes instanceof Array )
					{
						while( classes.length ) {
							element.addClassName( classes.shift() );
						}
					}
				}
				if( !!id ) {
					if( typeof( id ) === 'string' ) {
						element.id = id;
					}
				}
				
			}
			this.elements = $A();
			this.elements.main = new Element( 'div');
			this.elements.main.addClassName( 'atPanel' );
			
			this.elements.main.addClassName( panelType );
						
			if( !!options && !!options.names && !!options.names.panel ) {
			    if( !options.names.panel.id ) {
				    options.names.panel.id = 'atPanel' +  Math.random().toString().substring( 3, 8 );
				}
				addNames( this.elements.main, options.names.panel.id, options.names.panel.classes );
			}
						
			for( var i = 0; i < childElements.length; i++ ) {
				var type = childElements[ i ];
				var newElement = new Element( 'div' );
				newElement.addClassName( ( 'at-panel-' + type ).camelize() );
				if( !!options && !!options.names && !!options.names[ type ] ) {
					addNames( newElement, options.names[ type ].id, options.names[ type ].classes );
				}
				this.elements[ type ]  = newElement;
				this.elements.main.insert( newElement );
			}
			
			this.elements.title = new Element( 'h3' );
			this.elements.header.insert( this.elements.title );
			
			if( !!options ) {
				if( !!options.title ) {
					this.elements.title.update( options.title );
				}
				if( !!options.relative ) {
					var position = {};
					position[ options.position || 'bottom' ] = this.elements.main;
					$( options.relative ).insert( position );
				}
			}
			
		}, 
		'appendTo': function( parent ) {	
		   /**
			* Function: appendTo
			* Appends the panel to another Element
			* 
			* Parameters:
			* parent - { string | HTMLElement } append panel to this element
			* 
			* Returns:
			* { HTMLElement } the panel object
			*/
			$( parent ).insert( this.elements.main );
			return this;
			
		}, 
		'hide': function(){		
		   /**
			* Function: hide
			* Hides the panel
			*
			*/	
			this.elements.main.hide();
		}, 
		'show': function(){	
		   /**
			* Function: show
			* Shows the panel
			*
			*/	
			this.elements.main.show();
		}, 
		'CLASS' : 'Astun.JS.UI.Panel'
	}
 );


Astun.JS.UI.Panel.ContentSwitcher = Class.create( 
	Astun.JS.UI.Panel, 
	{
   /**
	* Constructor: Astun.JS.UI.Panel.ContentSwitcher
	* Create { Astun.JS.UI.Panel } with switchable content.
	* Example: 
	*		<div class="atPanel atPanelContentSwitcher">
	*			<div class="atPanelHeader">
	*				<h3>Panel header</h3>
	*			</div>
	*			<div class="atPanelContent">
	*				<dl>
	*					<dt>[ label ]</dt>
	*					<dd>[ text ]</dd>
	*					...
	*           </div>
	*			<div class="atPanelFooter"></div>
	*		</div>
	* Parameters: 
	* options - see { Astun.JS.UI.Panel }
	* 
	* Returns:
	* { HTMLElement | boolean } - Returns the inserted element or false if 
	* creation failed.
	*/
		'initialize': function( $super, options ) {
	
			$super( options );
			
			/* Private properties */
			var names = [ ];
			var labels = {};
			var contents = {};
			var current = null;
			var contentlist = new Element( 'dl' );
			this.elements.content.update( contentlist );
			
			var select = function( name ) {
				//basically placeholder in case want to allow custom select function 
				var element = contents[ name ]
				labels[ name ].addClassName( 'atSelected' );
				return element.show();
			}
			
			var deselect = function( name ) {
				//basically placeholder in case want to allow custom deselect function
				var element = contents[ name ]
				labels[ name ].removeClassName( 'atSelected' );
				return element.hide();
			}
			
			var updateClasses = function () {
			   /**
				* Function: updateClasses
				* Sets utility classes on items.
				*
				* Parameters: none
				*/	
				var upperBound = names.length - 1;
				
				for( var i = 0; i < names.length; i++ ) {					
					var label = labels[ names[ i ] ]; 
					var content = contents[ names[ i ] ]; 
					
					if( label.hasClassName( 'atFirst' ) ) {
						if( i !== 0 ) {
							label.removeClassName( 'atFirst' );
						}
					}
					else if (i === 0 ) {
						label.addClassName( 'atFirst' );
					}
					
					if( content.hasClassName( 'atFirst' ) ) {
						if( i !== 0 ) {
							label.removeClassName( 'atFirst' );
						}
					}
					else if (i === 0 ) {
						content.addClassName( 'atFirst' );
					}
					
					if( label.hasClassName( 'atLast' ) ) {
						if( i !== upperBound ) {
							label.removeClassName( 'atLast' );
						}
					}
					else if (i === upperBound ) {
						label.addClassName( 'atLast' );
					}
					
					if( label.hasClassName( 'atFirst' ) ) {
						if( i !== upperBound ) {
							label.removeClassName( 'atLast' );
						}
					}
					else if (i === upperBound ) {
						label.addClassName( 'atLast' );
					}
				}
			
			}
			
			/* Methods with access to private functions and properties */
			
			this.addItem = function( name, label, content ) {	
			   /**
				* Function: addItem
				* Adds some content with label to the panel.
				*
				* Parameters: 
				* name - { string } Unique identifier for item to add
				* label - { string | HTMLElement} This will be the identifier 
				*         for the content and used as the clickable item to 
				*         show the content.
				* content - { string | HTMLElement } HTML content to be shown.
				*/	
				if( label.blank() ||( names.length && names.indexOf( names ) !== -1 ) ) {
					return; // Can't add content with no name or with name that's already used.
				}
				names.push( name );
				
				var identifier = this.elements.main.id + '_' + name;
				var anchor = new Element( 'a', { 'name': identifier } ).update( label );
				anchor.id = identifier;
						
				var labelElement = new Element( 'dt' ).update( anchor );
				var contentElement = new Element( 'dd' ).update( content );
				
				labels[ name ] = labelElement;
				contents[ name ] = contentElement;				
				deselect( name );
				
				if( !current ) {
					current = name;
					this.switchTo( name );
				}
				
				var labelClick = function( evt ) {
					try {
						this.switchTo( evt.element().name.split( '_' ).pop() );
					}
					finally {
						Event.stop( evt );
					}
				}
				
				Event.observe( anchor, 'click', labelClick.bindAsEventListener( this ) );
				contentlist.insert( labelElement ).insert( contentElement );
				updateClasses();
			}
			
			this.removeItem = function( name ) {	
			   /**
				* Function: addItem
				* Adds some content with label to the panel.
				*
				* Parameters: 
				* label - { string } This will be the identifier for the content and 
				*         used as the clickable item to show the content.
				* content - { string | HTMLElement } HTML content to be shown.
				*/
				var i = names.indexOf( name );
				if( i === -1 ) {
					return; // Can't find name
				}
				names.splice( i, 1 );
				
				labels[ name ].remove();
				delete labels[ name ];
				
				contents[ name ].remove();
				delete contents[ name ];
				
				if( current === name && names.length ) {
					this.switchTo( names[ 0 ] );
				}
				updateClasses();
				
			}
			
			this.switchTo = function( name ) {
				if( current ) {
					deselect( current );
				}	
				select( name );
				current = name;
			}
			
			
		}, 
		'CLASS' : 'Astun.JS.UI.Panel.ContentSwitcher'
	}
 );


Astun.JS.UI.Panel.FindAddress = Class.create( 
	Astun.JS.UI.Panel, 
	{
   /**
	* Constructor: Astun.JS.UI.Panel.FindAddress
	* Create { Astun.JS.UI.Panel } with address search and selector.
	*
	* Parameters: 
	* options - see { Astun.JS.UI.Panel }
	* eventElement - { HTMLElement } the element on which to fire and check for 
	*		address	search events.  E.g. the mapElement property of an
	*		Astun.iShareMaps.OLMap control.
	* listSize = { Integer } how many results listed per page.
	*
	* Returns:
	* new instance of class.
	*/
		/*'initialize': function( $super, eventElement, listSize, options ) {
			options.title = options.title || 'Find address';
			listSize = listSize || 10;
			
			
			$super( options );
			
			var fieldname = 'atTxtSearch';
			var inputField;
			var goButton;
			var resultsList;
			var resultsNav;
			var resultsClear;
			var findPage = 0;
			var searchString;
			
			this.elements.content.update( 
				new Element( 'form', { 'method': 'get', 'action': 'atFindAddress.aspx' } ).update( 
						inputField = new Element( 'input', { 'type': 'text', 'id': fieldname, 'name': fieldname } ) 
					).insert(
						goButton = new Element( 'input', { 'type': 'submit', 'value': 'Go' } )
					)
			);
			
			this.elements.content.insert( 
				this.elements.results = new Element( 'div')
			);
			
			var goFindAddress = function( ) {
				searchString = inputField.value;
				eventElement.fire( 'astun:findAddress', { 'searchString': searchString, 'limit': listSize, 'offset': findPage * listSize } );
			}
			
			var writeResults = function( evt ) {
				var results = evt.memo.results;
				var countFirst = evt.memo.offset + 1;
				var countLast = evt.memo.offset + evt.memo.limit;
				if( resultsNav ) {
					resultsNav.update('');
				}
				( resultsList ) ? this.elements.results.update( resultsList.update('') ) : this.elements.results.insert( resultsList = new Element ( 'ol' ) );
				resultsList.setAttribute( 'start', countFirst );
				if( results.name === 'FindAddress' ) {
					var count = results.data.length;
					var addresses = results.data;
					for( var j = 0; j < results.columns.length; j++ ) {
						switch( results.columns[ j ] ) {
							case 'DisplayName': 
								var iName = j;
								break;
							case 'UniqueId': 
								var iUID = j;
								break;
							case 'X': 
								var iX = j;
								break;
							case 'Y': 
								var iY = j;
								break;
							default:
								break;
						}
					}
					
					for( var i = 0; i < count; ++i ) {
						var addressItem;
						var addressLink;
						resultsList.insert( 
							addressItem = new Element( 'li' ).update( 
								addressLink = new Element( 'a', { 'href': '#address' + i } ).update(
									addresses[i][iName] 
								)
							) 
						);
						Event.observe( addressLink, 'click', function( evt ) {
							var i = evt.element().href.split('#address')[1] - 0;
							Event.stop( evt );
							eventElement.fire( 'astun:setAddress', {
								'address': addresses[i][iName],
								'uid': addresses[i][iUID],
								'x': addresses[i][iX],
								'y': addresses[i][iY]								
							} );
							return false;
						}.bindAsEventListener( this ) );
					}
				}
				if( countFirst > 1 || evt.memo.more ) {
					this.elements.results.insert( resultsNav = new Element( 'ul' ) );		
					if( countFirst > 1) {
						var prevNav;
						resultsNav.insert( 
							prevNav = new Element( 'li' ).update( 
								new Element( 'a', { 'href': 'atFindAddress.aspx?searchString=' + searchString + '&amp;page=' + ( findPage - 1 ) } ).update( '< previous' ) 
							)
						);
						prevNav.addClassName( 'atResultsNavBackWard' );
						Event.observe( prevNav, 'click', function( evt ) {
							--findPage;
							goFindAddress( evt );
							Event.stop( evt );
						}.bindAsEventListener( this )  );
						
					}		
					if( evt.memo.more ) {
						var nextNav;
						resultsNav.insert( 
							nextNav = new Element( 'li' ).update( 
								new Element( 'a', { 'href': 'atFindAddress.aspx?searchString=' + searchString + '&amp;page=' + ( findPage + 1 ) } ).update( 'next >' ) 
							)
						);
						nextNav.addClassName( 'atResultsNavForward' );
						Event.observe( nextNav, 'click', function( evt ) {
							++findPage;
							goFindAddress( evt );
							Event.stop( evt );
						}.bindAsEventListener( this )  );
						
					}
				}
				var clearLink;
				this.elements.results.insert( resultsClear = new Element( 'p' ).update( clearLink = new Element( 'a', { 'href': '#' } ).update( 'Clear results' ) ) );
				
				Event.observe( clearLink, 'click', function( evt ) {
					this.elements.results.update( '' );
					Event.stop( evt );
				}.bindAsEventListener( this )  );
				
			}
						
			Event.observe( eventElement, 'astun:addressesFound', writeResults.bindAsEventListener( this ) );
			Event.observe( eventElement, 'astun:addressesNotFound', function( evt ) {
				this.elements.results.update(
					new Element( 'p' ).update(
						'No results found.'
					)	
				);
			}.bindAsEventListener( this ) );
			
			Event.observe( goButton, 'click', function( evt ) {
				findPage = 0;
				goFindAddress();
				Event.stop( evt );
			} );
			
			var inputAddress = function ( evt ) {
				var keynum = evt.keyCode || evt.which;
				if ( keynum == 13 ) { 
					Event.stop( evt );
				}
				else {
					clearTimeout( this.inputTimeout );
					this.inputTimeout = setTimeout( function(){
						findPage = 0;
						goFindAddress();				
					}, 250 );
				}
				if( evt.stopPropogation ) {
					evt.stopPropogation();
				}
				else {
					evt.cancelBubble = true;
				}
			}
			
			Event.observe( inputField, 'keydown', inputAddress.bindAsEventListener( this ) );
			//Event.observe( inputField, 'keypress', function( evt ) { Event.stop( evt ) } );
			
		}, 
		'CLASS': 'Astun.JS.UI.Panel.FindAddress'
		*/
	}
 );
 
 
 
Astun.JS.UI.Panel.FaultLayers = Class.create( 
	Astun.JS.UI.Panel, 
	{
   /**
	* Constructor: Astun.JS.UI.Panel.FaultLayers
	* Create { Astun.JS.UI.Panel } with fault layer selector.
	*
	* Parameters: 
	* options - see { Astun.JS.UI.Panel }
	* eventElement - { HTMLElement } the element on which to fire and check for 
	*		address	search events.  E.g. the mapElement property of an
	*		Astun.iShareMaps.OLMap control.
	* 
	* Returns:
	* new instance of class.
	*/
		'initialize': function( $super, eventElement, options ) {		
			
			$super( options );
			Event.observe( eventElement, 'astun:faultlogger_layersupdated', function( evt ) {
				var layers = evt.memo;
				this.setLayers( layers.list, layers.current );
			}.bindAsEventListener( this ) );
			this.eventElement = eventElement;
		},
		'setLayers': function( faults, current ) {
		   /**
			* Function: setLayers
			* Updates the fault layers list and sets up events
			* 
			* Parameters:
			* fault - { Array } list of available layers.
			* 
			* Returns:
			* { HTMLElement } the panel object
			*/
			var list = true;		
			var layers;
			
			if( current.layerName ) {
				this.elements.title.update( 'You are reporting on:' );
			}			
			else {
				this.elements.title.update( 'Select category to report:' );
			}
			
			if( faults.length > 1 ) {
				this.elements.content.update( layers = new Element( 'ul' ) );
			}
			else {
				this.elements.content.update( layers = new Element( 'p' ) );
			}
			
			
			for ( var i=0; i < faults.length;i++ )
			{
				var layer, image, imagelink, name, namelink;
				var faultLayer = faults[i]
				
				if( faults.length > 1 ) {
					layers.insert( layer = new Element( 'li' ) );
				}
				else {
					layer = layers;
				}
				
				
				
				layer.update( image = new Element( 'img', { 'src': faults[i].iconImage } ) );
				layer.insert( name = new Element( 'span' ).update( faults[i].displayName ) );
				if( faultLayer != current ) {
					var href = document.location.toString().replace( /type=.*[&$](.*)/ig, '$1' );
					href += ( href.indexOf('?') > 0 ) ? '&' : '?' ;
					href += 'type=' + faultLayer.layerName;
					layer.insert( imageLink = new Element( 'a', { 'href': href } ) );
					layer.insert( nameLink = new Element( 'a', { 'href': href } ) );
					imageLink.update( image );
					nameLink.update( name );
					
					var setLayer = function( faultLayer, index, evt ) {
						this.eventElement.fire( 'astun:faultlogger_setlayer', { 
							'name':  faultLayer.layerName.replace( ' ', '_' ), 
							'index': index } 
						);
						if( evt ) {
							Event.stop(evt);
						}
					}
						
					Event.observe( imageLink, 'click', setLayer.curry( faultLayer, i ).bindAsEventListener( this ) );					
					Event.observe( nameLink, 'click', setLayer.curry( faultLayer, i ).bindAsEventListener( this ) );
					
				};
				
			}				
		},
		'CLASS': 'Astun.JS.UI.Panel.FaultLayers'
	}
 );
 
 Astun.JS.UI.Panel.MapLayers = Class.create( 
	Astun.JS.UI.Panel, 
	{
   /**
	* Constructor: Astun.JS.UI.Panel.MapLayers
	* Create { Astun.JS.UI.Panel } with layer list for configuring OL map 
	* layers.
	* Initially this just controls ISM data layers and just duplicates the work
	* that was done by the similar layer in old-ISM map.
	*
	* Example: 
	*		<div class="atPanel atPanelMapLayers">
	*			<div class="atPanelHeader">
	*				<h3>Panel header</h3>
	*			</div>
	*			<div class="atPanelContent">
	*				<dl class="atLayerControlPanel">
	*					<dt>[ label ]</dt>
	*					<dd>[ text ]</dd>
	*					...
	*           </div>
	*			<div class="atPanelFooter"></div>
	*		</div>
	* Parameters: 
	* options - see { Astun.JS.UI.Panel }
	* 
	* Returns:
	* { HTMLElement | boolean } - Returns the inserted element or false if 
	* creation failed.
	*/
		'initialize': function( $super, eventElement, options ) {
	
			$super( options );
			this.eventElement = $(eventElement);
		    this.layers = $H({});
		    this.groups = $H({});
		    Event.observe(this.eventElement, 'astun:sourceLoad', this.loadLayers.bindAsEventListener(this));
		
	    }, 
	    /**
	     * Method: setFindNearestLayer
	     * Sets image to indicate on which layer Find Nearest has been performed,
	     * resets image on previous Find Nearest layer (if any) and shows layer if hidden.
	     */
	    setFindNearestLayer : function ( element ) {
	      
		    this.resetFindNearestLayer();
		    element.findNearest = true;
		    element.queryIcon.update( new Element('img', {'src': 'images/atLayerNoQuery.png'}));
		    if (!element.layerObject.currentlyVisible) {
			    element.layerObject.toggle();
		    }
		    this.findNearestLayer = element;
	    },
	
        /**
	     * Method: resetFindNearestLayer
	     * Resets image on previous Find Nearest layer (if any).
         */
	    resetFindNearestLayer : function ( ) {
		    if (!this.findNearestLayer) {
			    return;
		    }
		    var element = this.findNearestLayer;
		    this.findNearestLayer = null;
		    element.findNearest = false;	
		    element.queryIcon.update( new Element('img', {'src': 'images/atLayerQuery.png'}));
		
	    },
        /**
       	 * Method: loadLayers 
	     * Loads layer information into the panel and sets up user interface.
	     * 
	     * Parameters:
	     * event - {event} that called this function.
         */
	    loadLayers: function ( event ) {
	        
	        var panelFade = new Fx.Opacity(this.elements.content,{duration: 1000});
            panelFade.hide();
		    this.elements.content.setStyle( { 'height': '0px', 'overflow': 'hidden' } );
		
		    var setFindNearestLayerEvent = function ( event ) {
			    /*
			     * Wrapper for showFindNearestLayer, identifies the Find Nearest layer
			     */
			    if (!(event.memo.type === 'findNearest') || !event.memo.displayName) {
				    return;
			    }
			    var findNearestName = event.memo.displayName
			    var layerElement = this.layers.find(
			    function (l){
				    return l.value.layerObject.displayName == findNearestName;
			    }
			    );
			    if (layerElement) {
				    this.setFindNearestLayer (layerElement.value);
			    }
			    else {
				    alert('Layer: "' + findNearestName + '" not found.');
			    }
		    }
		
		
		    var resetFindNearestLayerEvent = function ( event ) {
			    if (!event.memo.type === 'findNearest') {
				    return;
			    }
			    this.resetFindNearestLayer();
		    }
		
		
		    var showHideLayer = function ( element ) {
			
			    var layerObject = element.layerObject;
			
			    var showHideTemplate = new Template (
				    'Click to #{action} #{layer} on the map'
			    );
			
			    var findMyNearestTemplate = new Template (
				    'Show the nearest #{results} #{layer} within #{distance} #{measurement}'
			    );
			
			    var showMyTemplate = new Template (
				    'Show my #{layer}'
			    );
			
			    var templateFields = {
				    action: (layerObject.currentlyVisible) ? 'hide' : 'show',
				    layer: layerObject.displayName,
				    results: (layerObject.query.findNearest) ? layerObject.query.findNearest.maxResults : '',
				    distance: (layerObject.query.findNearest) ? layerObject.query.findNearest.distance : '',
				    measurement: 'metres'
			    }
		
			    if (layerObject.query.showMy) {
				    var queryTemplate = showMyTemplate;
			    } 
			    else {
				    var queryTemplate = findMyNearestTemplate;
			    }
						
			    var showHideTitle = showHideTemplate.evaluate(templateFields);
			
			    element.textLink.setAttribute('title', showHideTitle);
			    element.layerIcon.setAttribute('title', showHideTitle);
			    element.queryIcon.setAttribute('title', queryTemplate.evaluate(templateFields))
			
		
			    if (element.layerObject.currentlyVisible) {
				    element.addClassName('atLayerShown');
			    }
			    else {
				    element.removeClassName('atLayerShown');
			    }
			
		
		    }
		
		
		    var updateLayerGroupEvent = function ( evt ) {
			    // event observer for updateLayerGroup function
			    updateLayerGroup(this.groups.get(evt.memo.layerGroup.displayName));				
		
		    }
		
		    var updateLayerGroup = function ( element ) {			
				var action = 'show';
			    if (element.groupObject.activeLayers.length  === element.groupObject.activeShown) {
				    element.addClassName('atAllLayersShown');
				    element.removeClassName('atNoLayersShown');
				    if (element.collapsed) {
					    element.expandCollapse();
				    }
				    action = 'hide';
			    } 
			    else if( element.groupObject.activeShown === 0 ) {
				    element.removeClassName('atAllLayersShown');
			        element.addClassName( 'atNoLayersShown' );
			    }
			    else { 
				    element.removeClassName('atAllLayersShown');
				    element.removeClassName('atNoLayersShown');
			    }
			    element.layersIcon.writeAttribute('title', 'Click to ' + action + ' all ' + element.groupObject.displayName + ' layers on the map');
		
		    }
		
		    var updateLayerEvent = function ( event ) {
			    // event observer for updateLayer function
			    updateLayer(this.layers.get(event.memo.layer.layerName));				
		
		    }
		
		    var updateLayer = function ( element ) {
			    showHideLayer(element);
			    updateLayerGroup(element.groupElement);
		    }
		
		
		    
		
		    var groupClick = {};
		
		    groupClick.toggleLayers = function( evt ) {
			    // {this} is bound to the groupElement DOM object, which has a 
			    // custom property referring back to the layer control's group object 
			    if (this.groupObject.activeLayers.length === this.groupObject.activeShown) {
				    this.groupObject.hideLayers();
			    }
			    else {
				    this.groupObject.showLayers();
			    }
			
			    Event.stop(evt);	
			    return false;			
		    }
		
		
		
		    var layerClick = {} 
		
		    layerClick.toggle = function( evt ) {
			    // {this} is bound to the layerElement DOM object, which has a 
			    // custom property referring back to the layer control's layer object
			    this.layerObject.toggle();
			    Event.stop( evt );		
			    return false;	
		    }
		
		    layerClick.query = function( evt ) {
			    // {this} is bound to the layerElement DOM object, which has a 
			    // custom property referring back to the layer control's layer object
			    this.groupElement.panel.eventElement.fire("astun:layerQuery", {layer: this.layerObject, reset : this.findNearest});
			    Event.stop( evt );		
			    return false;				
		    }
		
		
		    var makeLayerGroup = function (panel, parentElement, groupObject, click ) {
		
			    var groupElement = new Element('dt')
			    groupElement.addClassName( 'atLayerControlPanelGroup' );
			    groupElement.panel = panel;
			
			    var action = (groupObject.activeLayers.length === groupObject.activeShown) ? 'hide' : 'show';
			
			    var layersIconLink = new Element('a', {
				    'href': ' '		
			    });
			    
			    layersIconLink.addClassName( 'atGroupLayersIcon' );		
			    
			    var displayIconLink = new Element('a', {'href': ' ' } );
                displayIconLink.addClassName( 'atGroupDisplayIcon' );
			    var displayNameLink = new Element('a', {'href': ' ' } );
			    displayNameLink.addClassName( 'atGroupLabel' );
			
			    displayNameLink.update( groupObject.displayName );
			    groupElement.update(layersIconLink).insert(displayNameLink).insert(displayIconLink);
			    
			    groupElement.expandCollapse = function( evt, collapseTime ) {
			    
			        if( evt ) {
			            evt.stop();
			        }
			        
			        if( !collapseTime && collapseTime !== 0 ) {
			            collapseTime = 350
			        }
		
			        var collapsedClass = 'atGroupCollapsed';
			
			        this.groupLayers.each( function ( layerElement ) {
				        var heightFx = new Fx.Height(layerElement,{duration: collapseTime});
				        heightFx.toggle();	
				        layerElement.expandCollapse( collapseTime );		        
			        });
			
			        if (!!this.collapsed) {
				        this.removeClassName(collapsedClass);
				        this.collapsed = false;
			        }
			        else {
				        this.addClassName(collapsedClass);
				        this.collapsed = true;				
			        }
			        var openGroups = [];
			        this.panel.groups.each(function(group){
				        if (!group.value.collapsed){
					        openGroups.push(group.key);
				        }				
			        });

			       this.panel.eventElement.fire('astun:saveSetting', {'setting': 'layerPanelOpenGroups', 'value': openGroups.toJSON()});
			       return false;
			
		        }
			    
			
			    Event.observe(layersIconLink, 'click', click.toggleLayers.bindAsEventListener(groupElement));
			    Event.observe(displayIconLink, 'click', groupElement.expandCollapse.bindAsEventListener(groupElement) );
			    Event.observe(displayNameLink, 'click', groupElement.expandCollapse.bindAsEventListener(groupElement) );
			    
			    parentElement.appendChild(groupElement);
			    groupElement.layerParent = parentElement;
			    groupElement.layersIcon = layersIconLink;
			
			    return groupElement;
			
		    }
		
		    var makeLayer = function ( parentElement, layerObject, layerClick, alt, first, last ) {
			
			    var collapsedClass = 'atGroupCollapsed';
			    var layerElement = new Element('dd');
			    layerElement.addClassName( 'atLayerControlPanelLayer' );
			    layerElement.addClassName( 'atAlt' + alt );
			    if( first ) {
			        layerElement.addClassName( 'atFirst' );
			    }
			    if( last ) {
			        layerElement.addClassName( 'atLast' );
			    }
			
			    var textLink = new Element('a', {'href': ' ' });
			    textLink.addClassName( 'atLayerLabel' );
			    textLink.update( layerObject.displayName );
			
			    var layerIcon = new Element('a', {'href': ' ' } );
			    layerIcon.addClassName( 'atLayerIcon' );
			    layerIcon.update(new Element('img', {'src': layerObject.icon}));
			
			    Event.observe(layerIcon, 'click', layerClick.toggle.bindAsEventListener(layerElement));
			    Event.observe(textLink, 'click', layerClick.toggle.bindAsEventListener(layerElement));
			
			    if (layerObject.query && (layerObject.query.bufferSearch || layerObject.query.showMy || layerObject.query.findNearest)) {
				    var queryIcon = new Element('a', {'href': ' ' });
				    queryIcon.addClassName( 'atQueryIcon' );
				    queryIcon.update( new Element('img', {'src': 'images/atLayerQuery.png'}));
				    Event.observe(queryIcon, 'click', layerClick.query.bindAsEventListener(layerElement));
			    }
			    else {
				    var queryIcon = new Element('span').update( 
				        new Element('img', {'src': 'images/atLayerNAQuery.png'})
				    );
				    queryIcon.addClassName( 'atQueryIcon' );
			    }
			
			    layerElement.update(new Element('p').update(queryIcon).insert(layerIcon).insert(textLink));
			
			    layerElement.queryIcon = queryIcon;
			    layerElement.layerIcon = layerIcon;
			    layerElement.textLink = textLink;
			
			    layerElement.expandCollapse = function ( delay ) {
			        if (!!this.collapsed) {
				        this.removeClassName(collapsedClass);
				        this.collapsed = false;
			        }
			        else {
				        setTimeout( function() { this.addClassName(collapsedClass) }.bindAsEventListener( this ), delay );
				        this.collapsed = true;				
			        }
			    }
			
			
			    parentElement.appendChild(layerElement);
			    return layerElement;
			
		    }
		
		
		    this.layerControl = event.memo.layerControl;
		    var groupParentElement = new Element('dl');
		    groupParentElement.addClassName( 'atLayerControlPanel' );
		
		    var loadGroups = function( uncollapsedSetting ) {
		        
				var uncollapsed = (!!uncollapsedSetting) ? uncollapsedSetting.evalJSON() : false;
			    
		        for ( var groupId = 0, groupLimit = this.layerControl.layerGroups.length; groupId < groupLimit; ++groupId ) {
			        var groupObject = this.layerControl.layerGroups[groupId];
			        if (groupObject.activeLayers.length) {
				        var groupElement = makeLayerGroup(this, groupParentElement, groupObject, groupClick);
				        var layerParent = groupElement.layerParent;
				        groupElement.groupLayers = $A([]);
				        for (var layerId = 0, layerLimit = groupObject.activeLayers.length; layerId < layerLimit; ++layerId) {
					        var layerObject = groupObject.activeLayers[layerId];					
					        var layerElement = makeLayer(
					            layerParent, 
					            layerObject, 
					            layerClick, 
					            layerId % 2, 
					            layerId === 0, 
					            layerId === layerLimit - 1
					        );
					        layerElement.groupElement = groupElement;
					        layerElement.layerObject = layerObject;
					        groupElement.groupLayers.push(layerElement);
					        this.layers.set(layerObject.layerName, layerElement);
					        showHideLayer(layerElement);
					
				        }
				        this.groups.set(groupObject.displayName, groupElement);
				        groupElement.groupObject = groupObject;
				        updateLayerGroup( groupElement );
			        }
		        }  
		        if (!!this.groups.size > 0) {			
			
			        this.elements.content.update(groupParentElement);
			        var collapsed = $A( this.groups.keys() );
			        
			        setTimeout( function () {
			            if( uncollapsed && uncollapsed.length > 0 ) {
				            uncollapsed.each( function( g ){
					            collapsed = collapsed.without( g );
				            }.bind(this) );
				        }
				        
			            collapsed.each( function( groupName ) {
			                var ge = this.groups.get( groupName );
			                ge.expandCollapse( false, 0 );
			            }.bind( this ) );
			            
				        setTimeout( function() { 
				            this.elements.content.setStyle( { 'height': 'auto' } );
		                    panelFade.toggle();
				        }.bindAsEventListener( this ), 350 );
				    }.bindAsEventListener( this ), 0 );
					    
			        Event.observe(this.eventElement, 'astun:layerShow', updateLayerEvent.bindAsEventListener(this));
			        Event.observe(this.eventElement, 'astun:layerHide', updateLayerEvent.bindAsEventListener(this));
			        Event.observe(this.eventElement, 'astun:showLayerGroup', updateLayerGroupEvent.bindAsEventListener(this));
			        Event.observe(this.eventElement, 'astun:hideLayerGroup', updateLayerGroupEvent.bindAsEventListener(this));
			        Event.observe(this.eventElement, 'astun:resultsReceived', setFindNearestLayerEvent.bindAsEventListener(this));
			        Event.observe(this.eventElement, 'astun:resultsCleared', resetFindNearestLayerEvent.bindAsEventListener(this));
			        
			        
				    
		        }
		        else {
			        this.elements.content.update('<p>No layers of type <strong>' + this.LAYERTYPES + '</strong> found.')
		        }
		    }
		    this.loadTimer = setTimeout( function() { 
		        this.eventElement.fire('astun:loadSetting', {
			        setting: 'layerPanelOpenGroups',
			        loadFunction: loadGroups.bind(this)
		        });
		    }.bind( this ), 0);
		
		  
	    },
		'LAYERTYPES' : 'base',
		'CLASS' : 'Astun.JS.UI.Panel.MapLayers'
	}
 );
 
 
Astun.JS.UI.Panel.FindNearest = Class.create( 
	Astun.JS.UI.Panel, 
	{
   /**
	* Constructor: Astun.JS.UI.Panel.FindNearest
	* Create { Astun.JS.UI.Panel } with find nearest controls.
	*
	* Parameters: 
	* options - see { Astun.JS.UI.Panel }
	* eventElement - { HTMLElement } the element on which to fire and check for 
	*		address	search events.  E.g. the mapElement property of an
	*		Astun.iShareMaps.OLMap control.
	* 
	* Returns:
	* new instance of class.
	*/
		'initialize': function( $super, eventElement, distances, results, options ) {		
			
			$super( options );
		    this.eventElement = $( eventElement );
		    this.distances = distances || [ '250~250m','500~0.5km','1000~1.0km','2500~2.5km' ];
		    this.results = results || [ '2~Two results','5~Five results','10~Ten results','25~Twenty five results','50~Fifty results' ]
		    this.layers = $H( {} );
		    this.pseudoLayer = {
			    'layerName': '',
			    'displayName': '',
			    'query': {
				    'findNearest': {
					    'distance': 0,
					    'maxResults': 0
				    }
			    }
		    }
		    Event.observe( this.eventElement, 'astun:sourceLoad', this.loadLayers.bindAsEventListener( this ) );
		
	    },
	    loadLayers: function ( evt ) {	
	
		    var loadLayers = {};
		
		    this.layerControl = evt.memo.layerControl;
		    var parentElement = new Element( 'fieldset', {'id': 'atFindNearestPanel'} );		
		    var This = this;
		    var layerSelector = Class.create ( 
		    {
			    initialize: function ( className ) {
				    this.layers = $H( {} );
				    this.element = new Element( 'select' );
				    if ( className ) {
					    this.element.addClassName( className );
				    }
			    },
			    addLayer: function ( layerName, displayName ) {
				    var option = new Element( 'option', {'value': layerName} ).update( displayName );
				    if ( !this.layers.keys( ).length ) {
					    option.writeAttribute( {'selected': 'selected'} );
				    }
				    this.layers.set( layerName, option );
				    this.element.insert( option );
				
			    },
			    removeLayer: function ( layerName ) {
				    this.layers.get( layerName ).remove( );
				    this.layers.remove( layerName );
			    }
		    } )
		
		    var distanceSelector = Class.create( 
		    {
			    initialize: function ( distances, className ) {
				    /*
				     * distances {String} expected to be array, 
				     * with items of the form '[ distance ]~[ displayText ]'
				     */
				    this.element = new Element( 'select' );	
				    this.distances = $H( {} );
				    while ( distances.length ) {
					    var d = distances.shift( ).split( '~' ); 
					    this.addDistance( d[ 0 ], d[ 1 ] );
				    }
				    if ( className ) {
					    this.element.addClassName( className );
				    }
			    },
			    addDistance: function( distance, displayText ) {
				    var option = new Element( 'option', {'value': distance} ).update( displayText );
				    if ( !this.distances.keys( ).length ) {
					    option.writeAttribute( {'selected': 'selected'} );
				    }
				    this.distances.set( distance, option );
				    this.element.insert( option );
			    },
			    removeDistance: function( distance ) {
				    this.distances.get( distance ).remove( );
				    this.distances.remove( distance );
			    }
		    } )
		
		
		    var resultsSelector = Class.create( 
		    {
			    initialize: function ( results, className ) {
				    /*
				     * results {String} expected to be array, 
				     * with items of the form '[ results ]~[ displayText ]'
				     */
				    this.element = new Element( 'select' );
				    this.results = $H( {} );
				    while ( results.length ) {
					    var d = results.shift( ).split( '~' ); 
					    this.addResult( d[ 0 ], d[ 1 ] );
				    }
				    if ( className ) {
					    this.element.addClassName( className );
				    }		
			    },
			    addResult: function( result, displayText ) {
				    var option = new Element( 'option', {'value': result} ).update( displayText );
				    if ( !this.results.keys( ).length ) {
					    option.writeAttribute( {'selected': 'selected'} );
				    }
				    this.results.set( result, option );
				    this.element.insert( option );
			    },
			    removeResult: function( result ) {
				    this.results.get( result ).remove( );
				    this.results.remove( result );
			    }
		    } )
		
		    var ls = new layerSelector( 'layerList' );
		    for ( 
			    loadLayers.groupId = 0, loadLayers.groupMax = this.layerControl.layerGroups.length; 
			    loadLayers.groupId < loadLayers.groupMax; 
			    ++loadLayers.groupId 
		    ) {
			    var groupObject = this.layerControl.layerGroups[ loadLayers.groupId ];
			    for ( 
				    loadLayers.layerId = 0, loadLayers.layerMax = groupObject.activeLayers.length; 
				    loadLayers.layerId < loadLayers.layerMax; 
				    ++loadLayers.layerId 
			    ) {
				    var layerObject = groupObject.activeLayers[ loadLayers.layerId ];
				    if ( layerObject.query.findNearest ) {
					    ls.addLayer( layerObject.layerName, layerObject.displayName );
				    }
			    }
		    }
		    if ( ls.layers.size( ) > 0 ) {
			    Event.observe( 
				    ls.element, 
				    'change', 
				    function( ){
					    This.pseudoLayer.layerName = ls.element.value;
					    This.pseudoLayer.displayName = ls.layers.get( ls.element.value ).innerHTML;
				    }
			     );
			    var ds = new distanceSelector( this.distances, 'distances' );
			    Event.observe( 
				    ds.element, 
				    'change', 
				    function( ){
					    This.pseudoLayer.query.findNearest.distance = ds.element.value;
				    }
			     );
			    var rs = new resultsSelector( this.results, 'resultsCount' );
			    Event.observe( 
				    rs.element, 
				    'change', 
				    function( ){
					    This.pseudoLayer.query.findNearest.maxResults = rs.element.value;
				    }
			     );
			    var go = new Element( 'input', {'type': 'submit', 'className': 'atFormInputSubmit', 'value': 'Go'} );
			    Event.observe( 
				    go, 
				    'click', 
				    function( evt ){
					    this.eventElement.fire( "astun:layerQuery", {'layer': This.pseudoLayer} );
					    Event.stop( evt );
					    return false;
				    }.bindAsEventListener( this )
			     );
			    var reset = new Element( 'p' );
			    var resetLink = new Element( 'a', {'href': '#atResetQuery'} );
			    reset.id = 'atResetQuery';
			    resetLink.update( 'Clear current query results' );
			    reset.update( resetLink );
			    Event.observe( 
				    reset, 
				    'click', 
				    function( evt ){
					    this.eventElement.fire( "astun:layerQuery", {'reset': true} );
					    Event.stop( evt );
					    return false;
				    }.bindAsEventListener( this )
			     );
			    this.pseudoLayer.layerName = ls.element.value;
			    this.pseudoLayer.displayName = ls.layers.get( ls.element.value ).innerHTML;
			    this.pseudoLayer.query.findNearest.distance = ds.element.value;
			    this.pseudoLayer.query.findNearest.maxResults = rs.element.value;
			    this.resetText = reset;
			    this.resetText.hide( );
			    this.elements.content.update( reset ).insert( new Element('p').update( ls.element ).insert( rs.element ).insert( ds.element ).insert( go ) );
		    } 
		    else {
			
			    this.elements.content.update( "<p>No layers with Find Nearest settings defined.</p>" );
		    }
		
		    var setFindNearestLayerEvent= function( evt ) {	
		        if( evt.memo.type === 'findNearest' ) {	
			        this.resetText.show( );
			    }
		    }
		    var resetFindNearestLayerEvent= function( evt ) {	
		        if( evt.memo.type === 'findNearest' ) {	
			        this.resetText.hide( );
			    }
		    }
		    Event.observe( this.eventElement, 'astun:resultsReceived', setFindNearestLayerEvent.bindAsEventListener( this ) );
		    Event.observe( this.eventElement, 'astun:resultsCleared', resetFindNearestLayerEvent.bindAsEventListener( this ) );
		
		    loadLayers = null;
	    },
		'CLASS': 'Astun.JS.UI.Panel.FindNearest'
	}
 );

 

