/***** Global variables *****/
if (window.location.href.indexOf("seamap-dev") > 0) {
	g_DEV = true;
	g_ROOT_URL = "http://seamap-dev.env.duke.edu";
} else {
	g_DEV = false;
	g_ROOT_URL = "http://seamap.env.duke.edu";
}

BASE_PLONE_URL = '/functions/';
BASE_URL = '/';


var map;
var gMapserver = null;
var gUrlPhp = g_ROOT_URL + "/seamap2/main/seamap2_gm.php";

// Google Maps initial center location and zoom level.
// Can be changed for each page.
var gInitLoc = {latitude: 10, longitude: 0, zoom: 1}; 	

var gPlone = BASE_PLONE_URL;		// For seamap-dev.env
var gIconBase = "/seamap2/icons/";

var myTileOverlay;
var gSeamapLayerOpacity = 1.0;
var gClickedPoint;
var gROITransparency = 0.7;
var gCell = null;
var gZoom = {"01": 5, "001": 9};
var gMaxDim = {maxWidth:320, maxHeight: 170};
var gPolygonInEdit = false;

// The following global variables are used in dataset_detail and species_profile
var gActiveLayer = "dist"	// [dist|point]
var gLayerName = "";		// Set when the point layer is shown. This is the name appeared in mapfile
var gRelatedLayerName = "";

var gYearMin;
var gYearMax;
var gYearStart;
var gYearEnd;
var gYearSlider1;
var gYearSlider2;

var gPrevTaxa = "";
var gRangeCriteria = false;
var gSpeciesList = [];
var gTemporalScale = 2;	// Century:0, Decade:1, Year:2, Month:3, Day:4; Season:-1; seasonal month:-2
var gDownloading = false;

var gWinW = GetWindowSize('width');

/* EXT JS version */
gQuery = {
	sp_tsn: [],	// List of TSNs comma-separated. Get value from species search textbox.
	sp_tsns: [],	// set from other methods. e.g. protected species checkbox in species page.
	sp_rank: "",	// taxonomic rank selected in taxon tree in Species Search.
	datasets: "",	// List of dataset IDs comma-separated, returned by internal queries
	dataset: [], 	// dataset choosen by user
	series: [],
	provider_datasets: "",	// Set when a provider is chosen in datasets.html
	spatial: "",
	temporal: "",
	criteria: "", 	// Additional search criteria mainly used for species search
	temporalZ: "",
	taxaColumn: "all_taxa",
	temporalScale: 2,		// Century:0, Decade:1, Year:2, Month:3, Day:4; Season:-1; seasonal month:-2
	publish: "publish",
	layer_type: "dataset",
	count_type: "record",	// [animal|record]
	additionalWhere: function(mode) {return {};},		// Replace this function with specialized one (e.g. for ESAS)
	
	query: function(mode) {
		var tsns = "";
		if (this.sp_tsns.length != 0) {
			tsns = this.sp_tsns.join(",");
		}
		if (this.sp_tsn.length > 0) {	// If a single species is chosen, devoid this.sp_tsns
			tsns = this.sp_tsn.join(",");
		}
		
		var datasets = "";
		if (this.datasets != "") {
			datasets = this.datasets;
		}
		if (this.provider_datasets != "") {
			datasets = this.provider_datasets;
		}
		if (this.dataset.length > 0) {
			datasets = this.dataset.join(",");
		}
		
		if (this.series.length > 0) {
			var series_quoted = this.series.collect(function(item){return "'" + item + "'"});
			var series = series_quoted.join(",");
		} else {
			var series = "";
		}
		
		var params = $H({toQueryString: mode, sp_tsn: tsns, datasets: datasets, spatial: this.spatial, temporal_scale: this.temporalScale, temporal: this.temporal, series: series, publish: this.publish});
		
		switch (mode) {
			case "chart":
			case "dist_layer":
				params = params.merge({criteria: this.criteria, sp_rank: this.sp_rank, count_type: this.count_type});
				break;
			case "chart_z_union":
				var spatial = this.spatialZ();
				params.update({spatial: spatial});
				break;
			case "z_union":
				var spatial = this.spatialZ();
				var spatial_effort = this.spatial;
				var temporal = this.temporalZ;
				params.update({spatial: spatial, spatial_effort: spatial_effort, temporal: temporal, series: series});
				break;
			default:		// facts
				break;
		}
		
		params = params.merge(this.additionalWhere(mode));
		var param_obj = params.toObject();
		return param_obj;		
	},
	
	toQueryString: function(mode) {
		var params = this.query(mode);
		var params_str = $H(params).toQueryString();
		return params_str;
	},
	
	spatialZ: function() {
		return this.spatial.replace(/geom(?=[,\s])/g, "_geom");
	},
	
	clearTemporal: function() {
		this.temporal = "";
		this.temporalZ = "";
	}
}

/* this custom RowSelectionModel allows multiple selection with single click.
Also clicking the selected row deselects it.
The default RowSelectionModel requires to do these with CTRL-Click
*/
Ext.ux.RowSelectionModelMSBSC = Ext.extend(Ext.grid.RowSelectionModel, {
	singleSelect: false,
	multiSelectWithClick: false,
	deselectWithClick: true,
	
	/* override selectRow */
    handleMouseDown : function(g, rowIndex, e){
        if(e.button !== 0 || this.isLocked()){
            return;
        };
        var view = this.grid.getView();
        if(e.shiftKey && !this.singleSelect && this.last !== false){
            var last = this.last;
            this.selectRange(last, rowIndex, e.ctrlKey);
            this.last = last; // reset the last
            view.focusRow(rowIndex);
        }else{
            var isSelected = this.isSelected(rowIndex);
            if((e.ctrlKey && isSelected) || (this.deselectWithClick && isSelected)){
                this.deselectRow(rowIndex);
            }else if(!isSelected || this.getCount() > 1){
                this.selectRow(rowIndex, e.ctrlKey || e.shiftKey || this.multiSelectWithClick);
                view.focusRow(rowIndex);
            }
        }
    }	 
});

if (Ext.isIE) {
	var gTableRow = 'block';
	var gTableCell = 'block';
} else {
	var gTableRow = 'table-row';
	var gTableCell = 'table-cell';
}


function load() {
	if (GBrowserIsCompatible()) {
		map = new GMap2($("map"), {draggableCursor:"default", backgroundColor: '#1D1C56'});
		map.addControl(new GLargeMapControl());
		//map.addControl(new GHierarchicalMapTypeControl());
		map.addControl(new GMapTypeControl());

		map.setCenter(new GLatLng(gInitLoc["latitude"], gInitLoc["longitude"]), gInitLoc["zoom"], G_SATELLITE_MAP);

		var copyright = new GCopyright(1,
		   new GLatLngBounds(new GLatLng(23,122),new GLatLng(46,151) ),
		    1, "OBIS-SEAMAP");

		var copyrightCollection = new GCopyrightCollection('SEAMAP Distribution');
		copyrightCollection.addCopyright(copyright);

		var myTileLayer = new GTileLayer(copyrightCollection, 1, 16);
		myTileLayer.getTileUrl = myGetTileUrl;
		myTileLayer.getOpacity = function() { return gSeamapLayerOpacity; }
		myTileLayer.isPng = function() { return false; }	// Somehow IE6 doesn't show overlays in PNG if there is a polygon.

		myTileOverlay = new GTileLayerOverlay(myTileLayer);

		map.addOverlay(myTileOverlay);

		GEvent.addListener(map, "click", on_click_identify);
		GEvent.addListener(map, "infowindowclose", erase_cell);
		if ($('current_position')) {
			GEvent.addListener(map, 'mousemove', on_mousemove_coordinate);
		}
		
		if (typeof(map_loaded) == 'function') {
			map_loaded();
		}
	}
}

function initialize_mapfile() {
	var layer_name = choose_layer(1, false);
	
	var action = "initialize_mapfile";
	var where = gQuery.toQueryString("dist_layer");
	gMapserver.request(action, mapfile_initialized,
		{
			layer_name: layer_name,
			layer_type: "dist",
			publish: gQuery.publish,
			taxa_column: "all_taxa"
		}, where, {});
}

function mapfile_initialized(oj) {
	var return_value = oj.responseText.evalJSON();
	gMapserver.parameters.sid = return_value.sid;
	load();
}


function update_map() {
	gMapserver.refresh();
	myTileOverlay.refresh();
}

/***** Mapserver layer definition *****/
// Return URL to get a tile image from Mapserver.
function myGetTileUrl(tile, zoom) {
    // max zoom plus 1
   	var layer_name = choose_layer(zoom, true);
    var projection = new GMercatorProjection(18);

    // Four vertices location in pixcel in GPoint coordinates
    var p1 = new GPoint(tile.x*256,tile.y*256);
    var p2 = new GPoint(p1.x+256,p1.y+256);

    // latitude/longitude of four vertices location in decimal degree
    var latlng1 = projection.fromPixelToLatLng(p1,zoom);
    var latlng2 = projection.fromPixelToLatLng(p2,zoom);

    var lat1 = latlng1.lat();
    var lon1 = latlng1.lng();
    var lat2 = latlng2.lat();
    var lon2 = latlng2.lng();

    // binding box for mapserver
    var minlat = Math.min(lat1,lat2);
    var minlon = Math.min(lon1,lon2);
    var maxlat = Math.max(lat1,lat2);
    var maxlon = Math.max(lon1,lon2);

	var mapext = minlon + "," + minlat + "," + maxlon + "," + maxlat;
	var parameters = {
		mode: "draw_map",
		layer_name: layer_name,
		layer_type: gActiveLayer,
		mapsize: "256+256",
		mapext: mapext,
		outputformat: gMapserver.parameters.outputformat,
		zoom: zoom
	}

    var url = gMapserver.mapserverRequest(parameters);		
    return url;
}


// EXT JS Version
function update_dist_layers() {
	var zoom = map.getZoom();
	var layer_name = choose_layer(zoom, false);

	var where = gQuery.query('dist_layer');
	
	if ($("frm_options") != null && typeof(get_env_time_range) != "undefined") {
		var time_range = get_env_time_range();
		if (time_range.start == "" || time_range.temporal == "") {
			alert("Wrong time range. Environmental layer won't show up.");
			return ture;
		}
	
		where["date_start"] = time_range.start;
		where["temporal_scale"] = time_range.temporal;
		where["env_type"] = radio_value("frm_options", "env_options");
	}
	
	var where_str = $H(where).toQueryString();
		
	gMapserver.request("update_dist_layers", custom_query_updated,
		{
			layer_name: layer_name,
			layer_type: gActiveLayer,
			taxa_column: gQuery.taxaColumn
		}, where_str, {});
}

function on_mousemove_coordinate(point) {
	$("current_position").update("X:" + point.lng().toFixed(2) + " Y:" + point.lat().toFixed(2));
}

function choose_legend(layer_name, cellsize) {
	var count_type = gQuery.count_type;
	var legend_name = gIconBase + "legend_" + layer_name + "_" + count_type + ".png";
	if (legend_name != $('img_legend').src) {
		$('img_legend').src = legend_name;
	}

	var legend_cellsize = gIconBase + "legend_cellsize_v2_" + cellsize + ".png";
	if (legend_cellsize != $('legend_cellsize').src) {
		$('legend_cellsize').src = legend_cellsize;
	}
}


function update_species_list_div() {
	// Species list is in gSpeciesList set by speces_search
	$('species_count').update(gSpeciesList.length);
	if (gSpeciesList.length > 0) {
		var s= "<table class='listing' cellspacing='0' cellpadding='3'>";
		var prev_taxa = "";
		for (var i = 0; i < gSpeciesList.length; i++) {
			var species = gSpeciesList[i];
			var taxa = species.obis_taxa;
			var sp_tsn = species.sp_tsn;
			if (prev_taxa != taxa) {
				s += "<tr class='provider_row'><td colspan='5'>" + taxa + "</td></tr>";
				s += "<tr _class='provider_row'><th>Scientific</th><th>Common</th><th>Rank</th><th>#obs.</th><th>#datasets</th></tr>";
			}
			s += "<tr onmouseover='change_bgcolor(this, \"#A2BBC6\");  this.style.cursor = \"pointer\";' onmouseout='change_bgcolor(this, \"transparent\")' onclick='choose_species(\"" + species.scientific_name + "\");'>";
			s += "<td><a class='species_link' title='take you to the species profile page' href='species_profile.php?id=" + sp_tsn + "'>" + species.scientific_name + "</a></td>";
			s += "<td>" + species.common_name + "</td><td>" + species.sp_rank + "</td>";
			s += "<td align='right'>" + int_format(species.records, false) + "</td>";
			s += "<td align='right'>" + int_format(species.datasets, false) + "</td>";
			s += "</tr>";
			prev_taxa = taxa;
		}
		s += "</table>";
	} else {
		var s = "Please sppecify species or draw your region of interest first.<BR>";
	}
	$('species_list').update(s);
}


function species_search(species) {
	var parameters = $H({criteria: species, start_at:0, num_species:-1}).toQueryString();
	var url = gPlone + "getSpecies?" + parameters;
	new Ajax.Request(url,
		{
			method: 'GET',
			onSuccess: species_search_results,
			asynchronous: false
		});

	return gQuery.sp_tsns;	// gQuery.sp_tsns is set in species_search_results
}


function build_range_select(chart_data) {
	gYearMax = chart_data.year_max;
	gYearMin = chart_data.year_min;
	gYearStart = gYearMin;
	gYearEnd = gYearMax;
	gYearSlider1 = gYearStart;
	gYearSlider2 = gYearEnd;

	/* for lined up years */
	var years = chart_data.years;
	if (years.length > 20) {
		var year_class = "year_narrow";
		var cell_width = 20;	// 18 + 2 (border width)
		var diff = 0;			// 23 - 23
	} else {
		var year_class = "year_wide";
		var cell_width = 23;	// 21 + 2 (border width)
		var diff = 3;			// 23 - 20
	}

	if (gYearMax == 104) {		// Season
		var year_class = "season";
		var cell_width = 44;	// 42 + 2 for season
		$("charts_div").removeClassName("chart_year");
		$("charts_div").addClassName("chart_season");
	} else {	// Year, month, day and seasonal month
		$("charts_div").removeClassName("chart_season");
		$("charts_div").addClassName("chart_year");
	}

	var data_max = chart_data.data_max;
	if (data_max == 14 || data_max == 15) {
		var padding_left = 19 - diff;
	} else if (data_max < 4) {
		var padding_left = 13 - diff;
	} else if (data_max < 10) {
		var padding_left = 5 - diff;
	} else if (data_max < 100) {
		var padding_left = 11 - diff;
	} else if (data_max < 1000) {
		var padding_left = 17 - diff;
	} else if (data_max < 10000) {
		var padding_left = 24 - diff;
	} else if (data_max < 100000) {
		var padding_left = 30 - diff;
	} else {
		var padding_left = 36 - diff;
	}
	$("div_table").style.paddingLeft = padding_left + "px";
	$("div_slider").style.paddingLeft = padding_left + "px";


	var table = "<table cellpadding=0 style='' id='year_table'>";
	var cell_id = "year_" + (gYearMin - 1);
	var row1 = "<td><div id='" + cell_id + "' class='" + year_class + " chart_cell'></div></td>";
	var row2 = "<td>NA</td>";
	for (var i = 0; i < years.length; i++) {
		var year = parseInt(years[i]);
		if (gQuery.temporalScale == -1) {
			var season_labels = {101:'Wi', 102:'Sp', 103:'Su', 104:'Fa'};
			var year_str = season_labels[year];
		} else {
			var year_str = year.toString().substring(2);
		}
		var cell_id = "year_" + year;
		var div = "<div id='" + cell_id + "' class='" + year_class + " chart_cell year_selected'></div>";
		row1 += "<td>" + div + "</td>";
		row2 += "<td>" + year_str + "</td>";
		
		if (i > 0 && (i + 1) % 45 == 0) {	// chart break if there are more than 45 data.
			var cell_id = "year_" + (gYearMax + 1);
			row1 += "<td><div id='" + cell_id + "' class='" + year_class + " chart_cell'></div></td>";
			row1 += "<td><div id='" + cell_id + "' class='" + year_class + " chart_cell'></div></td>";
			row2 += "<td>NA</td>";
			row2 += "<td>NA</td>";
			if (typeof(gDataset) != "undefined" && gDataset.id == 533) {	// Melissa's dataset 533 has wider buffer to accommodate effort bar chart on the rifht-hand side Y-axis.
				// species profile page does not define gDataset.
				row1 += "<td><div id='" + cell_id + "' class='" + year_class + " chart_cell' style='width:10px'></div></td>";
				row2 += "<td>NA</td>";	
			}
		} 
	}
	var cell_id = "year_" + (gYearMax + 1);
	row1 += "<td><div id='" + cell_id + "' class='" + year_class + " chart_cell'></div></td>";
	row2 += "<td>NA</td>";
	table += "<tr>" + row1 + "</tr>";
	table += "</table>";

	$('div_table').update(table);

	// Build slider
	var year_start_div = "<img id='slider1' class='slider' src='" + gIconBase + "seamap2_slidebar_v01.png' style='z-index:60;' title='slide the bar to set time range' width='" + cell_width + "' height='20'>";
	var year_end_div = "<img id='slider2' class='slider' src='" + gIconBase + "seamap2_slidebar_v01.png' style='z-index:60;' title='slide the bar to set time range' width='" + cell_width + "' height='20'>";
	$('div_slider').update(year_start_div + year_end_div);

	// Adjust the slider table width, so the second bar shows up.
	var table_width = $('year_table').getWidth();
	$('div_slider').style.width = (table_width) + 'px';
	$('slider1').style.left = cell_width + "px";	// skip the first 'NA' cell
	$('slider2').style.left = (table_width - cell_width * 3) + "px";	// 71 = 27 (image width) * 3; Make the slidebar width the same as table cell width

	//new Draggable('slider1',{zindex: 10, constraint:'horizontal', snap:[27,0], onEnd:identify_target_year});
	if (Ext.isIE7) {
		// IE7 doesn't like snap. It tends to loose focus on the slidebar image
		new Draggable('slider1',{zindex: 10, constraint:'horizontal', onEnd:identify_target_year, endeffect:null, scrollSensitivity:5, scrollSpeed:50});
		new Draggable('slider2',{zindex: 10, constraint:'horizontal', onEnd:identify_target_year, endeffect:null, scrollSensitivity:5, scrollSpeed:50});
	} else {
		new Draggable('slider1',{zindex: 10, constraint:'horizontal', snap:[cell_width,0], onEnd:identify_target_year});
		new Draggable('slider2',{zindex: 10, constraint:'horizontal', snap:[cell_width,0], onEnd:identify_target_year});
	}

	if (Ext.isIE) {
		// IE doesn't adjust the height.
		$('div_chart').style.height = 200;
	}
}

function identify_target_year(slider) {
	var slider_offset = slider.element.cumulativeOffset()[0];

	$$("div.chart_cell").each(function (item) {
		var year_offset = $(item).cumulativeOffset()[0];
		if (slider_offset < year_offset + 17 && slider_offset > year_offset - 17) {
			slider_dropped(slider.element, $(item));
			return true;
		}
	});
	
}

function slider_dropped(slider, dropped_year) {
	if (slider.id == 'slider1') {
		var tempYear = parseInt(dropped_year.id.split("_")[1]);
		if (tempYear == gYearSlider1) {
			return true;
		}
		if (tempYear == gYearMin - 1 || tempYear == gYearMax + 1) {		// NA cell
			gYearSlider1 = -1;
		} else {
			gYearSlider1 = tempYear;
		}
	}

	if (slider.id == 'slider2') {
		var tempYear = parseInt(dropped_year.id.split("_")[1]);
		if (tempYear == gYearSlider2) {
			return true;
		}
		if (tempYear == gYearMin - 1 || tempYear == gYearMax + 1) {		// NA cell
			gYearSlider2 = -1;
		} else {
			gYearSlider2 = tempYear;
		}
	}

	gYearStart = Math.min(gYearSlider1, gYearSlider2);
	gYearEnd = Math.max(gYearSlider1, gYearSlider2);
	if (gYearStart == -1) {
		gYearStart = gYearEnd;
	}

	$$("div.chart_cell").each(function (item) {
		var year = $(item).id.replace("year_", "");
		if (year >= gYearStart && year <= gYearEnd) {
			$(item).addClassName('year_selected');
		} else {
			$(item).removeClassName('year_selected');
		}
	});

	build_time_range_query();
	
	if (typeof(time_range_changed) != 'undefined') {
		time_range_changed();
	}
}

function build_time_range_query() {
	if ((gYearMin != gYearStart || gYearMax != gYearEnd)) {
		var date_part = "obs_year";										// Default for 0|1|2
		var date_part_z = "to_char(_obs_datetimez, 'YYYY')::int";		// Default for 0|1|2
		switch (gQuery.temporalScale) {
			case -1:	// Season
			case -2:	// Seasonal month
				var months = "";
				var delimiter = "";
				if (gQuery.temporalScale == -1) {
					var seasons = {101: "12, 1, 2", 102: "3, 4, 5", 103: "6, 7, 8", 104: "9, 10, 11"};
				} else {
					var seasons = {101: "1", 102: "2", 103: "3", 104: "4", 105: "5", 106: "6", 107: "7", 108: "8", 109: "9", 110: "10", 111: "11", 112: "12"};
				}
				for (var j = gYearStart; j <= gYearEnd; j++) {
					months += delimiter + seasons[j];
					delimiter = ", ";
				}
				var where = "date_part in (" + months + ")";
				var date_part = "obs_month";
				var date_part_z = "date_part('month', _obs_datetimez)";
				break;
			case 0:	// Century
				// As Mapserver seems to regard a plus as a spetial character,
				// you con't do this: ((obs_year - 1) / 100)::int + 1 >= " + gYearStart
				var where = "((date_part - 1) / 100)::int >= " + gYearStart + " - 1 and ((date_part - 1) / 100)::int <= " + gYearEnd + " - 1";
				break;
			case 1:	// Decade
				var where = "((date_part / 10)::int) * 10 >= " + gYearStart + " and ((date_part / 10)::int) * 10 <= " + gYearEnd;
				break;
			case 2:	// Year
				var where = "date_part >= " + gYearStart + " and date_part <= " + gYearEnd;
				break;
			case 3:	// Month
				var where = "date_part >= to_date('" + gYearStart + "01', 'YYYYMMDD') and date_part < to_date('" + (gYearEnd + 1) + "01', 'YYYYMMDD')";
				var date_part = "date_min";
				var date_part_z = "_obs_datetimez";
				break;
			case 4:	// Day
				var where = "date_part >= to_date('" + gYearStart + "', 'YYYYMMDD') and date_part < to_date('" + (gYearEnd + 1) + "', 'YYYYMMDD')";
				var date_part = "date_min";
				var date_part_z = "_obs_datetimez";
				break;
		}
	} else {
		var where = "";
	}
	gQuery.temporal = where.replace(/date_part/g, date_part);
	gQuery.temporalZ = where.replace(/date_part/g, date_part_z);
}


/***** Year or Season *****/
function year_or_season(button, which) {
	gQuery.temporalScale = which;	
	gQuery.clearTemporal();
	
	$$("img.time_button").each(function (item) {
		$(item).removeClassName('button_selected');
		$(item).src = gIconBase + $(item).id + '.png';
	});
	$(button).src = gIconBase + $(button).id + "_selected.png";
	$(button).addClassName('button_selected');
	
	if (typeof(adjust_scale_bar) == "function") {
		adjust_scale_bar(Math.abs(which));
	}

	if (typeof(year_or_season_changed) == "function") {
		year_or_season_changed();
	}
}

/***** Google Maps custom controls *****/


/***** Identeify start *****/
function on_click_identify(overlay, point) {
	if (NavigationTools.activeTool() != "default") {
		return false;
	}

	map.closeInfoWindow();	// This causes the existing highlighted cell to be removed.

	// GM's default polygon click passes point as undefined, so ignore the event.
	// Custom polygon click event listener passes both overlay and point.
	if (overlay && typeof(point) == 'undefined') {
		return false;
	}

	//$('please_wait').show();
	gClickedPoint = point;
	var lat = point.lat();
	var lon = point.lng();

	// Calculate map extent.
	var projection = new GMercatorProjection(18);
	var pixel_point = projection.fromLatLngToPixel(point, map.getZoom());
	var tolerance_pixel = 255;
	var p2 = projection.fromPixelToLatLng(new GPoint(pixel_point.x + tolerance_pixel, pixel_point.y + tolerance_pixel), map.getZoom());
	var buffer = Math.abs(lon - p2.lng());
	var mapext = [Math.max(lon - buffer, -180), Math.max(lat - buffer, -90), Math.min(lon + buffer, 180), Math.min(lat + buffer, 90)].join(",");
	// gActiveLayer is always 'dist' for dist_map.html and datasets.html
	// It is used in dataset_detail.php when it shows observation points. [dist|point]
	if (gActiveLayer == 'dist') {
		highlight_cell(lon, lat);
	}
	if (gActiveLayer == 'dist') {
		var layer_name = choose_layer(map.getZoom(), false);
	} else {
		var layer_name = "";
	}
	gMapserver.request("identify", show_identify,
		{
			latitude: lat,
			longitude: lon,
			mapext: mapext,
			layer_name: layer_name
		}, "", {});
}

function highlight_cell(lon, lat) {
	var zoom = map.getZoom();
	if (zoom < gZoom["01"]) {
		var times = 1;
		var shift = 0.5;
	} else if (zoom < gZoom["001"]) {
		var times = 10;
		var shift = 0.05;
	} else {
		var times = 100;
		var shift = 0.005;
	}
	//lon = Math.round(lon * times) / times;
	lon = Math.floor(lon * times) / times;
	lat = Math.round(lat * times) / times;
	//var point1 = new GLatLng(lat + shift, lon + shift);
	//var point2 = new GLatLng(lat - shift, lon - shift);
	var point1 = new GLatLng(lat + shift, lon + 1 / times);
	var point2 = new GLatLng(lat - shift, lon);

	var options = {color: "#DDDDDD", weight: 3, opacity: 1};
	gCell = getRectangle(point1, point2, options);
	map.addOverlay(gCell);
}

function erase_cell() {
	if (gCell != null) {
		map.removeOverlay(gCell);
		gCell = null;
	}
}

function show_identify(oj) {
	var msg = oj.responseText;
	map.openInfoWindowHtml(gClickedPoint, msg, gMaxDim);
}
/***** Identeify end *****/

/***** Navigation Tools *****/
// Tool management
var gClickHandler;
var gMousemoveHandler;
var gFirstPoint;
var gSecondPoint;
var gExtent;
var gROIs = [];

var gStep = 1;

var NavigationTools = {
	tools: [],
	toolStack: [{tool_id: 'default', activate: function(){}, deactivate: function(){}}],

	add: function (imgID) {
		var element = $(imgID);
		var action_name = element.id;

		this.tools.push(action_name);

		element.observe('mouseover',
			function (event) {
				if(NavigationTools.activeTool() != action_name) {
					element.src = gIconBase + "icon_" + action_name + '_hover.png'
				}
			});

		element.observe('mouseout',
			function (event) {
				if(NavigationTools.activeTool() != action_name) {
					element.src = gIconBase + "icon_" + action_name + '.png'
				}
			});
	},

	activate: function (tool_id, activate_func, deactivate_func) {
		theTool = $(tool_id);

		if (this.activeTool() != tool_id) {
			this.deactivateCurrentTool();
			theTool.src = gIconBase + "icon_" + tool_id + '_selected.png';
			var active_tool = {tool_id: tool_id, activate: activate_func, deactivate: deactivate_func};
			this.toolStack.push(active_tool);
			activate_func();
		} else {
			this.deactivateCurrentTool();
		}
	},

	activeTool: function() {
		return this.toolStack[this.toolStack.length - 1].tool_id;
	},

	deactivateCurrentTool: function() {
		var active_tool = this.toolStack[this.toolStack.length - 1];
		if (active_tool.tool_id != 'default') {
			$(active_tool.tool_id).src = gIconBase + "icon_" + active_tool.tool_id + '.png';
			active_tool.deactivate();
			this.toolStack.pop();
		}
	}
}


function activate_zoomin() {
	gClickHandler = GEvent.addListener(map, "click", placePoint);
	onFinishPlacePoint = zoomin;
}

function deactivate_zoomin() {
	// Deactivate
	GEvent.removeListener(gClickHandler);
}

function placePoint(marker, point) {
	// Ignore event issued by polyline click
	if (marker && (typeof(point) == 'undefined')) {
		return false;
	}

	if (gFirstPoint == null) {
		if (gExtent) {
			map.removeOverlay(gExtent);
		}
		gFirstPoint = point;
		gMousemoveHandler = GEvent.addListener(map, "mousemove", drawExtent);
	} else {
		GEvent.removeListener(gMousemoveHandler);
		onFinishPlacePoint();
		gFirstPoint = null;
	}
}

function drawExtent(latlong) {
	if (gStep % 3 != 0) {
		gStep++;
		if (gStep > 3) {
			gStep = 1;
		}
		return false;
	}

	gSecondPoint = latlong;

	if (gExtent) {
		map.removeOverlay(gExtent);
	}

	//fillLocationBoxes(latlong);
	gExtent = getRectangle(gFirstPoint, gSecondPoint, {onclick: placePoint});
	map.addOverlay(gExtent);
}

function getRectangle(point1, point2, options) {
	var options_default = {color: "#FFFFFF", weight: 3, opacity: 1.0};
	options = $H(options_default).merge(options).toObject();
	var linePoints = cornerPoints(point1, point2);

	var extent = new GPolyline(linePoints, options.color, options.weight, options.opacity);
	if (options.onclick) {
		GEvent.addListener(extent,"click",function(point){
			//placePoint(extent, point);
			options.onclick(extent, point);
		});
	}
	return extent;
}

function cornerPoints(point1, point2) {
	var lats = Array();
	var lngs = Array();
	lats[0] = point1.lat();
	lngs[0] = point1.lng();
	lats[1] = point2.lat();
	lngs[1] = point2.lng();
	var point3 = new GLatLng(lats[1], lngs[0]);
	var point4 = new GLatLng(lats[0], lngs[1]);

	var linePoints = Array();
	linePoints[0] = point1;
	linePoints[1] = point3 ;
	linePoints[2] = point2;
	linePoints[3] = point4;
	linePoints[4] = point1;

	return linePoints;
}

function getPolygon(points, color_index) {
	var index = gROIs.length;
	poly_opacity = 1.0 - gROITransparency;
	poly_colors = new Array("#FFFF00", "#66FF00", "#FF33CC", "#FF0066", "#00FFFF");
	if (color_index == 'undefined' || color_index == null) {
		var color_index = index % 5;
	}
	poly_color = poly_colors[color_index];
	
	var polygon = new GPolygon(points, poly_color, 2, 0.8, poly_color, poly_opacity);
	GEvent.addListener(polygon,"click",function(point){
		gClickedPolygon = polygon;
		switch (NavigationTools.activeTool()) {
			case "tool_draw_polygon":
				polygon.enableEditing();
				break;
			case "tool_remove_polygon":
				remove_polygon(polygon);
				break;
			default:
        		on_click_identify(polygon, point);
		}
      });
	map.addOverlay(polygon);
	gROIs.push(polygon);
	return polygon;
}

function zoomin () {
	zoomTo();

	// Clear the extent polyline
	map.removeOverlay(gExtent);
	NavigationTools.deactivateCurrentTool();
}

function zoomTo() {
	if (gFirstPoint.lng() < gSecondPoint.lng()) {
		var sw = gFirstPoint;
		var ne = gSecondPoint;
	} else {
		var sw = gSecondPoint;
		var ne = gFirstPoint;
	}
	
	zoomToSwNe(sw, ne, -1);
}

function zoomToSwNe(sw, ne, max_zoom) {
	if (ne != null) {
		var aBound = new GLatLngBounds(sw, ne);
		var aCenter = aBound.getCenter();
		var aZoomLevel = map.getBoundsZoomLevel(aBound);
	} else {
		var aCenter = sw;
		var aZoomLevel = max_zoom;
	}
	map.panTo(aCenter);
	map.setCenter(aCenter);

	/*
	if (aZoomLevel - map.getZoom() > 2) {
		aZoomLevel -= 1;
	} 
	*/
	
	if (max_zoom > 0 && aZoomLevel > max_zoom) {
		aZoomLevel = max_zoom;
	}
	map.setZoom(aZoomLevel);
}

function zoomToBOX3D(extent) {
	//extent = extent.replace("BOX3D(", "");
	//extent = extent.replace(")", "");
	if (extent.indexOf("BOX") >= 0) {
		extent =  extent.substring(extent.indexOf("(") + 1, extent.indexOf(")"));
	}
	extent = extent.split(",");
	var sw = extent[0].split(" ");
	var ne = extent[1].split(" ");
	
	// In rare cases where just one observation is found, you can't define extent.
	// In that case, zoom in to the one observation.
	if (sw[1] != ne[1] && sw[0] != ne[0]) {
		var sw = new GLatLng(sw[1], sw[0]);
		var ne = new GLatLng(ne[1], ne[0]);
		var zoom = 8;
	} else {
		var sw = new GLatLng(sw[1], sw[0]);
		var ne = null;
		var zoom = 4;		// arbitrary
	}
	zoomToSwNe(sw, ne, zoom);
}

function zoomToPoint(lon, lat, zoom) {
	if (zoom == -1) {
		var current_zoom = map.getZoom();
		if (current_zoom < 7) {
			zoom = 7;
		} else {
			zoom = current_zoom;
		}
	}
	var aCenter = new GLatLng(lat, lon);
	map.panTo(aCenter);
	map.setCenter(aCenter);
	map.setZoom(zoom);
}

function activate_draw_rect() {
	gClickHandler = GEvent.addListener(map, "click", placePoint);

	for (var i = 0; i < gROIs.length; i++) {
		// Shapefiles may be a part of gROIs (e.g. exercise_areas). Then, you need to skip them.
		if (typeof(gROIs[i].type) != "string" || gROIs[i].type != "shapefile") {
			gROIs[i].enableEditing();
		}
	}
	
	onFinishPlacePoint = draw_rect;
}

function draw_rect() {
	map.removeOverlay(gExtent);
	
	//var roi = getPolygon(gFirstPoint, gSecondPoint);
	var points = cornerPoints(gFirstPoint, gSecondPoint);
	var polygon = getPolygon(points);

	NavigationTools.deactivateCurrentTool();	
	zoomTo();
}

function deactivate_draw_rect() {
	GEvent.removeListener(gClickHandler);
	deactivate_draw_polygon();
}

function activate_draw_polygon() {
	for (var i = 0; i < gROIs.length; i++) {
		// Shapefiles may be a part of gROIs (e.g. exercise_areas). Then, you need to skip them.
		if (typeof(gROIs[i].type) != "string" || gROIs[i].type != "shapefile") {
			gROIs[i].enableEditing();
		}
	}
	
	gPolygonInEdit = true;		// Turned to false in finish_drawing.
	var polygon = getPolygon([]);
	GEvent.addListener(polygon, "endline", finish_drawing);
	polygon.enableDrawing();

}

function finish_drawing() {
	gPolygonInEdit = false;
	var extent = this.getBounds();
	var sw = extent.getSouthWest();
	var ne = extent.getNorthEast();
	zoomToSwNe(sw, ne, -1);
	NavigationTools.deactivateCurrentTool();
}

function deactivate_draw_polygon() {
	if (gPolygonInEdit) {	// This means a new polygon is left unfinished.
		gROIs[gROIs.length - 1].disableEditing();
		map.removeOverlay(gROIs[gROIs.length - 1]);
		gROIs.pop();
	}
	for (var i = 0; i < gROIs.length; i++) {
		// Shapefiles may be a part of gROIs (e.g. exercise_areas). Then, you need to skip them.
		if (typeof(gROIs[i].type) != "string" || gROIs[i].type != "shapefile") {
			gROIs[i].disableEditing();
		}
	}
	gPolygonInEdit = false; 
	
	gQuery.spatial = build_spatial_query();

	if (typeof(roi_updated) != "undefined") {
		roi_updated();
	}
}

function point_icon () {
	var icon = new GIcon();
	icon.image = gIconBase + "marker_red.png";
	icon.iconSize = new GSize(16, 16);
	icon.iconAnchor = new GPoint(8, 8);
	icon.infoWindowAnchor = new GPoint(8, 8);
	return icon;
}

function build_spatial_query() {
	var coords = "";
	var delimiter = "";

	if (gROIs.length == 0) {
		return "";
	}

	// Obtain all points from all polygons
	var delimiter_poly = "";
	for (var k = 0; k < gROIs.length; k++) {
		var delimiter_point = "";
		var aPolygon = gROIs[k];
		if (typeof(aPolygon.type) == "string" && aPolygon.type == "shapefile") {	// shapefile
			if (aPolygon.coords != "") {
				coords += "((";
				coords += aPolygon.coords;
				coords += "))";
			}
		} else {		// GPolygon
			coords += delimiter_poly + "((";
			for (var l = 0; l < aPolygon.getVertexCount(); l++) {
				var aVertex = aPolygon.getVertex(l);
				coords += delimiter_point + aVertex.lng().toFixed(8) + " " + aVertex.lat().toFixed(8);
				delimiter_point = ",";
			}
			coords += "))";
		}
		delimiter_poly = ",";
	}

	if (coords != "") {
		//var query = "(geom && geometryfromtext('MULTIPOLYGON(" + coords + ")', -1)";
		//query += " and Within(geom, geometryfromtext('MULTIPOLYGON(" + coords + ")', -1)))";
		var query = "st_within(geom, geometryfromtext('MULTIPOLYGON(" + coords + ")', -1))";	// in seamapsql8, implicit bounding box query is don with st_within.
	} else {
		var query = "";
	}
	return query;
}

function activate_remove_roi() {

}

function deactivate_remove_roi() {
	if (typeof(roi_updated) != "undefined") {
		roi_updated();
	}
}

function remove_polygon(polygon) {
	gROIs = gROIs.without(polygon);
	map.removeOverlay(polygon);
	map.closeInfoWindow();
	gQuery.spatial = build_spatial_query();
	NavigationTools.deactivateCurrentTool();
}


function polygon_transparency(slider, value) {		// EXT JS version
	gROITransparency = value / 100;
	change_transparency(gROITransparency);
}

function change_transparency(transparency) {
	for (var i = 0; i < gROIs.length; i++) {
		if (typeof(gROIs[i].type) != "string") {
			gROIs[i].opacity = 1 - transparency;
			gROIs[i].redraw(true);
		}
	}
}


/***** Change color of rows upon mouseover in species_list and dataset_list divs. *****/
function change_bgcolor(item, color) {
	$(item).style.backgroundColor = color;
}

/***** When the user clicks on the species in the popup... *****/
function species_observed(tsns) {
	if (tsns != "") {
		var parameters = $H({sp_tsns: tsns, start_at:0, num_species:-1}).toQueryString();
		var url = gPlone + "getSpecies?" + parameters;
		new Ajax.Request(url,
			{
				method: 'GET',
				onSuccess: update_taxa_infowindow
			});
	} else {
		var s = alert("No species observed in this cell.");
	}
}

function update_taxa_infowindow(oj) {
	var species_info = eval("(" + oj.responseText + ")");
	var species = species_info.species_list;
	s = "";
	for (var i = 0; i < species.length; i++) {
		if (species[i].common_name != "") {
			var common_name = " (" + species[i].common_name + ")";
		} else {
			var common_name = "";
		}
		s += "<a href='javascript:void(0);' class='species_link' onclick='choose_species(\"" + species[i].scientific_name + "\")';>" + species[i].scientific_name + "</a>" + common_name + "<BR>";
	}
	s = "<div style='height:180px; overflow:auto;'>" + s + "</div>";
	var infowindow = map.getInfoWindow();
	map.openInfoWindowHtml(infowindow.getPoint(), s, {maxHeight: 200, maxWidth:400});
}

function set_temporal_scale(scale) {
	gTemporalScale = scale;
}

function open_mapper(id, layer_type, color_by) {
	var url = "/mapper";

	var parameters = $H({'id': id, 'type': layer_type}).toQueryString();
	window.open(url + "?" + parameters);
}

function open_google_earth(key, value, whole) {
	// key = [dataset|sp_tsn]
	if (whole || (gQuery.spatial == "" && gQuery.temporal == "" && ((key == "dataset" && gQuery.sp_tsn.length == 0) || key == "sp_tsn" && gQuery.dataset.length == 0))) {
		var params =  key + "=" + value;
	} else {
		var params = gQuery.toQueryString("facts");
	}
	var modes = {'dataset': 'dataset', 'sp_tsn': 'species'};
	var random = get_random();
	var url = g_ROOT_URL + "/seamap2/mapper/kml.php?" + params + "&mode=" + modes[key] + "&" + random;
	window.open(url);
}

function show_species_pic(url, parameters) {
	var map_pos = $('map').cumulativeOffset();
	$(parameters.div).setStyle({top: (map_pos.top + parameters.offset_top) + "px", left: (map_pos.left + parameters.offset_left) + "px"});
	$(parameters.pic).src = url;	
	//new Effect.Grow(parameters.div, {duration:2.0, delay:1.0, beforeStart: before_show_pic.bind(parameters), afterFinish: function(){gLock = false;}});
	// Image position unpredictable with Grow.
	// It seems Opacity does not change display attribute. So, first change opacity to 0.1 while the element is still hidden.
	// then show the element (display:block) and get it back to opacity 1.
	new Effect.Opacity(parameters.div, {duration:0.1, from:1.0, to:0.1, afterFinish: function(){$(parameters.div).show(); new Effect.Opacity(parameters.div, {duration:2.0, from:0.1, to:1});}});	
}


function hide_species_pic() {
	$('species_pic_div').hide();
}

/* not in use in EXT JS version
function show_hide_map() {
	toggle_row('map_row');
	if ($('map_row').visible()) {
		$('img_show_hide').src = $('img_show_hide').src.replace('expand_', 'hide_');
	} else {
		$('img_show_hide').src = $('img_show_hide').src.replace('hide_', 'expand_');
	}
}
*/

/*
function toggle_row(element) {
	if ($(element).visible()) {
		$(element).hide();
	} else {
		$(element).style.display = gTableRow;
	}

	window_resize();
}
*/

function button_onoff(element, class_name) {
	if ($(element).hasClassName('button_selected')) {
		return true;
	}
	$$(class_name).each(function(item) {
		$(item).removeClassName('button_selected');
		$(item).src = $(item).src.replace('_selected.png', '.png');
	});
	
	$(element).addClassName('button_selected');
	$(element).src = $(element).src.replace('_hover.png', '_selected.png');
}



function map_cursor(cursor) {
	var dragObj = map.getDragObject();
	dragObj.setDraggableCursor(cursor); 	
}


function cancel_bubble(e) {
	if (!e) var e = window.event;
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();	
}


function custom_query_updated(oj) {
	var msg = oj.responseText.evalJSON();
	update_map();

	/*
	Env legends are pre-generated ones stored in /var/www/seamap2/icons.
	The file name is given by seamap2_gm.php (two locations: custom_query and onoff_env_layer
	To udate the legend, call mapserver:
	http://seamap.env.duke.edu/cgi-bin/mapserv?map=/var/www/cache/maps/48f8a3f959b73.map&mode=legend&layer=env_layer
	*/
	if (msg.env_type != "N") {
		var legend_url = msg.env_legend;
		var legend_title = msg.env_layer_desc;
	} else {
		var legend_url = "/seamap2/icons/blank.png";
		var legend_title = "None";
	}
	$$('img.env_legend').each(function(item){item.src = legend_url});
	$$('div.current_env_layer').each(function(item){item.update(legend_title)});
}

function load_env_desc() {
	var container = "env_desc";
	url = "/datasets/content/env_desc.html";
	new Ajax.Updater(container, url, {onComplete: window_resize});
}


/* EXT JS version */
function toggle_graph_panel() {
	/* Graph region is hidden by default. It's not collapsible. */
	var graph_button = $('tool_graph');
	var margin = 5;		// graph_region.height does not include margin. Ideally, margin could be obtained by code.
	var layout = border.layout;
	var graph_panel = Ext.getCmp("graph_and_env_panel");
	var map_panel = Ext.getCmp("map_list_legend_graph_panel");
	
	if (graph_panel.hidden) {
		map_panel.setHeight(map_panel.getSize().height + graph_panel.height + margin);
		graph_panel.show();
		border.doLayout(true);
		
		if (Ext.isIE) {		// Stupid IE can't handle env option panel's collapse bar properly.
			$("env_option_panel-xcollapsed").setStyle({width: "20px"});
			if (!graph_panel.layout.east.isCollapsed) {
				graph_panel.layout.east.panel.setWidth(300);	// Stupid IE can't handle env option panel properly.
			}
		}
		
		if (gQuery.temporal == "") {
			if (gQuery.sp_tsn.length == 0 && gQuery.dataset.length == 0 && gQuery.provider_datasets == "" && gQuery.spatial == "" && gQuery.criteria == "" && gQuery.sp_tsns.length == 0) {
				alert("Graph is available when at least one criterion (species, dataset, provider, region of interest) is specified.");
			} else {
				update_chart();
			}
		}
		graph_button.addClassName("button_selected");
	} else {
		//graph_region.panel.layout.east.panel.collapse();	// also collapse the env option panel.
		graph_panel.hide();
		map_panel.setHeight(map_panel.getSize().height - graph_panel.height - margin);
		border.doLayout(true);
		graph_button.removeClassName("button_selected");
	}
}

function toggle_env_option_panel() {
	var graph_panel = Ext.getCmp("graph_and_env_panel");
	var env_region = graph_panel.layout.east;
	
	if (graph_panel.hidden) {
		toggle_graph_panel();
		env_region.panel.expand();
	} else {
		if (env_region.isCollapsed) {
			env_region.panel.expand();
		} else {
			env_region.panel.collapse();
		}
	}
}

/***** Environmental layers *****/
function onoff_env_layer(env_type) {
	var action = "onoff_env_layer";
	gDontUpdateChart = true;
	
	if (env_type != "bath") {
		var time_range = get_env_time_range();
		if (time_range.start == "" || time_range.temporal == "") {
			alert("Wrong time range. Please enter a date in 'yyyy-mm-dd' format.");
			return true;
		}
	} else {
		var time_range = {temporal: "", start: ""};
	}
	gMapserver.request(action, custom_query_updated,
		{
			layer_name: "env_layer",
			temporal_scale: time_range.temporal,
			date_start: time_range.start,
			env_type: env_type,
			status: env_type != "N"
		}, "");
		
	toggle_east_toolbar(env_type == "N");
	if (env_type == "N") {
		switch_card(0, "general_info_panel");
	}
}

function get_env_time_range() {
	if (radio_value("frm_options", "env_time_range_options") == "sync") {
		var start = gYearStart;
		var temporal = gQuery.temporalScale;
	} else {
		if ($("env_time_range").value == "") {
			var start = "";
		} else {
			var start = $("env_time_range").value.replace(/[-\/]/g, "");
		}
		
		if (start.length == 4) {
			var temporal = 2;
		} else if (start.length == 6) {
			var temporal = 3;
		} else if (start.length == 8) {
			var temporal = 4;
		} else {
			var temporal = "";
		}
	}
	
	var time_range = {start: start, temporal: temporal};
	return time_range;
}

function env_time_range_options_changed() {
	var time_range_option = radio_value("frm_options", "env_time_range_options");
	if (time_range_option == "sync") {
		$("env_time_range").disabled = "disabled";
	} else {
		$("env_time_range").disabled = false;
	}
	var env_type = radio_value("frm_options", "env_options");
	if (env_type != "N") {
		onoff_env_layer(env_type);
	}
}

function popup_dataset_list(datasets) {
	gMapserver.request("get_dataset_name", show_popup_dataset_list,
		{
			dataid: datasets
		}, "");
}

function show_popup_dataset_list(oj) {
	var dataset_list = oj.responseText.evalJSON();
	var msg = "<div style='width:350px; height:150px'><table class='listing'><thead><tr><th>Name (ID)</th></tr></thead><tbody>";
	for (var i = 0; i < dataset_list.length; i++) {
		msg += "<tr><td>" + dataset_list[i] + "</td></tr>";
	}
	msg += "</tbody></table></div>";
	map.openInfoWindowHtml(gClickedPoint, msg, {maxWidth:400, maxHeight:200});
}

// Download data. Used in Datset/Species Profile pages and Online Mapper.
function download_data(taxa, format, data_selection) {
	if (typeof(gDataset) != "undefined" && gActiveLayer == "point" && data_selection != "whole" && gQuery.sp_tsn.length == 0 && gQuery.spatial == "" && gQuery.temporal == "") {
		// If the user happened to click "data on map" while point layer is active and no criteria are set in the Dataset Page,
		// this request should be redirected to the already zipped CSV/shapefile. (e.g. seamapxx.zip or seamapxx_csv.zip)
		data_selection = "whole";		
	}
	
	var params = {sid: gMapserver.parameters.sid, mode: "start", layer_type: gActiveLayer, format: format, sp_class: taxa, data_selection: data_selection};
	
	if (data_selection == "whole") {
		if (typeof(gDataset) != "undefined") {
			params["layer_type"] = "dataset";
			params["dataset_id"] = gDataset.id;
		} else {
			params["layer_type"] = "point";
			var where = "sp_tsn=" + gQuery.sp_tsn[0];
			where += "&publish=" + gQuery.publish;
		}
	} else {
		switch (gActiveLayer) {
			case "dist":
				var layer_name = choose_layer(map.getZoom(), false);
				if (layer_name == "dist_taxa_multideg") {
					alert("At this moment, data download for multi-resolution map is not availalbe.\nPlease choose other resolutions (e.g. 0.01/0.1/1).");
					return true;
				}
				var where_type = "facts";
				params["layer_name"] = layer_name;
				break;
			case "point":
				var where_type = "z_union";
				break;
		}
		
		var where = gQuery.toQueryString(where_type);
	}
	
	var url = "/seamap2/main/download.php?" + $H(params).toQueryString();
	url += "&" + where;
	url += "&" + get_random();
	new Ajax.Request(url,
		{
			method: 'POST',
			onSuccess: download_started
		});	
}

function download_started(oj) {
	var msg = oj.responseText.evalJSON();
	var request_id = msg["request_id"];
	var now = (new Date()).format("Y-m-d H:i:s");
	$("download_status").update("Request ID: " + request_id + "<br/>Requested: " + now);
	gDownloadTsk = {
		run: check_download_status.createDelegate(this, [request_id]),
		interval: 2000,
		duration: 1200000		// 20 minutes
	}
	Ext.TaskMgr.start(gDownloadTsk);	
}

function check_download_status(request_id) {
	if (!gDownloading) {
		var url = "/seamap2/main/download.php?";
		url += "&mode=status" + "&sid=" + gMapserver.parameters.sid + "&request_id=" + request_id;
		new Ajax.Request(url,
			{
				method: 'POST',
				onSuccess: download_status
			});		
		gDownloading = true;
	}
}

function download_status(oj) {
	var msg = oj.responseText.evalJSON();
	if (msg["date_updated"] != "" &&  msg["status"] != "") {
		var now = (new Date()).format("H:i:s");
		$("download_status").insert({top: now + ": " + msg["status"] + "<br/>"});
	}
	if (msg["status"].indexOf("Ready to download") > -1 || msg["status"].indexOf("Error") > -1) {
		Ext.TaskMgr.stop(gDownloadTsk);
	}
	if (msg["status"].indexOf("Ready to download") > -1) {	
		var request_id = msg["request_id"];
		var url = "/seamap2/main/download.php?";
		url += "mode=download" + "&sid=" + gMapserver.parameters.sid + "&request_id=" + request_id;
		window.open(url, "download");
		var link = msg["download_url"];
		$("download_status").insert({top: "If download failed, try the link below.<br/><a href='" + link + "' target='download'>Download</a><br/>"});
	}
	gDownloading = false;
}

function download_desc(description, target) {
	if (typeof(target) == "undefined") {
		var div = $("download_status");
	} else {
		var div = $(target);	
	}
	// Do not display the description if the download is in progress.
	if (div.innerHTML.indexOf("Request ID") == -1 || div.innerHTML.indexOf("Ready to download") > 0) {
		div.update(description);
	}
}

function show_help_tip(tip_id) {
	var container = "div_help_tip";
	var help_path = "/help/";
	url = help_path + "tip_" + tip_id + ".html" + "?" + get_random();
	new Ajax.Updater(container, url);
	
	var title = tip_id.replace(/_/g, " ").capitalize();
	
	if (!Ext.getCmp("help_tip_window")) {
		var win = new Ext.Window({
			id: "help_tip_window",
			title: title,
			contentEl: 'div_help_tip',
			autoScroll: true,
			autoShow: true,
			layout      : 'fit',
			width       : 800,
			height      : 400,
			closeAction :'hide',
			plain       : true,
			bodyStyle: 'text-align: left; background-color:#FFFFFF',
			bodyCfg: {
		        tag: 'center',
		        cls: 'help_tip'
		    }
		});
	} else {
		var win = Ext.getCmp("help_tip_window");
		win.setTitle(title);
	}
	
	win.show();	
}

/***** Common Javascript Functions *****/
function GetWindowSize(type){
	var dimensions = document.viewport.getDimensions();
    switch(type){
	case "width":
		return dimensions.width;
	case "height":
		return dimensions.height;
	default:
		return dimensions;
    }
}


function isNumeric(x) {
	// I use this function like this: if (isNumeric(myVar)) { }
	// regular expression that validates a value is numeric
	var RegExp = /^[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?\b$/; // Note: this WILL allow a number that ends in a decimal: -452.
	// compare the argument to the RegEx
	// the 'match' function returns 0 if the value didn't match
	//var result = x.match(RegExp);
	var result = RegExp.test(x);
	return result;
}

function int_format(a_number, short_format) {
	if (a_number == "") {
		var s = "0";
	} else {
		a_number = parseInt(a_number);
		if (short_format) {
			if (a_number >= 1000000) {
				a_number = a_number / 1000000;
				var s = number_format(a_number, 2, '.', ',') + "M";
			} else if (a_number >= 100000) {
				a_number = a_number / 1000;
				var s = number_format(a_number, 1, '.', ',') + "K";
			} else {
				var s = number_format(a_number, 0, '.', ',');
			}
		} else {
			var s = number_format(a_number, 0, '.', ',');
		}
	}
	return s;
}

// number_format is copied from php.js.
// consider including this function in common.js or include php.js itself.
function number_format( number, decimals, dec_point, thousands_sep ) {
    // Format a number with grouped thousands
    // 
    // +    discuss at: http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
    // +       version: 804.1712
    // +   original by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // +     bugfix by: Michael White (http://crestidg.com)
    // +     bugfix by: Benjamin Lupton
    // +     bugfix by: Allan Jensen (http://www.winternet.no)
    // +    revised by: Jonas Raoni Soares Silva (http://www.jsfromhell.com)    
    // *     example 1: number_format(1234.5678, 2, '.', '');
    // *     returns 1: 1234.57     

    var n = number, c = isNaN(decimals = Math.abs(decimals)) ? 2 : decimals;
    var d = dec_point == undefined ? "," : dec_point;
    var t = thousands_sep == undefined ? "." : thousands_sep, s = n < 0 ? "-" : "";
    var i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", j = (j = i.length) > 3 ? j % 3 : 0;
    
    return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
}// }}}

function radio_value(form_name, radio_name) {
	var value = Form.getInputs(form_name,'radio',radio_name).find(function(radio) { return radio.checked; }).value;

	return value;
}

function wait(miliseconds) {
	var date = new Date();
	var curDate = null;

	do {
		curDate = new Date();
	} while(curDate-date < miliseconds);
}

function get_random() {
	// EXT JS required.
	var random = (new Date()).format("mdYHis");	// e.g. 40302009134542 <= 2009-04-30 13:45:42
	return random;
}

/***** XML Utility *****/
function getXmlNodeValue(xml, node_name) {
	if (xml.getElementsByTagName(node_name).length > 0) {
		if (xml.getElementsByTagName(node_name)[0].childNodes.length > 0) {
			var value = xml.getElementsByTagName(node_name)[0].childNodes[0].nodeValue;
		} else {
			var value = "";
		}
	} else {
		var value = "";
	}

	return value;
}