if (Garmin == undefined) var Garmin = {};
if (Garmin.Foto == undefined) Garmin.Foto = {};


/** PhotoLoader is a Garmin Communicator - Google Maps - Photo Service mashup.
 * It allows users to:
 * <ol>
 * <li>Jump to a map location given a description (ie Eiffel Tower)</li>
 * <li>Display geocoded photos from that location using photo service APIs.</li>
 * <li>Filter and scroll through available pictures.</li>
 * <li>Save located pictures to a mass-storage Garmin device (ie Nuvi or Zumo)</li>
 * <li>The user can then view a picture on the device or navigate to where the picture was taken.</li>
 * </ol>
 * 
 * @class Garmin.Photo.PhotoLoader
 * @constructor 
 */
Garmin.Foto.PhotoLoader = Class.create();
Garmin.Foto.PhotoLoader.prototype = {
	
	// username attribute to later be received through examination of the param
	// should be set to null here if using iframe, and the getParam call should be uncommented
	username: ($("user").innerHTML == "") ? null : $("user").innerHTML,
	// param passed into the ext template.  used to conditionally show if the user
	// is signed in (show welcome) else (show sign in form)
	signInParam: null,
	// param passed into the ext template.  used to conditionally show if the user
	// is has communicator plugin installed
	communicatorParam: null,
	// sets path so api doesn't have fragile source paths
	imagePath: $("imagePath").innerHTML,
	initialize: function(map, paginator) {
		// initiate quicktips
		Ext.QuickTips.init();
	        
		//ui
		this.map = map;
		this.paginator = paginator;
		this.map.loader = this;
		
		// b/c of how gc is handling username as a jsf param with a ui:include, we're not
		// parsing params.  still useful function, uncomment when using iframe		
//		this.getParam('username');
		this.setTemplateValues();
		
       	       	
       	// we need to close the info window on google map on zoom
       	// because we are placing the window with an offset based
       	// on lat lng which gets screwed up once we zoom
       	GEvent.addListener(this.map, "zoomend", function(event){
       		if (!this.map.getInfoWindow().isHidden()) {
       			this.map.closeInfoWindow();
       			if (this.getCurrentPhoto() != null) {
       				var marker = this.getPhotoAPI().lookupOverlay(this.getCurrentPhoto());
       				this.getPhotoAPI().showInfoWindow(marker);
       			}
       		}
       	}.bind(this));
		Event.observe($("location"), "keypress", this.onKeyPressLocation.bindAsEventListener(this));
		this.geocoder = new Garmin.Geocode();
		this.geocoder.register(this);
		this.kmlWriteMode = false;
		this.resetPhotoPages = true;

		this.devicePhotoPath = "Garmin/JPEG/";
		this.photoKeepers = $H({}); //prototype Hash instance
		this.infoWindowTemplate = new Template(
			"<div id='infowin' style='height:#{panelHeight}px; width: #{width}px'>" +
				"<a href='http://www.panoramio.com/' target='_blank'>" +
					"<img src='"+this.imagePath+"/panoramio_logo_small.png' alt='Panoramio' />" +
				"<\/a>" +
				"<a id='photo_infowin' target='_blank' href='#{photoPageURL}'>" +
					"<img width='#{width}' " +
					"height='#{height}' " +
					"src='#{photoURL}' " +
					"title='#{photoURL}' " +
					"alt='#{title}' />" +
				"<\/a>" +
				"<div style='overflow: hidden; width: #{width}px;'>" +
					"<span style='float: right;'>" +
						"<input onclick='loader.addSinglePhoto(#{id})' " +
						"id='saveToDevice' " +
						"type='submit' " +
						"name='submit' " +
						"value=' ' />" +
					"<\/span>" +
					"<p id='mapPhotoTitle'>" +
						"<span>#{title}<\/span>" +
					"</p>" +
					"<p id='mapPhotoAuthor'>" +
						"<span>" + bundle_photos.postedBy + " </span>" + 
						"<a target='_blank' href='#{ownerURL}'>#{ownerName}<\/a>" +
					"<\/p>" +
				"<\/div>" +
			"<\/div>"
		);
		
		this.previewPhotoTemplate = new Template(
			"<div class='widget_w'><div class='widget_content'>" + 
				"<div id='previewPhotoImgDiv'>" +
					"<div id='innerPreviewPhotoImgDiv'>" +
						"<a id='photoPreviewLink' target='_blank' href='#{photoPageURL}'>" +
							"<img id='photoPreviewImage' " +
							"src='#{photoURL}' " +
							"alt='#{title}' " +
							"title='#{photoURL}' \/>" +
						"<\/a>" +
						"<div id='photoImageAuthor'>" + bundle_photos.postedBy + 
						" <a target='_blank' href='#{ownerURL}'>#{ownerName}<\/a><\/div>" +
					"<\/div>" +
				"<\/div>" + 
				"<div id='photoTitleDiv'>" +
					"<input id='titleEditText' type='text' size='40' maxlength='35' value='#{title}' \/>" +
				"<\/div>" +
				"<div>" +
					"<input id='changeLocationButton' title='" + bundle_photos.tooltips_move + 
					"' class='smallButton' alt='" + bundle_photos.photoConnectUI_drag + 
					"' type='button' value='" + bundle_photos.changeLocationButton + 
					"' onclick='loader.doMovePhoto()' \/>&nbsp;" +
					"<input id='removeButton' title='" + bundle_photos.tooltips_remove + 
					"' class='smallButton' type='button' value='" + bundle_photos.removeButton + 
					"' onclick='loader.doRemovePhoto()' \/>" + 
				"<\/div>" +
			"<\/div><\/div>"
		);
		
		this.kmlPlacemarkTemplate = new Template(
		  "<Placemark>\n" +
			"<name>#{title}</name>\n" +
			"<styleUrl>#Icon_#{id}</styleUrl>\n" +
			"<description>" + bundle_photos.postedBy + " #{ownerName}</description>\n" +
			"<Point>" +
				"<coordinates>#{lng},#{lat}</coordinates>" +
			"</Point>\n" +
		  "</Placemark>\n"
		);
		
		this.kmlIconStyleTemplate = new Template(
		  "<Style id='Icon_#{id}'>\n" + 
		    "<BalloonStyle>\n" +
		    	"<text><![CDATA[" +
			  		"<p><a href='http://www.panoramio.com/' target='_blank'>" +
			  			"<img src='http://www.panoramio.com/img/logo-small.gif' " +
			  			"width='119px' height='25px' alt='Panoramio logo' \/>" +
			  		"</a></p>" +
					"<p><a id='photo_infowin' target='_blank' href='#{photoPageURL}'>" +
						"<img width='#{width}' height='#{height}' src='#{photoURL}'/>" +
					"</a></p>" +
					"<p><span><strong>#{title}</strong></span><br />" +
					"<span>" + bundle_photos.postedBy + 
					" <a target='_blank' href='#{ownerURL}'>#{ownerName}</a></span></p>" +
					"<p><a target='_blank' href='http://www.panoramio.com/upload/'>" + 
					bundle_photos.photoConnectUI_upload + ">></a></p>" +
		    	"]]></text>\n" +
		    "</BalloonStyle>\n" +
		  	"<IconStyle>\n" +
				"<scale>0.5</scale>\n" +
				"<Icon><href>#{iconURL}</href></Icon>\n" +
		  	"</IconStyle>\n" +
			"<LabelStyle>\n" +
				"<color>00ffffff</color>\n" + 
			"</LabelStyle>\n" + 
		  "</Style>\n"
		);

		this.setPhotoAPI( new Garmin.Foto.PanoramioPhotoAPI(this.map, this.infoWindowTemplate) );
		this.currentPhoto = null;
		this.PhotoRecordDef = Ext.data.Record.create([
			{name: 'id'},
			{name: 'title'},
			{name: 'photoURL'},
			{name: 'width', type: 'int'},
			{name: 'height', type: 'int'}
		]);
		var photoReader = new Ext.data.JsonReader({
			root: "photos", 
			id: "id"
		}, this.PhotoRecordDef);	//PROBABLY DON'T NEED THIS - totalProperty: "count",
		this.store = new Ext.data.Store({reader:photoReader});
		
		/**
		 * template to inject html onto the page for the getting started area of photos 
		 * page when the user first looks at the page.
		 * template has two conditional params, to show whether the user is signed in, 
		 * and to show if the user has communicator plugin.
		 */
		this.startTemplate = new Template(
			"<div id='gettingStartedDiv'>" +
		    	"<div id='gettingStartedTitle'>" + bundle_photos.help_start + "</div>" +
				"<div id='gettingStartedContent'>" +
		            "<ul>" +
		              "<li><span>1</span><img src='"+this.imagePath+"/gs_step1.png'/>" +
		                bundle_photos.photoConnectUI_start_device +
		              "<li id='signInBox'><span>2</span>#{signin}" +
		              "<li><span>3</span>#{communicator}" +
		              "<li><span>4</span><img src='"+this.imagePath+"/gs_step4.png'/>" +
		              	bundle_photos.photoConnectUI_start_fun +
		              "<li class='gettingStartedLast'><span>5</span><img src='" +
		              this.imagePath + "/gs_step5.png'/>" +
		              bundle_photos.photoConnectUI_start_send +
		            "</ul>" +
		      	"</div>" +
			"</div>" 
		);
		
		/**
		 * properties to evaluate into startTemplate.  conditional to show whether the user
		 * is signed in and whether user has communicator plugin
		 */
		this.start = {communicator: this.communicatorParam, signin: this.signInParam};
		
		
		var stagingTemplate = new Ext.Template(
			"<span id='{id}' class='thumb-wrap'>" +
				"<span class='thumb'>" +
					"<img src='{photoURL}' alt='{title}' title='{title:htmlEncode}' \/>" +
				"<\/span>" +
			"<\/span>" 
		);
		stagingTemplate.compile();
		this.view = new Ext.View("stagingAreaDiv", stagingTemplate, { //class='ychooser-view'
			singleSelect: true,
			selectedClass: "x-view-selected",
			store: this.store
		});
		this.view.prepareData = function(photo) {
			return photo;
		};

		this.view.on("click", function(vw, index, node, e) {
			loader.getPhotoAPI().dimOldMarker();
			var photo = loader.photoKeepers[node.id];
			var marker = loader.getPhotoAPI().lookupOverlay(photo);
			if (marker) {
				loader.getPhotoAPI().highlightMarker(marker);
				loader.map.closeInfoWindow();
			}
			if (photo) {
				loader.previewPhoto(photo, index);
			}
		});
		this.view.on("dblclick", function(vw, index, node, e) {
			loader.getPhotoAPI().dimOldMarker();
			var photo = loader.photoKeepers[node.id];
			var marker = loader.getPhotoAPI().lookupOverlay(photo);
			if (marker) {
				loader.map.closeInfoWindow();
				loader.getPhotoAPI().highlightMarker(marker);
			}
			loader.panToPhoto(photo);
		});
 	},
 	
 	/**
 	 * assigns values to the startTemplate, conditionally.  
 	 * Sets image and text depending if the user has communicator plugin 
 	 * installed into communicatorParam.
 	 * Sets image and form, or welcome text depending if username has been
 	 * passed in as a param from parent page (if the user is signed in) into
 	 * signInParam.  Calls authenticate method that must be populated on the parent page 
 	 * to actually sign the user into the containing page.
 	 */
 	setTemplateValues: function (){
 		if (PluginDetect.detectGarminCommunicatorPlugin()){
 			this.communicatorParam = "<img src='"+this.imagePath+"/gs_communicator_yes.png' />" + 
 									bundle_photos.photoConnectUI_start_pluginInstalled + "</li>";
 		}else {
 			this.communicatorParam = "<img src='"+this.imagePath+"/gs_communicator_no.png' />" + 
 									bundle_photos.photoConnectUI_start_communicator + "</li>";
 		}
 		if ( this.username != null){
 			this.signInParam = "<img src='"+this.imagePath+"/gs_signin_yes.png' />" + 
 								bundle_photos.photoConnectUI_start_signedIn + "</li>";
 		}else {
 			
 			this.signInParam = "<img src='"+this.imagePath+"/gs_signin_no.png'/> " +
 					"<div class='title'>" + 
 						bundle_photos.photoConnectUI_start_notSignedIn + 
 					"</div>" +
                	"<form name='loginInsert'> " +
                		"<div class='field'>" +
                			"<div class='loginLabel'>" + 
                				bundle_photos.photoConnectUI_start_username + 
                			"</div><input name='username' type='text' />" +
                		"</div>" +
                		"<div class='field'>" +
                			"<div class='loginLabel'>" + 
                				bundle_photos.photoConnectUI_start_password + 
                			"</div><input name='password' type='password' />" +
                		"</div>" +
                		"<div id='signInButton'>" +
                			"<input name='signInButton' value='" + 
                			bundle_photos.photoConnectUI_start_signIn + 
                			"' type='button' " +
                			"onclick='fotoConnect.iFrameLoader.authenticate(username.value, " +
                														   "password.value, " +
                														   "rememberMe.value);'/>" +
                		"</div>" +
                		//rememberMe is commented out currently because its not working.
                		//doesn't store the session.  i'm not positive why its not calling the 
                		//same seam components when the authenticate method is called.
                		"<div id='rememberMe' style='display:none'>" +
                			"<input name='rememberMe' type='checkbox' value='' />" +
                			"<div class='label'>" + 
                				bundle_photos.photoConnectUI_rememberMe + 
                			"</div>" +
                		"</div>" +
                	"</form>"
 		}
 	},
 	
 	/**authenticate method must be over ridden by the page containing the iframe; 
 	 * authenticates user and signs the user in to page.
 	 * If method is not populated in container alert will pop up.
 	 * 
 	 * @param username String the user's user name
 	 * @param password String the user's password
 	 * @param rememberMe boolean user's preference in checkbox
 	 */
 	authenticate: function(username, password, rememberMe){
 		alert ("please refresh containing page");
 	},
 	
 	/**
 	 * sets the value of the username given the parameter passed into the page 
 	 * if the username param has no value, then username is null.
 	 * Uses regexes to find the param from the href.  internet says this is the 
 	 * best solution
 	 * 
 	 * @param name String the name of the param being requested
 	 */
 	getParam: function ( name ){
	  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
	  var regexS = "[\\?&]"+name+"=([^&#]*)";
	  var regex = new RegExp( regexS );
	  var results = regex.exec( window.location.href );
	  if( results == null || results[1] == "" || results[1] == null)
	    this.username = null;
	  else
	    this.username = results[1];
	},
	
	getKmlWriteMode: function() { return this.kmlWriteMode; },
	setKmlWriteMode: function(kmlWriteMode) { this.kmlWriteMode = kmlWriteMode; },
	getCurrentPhoto: function() { return this.currentPhoto; },
	setCurrentPhoto: function(currentPhoto) { this.currentPhoto = currentPhoto; },
	getStagedPhotos: function() { return this.photoKeepers; },

	getStagedPhotoArray: function() {
		return Object.values(this.photoKeepers).findAll( 
			function(obj) { return (obj.getPhotoID != undefined); }
		);
	},

	/** should be called when map div changes size */
	checkResize: function() {
		this.map.checkResize();
	},

	/**
	 * if there are no photos when function called, getting started template remains,
	 * otherwise the staging area is populated
	 * 
	 * @param photo object the current photo
	 * @param index int 
	 */
	previewPhoto: function(photo, index) {
		if (!photo) {
			$("previewPhotoItemDiv").innerHTML = this.startTemplate.evaluate(this.start);
		} else if (this.photoKeepers[photo.getPhotoID()]) {
			$("previewPhotoItemDiv").innerHTML = this.previewPhotoTemplate.evaluate(photo.properties());
			var photoImageDiv = $("previewPhotoImgDiv");
			var innerPhotoImageDiv = $("innerPreviewPhotoImgDiv");
			var photoImage = $("photoPreviewImage");
			var titleField = new Ext.form.InlineTextField({});
			titleField.applyTo("titleEditText");
			titleField.el.addClass("editable");
			Ext.QuickTips.register({
				text: bundle_photos.photoTitleHint,
				target: titleField.el
			});
			
			Event.observe($("titleEditText"), "blur", this.onBlurPhotoTitle.bindAsEventListener(this));
			this.setCurrentPhoto(photo);
			if (!index) 
				index = this.view.indexOf(photo.getPhotoID());
			if (index >= 0) 
				this.view.select(index);
		}
	},
 
	 /**
	  * addAllPhotos or add single photo buttons action.  When the user is not signed in and 
	  * attempts to add photos, popup will alert the user that they have to sign in if they wish to
	  * continue.  When the user doesn't have the plugin install, pop up will alert 
	  * user they must install plugin.  both pop ups point in line to their respective components
	  * in the getting started div.  Both pop ups are closable 
	  * @return boolean false, if user isn't signed in or doesn't have communicator
	  */
	popUpNotifications: function() {
		if (this.username == null){
			$("popUpContent").innerHTML = 
				"<div id='popupSignIn'>" +
					"<div class='popupSmall'>" +
						"<div class='popupCloseButton'>" +
							"<a onclick='Ext.get(\"popupSignIn\").toggle()'>" +
							"<img src='"+this.imagePath+"/icon_close.png' /></a>" +
						"</div>" + bundle_photos.popups_signin + 
					"</div>" +
				"</div>";
				return false;
		}else if (!PluginDetect.detectGarminCommunicatorPlugin()){
			$("popUpContent").innerHTML = 
				"<div id='popupCommunicator'>" +
					"<div class='popupSmall'>" +
						"<div class='popupCloseButton'>" +
							"<a onclick='Ext.get(\"popupCommunicator\").toggle()'>" +
							"<img src='"+this.imagePath+"/icon_close.png' /></a>" +
						"</div>" + bundle_photos.popups_plugin + 
					"</div>" +
				"</div>";
				return false;
		}else {
			return true;
		}
	},
	  
	  
	addAllPhotos: function() {
		if (this.popUpNotifications()) {
			this.map.closeInfoWindow();
			var _loader = this;
			var photos = this.getPhotoAPI().getPhotos();
			photos.each(function(photo) { _loader.addPhoto(photo) });
			this.previewPhoto(photos[0]);
		}
	},
	
	addPhoto: function(photo) {
		if (this.popUpNotifications()) {
			if (!this.photoKeepers[photo.getPhotoID()]) {
				this.photoKeepers[photo.getPhotoID()] = photo;
				var record = new this.PhotoRecordDef(photo.properties(), photo.getPhotoID());
				this.store.add([record]);
				this.view.refresh();			
				fotoConnect.toggleStaggingControls(true);
			}
		}
	},
	
	doMovePhoto: function() {
		var nodes = this.view.getSelectedNodes();
		if (nodes.size() > 0 && nodes[0]) {
			this.relocatePhoto(nodes[0].id);
		} else {
			alert(bundle_photos.photoConnectUI_alert_move);
		}
	},

	/** This clears the map and creates GMarker for this photo with draggable=true */
	relocatePhoto: function(photoOrId) {
		var photo = (typeof photoOrId == 'string' || typeof photoOrId == 'number') ? this.photoKeepers[photoOrId] : photoOrId;
		if (!photo) {
			alert('PhotoLoader.relocatePhoto: NO photo or photo ID specified');
		} else if (!this.photoKeepers[photo.getPhotoID()]) {
			alert('PhotoLoader.relocatePhoto: photo '+photo.getPhotoID()+' not in list');
		} else {
			//var marker = this.getPhotoAPI().lookupOverlay(photo);
			//if (marker) {
			//	log("Relocate enabled on photo "+photo.getPhotoID());
			//	this.getPhotoAPI().removeOverlay(photo);
			//}
			//marker = this.getPhotoAPI().createOverlay(photo, true);
		  	this.map.clearOverlays(); //clear existing markers
			this.getPhotoAPI().overlayHash = new Hash();
			var marker = this.getPhotoAPI().createMoveOverlay(photo);
			this.map.panTo(marker.getPoint());
		}
	},
	
	/** Center this photo on map and hightlight.  If photo overlay is not in the current photo set, it creates one.  */
	panToPhoto: function(photoOrId) {
		var photo = (typeof photoOrId == 'string' || typeof photoOrId == 'number') ? this.photoKeepers[photoOrId] : photoOrId;
		if (photo) {
			var marker = this.getPhotoAPI().lookupOverlay(photo);
			if (!marker) {
				marker = this.getPhotoAPI().createOverlay(photo);
			}
			log("panToPhoto enabled on photo "+photo.getPhotoID());
			this.map.panTo(marker.getPoint());
			this.getPhotoAPI().highlightMarker(marker);
		}
	},
	
	doRemovePhoto: function() {
		var nodes = this.view.getSelectedNodes();
		if (nodes.size() > 0 && nodes[0]) {
			this.removePhoto(nodes[0].id);
			this.getPhotoAPI().dimOldMarker();
		} else {
			alert(bundle_photos.photoConnectUI_alert_remove);
		}
	},

	removePhoto: function(photoOrId) {
		var photo = (typeof photoOrId == 'string' || typeof photoOrId == 'number') ? this.photoKeepers[photoOrId] : photoOrId;
		var record = this.store.getById(photo.getPhotoID());
		if (!photo) {
			alert('PhotoLoader.removePhoto: NO photo or photo ID specified');
		} else if (!this.photoKeepers[photo.getPhotoID()]) {
			alert('PhotoLoader.removePhoto: photo '+photo.getPhotoID()+' not in list');
		} else if (!record) {
			alert('PhotoLoader.removePhoto: photo '+photo.getPhotoID()+' record not in Ext.data.Store');
		} else {
			var index = this.view.indexOf(photo.getPhotoID());
			this.photoKeepers.remove(photo.getPhotoID());
			this.store.remove(record);
			this.view.refresh();
			if (this.store.getCount() > 0) { //select prev photo
				index = index==0 ? 0 : index-1;
				var node = this.view.getNode(index);
				photo = this.photoKeepers[ node ? node.id : null];
				this.previewPhoto(photo, index);
			} else {
				this.previewPhoto(null);				
			}
		}
		if (this.store.getCount() < 1) {
			fotoConnect.toggleStaggingControls(false);
		}
	},
	
	/** Save staged photos as KML. */
	saveAsKML: function() {
		var kml = this._createKML();
		log(kml);
		$("kmlDownloadForm").kml.value = kml;
		$("kmlDownloadForm").submit();
	},

	/** construct the KML XML data from staged photos */
	_createKML: function() {
		var styles = "";
		var placemarks = "";
		var styleTemplate = this.kmlIconStyleTemplate;
		var placemarkTemplate = this.kmlPlacemarkTemplate;
		this.getStagedPhotoArray().each( function(photo) {
			var properties = photo.properties();
			styles     += styleTemplate.evaluate( properties );
			placemarks += placemarkTemplate.evaluate( properties );
		});
		var kml =
		"<?xml version='1.0' encoding='UTF-8'?>\n" + 
		"<kml xmlns='http://earth.google.com/kml/2.2'>\n" +
			"<Document>\n" +
			styles +
			placemarks +
			"</Document>\n" +
		"</kml>";
		return kml;
	},

	/** set page of photos to display and load photos */
	setPage: function(page) {
		loader.photoAPI.setPage(page);
		loader.updateMap(false); //don't reset on page updates
	},

	onKeyPressLocation: function(event) {
		if (event.keyCode == Event.KEY_RETURN) {
			this.geocode();
   		}
	},
	
	onBlurPhotoTitle: function(event) {
		var photo = this.getCurrentPhoto();
		var title = $F("titleEditText");
		log("onBlurPhotoTitle photo="+photo.getPhotoID()+", title="+title);
		if (photo && photo.getTitle() != title) {
			photo.setTitle(title);
			var record = this.store.getById(photo.getPhotoID());
			record.set("title", title);
   		}
	},
	
	setPhotoAPI: function(photoAPI) {
		this.photoAPI = photoAPI;
		this.photoAPI.setMap(this.map);
		this.photoAPI.reset();
	},
	 
	getPhotoAPI: function() {
		return this.photoAPI;
	},
	
	getDevicePhotoPath: function() {
		return this.devicePhotoPath;
	},

	/** asynch callback destination for geocoder. */
	geocode: function() {
		var location = $F("location");
		if (location) {
			//log("geocode: location="+this.location.value)
			this.geocoder.findLatLng(location);
		} else {
			this.updateMap(true);
		}
	},

	/** Handles call-back from geocoder.
	 * @private
	 * @param {Object} waypoint, fileName and controller in JSON wrapper.
	 */
	onFinishedFindLatLon: function(json) {
		//log("onFinishedFindLatLon: lat="+json.waypoint.getLat() + ", lon="+json.waypoint.getLng())
		this.map.setCenter(new GLatLng(json.waypoint.getLat(), json.waypoint.getLng()), this.map.getZoom());
		this.updateMap(true);
	},

	/** add single photo to stagging area. */
	addSinglePhoto: function(photoID) {
		this.map.closeInfoWindow();
		var photo = this.getPhotoAPI().lookupPhoto(photoID);
		log("adding photo "+photo.getPhotoID());
		this.addPhoto(photo);
		this.previewPhoto(photo);
	},

	/** Called on the form submission: updates the map by 
	 * placing markers on it at the appropriate places.
	 * @param reset if reset is true then page will be set back to position 1. Don't use when paging.
	 */
	updateMap: function(reset) {
		if (reset && this.photoAPI) {
			this.photoAPI.reset();
			this.resetPhotoPages = true;
		}
	  	var queryURL = this.photoAPI.constructQueryURL(reset);
	  	log(queryURL);
	  	this.map.clearOverlays(); //clear existing markers
	  	this.setScript(queryURL, "photoAPI");
	},

	/** Panoramio photo query callback destination */
	storePhotos: function(json) {
			this.photoAPI.showPhotoIcons(json);
			if (this.resetPhotoPages) {
				this.paginator.reset(0, this.getPhotoAPI().getTotalPages());
			}
			this.resetPhotoPages = false;
		try {
		} catch (e) {
			this.handleException(e.message);
		}
	},

	/** Add or replace JavaScript DOM element to HEAD. */
	setScript: function(src, id) { 
	  	var script = document.createElement('script');
	  	Element.extend(script); 
	  	script.src = src; 
	  	script.type = 'text/javascript';
	  	var head = document.getElementsByTagName('head').item(0); 
	  	if (id) {
		  	script.id = id;
		  	if ($(id)) {
		  		//log("removing scirpt id="+id);
		  		$(id).remove();
		  	}
		}
	  	head.appendChild(script); 
	},

	/** Handles call-back from geocoder and forwards call to onException on registered listeners.
	 * @private
	 * @param {Error} error wrapped in JSON 'msg' object.
	 */
	onException: function(json) {
    		this.handleException(json.msg.message);
	},

	handleException: function(msg) {
	    alert("Error: " + msg);
	},
	
	setStatus: function(statusText) {
	    $("statusText").innerHTML = statusText;
	}
};
