/*
Call generic wms service for GoogleMaps v2
John Deck, UC Berkeley
Inspiration & Code from:
	Mike Williams http://www.econym.demon.co.uk/googlemaps2/ V2 Reference & custommap code
	Brian Flood http://www.spatialdatalogic.com/cs/blogs/brian_flood/archive/2005/07/11/39.aspx V1 WMS code
	Kyle Mulka http://blog.kylemulka.com/?p=287  V1 WMS code modifications
	http://search.cpan.org/src/RRWO/GPS-Lowrance-0.31/lib/Geo/Coordinates/MercatorMeters.pm
*/

var MAGIC_NUMBER=6356752.3142;
var DEG2RAD=0.0174532922519943;
var PI=3.14159267;

function dd2MercMetersLng(p_lng) {
	return MAGIC_NUMBER*(p_lng*DEG2RAD);
}

function dd2MercMetersLat(p_lat) {
	if (p_lat >= 85) p_lat=85;
	if (p_lat <= -85) p_lat=-85;
	return MAGIC_NUMBER*Math.log(Math.tan(((p_lat*DEG2RAD)+(PI/2)) /2));
}

customGetTileUrl=function(tile, zoom, unbounded) {
	if (typeof(window['this.myStyles'])=="undefined") {
        this.myStyles="";
    }
	var lULP = new GPoint(tile.x*256,(tile.y+1)*256);
	var lLRP = new GPoint((tile.x+1)*256,tile.y*256);
	var lUL = G_NORMAL_MAP.getProjection().fromPixelToLatLng(lULP, zoom, unbounded);
	var lLR = G_NORMAL_MAP.getProjection().fromPixelToLatLng(lLRP, zoom, unbounded);
	// switch between Mercator and DD if merczoomlevel is set
	if (this.myMercZoomLevel!=0 && zoom < this.myMercZoomLevel) {
    	var lBbox=dd2MercMetersLng(lUL.x)+","+dd2MercMetersLat(lUL.y)+","+dd2MercMetersLng(lLR.x)+","+dd2MercMetersLat(lLR.y);
    	var lSRS="EPSG:54004";
	} else {
    	var lBbox=lUL.x+","+lUL.y+","+lLR.x+","+lLR.y;
    	var lSRS="EPSG:4326";
	}
	var lURL=this.getMyBaseURL(tile, zoom);
	lURL+="&REQUEST=GetMap";
	lURL+="&SERVICE=WMS";
	lURL+="&VERSION=1.1.1";
	lURL+="&LAYERS="+this.myLayers;
	lURL+="&STYLES="+this.myStyles; lURL+="&FORMAT="+this.myFormat;
//	lURL+="&BGCOLOR=0xFFFFFF";
	lURL+="&TRANSPARENT=TRUE";
	lURL+="&SRS="+lSRS;
	lURL+="&BBOX="+lBbox;
	lURL+="&WIDTH=256";
	lURL+="&HEIGHT=256";
	lURL+="&reaspect=false";
	return lURL;
}// ===================================================================
// Author: Matt Kruse <matt@ajaxtoolbox.com>
// WWW: http://www.AjaxToolbox.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

/**
 * The AjaxRequest class is a wrapper for the XMLHttpRequest objects which 
 * are available in most modern browsers. It simplifies the interfaces for
 * making Ajax requests, adds commonly-used convenience methods, and makes 
 * the process of handling state changes more intuitive.
 * An object may be instantiated and used, or the Class methods may be used 
 * which internally create an AjaxRequest object.
 */
function AjaxRequest() {
	var req = new Object();
	
	// -------------------
	// Instance properties
	// -------------------

	/**
	 * Timeout period (in ms) until an async request will be aborted, and
	 * the onTimeout function will be called
	 */
	req.timeout = null;
	
	/**
	 *	Since some browsers cache GET requests via XMLHttpRequest, an
	 * additional parameter called AjaxRequestUniqueId will be added to
	 * the request URI with a unique numeric value appended so that the requested
	 * URL will not be cached.
	 */
	req.generateUniqueUrl = true;
	
	/**
	 * The url that the request will be made to, which defaults to the current 
	 * url of the window
	 */
	req.url = window.location.href;
	
	/**
	 * The method of the request, either GET (default), POST, or HEAD
	 */
	req.method = "GET";
	
	/**
	 * Whether or not the request will be asynchronous. In general, synchronous 
	 * requests should not be used so this should rarely be changed from true
	 */
	req.async = true;
	
	/**
	 * The username used to access the URL
	 */
	req.username = null;
	
	/**
	 * The password used to access the URL
	 */
	req.password = null;
	
	/**
	 * The parameters is an object holding name/value pairs which will be 
	 * added to the url for a GET request or the request content for a POST request
	 */
	req.parameters = new Object();
	
	/**
	 * The sequential index number of this request, updated internally
	 */
	req.requestIndex = AjaxRequest.numAjaxRequests++;
	
	/**
	 * Indicates whether a response has been received yet from the server
	 */
	req.responseReceived = false;
	
	/**
	 * The name of the group that this request belongs to, for activity 
	 * monitoring purposes
	 */
	req.groupName = null;
	
	/**
	 * The query string to be added to the end of a GET request, in proper 
	 * URIEncoded format
	 */
	req.queryString = "";
	
	/**
	 * After a response has been received, this will hold the text contents of 
	 * the response - even in case of error
	 */
	req.responseText = null;
	
	/**
	 * After a response has been received, this will hold the XML content
	 */
	req.responseXML = null;
	
	/**
	 * After a response has been received, this will hold the status code of 
	 * the response as returned by the server.
	 */
	req.status = null;
	
	/**
	 * After a response has been received, this will hold the text description 
	 * of the response code
	 */
	req.statusText = null;

	/**
	 * An internal flag to indicate whether the request has been aborted
	 */
	req.aborted = false;
	
	/**
	 * The XMLHttpRequest object used internally
	 */
	req.xmlHttpRequest = null;

	// --------------
	// Event handlers
	// --------------
	
	/**
	 * If a timeout period is set, and it is reached before a response is 
	 * received, a function reference assigned to onTimeout will be called
	 */
	req.onTimeout = null; 
	
	/**
	 * A function reference assigned will be called when readyState=1
	 */
	req.onLoading = null;

	/**
	 * A function reference assigned will be called when readyState=2
	 */
	req.onLoaded = null;

	/**
	 * A function reference assigned will be called when readyState=3
	 */
	req.onInteractive = null;

	/**
	 * A function reference assigned will be called when readyState=4
	 */
	req.onComplete = null;

	/**
	 * A function reference assigned will be called after onComplete, if 
	 * the statusCode=200
	 */
	req.onSuccess = null;

	/**
	 * A function reference assigned will be called after onComplete, if 
	 * the statusCode != 200
	 */
	req.onError = null;
	
	/**
	 * If this request has a group name, this function reference will be called 
	 * and passed the group name if this is the first request in the group to 
	 * become active
	 */
	req.onGroupBegin = null;

	/**
	 * If this request has a group name, and this request is the last request 
	 * in the group to complete, this function reference will be called
	 */
	req.onGroupEnd = null;

	// Get the XMLHttpRequest object itself
	req.xmlHttpRequest = AjaxRequest.getXmlHttpRequest();
	if (req.xmlHttpRequest==null) { return null; }
	
	// -------------------------------------------------------
	// Attach the event handlers for the XMLHttpRequest object
	// -------------------------------------------------------
	req.xmlHttpRequest.onreadystatechange = 
	function() {
		if (req==null || req.xmlHttpRequest==null) { return; }
		if (req.xmlHttpRequest.readyState==1) { req.onLoadingInternal(req); }
		if (req.xmlHttpRequest.readyState==2) { req.onLoadedInternal(req); }
		if (req.xmlHttpRequest.readyState==3) { req.onInteractiveInternal(req); }
		if (req.xmlHttpRequest.readyState==4) { req.onCompleteInternal(req); }
	};
	
	// ---------------------------------------------------------------------------
	// Internal event handlers that fire, and in turn fire the user event handlers
	// ---------------------------------------------------------------------------
	// Flags to keep track if each event has been handled, in case of 
	// multiple calls (some browsers may call the onreadystatechange 
	// multiple times for the same state)
	req.onLoadingInternalHandled = false;
	req.onLoadedInternalHandled = false;
	req.onInteractiveInternalHandled = false;
	req.onCompleteInternalHandled = false;
	req.onLoadingInternal = 
		function() {
			if (req.onLoadingInternalHandled) { return; }
			AjaxRequest.numActiveAjaxRequests++;
			if (AjaxRequest.numActiveAjaxRequests==1 && typeof(window['AjaxRequestBegin'])=="function") {
				AjaxRequestBegin();
			}
			if (req.groupName!=null) {
				if (typeof(AjaxRequest.numActiveAjaxGroupRequests[req.groupName])=="undefined") {
					AjaxRequest.numActiveAjaxGroupRequests[req.groupName] = 0;
				}
				AjaxRequest.numActiveAjaxGroupRequests[req.groupName]++;
				if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==1 && typeof(req.onGroupBegin)=="function") {
					req.onGroupBegin(req.groupName);
				}
			}
			if (typeof(req.onLoading)=="function") {
				req.onLoading(req);
			}
			req.onLoadingInternalHandled = true;
		};
	req.onLoadedInternal = 
		function() {
			if (req.onLoadedInternalHandled) { return; }
			if (typeof(req.onLoaded)=="function") {
				req.onLoaded(req);
			}
			req.onLoadedInternalHandled = true;
		};
	req.onInteractiveInternal = 
		function() {
			if (req.onInteractiveInternalHandled) { return; }
			if (typeof(req.onInteractive)=="function") {
				req.onInteractive(req);
			}
			req.onInteractiveInternalHandled = true;
		};
	req.onCompleteInternal = 
		function() {
			if (req.onCompleteInternalHandled || req.aborted) { return; }
			req.onCompleteInternalHandled = true;
			AjaxRequest.numActiveAjaxRequests--;
			if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
				AjaxRequestEnd(req.groupName);
			}
			if (req.groupName!=null) {
				AjaxRequest.numActiveAjaxGroupRequests[req.groupName]--;
				if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==0 && typeof(req.onGroupEnd)=="function") {
					req.onGroupEnd(req.groupName);
				}
			}
			req.responseReceived = true;
			req.status = req.xmlHttpRequest.status;
			req.statusText = req.xmlHttpRequest.statusText;
			req.responseText = req.xmlHttpRequest.responseText;
			req.responseXML = req.xmlHttpRequest.responseXML;
			if (typeof(req.onComplete)=="function") {
				req.onComplete(req);
			}
			if (req.xmlHttpRequest.status==200 && typeof(req.onSuccess)=="function") {
				req.onSuccess(req);
			}
			else if (typeof(req.onError)=="function") {
				req.onError(req);
			}

			// Clean up so IE doesn't leak memory
			delete req.xmlHttpRequest['onreadystatechange'];
			req.xmlHttpRequest = null;
		};
	req.onTimeoutInternal = 
		function() {
			if (req!=null && req.xmlHttpRequest!=null && !req.onCompleteInternalHandled) {
				req.aborted = true;
				req.xmlHttpRequest.abort();
				AjaxRequest.numActiveAjaxRequests--;
				if (AjaxRequest.numActiveAjaxRequests==0 && typeof(window['AjaxRequestEnd'])=="function") {
					AjaxRequestEnd(req.groupName);
				}
				if (req.groupName!=null) {
					AjaxRequest.numActiveAjaxGroupRequests[req.groupName]--;
					if (AjaxRequest.numActiveAjaxGroupRequests[req.groupName]==0 && typeof(req.onGroupEnd)=="function") {
						req.onGroupEnd(req.groupName);
					}
				}
				if (typeof(req.onTimeout)=="function") {
					req.onTimeout(req);
				}
			// Opera won't fire onreadystatechange after abort, but other browsers do. 
			// So we can't rely on the onreadystate function getting called. Clean up here!
			delete req.xmlHttpRequest['onreadystatechange'];
			req.xmlHttpRequest = null;
			}
		};

	// ----------------
	// Instance methods
	// ----------------
	/**
	 * The process method is called to actually make the request. It builds the
	 * querystring for GET requests (the content for POST requests), sets the
	 * appropriate headers if necessary, and calls the 
	 * XMLHttpRequest.send() method
	*/
	req.process = 
		function() {
			if (req.xmlHttpRequest!=null) {
				// Some logic to get the real request URL
				if (req.generateUniqueUrl && req.method=="GET") {
					req.parameters["AjaxRequestUniqueId"] = new Date().getTime() + "" + req.requestIndex;
				}
				var content = null; // For POST requests, to hold query string
				for (var i in req.parameters) {
					if (req.queryString.length>0) { req.queryString += "&"; }
					req.queryString += encodeURIComponent(i) + "=" + encodeURIComponent(req.parameters[i]);
				}
				if (req.method=="GET") {
					if (req.queryString.length>0) {
						req.url += ((req.url.indexOf("?")>-1)?"&":"?") + req.queryString;
					}
				}
				req.xmlHttpRequest.open(req.method,req.url,req.async,req.username,req.password);
				if (req.method=="POST") {
					if (typeof(req.xmlHttpRequest.setRequestHeader)!="undefined") {
						req.xmlHttpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
					}
					content = req.queryString;
				}
				if (req.timeout>0) {
					setTimeout(req.onTimeoutInternal,req.timeout);
				}
				req.xmlHttpRequest.send(content);
			}
		};

	/**
	 * An internal function to handle an Object argument, which may contain
	 * either AjaxRequest field values or parameter name/values
	 */
	req.handleArguments = 
		function(args) {
			for (var i in args) {
				// If the AjaxRequest object doesn't have a property which was passed, treat it as a url parameter
				if (typeof(req[i])=="undefined") {
					req.parameters[i] = args[i];
				}
				else {
					req[i] = args[i];
				}
			}
		};

	/**
	 * Returns the results of XMLHttpRequest.getAllResponseHeaders().
	 * Only available after a response has been returned
	 */
	req.getAllResponseHeaders =
		function() {
			if (req.xmlHttpRequest!=null) {
				if (req.responseReceived) {
					return req.xmlHttpRequest.getAllResponseHeaders();
				}
				alert("Cannot getAllResponseHeaders because a response has not yet been received");
			}
		};

	/**
	 * Returns the the value of a response header as returned by 
	 * XMLHttpRequest,getResponseHeader().
	 * Only available after a response has been returned
	 */
	req.getResponseHeader =
		function(headerName) {
			if (req.xmlHttpRequest!=null) {
				if (req.responseReceived) {
					return req.xmlHttpRequest.getResponseHeader(headerName);
				}
				alert("Cannot getResponseHeader because a response has not yet been received");
			}
		};

	return req;
}

// ---------------------------------------
// Static methods of the AjaxRequest class
// ---------------------------------------

/**
 * Returns an XMLHttpRequest object, either as a core object or an ActiveX 
 * implementation. If an object cannot be instantiated, it will return null;
 */
AjaxRequest.getXmlHttpRequest = function() {
	if (window.XMLHttpRequest) {
		return new XMLHttpRequest();
	}
	else if (window.ActiveXObject) {
		// Based on http://jibbering.com/2002/4/httprequest.html
		/*@cc_on @*/
		/*@if (@_jscript_version >= 5)
		try {
			return new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {
			try {
				return new ActiveXObject("Microsoft.XMLHTTP");
			} catch (E) {
				return null;
			}
		}
		@end @*/
	}
	else {
		return null;
	}
};

/**
 * See if any request is active in the background
 */
AjaxRequest.isActive = function() {
	return (AjaxRequest.numActiveAjaxRequests>0);
};

/**
 * Make a GET request. Pass an object containing parameters and arguments as 
 * the second argument.
 * These areguments may be either AjaxRequest properties to set on the request 
 * object or name/values to set in the request querystring.
 */
AjaxRequest.get = function(args) {
	AjaxRequest.doRequest("GET",args);
};

/**
 * Make a POST request. Pass an object containing parameters and arguments as 
 * the second argument.
 * These areguments may be either AjaxRequest properties to set on the request 
 * object or name/values to set in the request querystring.
 */
AjaxRequest.post = function(args) {
	AjaxRequest.doRequest("POST",args);
};

/**
 * The internal method used by the .get() and .post() methods
 */
AjaxRequest.doRequest = function(method,args) {
	if (typeof(args)!="undefined" && args!=null) {
		var myRequest = new AjaxRequest();
		myRequest.method = method;
		myRequest.handleArguments(args);
		myRequest.process();
	}
}	;

/**
 * Submit a form. The requested URL will be the form's ACTION, and the request 
 * method will be the form's METHOD.
 * Returns true if the submittal was handled successfully, else false so it 
 * can easily be used with an onSubmit event for a form, and fallback to 
 * submitting the form normally.
 */
AjaxRequest.submit = function(theform, args) {
	var myRequest = new AjaxRequest();
	if (myRequest==null) { return false; }
	var serializedForm = AjaxRequest.serializeForm(theform);
	myRequest.method = theform.method.toUpperCase();
	myRequest.url = theform.action;
	myRequest.handleArguments(args);
	myRequest.queryString = serializedForm;
	myRequest.process();
	return true;
};

/**
 * Serialize a form into a format which can be sent as a GET string or a POST 
 * content.It correctly ignores disabled fields, maintains order of the fields 
 * as in the elements[] array. The 'file' input type is not supported, as 
 * its content is not available to javascript. This method is used internally
 * by the submit class method.
 */
AjaxRequest.serializeForm = function(theform) {
	var els = theform.elements;
	var len = els.length;
	var queryString = "";
	this.addField = 
		function(name,value) { 
			if (queryString.length>0) { 
				queryString += "&";
			}
			queryString += encodeURIComponent(name) + "=" + encodeURIComponent(value);
		};
	for (var i=0; i<len; i++) {
		var el = els[i];
		if (!el.disabled) {
			switch(el.type) {
				case 'text': case 'password': case 'hidden': case 'textarea': 
					this.addField(el.name,el.value);
					break;
				case 'select-one':
					if (el.selectedIndex>=0) {
						this.addField(el.name,el.options[el.selectedIndex].value);
					}
					break;
				case 'select-multiple':
					for (var j=0; j<el.options.length; j++) {
						if (el.options[j].selected) {
							this.addField(el.name,el.options[j].value);
						}
					}
					break;
				case 'checkbox': case 'radio':
					if (el.checked) {
						this.addField(el.name,el.value);
					}
					break;
			}
		}
	}
	return queryString;
};

// -----------------------
// Static Class variables
// -----------------------

/**
 * The number of total AjaxRequest objects currently active and running
 */
AjaxRequest.numActiveAjaxRequests = 0;

/**
 * An object holding the number of active requests for each group
 */
AjaxRequest.numActiveAjaxGroupRequests = new Object();

/**
 * The total number of AjaxRequest objects instantiated
 */
AjaxRequest.numAjaxRequests = 0;

/**
 * Class TileMap
 * Creates a new TileMap on top of Google Maps.
 * @constructor
 */
function TileMap(mapId, width, height) {
    this.mapId = mapId;
    this.map = null;
    this.width = width;
    this.height = height;
    this.tileHosts = null;

    this.layerName = 'Tiles';

    // needed to hold the Tiles Currently Visible (GRID)
    this.selectedTiles = {};
    this.inactiveTiles = {};
    this.numSelected = 0;
    this.numDeselected = 0;
    this.mode = 'selection';
    this.requestQueue = [];
    this.requestTimeout = 2000;
    this.requestInProgess = false;
}

TileMap.prototype.getMap = function() {
    return this.map;
};

/**
 * use up to 4 x 2 connections per time
 * @return one of the tileHost url's from the tileHosts property
 */
TileMap.prototype.getMyBaseUrl = function(tile, zoom) {
    var i = ((tile.x + tile.y) % this.tileHosts.length - 1) + 1;
    // add the tile grid layer (GRID)
    return this.tileHosts[i];
};

/**
 * In custom mode you can register the tileclicked event to a TileMap object:
 * GEvent.addListener(tileMap, 'tileclicked', customFunction);
 * Current available modes are: selection, custom
 * @param   {String}    mode    selection
 */
TileMap.prototype.initialize = function(mode) {
    if (!GBrowserIsCompatible()) {
        return false;
    }
    this.mode = mode ? mode : this.mode;
    var map = new GMap2(document.getElementById(this.mapId), { mapTypes: [G_PHYSICAL_MAP], size: new GSize(this.width, this.height) });

    // adding some custom props
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl());

    var pos = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(100,10));
    map.addControl(new GScaleControl(),pos);
    var ctrlOverview = new GOverviewMapControl();
    map.addControl(ctrlOverview);
    ctrlOverview.hide();

    //map.enableDoubleClickZoom(); // since 2.58

    this.map = map;
    return true;
};

// Let's startup the map
TileMap.prototype.start = function() {
    var me = this;
    var map = this.map;

    // event handlers to handle the selection of tiles (GRID)
    GEvent.addListener(map, 'click', function(overlay, latlng) { return me.getTileCoords(overlay, latlng); });

    map.setCenter(new GLatLng(51.6379664847,5.22292353547), 7);
    map.addOverlay(this.getTileLayer(map.getCurrentMapType()));      // call this after setCenter

};

TileMap.prototype.getTileLayer = function(baseMapType) {
    var me = this;

    /* create copyright for layer */
    var myCopyright = new GCopyrightCollection(" ");
    myCopyright.addCopyright(new GCopyright('PrezentCopy',new GLatLngBounds(new GLatLng(-90,-180), new GLatLng(90,180)), 0, '<a href="http://www.prezent.nl/" title="Prezent Internet B.V.">Prezent<\/a>'));

    var tileLayer= new GTileLayer(myCopyright, 1, 17, { opacity:0.8 } );

    tileLayer.getMyBaseURL = function(tile, zoom) { return me.getMyBaseUrl(tile, zoom); };
    tileLayer.myMercZoomLevel = 4;
    tileLayer.myLayers='selectionGrid';

    tileLayer.myFormat='image/gif';
    tileLayer.getTileUrl=customGetTileUrl;

    baseMapType.getMaximumResolution = function() { return 8; };
    baseMapType.getMinimumResolution = function() { return 4; };
    var tileLayerOverlay = new GTileLayerOverlay(tileLayer);

    return tileLayerOverlay;
};

// called when the clicked on the map
TileMap.prototype.getTileCoords = function(overlay, latlng) {
    var map = this.map;
    var me = this;

    if (overlay != null && 'tileName' in overlay) {

        var name = overlay.tileName;
        if (this.mode == 'custom') {
            GEvent.trigger(this, 'activetileclicked', this.selectedTiles[name]);
        } else {
            this.removeTile(name);
        }
        return;
    } else if (overlay != null && 'inactiveTile' in overlay) {
        GEvent.trigger(this, 'inactivetileclicked', overlay.inactiveTile);
        return;
    }

    function cleanUpRequest(req) { me.requestQueue.shift(); me.requestInProgress = false; me.handleRequest(); };

    if (! latlng) {
    	return;
    }

    this.requestQueue.push({
        'url': '/'+projectUrl+'/portal/xml/getTileInformation.cmd'
        , 'parameters':{ 'mode' : this.mode,  'lat':latlng.lat(), 'lng':latlng.lng(), 'nr':  (this.numSelected - this.numDeselected) + 1 }
        , 'onSuccess':function(req) { me.tilePolygonReceived(req);  cleanUpRequest(req); }
        , 'onError':function(req) { alert('Error!\nStatusText='+req.statusText+'\nContents='+req.responseText); cleanUpRequest(req); }
        , 'timeout': me.requestTimeout
        , 'onTimeout': cleanUpRequest
    });

    this.handleRequest();
};

TileMap.prototype.handleRequest = function() {
    if (! this.requestInProgress && this.requestQueue.length > 0 ) {
        this.requestInProgress = true;
        this.requestQueue[0].parameters.nr = (this.numSelected - this.numDeselected) + 1;
        AjaxRequest.post(this.requestQueue[0]);
    }
};

// remove tile
TileMap.prototype.removeTile = function(name, noTrigger) {
    this.map.removeOverlay( this.selectedTiles[name].polygon);
    this.map.removeOverlay( this.selectedTiles[name].number);
    var deselectedTile = this.selectedTiles[name];
    ++this.numDeselected;
    delete(this.selectedTiles[name]);

    // Trigger tiledeselected event
    if (noTrigger==undefined) {
    	GEvent.trigger(this, 'tiledeselected', deselectedTile);
    }
};

// add tile
TileMap.prototype.addTile = function(tile, noTrigger) {
    var map = this.map;
    var name = tile.id

    ++this.numSelected; // update numSelected.

	if ('isDemo' in tile && tile.isDemo) {
		var backColor	= '#ff0099';
		var borderColor	= '#990033';
	} else if ('colors' in tile && tile.colors.length == 2) {
		var backColor	= tile.colors[0];
		var borderColor	= tile.colors[1];
	}
	else {
		var backColor	= '#000099';
		var borderColor	= '#003ff3';
	}

    this.selectedTiles[name] = {
        polygon: new GPolygon(tile.coords, borderColor, 1, 1, backColor, 0.6),
        number: new ContentOverlay(new GLatLngBounds(tile.coords[3], tile.coords[1]), 'numberOverlay', this.numSelected, tile.name),
        num: this.numSelected
    };

    this.merge(this.selectedTiles[name], tile);

    this.selectedTiles[name].polygon.tileName = this.selectedTiles[name].number.tileName = name;

    map.addOverlay(this.selectedTiles[name].polygon);
    map.addOverlay(this.selectedTiles[name].number);


    // Trigger tileselected event
    if (noTrigger==undefined) {
	    GEvent.trigger(this, 'tileselected', this.selectedTiles[name]);
	}
};

// called when we recieved a response from our ajax call
TileMap.prototype.tilePolygonReceived = function(req) {
    var xmlResponse = req.responseXML.documentElement;
    var tiles = xmlResponse.getElementsByTagName("tile");

    for (var i=0; i<tiles.length; i++) {
        var name = tiles[i].getElementsByTagName("name")[0].firstChild.data;

        if (this.mode == 'custom') {
            // Trigger tileselected event
            var tile = { id: name };
            GEvent.trigger(this, 'tileclicked', tile, req);
        }
        else if (this.mode == 'selection') {
            this.handleSelection(tiles[i])
        }
    }
};

TileMap.prototype.handleSelection = function(tileNode) {
    var name = tileNode.getElementsByTagName("name")[0].firstChild.data;

    if (this.selectedTiles[name]) {
        // tile already exists remove it again
        this.removeTile(name);
    }
    else {
        var selected = tileNode.getElementsByTagName("selected")[0].firstChild.data == 1;
        var coordset = tileNode.getElementsByTagName("coordset");

        var infoNodes = tileNode.childNodes;

        // handle the coords
        if (! selected) {
            // it's not currently selected
            for (var j=0; j < coordset.length; j++) {
                var coords = coordset[j].getElementsByTagName("coord");
                var geoCoordArr = [coords.length];
                for (var x=0; x<coords.length; x++) {
                    var lat = coords[x].getElementsByTagName("lat")[0].firstChild.data;
                    var lng = coords[x].getElementsByTagName("lng")[0].firstChild.data;

                    geoCoordArr[x] = new GLatLng(lat,lng);
                }

                var tile = {
                    id: name,
                    coords: geoCoordArr
                };
                // set extra properties
                for (var k = 3; k < infoNodes.length; k++) {
                    tile[infoNodes[k].tagName] = infoNodes[k].firstChild.data;
                }

                // Add tile to selection
                this.addTile(tile);

            }
        }
    }
};

TileMap.prototype.clearSelection = function(tiles) {
    for (var i in this.selectedTiles) {
        this.removeTile(i,true);
    }
    GEvent.trigger(this, 'tiledeselected');
    this.numSelected = 0;
    this.numDeselected = 0;
    this.requestQueue = [];
};

/**
 * @param   {Object}    tiles   object with the following properties:
 * id, price, coords [{lat: l, lng: l}, ...]
 */
TileMap.prototype.loadSelection = function(tiles) {

	if (tiles.length>0) {
		var tileList = Array();
	    for (var i=0; i < tiles.length; i++) {
	        this.addTile(tiles[i],true);
	        tileList[i] = this.selectedTiles[tiles[i].id];
	    }

	    GEvent.trigger(this, 'tileselected', tileList);

	    for (var i=0; i< tiles.length; i++) {
	        delete(tiles[i]);   // delete element to free up some memory
	        delete(tileList[i]);
	    }
	}
};


TileMap.prototype.addInactiveTile = function(tile) {
    var map = this.map;
    var name = tile.id

    var polygon = new GPolygon(tile.coords, "#003ff3", 1, 1, "#ffffff", 0.7);
    polygon.inactiveTile = tile;

    map.addOverlay(polygon);

};
/**
 * @param   {Object}    tiles   object with the following properties:
 * id, coords [{lat: l, lng: l}, ...]
 */
TileMap.prototype.loadInactiveSelection = function(tiles) {
    for (var i=0; i < tiles.length; i++) {
        this.addInactiveTile(tiles[i]);
        delete(tiles[i]);   // delete element to free up some memory
    }
};


TileMap.prototype.merge = function(obj1, obj2, overwrite) {

    for (var i in obj2) {
        if (overwrite || ! (i in obj1)) {
            obj1[i] = obj2[i];
        }
    }
};

// ************ End Of TileMap class ************** //

/**
 * A ContentOverlay is a simple overlay that outlines a lat/lng bounds on the
 * map. It can be styled by passing in a className and you can pass the (html) content too.
 * @constructor
 */
function ContentOverlay(bounds, className, content, title) {
    this.bounds_ = bounds;
    this.className_ = className;
    this.content_ = content;
    this.title_ = title ? title : '';
}
ContentOverlay.prototype = new GOverlay();

/**
 * Initialize ContentOverlay
 * @param   {GMap2} map
 */
ContentOverlay.prototype.initialize = function(map) {
    // Create the DIV representing our rectangle
    var div = document.createElement("div");
    div.className = this.className_;
    div.innerHTML = this.content_;
    if (this.title_ != '') {
        div.setAttribute('title', this.title_);
    }

    // We add ourselves to the Marker pane, so that we sit above the polygons
    map.getPane(G_MAP_MARKER_PANE).appendChild(div);

    this.map_ = map;
    this.div_ = div;
};


ContentOverlay.prototype.remove = function() {
    this.div_.parentNode.removeChild(this.div_);
};
ContentOverlay.prototype.copy = function() {
    return new ContentOverlay(this.bounds_, this.className_, this.content_, this.title_);
};
ContentOverlay.prototype.redraw = function(force) {
    // We only need to redraw if the coordinate system has changed
    if (!force) {
        return;
    }

    var zoom = this.map_.getZoom();
    var sizes = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 6, 6: 9, 7: 12, 8: 15 };

    // set font size according to zoom level.
    this.div_.style.fontSize = ((zoom in sizes) ? sizes[zoom] : 18) + 'px';

    // Calculate the DIV coordinates of two opposite corners of our bounds to
    // get the size and position of our rectangle
    var c1 = this.map_.fromLatLngToDivPixel(this.bounds_.getSouthWest());
    var c2 = this.map_.fromLatLngToDivPixel(this.bounds_.getNorthEast());

    // Now position our DIV based on the DIV coordinates of our bounds
    this.div_.style.width = Math.abs(c2.x - c1.x) + "px";
    this.div_.style.height = Math.abs(c2.y - c1.y) + "px";
    this.div_.style.left = (Math.min(c2.x, c1.x)) + "px";
    this.div_.style.top = (Math.min(c2.y, c1.y)) + "px";
};
// setup event handlers to load/unload the map.
pDomApi.addEvent(window, 'domload', function() {
    var tileMap = new TileMap('mapSelection', 670, 450);
    if (tileMap.initialize('custom')) {
        tileMap.tileHosts = hosts;
        tileMap.start();
        tileMap.loadSelection(tileSelection);
        GEvent.addListener(tileMap, 'activetileclicked', handleActiveTileClick);

        // A tile has been clicked and we are sure it is not a selected one
        GEvent.addListener(tileMap, 'tileclicked', function(tile, req) { handleInactiveTileClick(tile, req, tileMap); });
    }
});
pDomApi.addEvent(window, 'unload', GUnload);


function handleActiveTileClick(tile) {
	// tiles is a global var passed by PHP
	var len = tiles.length;

	// Loop through purchaces tiles to see if user has clicked on one of his tiles.
	if (tile.id in tiles) {

		// Indeed, our dear user has clicked a purchased tile.
		// Let's give him what he wants and redirect him to the
		// Tile detail page.
		window.location.href = "/" + projectUrl + "/portal/detail/show.cmd/" + tiles[tile.id];
		return false;
	}
}

function handleInactiveTileClick(tile, req, tileMap) {
	var map = tileMap.getMap();
	var latLng = new GLatLng(req.parameters.lat, req.parameters.lng);
	var parts =  translation.msg_select_tile.split('%s');
	var cont = document.createElement('div');
	cont.className = 'popupMessage';
	var link = document.createElement('a');
	link.setAttribute('href', '/'+projectUrl+'/portal/order/createSelection.cmd/' + tile.id);
	link.setAttribute('title', translation.txt_select_tile_link);
	link.appendChild(document.createTextNode(translation.txt_select_tile_link));

	cont.appendChild(document.createTextNode(parts[0]));
	cont.appendChild(link);
	if (1 in parts) {
		cont.appendChild(document.createTextNode(parts[1]));
	}

	// Show info window
	map.openInfoWindow(latLng, cont);
}
