/**
 * This control object is used to control the
 * program flow and provide a set of 
 * functions ot interact between google maps plugin,
 * resources and the server.
 */
var RControl = Class.create();
RControl.prototype = {
	
	findUrl : '/resources;xhr_find',
	
	/**
	 * Define here the available states.
	 */
	/* No action is performed now */
	sClear : 'clear',
	
	/* The user is using the search control. */
	sFind : 'find',
	
	/* The user is creating or editing a NON GEO resource */
	sNonGeoResource : 'nonGeoResource',
	
	/* The user is creating or editing a GEOresource */
	sGeoResource : 'geoResource',
	
	/* The user is zooming */
	sZoom : 'zoom',
	
	/**
	 * Thesse search types are knowen and can be used.
	 */
	fBB : 'boundingBox',
	fRadial : 'radial',
	
	/**
	 * The user is able to choose between theese
	 * tabs:
	 */
	rTabs : ['resource_details_show', 'resource_details_edit', 'resource_details_images'],
	
	/**
	 * Theese setting tabs are possible to access.
	 */
	tTags : 'resources_tags',
	tResults : 'resources_results',
	tSearch : 'resources_search',
	tJobs : 'resources_jobs',
	tExport : 'resources_export',
	
	resources : new Hash(),
	resourceOverlays : new Hash(),
	treeNodes : new Hash(),
	
	/* Set the maximum zoom level for a point */
	maxPointZoomLevel : 10,
	
	/**
	 * Instantiate RControl and assign the 
	 * current map. From no on, the map instance
	 * provide the rControl member to access this
	 * object.
	 * @param {GMap2} map
	 */
	initialize : function(map) {
		if (rControl) {
			throw 'This object should exsiting only one time';
		}
		this.map = map;
		this.resetTree();
		this.map.rControl = this;
		this.tree.rControl = this;
		
		/* Put the tabs into an array */
		this.sTabs = [
			this.tTags,
			this.tResults,
			this.tSearch,
			this.tJobs,
			this.tExport
		];
		
		/* Reset the current state to 'clear' */
		this.reset(this.sClear);
	},
	
	/**
	 * This function will be called if the servers
	 * prepared a new resource and want's the client
	 * to add it to an internal database.
	 * 
	 * The non geobased resources will be stored directly
	 * into a hash. For each geobased resource a Overlay
	 * will be created and this overlay will be stored.
	 * 
	 * Both are accessable by resource-id.
	 * 
	 * This function also adds a new node to the tree.
	 * 
	 * @param {SResource} resource The resource to add
	 * @param {Boolean} _addTreeNode Set the parameter to true if
	 * 	also a new tree node should be added. Default is true.
	 */
	addResource : function(resource, _addTreeNode) {
		var self = this;
		_addTreeNode = _addTreeNode == null ? true : _addTreeNode;
		if (!resource.id) {
			throw "Resource has no id. It can't be added!"
		}
		
		if (resource.position) {
			var overlay = new SResourceOverlay(resource);
			this.resourceOverlays[resource.id] = overlay;
			this.map.addOverlay(overlay);
			overlay.show();
		}
		this.resources[resource.id] = resource;
				
		/* Adds a resource to the tree */
		if (_addTreeNode) {
			this.addTreeNode(resource);
		}
		
		if (resource.hasChildren()) {
			resource.children.each(function(child) {
				self.addResource(child, true);
			});
		}
		
		/* 
		 * A new resource was added, zoom to the bounding
		 * box of all available resources on map.
		 */
		if (!this.isInTransaction()) {
			this.zoomToResources();
		}
		
		if (resource.isRoot() && resource.loaded) {
			/* Add change-event listener */
			resource.onChange = function() {
				self.fireOnEditGeoResource(resource);
			}
		}
	},
	
	/**
	 * Adds a tree node to the tree witch represents
	 * this resource.
	 * @param {SResource} resource The resource to add
	 */
	addTreeNode : function(resource) {
		
		var label = resource.label || ' --- ';
		if (resource.struct) {
			label += '<br />(' + resource.struct.name + ')';
		}
		
    var object = new Hash({
			label: label,
			id: resource.id
		});
		
		var opts = new Hash({
			deleteable: resource.parent == null ? resource.isDeleteable() : false,
			checkable: resource.position || resource.isGeo() ? true : false,
			checked: true
		});
		
		var rootNode = resource.parent ? this.treeNodes[resource.parent.id] : this.tree.getRoot();
    var node = new YAHOO.widget.ResourceNode(object, rootNode, false, opts);
		this.treeNodes[resource.id] = node;
		
		/* 
		 * If this object is in transaction with the
		 * server, then don't repaint the tree every time after
		 * a resource was added.
		 */
		if (!this.isInTransaction()) {
			this.tree.draw();
		}
	},
	
	/**
	 * Returns the resource with the given id.
	 * It can be either a SResource or a SGeoresource object.
	 * 
	 * @param {Integer} id The id of the searched resource.
	 * @return SResource, SGeoResource or null if not found.
	 */
	getResource : function(id) {
		return this.resources[id];
	},
	
	/**
	 * Returns the overlay witch resource has
	 * the given id.
	 * @param {Integer} id The id of the searched resource.
	 */
	getOverlay : function(id) {
		return this.resourceOverlays[id];
	},
	
	/**
	 * Finds a resource by id and removes it from
	 * Map. This resource will NOT be removed serverside!
	 * @param {Integer} id The id of the resource to remove
	 * @param {Boolean} rmTreeNode Set the value to true and also
	 * 	the treeNode will be removed. Default is true.
	 */
	removeResource : function(resource, rmTreeNode) {
		if (resource) {
			var self = this;
			rmTreeNode = rmTreeNode == null ? true : rmTreeNode;
			var overlay = this.resourceOverlays.remove(resource.id);
			this.resources.remove(resource.id);
			
			if (resource.hasChildren()) {
				resource.children.each(function(child) {
					self.removeResource(child, true);
				});
			}
			
			/* Remove the tree node if it's neccesary */
			if (rmTreeNode) {
				this.removeTreeNode(resource.id);
			}
			
			if (overlay) {
				this.map.removeOverlay(overlay);
				delete overlay;
			}
			
			if (resource) {
				resource.destroy();
				delete resource;
			}
			
			/* After resource was removed, repaint the map. */
			if (!this.isInTransaction()) {
				this.zoomToResources();
			}
		}
	},
	
	/**
	 * Removes the tree node witch represents the resource
	 * with the give id.
	 * @param {Integer} id The resource witch was removed
	 */
	removeTreeNode : function(id) {
		if (id) {
			var node = this.treeNodes.remove(id);
			if (node) {
				if (node.tree && this.tree.removeNode(node)) {
					if (!this.isInTransaction()) {
						this.tree.draw();
					}
				}
			}
			delete node;
		}
	},
	
	/**
	 * Replaces the resource with the given id,
	 * with the new resource. If the id's doesn't match
	 * nothing happens.
	 * @param {Integer} id The id of the old resource
	 * @param {SResource} newResource The new resource to add
	 */
	replaceResource : function(id, newResource) {
		if (id == newResource.id) {
			this.startTransaction();
				/* Remove resource but not the tree node */
				this.removeResource(this.getResource(id), false);
				/* Add the new resource but not a new tree node*/
				this.addResource(newResource, false);
				this.zoomToResource(newResource);
			/* End this transaction, redraw the tree component but don't zoom to all resources' */
			this.endTransaction(true, false);
		} else {
			throw "The old and new resource id's doesn't match!";
		}		
	},
	
	/**
	 * This function will remove and destroy all resources.
	 * The hash storrages will be created empty after then.
	 * If resource is within a google maps overlay,
	 * first the overlay will be removed, then the resource
	 * destroyed.
	 */
	clearResources : function(createNewHashes) {
		createNewHashes = createNewHashes == null ? true : createNewHashes;
		
		var self = this;
		if (this.resources) {
			this.resources.each(function(pair) {
				self.removeResource(pair.value);
			});
			delete this.resources;
		}
		if (this.resourceOverlays) {
			this.resourceOverlays.each(function(pair) {
				self.map.removeOverlay(pair.value);
				delete pair.value;
			});
			delete this.resourceOverlays;
		}
		
		if (this.treeNodes) {
			delete this.treeNodes;
		}
		
		if (createNewHashes) {
			this.resources = new Hash();
			this.resourceOverlays = new Hash();
			this.treeNodes = new Hash();		
		}
		this.resetTree();
		this.reset();
	},
	
	/**
	 * Adds a struct to a hash. To access it, use the getStruct
	 * function.
	 * @param {SStruct} struct The struct to add.
	 */
	addStruct : function(struct) {
		if (!this.structs) {
			this.structs = new Hash();
		}
		this.structs[struct.symbol] = struct;
	},
	
	/**
	 * Returns the struct with the given syambolm,
	 * or null if there is no such struct
	 * @param {Object} symbol
	 */
	getStruct : function(symbol) {
		return this.structs ? this.structs[symbol] : null;
	},
	
	/**
	 * Returns all resources that are geo based and shown on map.
	 * @return SResource[] An array with geo resources
	 */
	getGeoResources : function() {
		var geoResources = new Array();
		this.resourceOverlays.each(function(pair) { 
			geoResources.push(pair.value.resource);
		});
		return geoResources;
	},
	
	/**
	 * Starts a new geobased search. The only task is to
	 * submit the search parameters and show the indicator.
	 * From then on the server will proceed.
	 * 
	 * @param {Hash} hash A hash with parameters.
	 * @param {Boolean} includeForm Set to true if the the search form should be submitet too.
	 */
	find : function(hash, includeForm) {
		var self = this;
		includeForm = includeForm == null ? true : includeForm;
		hash = hash || $H();
		var form = document.getElementById('resource_search_form');
		var result = includeForm ? Form.serialize(form) + '&' + hash.toQueryString() : hash.toQueryString();
		var request = new Ajax.Request(this.findUrl, {
			asynchronous : true,
			parameters : result,
			
			onCreate : function() {
				Element.show("indicator");
				self.startTransaction();
				self.clearResources();
			},
			onComplete : function(response) {
				Element.hide("indicator")
				self.endTransaction(true);
				delete request;
			}
		});
	},
	
	/**
	 * Returns the current state of this control. If
	 * null then there is no action.
	 * 
	 * Every control has to set and to reset the state
	 * by itself!
	 * 
	 * @return String The current state.
	 */
	state : function() {
		return this.currentState;
	},
	
	/**
	 * Resets the state of the control. This means, that
	 * the current action ends here and a new action will
	 * be stated.
	 * @param {String} state The new state.
	 */
	reset : function(state) {
		state = state || this.sClear;
		switch(this.state()) {
			case this.sClear :
				break;
			case this.sNonGeoResource :
				if (this.resourceControl) {
					this.resourceControl.destroy();
					delete this.resourceControl;
				}
				break;
			case this.sGeoResource :
				if (this.resourceControl) {
					this.resourceControl.destroy();
					delete this.resourceControl;
				}
				break;
			case this.sFind :
				if (this.searchControl) {
					this.searchControl.destroy();
					delete this.searchControl;
				}
				break;
			case this.sZoom :
				if (this.zoomControl) {
					this.zoomControl.destroy();
					delete this.zoomControl;
				}
				break;
		}
		this.currentState = state;
	},
	
	/**
	 * The request functions are used to communicate
	 * with the server
	 */
	requestResourceUpdate : function(resource, full) {
		if (!resource.requestUpdateUrl) {
  		alert(t('tResourceActionUpdateFailure'));
  	} else {
  		var self = this;
			var request = new Ajax.Request(resource.requestUpdateUrl, {
				parameters : 'resource[json]=' + resource.serialize(full).toJSON(),
				
				onComplete : function(response) {
					Element.hide('indicator')
					delete request;
				},
				onCreate : function() {
					Element.show('indicator')
				}
			});
  	}
	},
	
	/**
	 * The request functions are used to communicate
	 * with the server
	 */
	requestGeoResourceUpdate : function(resource, full) {
		if (!resource.requestGeoUpdateUrl) {
  		alert(t('tResourceActionGeoUpdateFailure'));
  	} else {
  		var self = this;
			var request = new Ajax.Request(resource.requestGeoUpdateUrl, {
				parameters : 'resource[json]=' + resource.serialize(full).toJSON(),
				
				onComplete : function(response) {
					Element.hide('indicator')
					delete request;
				},
				onCreate : function() {
					Element.show('indicator')
				}
			});
  	}
	},
	
	/**
	 * This function is a dynamic loader for the tree
	 * and also the map. The intention is to reload the given resource
	 * completely. This function will send a request to the server 
	 * and wait for the responce. If the answer is positive the
	 * old resource will be replaced by the new one.
	 * @param {SResource} resource The resource to reload
	 * @param {Hash} options The options hash contains the callback function and
	 * 	some references to the tree.
	 */
	requestGeoResourceDetails : function(resource, options) {
		
		/* 
		 * This callback can be done from the tree, so
		 * get the components from options hash.
		 */
		var tree = options ? options.tree : null;
		var callback = options ? options.cb : null;
		var node = options ? options.node : null;
		var treeRequest = node && tree && callback;
		var self = this;
		
		/*
		 * If the given resource is already a primitive
		 * geo, then there id nothing to load.
		 */
		if (resource.isGeo() && resource.primitive && treeRequest) {
			node.hasIcon = false;
      node.expanded = true;
			callback();
			return true;
		}
		
		/* If this resource is primitive, 
		 * there is nothing to expand.
		 */
		if (!resource.requestGeoDetailsUrl) {
  		alert(t('tResourceActionGeoDetailsFailure'));
  	} else if (resource.requestGeoDetailsUrl) {
			var request = new Ajax.Request(resource.requestGeoDetailsUrl, {
        asynchronous : true,
				
        onCreate : function() {
            Element.show('indicator');
        },
        onComplete : function(response) {
            Element.hide('indicator');
						if (callback) {
							callback();
						}
						delete request;
        }
    	});
		} else {
			alert("Can't request resource information without geodetails url");
		}
		
  	return true;
  },
  
  /**
   * Creates a new Ajax request to delete the resource with the
   * given ID.
   *
   * @param {Integer} ID the ID of the resource to delete.
   */
  requestDeleteResource : function(id) {
  	var resource = this.getResource(id);
  	if (!resource.requestDeleteUrl) {
  		alert(t('tResourceActionDeleteFailure'));
  	} else if (confirm(t('tResourceActionDeleteConfirm') + ' (#' + id + ')')) {
	  	var self = this;
			var request = new Ajax.Request(resource.requestDeleteUrl, {
			  asynchronous : true,
			
			  onCreate : function() {
			  	Element.show('indicator');
				},
				onComplete : function(response) {
			  	Element.hide('indicator');
			  	self.reset();
					delete request;
			  },
			  onSuccess : function() {
					self.removeResource(resource);
				}
			});
		}
  },
  
  /**
   * Donates resource with the given ID. Resource will be reloaded
   * if it was successfully donated. 
   *
   * @param {Integer} ID the ID of the resource to donate.
   */
  requestResourceDonate : function(id) {
  	var resource = this.getResource(id);
  	if (!resource.requestDonateUrl) {
  		alert(t('tResourceActionDonateFailure'));
  	} else if (confirm(t('tResourceActionDonateConfirm'))) {
  		var self = this;
			var request = new Ajax.Request(resource.requestDonateUrl, {
			  asynchronous : true,
			
			  onCreate : function() {
			  	Element.show('indicator');
				},
				onComplete : function(response) {
			  	Element.hide('indicator');
			  	self.reset();
			  },
			  onSuccess : function() {
					self.find($H({'search[ids]': resource.id}));
				}
			});
  	}
  },
	
	/**
	 * Calculates the bounding box of the given resource
	 * and gets the center. Center the map to the center
	 * and zooms until the resource boudning box fits.
	 * 
	 * @param {SResource} resource
	 */
	zoomToResource : function(resource) {
		var bb = resource.getBoundingBox();
		if (bb && !this.latLngCompare(bb.getSouthWest(), bb.getNorthEast())) {
			var zoom = resource.loaded ? this.map.getBoundsZoomLevel(bb) : this.map.getZoom();
			this.map.setCenter(bb.getCenter(), zoom);
		} else if (resource.position) {
			this.map.setCenter(resource.position);
		}
	},
	
	/**
	 * Gets all boundings boxes of the gebased resources
	 * within the given array and zooms the map to the union
	 * of this bounding boxes. If no array is given, the
	 * geobased resource will be takes as default.
	 * @param {Array} array Array of resources
	 */
	zoomToResources : function(array) {
		array = array || this.getGeoResources();
		
		var b = new GLatLngBounds();
		array.each(function(resource) {
			if (resource.position) {
				b.extend(resource.position)
			}
		});
		if (!b.isEmpty()) {
			if (!this.latLngCompare(b.getSouthWest(), b.getNorthEast()))
				this.map.setCenter(b.getCenter(), this.map.getBoundsZoomLevel(b));
			else
				this.map.setCenter(b.getCenter());
		}
	},
	
	/**
	 * Ceters the map on the position of the given resource.
	 * @param {SResource} resource
	 */
	gotoResource : function(resource) {
		if (resource.position) {
			this.map.setCenter(resource.position);
		}
	},
	
	/**
	 * This Event will be fired, if the user wants to
	 * find geobased resources. The requested parameter
	 * tells the funktion about the search type he wishes.
	 * 
	 * @param {String} type Either boundingBox or radial are allowed
	 */
	fireOnFind : function(type) {
		if (!type) {
			throw 'Missing parameter : type (boundingBox | radial)';
		}
		
		if (type == this.fBB) {
			this.searchControl = new SResourceBBSearchControl();
		} else if(type == this.fRadial) {
			this.searchControl = new SResourceRadialSearchControl();
		} else {
			throw 'Unknown search type : ' + type;
		}
		
		/* 
		 * Show the search-tab, then the user can make some
		 * additional settings for the search.
		 */
		this.showSettingsTab(this.tSearch);
		
		/* Adds the control, then user can search. */
		this.map.addControl(this.searchControl);
	},
	
	/**
	 * This Event will be called, if user wants to create
	 * a new resource. The parameter is the struct-symbol of
	 * the resource to create.
	 * 
	 * @param {String} symbol The symbol of the struct witch should be
	 * 	instantiated.
	 */
	fireOnNewResource : function(symbol) {
		if (symbol == null) {
			throw 'Missing parameter : symbol (String)';
		}
		/* 
		 * Find the requested struct and if found, then create
		 * a new empty resource
		 */
		var struct;
		if (struct = this.structs[symbol]) {
			struct.isAGeo() ? this.fireOnNewGeoResource(struct) : this.fireOnNewNonGeoResource(struct, 'struct'); 
		} else {
			throw 'Struct not found. Critical';
		}
	},
	
	/**
	 * This function will be called indirectly from
	 * fireOnNewResource when its sure, that a non geobased
	 * resource should be created.
	 * 
	 * @param {SStruct} struct_or_resource Either a SStruct or a SResource
	 * 	instance.
	 * @param {clazz} The class of the first parameter. Either 'resource'
	 * 	or 'struct'
	 */
	fireOnNewNonGeoResource : function(struct_or_resource, clazz) {
		clazz = clazz || 'resource';
		this.resourceControl = new SResourceNonGeoControl(struct_or_resource, clazz);
		this.map.addControl(this.resourceControl);
	},
	
	/**
	 * This function will be called indirectly from
	 * fireOnNewResource when its sure, that a geobased
	 * resource should be created.
	 * 
	 * @param {SStruct} struct The struct to instantiate
	 */
	fireOnNewGeoResource : function(struct) {
		this.resourceControl = new SResourceGeoControl(struct, 'struct');
		this.map.addControl(this.resourceControl);
	},
	
	/**
	 * Will be fired if a geo based resource should be edited
	 * on map. 
	 * 
	 * @param {SResource} The resource to edit.
	 */
	fireOnEditGeoResource : function(resource) {
		var createNewControl = false;
		if (this.state() == this.sGeoResource) {
			if (this.resourceControl.resource.id != resource.id) {
				if (confirm(t('tGeoResourceCancelEditConfirm'))) {
					this.requestGeoResourceDetails(this.resourceControl.resource);
					createNewControl = true;
				}
			}
		} else {
			createNewControl = true;
		}
		
		if (createNewControl) {
			this.reset();
			this.resourceControl = new SResourceGeoControl(resource, 'resource');
			this.map.addControl(this.resourceControl);
		}
	},
	
	/**
	 * This function will be called, if a resource
	 * was selected by user. Depends on its state an
	 * unloaded geo resource will be loaded completetly.
	 * An unloaded non geo resource and a loaded geo resource 
	 * will load detailed information about itself.
	 * 
	 * @param {Object} id The requested resource.
	 */
	fireOnResourceSelected : function(id) {
		this.reset();
		var resource = this.getResource(id);
		
		if (resource.isAGeo() && !resource.loaded) {
			this.requestGeoResourceDetails(resource);
		} else {
			/* We can't request child information */
			if (resource && resource.parent) {
				resource = resource.getRoot();
			}
			this.resourceControl = new SResourceNonGeoControl(resource, 'resource');
			this.map.addControl(this.resourceControl);
		}
	},
	
	/**
	 * This function will be called, if user is using the
	 * zoom-in control to zoom on the map.
	 */
	fireOnZoomIn : function() {
		this.zoomControl = new SResourceZoomInControl();
		this.map.addControl(this.zoomControl);
	},
	
	/**
	 * If this event is fired, the map will be cleand.
	 * All resources will be removed from map.
	 */
	fireOnClear : function() {
		alert("fireOnClear not implemented");
	},
	
	/**
	 * Changes the map-cursor style.
	 */
	mapEnterEditMode : function() {
		var m = document.getElementById('map');
		m.className = m.className.replace(/view/, 'edit');
	},
	
	/**
	 * Reset the cursor style over the map container.
	 */
	mapLeaveEditMode : function() {
		var m = document.getElementById('map');
		m.className = m.className.replace(/edit/, 'view');
	},
	
	/**
	 * Shows the settings tab with given name.
	 * @param {String} name
	 */
	showSettingsTab : function(name) {
		this.showTab(this.sTabs, name);
	},
	
	/**
	 * Shows the resources tab with given name.
	 * @param {String} name
	 */
	showResourceTab : function(name) {
		this.showTab(this.rTabs, name);
	},
	
	/**
	 * Shows any tab of the given array witch is
	 * matching the given name.
	 * @param {Array} array An array of HTML-element id's
	 * @param {String} name The id of the searched tab.
	 */
	showTab : function(array, name) {
		if (!array) { return false; }
		array.each(function(t) {
			t == name ? Element.show(t) : Element.hide(t);
		});	
	},
	
	/**
	 * Compares thwo GLatLng points with each other.
	 * @param {GLatLng} first The first point
	 * @param {GLatLng} second Rhe second point to compare
	 * @return True if the points are equal, false otherwise
	 */
	latLngCompare : function(first, second) {
		return first.lat() == second.lat() && first.lng() == second.lng(); 
	},
	
	/**
	 * This function is using geocoder to get the rigth
	 * address. If address was found, center the map on
	 * finded point.
	 * @param {String} address A real address.
	 */
	gotoAddress : function(address) {
		var geocoder = new GClientGeocoder();
		var self = this;
		geocoder.getLatLng(address,
		function(point) {
			if (!point) {
				alert(address + ' ' + t('tAddressNotFound'));
			} else {
				self.map.setCenter(point, 13);
				self.map.openInfoWindowHtml(point, address);
			}
		});
	},
	
	/**
	 * Theese thrre functions handle the transaction behavior
	 * between server and client. If this object is in transaction
	 * to the tree will not be redrawen after each time a resource
	 * was added and the map will not zoom to the bounding box of all
	 * resources.
	 */
	startTransaction : function() {
		this.transaction = true;
	},
	
	isInTransaction : function() {
		return this.transaction || false;
	},
	
	endTransaction : function(redrawTree, _zoomToResources) {
		redrawTree = redrawTree == null ? true : redrawTree;
		_zoomToResources = _zoomToResources == null ? true : _zoomToResources;
		this.transaction = false;
		
		/* Redraw  */
		if (redrawTree && this.resources.size() > 0) {
			this.tree.draw();
			
		}
		/* Zoom to all resources */
		if (_zoomToResources) {
			this.zoomToResources();
		}
	},
	
	/**
	 * Removes the old tree component and replaces it by a new one.
	 */
	resetTree : function() {
		if (this.tree) {
			delete this.tree;
		}
		this.tree = new YAHOO.widget.TreeView("resources_results_tree");
		this.createTreeEvents();
	}, 
	
	
	/**
	 * Creates functions to handle three events
	 */
	createTreeEvents : function() {
		var self = this;
		this.tree.subscribe("labelClick", function(node) {
			self.fireOnResourceSelected(node.data.id);
    });
    
    this.tree.subscribe("checkClick", function(node) {
      var overlay = self.getOverlay(node.data.id);
      var resource = overlay ? overlay.resource : self.getResource(node.data.id);
      if (overlay) {
	      overlay.show();
      } else if (resource) {
      	resource.show();
      }
      
      /* Center resource on map */
      self.gotoResource(resource);
    });
    
    this.tree.subscribe("uncheckClick", function(node) {
      var overlay = self.getOverlay(node.data.id);
      var resource = overlay ? overlay.resource : self.getResource(node.data.id);
      overlay ? overlay.hide() : resource.hide();
    });
		
		this.tree.subscribe("deleteClick", function(node) {
      self.requestDeleteResource(node.data.id);
    });
		
		/*this.tree.setDynamicLoad(function(node, callback) {
			var opts = new Hash({
				node : node,
				cb : callback,
				tree : self.tree
			});
			self.requestGeoResourceDetails(self.getResource(node.data.id), opts);
    	return true;
    });*/
	},
	
	/**
	 * Destroys all created objects. Its very important to
	 * call this function before document unloading, because
	 * of the possible memory leaks in the browser. 
	 */
	destroy : function() {
		/* Remove all resources and don't create new hash tables.' */
		this.clearResources(false);
		delete this.tree;
	}
}

/**
 * Is used to invert a checkbox selection. Expects every checkbox
 * within a div container.
 * 
 * @param container The conatiner with all the divs.
 */
var invertCheckboxSelection = function(container) {
	var divs = document.getElementById(container).getElementsByTagName("div");
	if (divs) {
		for(i = 0; i < divs.length; i++) {
			var div = divs[i];
      var input = div.getElementsByTagName("input")[0];
      if (input && input.type == "checkbox") {
      	input.checked = !input.checked;
      }
	  }
	}
	return true;
}