 /*
	Based on ka-map with modifications by NeoZone A/S. 
	All modifications copyright (c) NeoZone A/S
 */

var myKaMap = null;
var myKaNavigator = null;
var myXmlOverlay = null;
var updateMap = updateMap_screen; //when in print mode (initializeForPrint called) this is changed
var dataselectionhandler = null;

//utility
function deleteAllChildNodes(node)
{
	if (node.hasChildNodes()) {
		while (node.childNodes.length >= 1) {
			node.removeChild(node.firstChild);       
		} 
	}
}

/*
   Ka-map initialization
*/
function initializeKaMap() {
    initDHTMLAPI();

	myKaMap = new kaMap( 'viewport' );
    myKaNavigator = new kaNavigator( myKaMap );
    //myKaNavigator.activate();

    myKaTracker = new kaMouseTracker(myKaMap);
    myKaTracker.activate();

    myKaMap.registerForEvent( KAMAP_INITIALIZED, null, myInitialized );
    myKaMap.registerForEvent( KAMAP_MAP_INITIALIZED, null, myMapInitialized );
    myKaMap.registerForEvent( KAMAP_MOUSE_TRACKER, null, myMouseMoved );

    myKaMap.initialize();
}

function myInitialized() {
	myKaMap.selectMap(globalSettings.mapName);
}

function myMapInitialized( eventID, mapName ) {
	if (myXmlOverlay == null) {
		myXmlOverlay = new kaXmlOverlay( myKaMap, 250 ); //a canvas for adding geometry objects
	}
	loadPolygons();
	if (globalSettings.copyrightString) {
		var xmin = myKaMap.aMaps[myKaMap.currentMap].currentExtents[0];
		var ymin = myKaMap.aMaps[myKaMap.currentMap].currentExtents[1];
		var xmax = myKaMap.aMaps[myKaMap.currentMap].currentExtents[2];
		var ymax = myKaMap.aMaps[myKaMap.currentMap].currentExtents[3];
		var xpos = xmin;
		var yoffset = Math.round((ymax - ymin)/35);
		var ypos = ymin + yoffset;
		var my_point = myXmlOverlay.addNewPoint('ppcopyright', xpos, ypos); 
		my_point.setInnerHtml("<div class='copyright'>" + globalSettings.copyrightString + "</div>");
	}
	dataselectionhandler.selectFirstDataset(); //will load data
}

/*
   Print mode initialization
*/

function initializeForPrint(dsname, dsid)
{
	updateMap = updateMap_print; //update the function pointer so we do the correct things for printing
	loadData(dsname, dsid);
	//setCopyright();
}

function findPos(obj) 
{
	var curleft = curtop = 0;
	if (obj.offsetParent) {
		curleft = obj.offsetLeft
		curtop = obj.offsetTop
		while (obj = obj.offsetParent) {
			curleft += obj.offsetLeft
			curtop += obj.offsetTop
		}
	}
	return [curleft,curtop];
}

function setCopyright()
{
	if (globalSettings.copyrightString) {
		var elem = $('viewport');
		var pos = findPos(elem);
		var cpelem = document.createElement("div");
		cpelem.id = 'copyrightstring';
		cpelem.style.position = 'absolute';
		//cpelem.style.zIndex = 1000;
		cpelem.style.left = pos[0];
		cpelem.style.top = pos[1] + elem.offsetHeight - 15;
		
		cpelem.innerHTML = globalSettings.copyrightString;
		//var parent = $('layoutframe');
		document.body.appendChild(cpelem);
	}
}

/*
	Polygon and data related functions
*/

function loadPolygons()
{
	//this assumes that a pinfo array exists from another js file
	var start = new Date();
	var polysadded = 0;
	for (var i = 0; i < pinfo.length; i++) {
		var my_point = myXmlOverlay.addNewPoint('pp'+i, 702882, 6161128);
		var my_polygon = new kaXmlPolygon(my_point);
		my_polygon.stroke = 1;
		my_polygon.color = '#ffffff';
		my_polygon.bcolor = '#000000';
		my_polygon.opacity = 0;
		my_polygon.bopacity = 1.0;
		my_polygon.readCoordinates(my_point, pinfo[i][0]);
		my_point.addGraphic(my_polygon);
		polysadded++;
	}
	var end = new Date() - start;
	showDebug("polygons added: " + polysadded + ", duration: " + end/1000 + " s.");
}

function minInArray(ar)
{
	var retval = Number.POSITIVE_INFINITY;
	for (var i = 0; i < ar.length; i++) {
		retval = Math.min(ar[i], retval);
	}
	return retval;
}

function maxInArray(ar)
{
	var retval = Number.NEGATIVE_INFINITY;
	for (var i = 0; i < ar.length; i++) {
		retval = Math.max(ar[i], retval);
	}
	return retval;
}

function loadData(xdsname, xdsid) {
    showProgress("Henter data...");
    var myAjax = new Ajax.Request('datajs/polyvalues.php',
	{
		method: 'get',
		parameters: { dsname: xdsname, dsid:xdsid },
		onSuccess: function(transport) { processLoadData(transport) }
		//onException: showError
	});
}

function showError(response)
{
	showProgress("An unknown error occured!");
}

function processLoadData(response)
{
	var valinfo = eval('(' + response.responseText + ')');
	globalSettings.setCurrentValues(valinfo);
	if (globalSettings.resetIntervalSettings) {
		globalSettings.intervalSettings = globalSettings.defaultIntervalSettings;
	}
	updateMap();
}

function filterToEntityInfo(entityinfo, valinfo)
{
	//return only the items from valinfo that are present in entityinfo
	var retval = {};
	for (var v in valinfo) {
		if (typeof entityinfo[v] != "undefined") {
			retval[v] = valinfo[v];
		}
	}
	return retval;
}

function calcDecimalPlaces(valinfo)
{
	var retval = 0;
	for (var v in valinfo) {
		var value = valinfo[v];
		var thisdecimalplaces = value.toString().substr(value.toString().indexOf(".")+1).length;
		if (thisdecimalplaces != value.toString().length) {
			retval = Math.max(thisdecimalplaces, retval);
		}
	}
	return retval;
}

function updateMap_shared(forprint)
{
	//things that needs to be done no matter whether we are displaying for screen or for print
	var valinfo = globalSettings.getCurrentValues();

	var decimalplaces = calcDecimalPlaces(valinfo);

	//calculate intervals
	var im = globalSettings.getIntervalSettings().getIntervalMethod();
	var noi = globalSettings.getIntervalSettings().getNumberOfIntervals();
	var intervals = im(noi, Object.values(filterToEntityInfo(einfo, valinfo)), decimalplaces, globalSettings.getIntervalSettings());
	globalSettings.getIntervalSettings().setCurrentIntervals(intervals);
	
	var membercount = new Array(intervals.length); //maintain a count of the number of values in each interval
	for (var i = 0; i < membercount.length; i++) membercount[i] = 0;
	var csm = globalSettings.getIntervalSettings().getColorscaleMethod();
	var colorscale = csm(intervals, globalSettings.getIntervalSettings().getReverseColors());
	
	//set the polygon colors (for print, only count members in each interval)
	for (var v in valinfo) {
		var entity = einfo[v]; //find corresponding object in the entities
		if (typeof entity != "undefined") {
			var value = valinfo[v];
			var index = findIntervalIndexForValue(value, intervals);
			membercount[index]++;
			if (!forprint) {
				var color = colorscale[index];
				for (var i = 0; i < entity.polys.length; i++) {
					var point = myXmlOverlay.getPointObject('pp' +  entity.polys[i]);
					point.graphics[0].color = color.asHex();
					point.graphics[0].opacity = 0.7; 
					point.graphics[0].draw(point); //redraw
				}
			}
		}
	}
	var legendhtml = getLegendHTML(intervals, colorscale, membercount, forprint, decimalplaces); 
	$('legend').innerHTML = legendhtml;
	
	updateEntityList(forprint, decimalplaces); 
	
	return [intervals, colorscale, decimalplaces];
}
	
	
function updateMap_print()
{
	var start = new Date();
	
	var intervalinfo = updateMap_shared(true);
	var intervals = intervalinfo[0];
	var colors = intervalinfo[1];
	var decimalplaces = intervalinfo[2];
	var minvaluestep = 1/Math.pow(10, decimalplaces+1);
	
	//Set the image url
	var vp = $('viewport');
	var height = vp.offsetHeight;
	var width = vp.offsetWidth;
	var tmpid = globalSettings.datasetName;
	var printbgmap = globalSettings.printBgMap;
	var printvalues = globalSettings.printValues;
	var valueindex = globalSettings.printValueIndex;
	var bbox = globalSettings.printBBox;
	var intervalstr = "";
	var colorstr = "";
	for (var i = 0; i < intervals.length; i++) {
		intervalstr += (i == 0? "" : "," + (intervals[i-1]+minvaluestep));
	}
	for (var i = 0; i < colors.length; i++) {
		colorstr += (colorstr == ""? "" : ",") + colors[i].asHex();
	}
	
	$('viewport').innerHTML = "<img onload='window.setTimeout(\"window.print()\", 300)' style='margin: 0px; padding: -5px;' height=" + height + " width=" + width + " src='imageprint.php?tmpid=" + tmpid + "&valueindex=" + valueindex + "&height=" + height + "&width=" + width + "&bbox=" + bbox + "&printbgmap=" +  printbgmap + "&printvalues=" +  printvalues +  "&intervals=" + intervalstr + "&colors=" + escape(colorstr) + "'>";

	var end = new Date() - start;
	showDebug("Refresh duration: " + end/1000 + " s.");
	showProgress("");

}

function updateMap_screen()
{
	var start = new Date();
	
	updateMap_shared(false);

	var intervalhtml = getIntervalChoiceHTML(globalSettings.getIntervalSettings()); //getIntervalChoiceHTML is defined by the layout generator
	$('intervalchoice').innerHTML = intervalhtml;
	
	var printhtml = getPrintHTML("printUrlCallBack"); //getPrintHTML is defined by the layout generator
	$('print').innerHTML = printhtml;
	
	var end = new Date() - start;
	showDebug("Refresh duration: " + end/1000 + " s.");
	showProgress("");
}

function printUrlCallBack(printbgmap, printvalues)
{
	//build a print URL
	var varchoicesstr = "";
	var selections = dataselectionhandler.getSelections();
	for (var selection in selections) {
		varchoicesstr += (varchoicesstr == ""? "" : ",") + selections[selection];
	}
	var bbox = "";
	for (var i = 0; i < myKaMap.aMaps[myKaMap.currentMap].currentExtents.length; i++) {
		bbox += (bbox == ""? "" : ",") + myKaMap.aMaps[myKaMap.currentMap].currentExtents[i];
	}
	
	var retval = "nzkamapprint.php?tmpid="+ globalSettings.datasetName
		+ "&valueindex=" + dataselectionhandler.getDataIndex()
		+ "&varchoices=" + varchoicesstr
		+ "&intervalname=" + globalSettings.getIntervalSettings().getIntervalName()
		+ "&colorscalename=" + globalSettings.getIntervalSettings().getColorscaleName()
		+ "&reversecolors=" + globalSettings.getIntervalSettings().getReverseColors()
		+ "&numintervals=" + globalSettings.getIntervalSettings().getNumberOfIntervals()
		+ "&sortcolumn=" + globalSettings.getSortInfo().column
		+ "&sortdesc=" + globalSettings.getSortInfo().descending
		+ "&bbox=" + bbox
		+ "&entityspec=" + globalSettings.entitySpec
		+ "&predefined=" + globalSettings.predefined
		+ "&mapheight=" + globalSettings.mapHeight
		+ "&mapwidth=" + globalSettings.mapWidth
		+ "&style=" + globalSettings.style
		+ "&decimalseparator=" + globalSettings.decimalseparator
		+ "&thousandseparator=" + globalSettings.thousandseparator
		;
	if (globalSettings.getIntervalSettings().getIntervalName() == "Brugerdefineret") {
		//add the current interval values
		retval += "&intervalvalues=" + globalSettings.getIntervalSettings().getCurrentIntervals().join(",");
	}
	retval += printbgmap? "&printbgmap=true" : "&printbgmap=false";
	retval += printvalues? "&printvalues=true" : "&printvalues=false";
	return retval;
}


function updateEntityList(forprint, decimalplaces)
{
	var entityhtml = getEntityListHTML(datasetinfo.geovariableinfo, einfo, globalSettings.getCurrentValues(), globalSettings.getSortInfo(), globalSettings.datasetName, forprint, globalSettings.entityDetailPage, decimalplaces); 
	$('entities').innerHTML = entityhtml;
}

/*
	Handling of intervals
*/
function deleteAllChildNodes(node)
{
	if (node.hasChildNodes()) {
		while (node.childNodes.length >= 1) {
			node.removeChild(node.firstChild);       
		} 
	}
}

function showIntervalOptions()
{
	var settings = globalSettings.getIntervalSettings();
	//make sure UI reflects current interval settings
	var elem = $('numintervalsselect');
	elem.selectedIndex = findOptionIndexByText(elem, settings.getNumberOfIntervals());
	elem = $('intervalmethodselect');
	elem.selectedIndex = findOptionIndexByText(elem, settings.getIntervalName());
	elem = $('colorscalemethodselect');
	elem.selectedIndex = findOptionIndexByText(elem, settings.getColorscaleName());
	elem = $('reversecolorscalecheckbox');
	elem.checked = settings.getReverseColors();
	
	showOrHideUserdefinedIntervals($('intervalmethodselect'));
	
	showdiv("intervaloptions");
}

function changeIntervalSettings()
{
	//make sure UI reflects current interval settings
	var elem = $('numintervalsselect');
	var numIntervals = elem.options[elem.selectedIndex].value;
	elem = $('intervalmethodselect');
	var intervalMethod = eval(elem.options[elem.selectedIndex].value);
	var intervalName = elem.options[elem.selectedIndex].text;
	elem = $('colorscalemethodselect');
	var colorscaleMethod = eval(elem.options[elem.selectedIndex].value);
	var colorscaleName = elem.options[elem.selectedIndex].text;
	elem = $('reversecolorscalecheckbox');
	var reverseColors = elem.checked;
	
	globalSettings.setIntervalSettings(new KapolymapIntervalSettings(numIntervals, intervalName, intervalMethod, colorscaleName, colorscaleMethod, reverseColors));
	hidediv("intervaloptions");
}

function getIntervalChoiceHTML(settings)
{
	var iname = settings.getIntervalName();
	return "<b>Intervalinddeling: <a title='Klik for at redigere intervalinddeling' class='clickable' href='javascript:showIntervalOptions();'>" + iname + "</a></b>";
}

function hideIntervalOptions()
{
	var shouldhideuserdefined = (globalSettings.getIntervalSettings().getIntervalMethod() != UserdefinedIntervals);
	$('userdefinedintervals').style.display = shouldhideuserdefined ? "none" : "block";
	$('intervaloptions').style.display = 'none';
}

function updateUserdefinedNumIntervals(sender)
{
	var intervalmethodelem = $('intervalmethodselect');
	if (intervalmethodelem.options[intervalmethodelem.selectedIndex].value == "UserdefinedIntervals") {
		buildUserdefinedIntervalsInputs();
	}
}

function showOrHideUserdefinedIntervals(sender)
{
	if (sender.options[sender.selectedIndex].value == "UserdefinedIntervals") {
		$('userdefinedintervals').style.display = "block";
		buildUserdefinedIntervalsInputs();
	} else {
		$('userdefinedintervals').style.display = "none";
	}
}

function buildUserdefinedIntervalsInputs()
{
	var re = new RegExp('\\.', "g");
	var area = $('userdefinedintervals');
	var numintervalsselect = $('numintervalsselect');
	var numintervals = numintervalsselect.options[numintervalsselect.selectedIndex].value;
	var currentIntervals = globalSettings.getIntervalSettings().getCurrentIntervals();
	deleteAllChildNodes(area);
	for (var i = 0; i < numintervals; i++) {
		var labelelem = document.createElement("span");
		labelelem.innerHTML = (i < 9? " " : "") + "Max. værdi " + (i+1) + ": ";
		var inputelem = document.createElement("input");
		inputelem.className = "numinput";
		if (i < currentIntervals.length) {
			inputelem.value = currentIntervals[i].toString().replace(re, globalSettings.decimalseparator);
		}
		area.appendChild(labelelem);
		area.appendChild(inputelem);
		area.appendChild(document.createElement("br"));
	}
}

/*
	Interval calculations
*/
function LinearIntervals(numintervals, data, decimalplaces)
{
	var retval = new Array();
	var minvalue = minInArray(data);
	var maxvalue = maxInArray(data);
	var intervalsize = parseFloat(((maxvalue-minvalue)/numintervals).toFixed(decimalplaces));
	var lastvalue = minvalue;
	for (var i = 0; i < numintervals; i++) {
		retval[i] = lastvalue + intervalsize;
		lastvalue = retval[i];
	}
	return retval;
}

function PercentileClosestObservationIntervals(numintervals, data, decimalplaces)
{
	//calculate the percentiles using this method:
	//http://www.xycoon.com/method_6.htm
	var retval = new Array();
	if (data.length == 0) return retval;
	var sorteddata = data.sort( function(a,b) { return a - b; } );
	var n = sorteddata.length;
	var actualnumintervals = 0;
	for (var i = 1; i <= numintervals; i++) {
		var p = i/numintervals;
		var np = n * p + 0.5;
		var index = Math.floor(np);
		if (index >= data.length) index = data.length-1;
		if (i == 1 || data[index] != retval[actualnumintervals-1]) { //we count i from 1, so we compensate here
			retval[actualnumintervals] = data[index]; //don't add a new interval unless the value has changed
			actualnumintervals++;
		}
	}
	return retval;
}

function UserdefinedIntervals(numintervals, data, decimalplaces)
{
	//userdefined intervals, read them from input elements
	var re = new RegExp(globalSettings.decimalseparator, "g");
	var retval = new Array();
	var area = $('userdefinedintervals');
	var startelem = area.firstChild;
	for (var i = 0; i < numintervals; i++) {
		while (startelem != null && startelem.tagName != "INPUT") {
			startelem = startelem.nextSibling;
		}
		if (startelem != null) {
			var numasstring = startelem.value;
			var internalvalue = numasstring.replace(re, ".");
			retval.push(parseFloat(internalvalue));
		}
		startelem = startelem.nextSibling;
	}
	return retval;
}

function IntervalsFromArray(numintervals, data, decimalplaces)
{
	return intervalarray;
}

function IndexNumbersIntervals(numintervals, data, decimalplaces)
{
	//A static division of intervals
	var retval = new Array();
	retval[0] = 60;
	retval[1] = 80;
	retval[2] = 99;
	retval[3] = 100;
	retval[4] = 120;
	retval[5] = 140;
	return retval;
}

function IndexNumbersPercentileIntervals(numintervals, data, decimalplaces)
{
	//A percentile division of intervals, with a center interval of 100
	var sorteddata = data.sort( function(a,b) { return a - b; } );
	var below100 = new Array();
	var above100 = new Array();
	var i = 0;
	while (sorteddata[i] < 100) {
		below100[i] = sorteddata[i];
		i++;
	}
	while (sorteddata[i] == 100) { i++; } //skip these
	var startabove = i;
	while (i < sorteddata.length) {
		above100[i-startabove] = sorteddata[i];
		i++;
	}

	var intervalsbelow100 = PercentileClosestObservationIntervals(Math.floor((numintervals-1)/2), below100);
	var intervalsabove100 = PercentileClosestObservationIntervals(Math.ceil((numintervals-1)/2), above100);
	var retval = new Array();
	retval = retval.concat(intervalsbelow100);
	retval[retval.length-1] = 99; //make sure last interval below 100 actually goes to 99
	retval.push(100); //Add the center interval
	retval = retval.concat(intervalsabove100);
	return retval;
}

/*
	color scale calculation
*/
function createRGBColorScale(intervals, reverse, minc, maxc)
{
	var mincolor = new Rico.Color( 255, 0, 0);
	var maxcolor = new Rico.Color( 0, 255, 0);
	if (arguments.length > 2) {
		mincolor = minc;
		maxcolor = maxc;
	}

	var retval = new Array();
	for (var i = 0; i < intervals.length; i++) {
		var factor = i/(intervals.length-1);
		retval[i] = new Rico.Color(0,0,0);
		for (var comp in retval[i].rgb) {
			retval[i].rgb[comp] = Math.round(mincolor.rgb[comp] + (maxcolor.rgb[comp] - mincolor.rgb[comp])*factor);
		}
	}
	return reverse? retval.reverse() : retval;
}

function createRGBBWColorScale(intervals, reverse, minc, maxc)
{
	var mincolor = new Rico.Color( 0, 0, 0);
	var maxcolor = new Rico.Color( 255, 255, 255);
	if (arguments.length > 2) {
		mincolor = minc;
		maxcolor = maxc;
	}

	var retval = new Array();
	for (var i = 0; i < intervals.length; i++) {
		var factor = i/(intervals.length-1);
		retval[i] = new Rico.Color(0,0,0);
		for (var comp in retval[i].rgb) {
			retval[i].rgb[comp] = Math.round(mincolor.rgb[comp] + (maxcolor.rgb[comp] - mincolor.rgb[comp])*factor);
		}
	}
	return reverse? retval.reverse() : retval;
}

function createHSBColorScale(intervals, reverse, minc, maxc)
{
	var mincolor = new Rico.Color( 255, 0, 0);
	var maxcolor = new Rico.Color( 0, 255, 0);
	if (arguments.length > 2) {
		mincolor = minc;
		maxcolor = maxc;
	}
	var retval = new Array();
	for (var i = 0; i < intervals.length; i++) {
		var factor = i/(intervals.length-1);
		var hsbcolor = new Rico.Color(0,0,0).asHSB();
		var minhsbcolor = mincolor.asHSB();
		var maxhsbcolor = maxcolor.asHSB();
		for (var comp in hsbcolor) {
			hsbcolor[comp] = minhsbcolor[comp] + (maxhsbcolor[comp] - minhsbcolor[comp])*factor;
		}
		retval[i] = new Rico.Color(0,0,0);
		retval[i].rgb = Rico.Color.HSBtoRGB(hsbcolor.h, hsbcolor.s, hsbcolor.b);
	}
	return reverse? retval.reverse() : retval;
	
}

function createDivergingSpectralColorScale(intervals, reverse, minc, maxc)
{
	/* a diverging spectral color scale, from http://colorbrewer.org */
	var retval = new Array(intervals.length);
	switch (intervals.length) {
		case 2:
			//2 intervals are actually not in the original color scale, but we support it, so we borrow from the 3-interval case
			retval[0] = new Rico.Color(252,141,89);
			retval[1] = new Rico.Color(153,213,148);
			break;
		case 3:
			retval[0] = new Rico.Color(252,141,89);
			retval[1] = new Rico.Color(255,255,191);
			retval[2] = new Rico.Color(153,213,148);
			break;
		case 4:
			retval[0] = new Rico.Color(215,25,28);
			retval[1] = new Rico.Color(253,174,97);
			retval[2] = new Rico.Color(171,221,164);
			retval[3] = new Rico.Color(43,131,186);
			break;
		case 5:
			retval[0] = new Rico.Color(215,25,28);
			retval[1] = new Rico.Color(253,174,97);
			retval[2] = new Rico.Color(255,255,191);
			retval[3] = new Rico.Color(171,221,164);
			retval[4] = new Rico.Color(43,131,186);
			break;
		case 6:
			retval[0] = new Rico.Color(213,62,79);
			retval[1] = new Rico.Color(252,141,89);
			retval[2] = new Rico.Color(254,224,139);
			retval[3] = new Rico.Color(230,245,152);
			retval[4] = new Rico.Color(153,213,148);
			retval[5] = new Rico.Color(50,136,189);
			break;
		case 7:
			retval[0] = new Rico.Color(213,62,79);
			retval[1] = new Rico.Color(252,141,89);
			retval[2] = new Rico.Color(254,224,139);
			retval[3] = new Rico.Color(255,255,191);
			retval[4] = new Rico.Color(230,245,152);
			retval[5] = new Rico.Color(153,213,148);
			retval[6] = new Rico.Color(50,136,189);
			break;
		case 8:
			retval[0] = new Rico.Color(213,62,79);
			retval[1] = new Rico.Color(244,109,67);
			retval[2] = new Rico.Color(253,174,97);
			retval[3] = new Rico.Color(254,224,139);
			retval[4] = new Rico.Color(230,245,152);
			retval[5] = new Rico.Color(171,221,164);
			retval[6] = new Rico.Color(102,194,165);
			retval[7] = new Rico.Color(50,136,189);
			break;
		case 9:
			retval[0] = new Rico.Color(213,62,79);
			retval[1] = new Rico.Color(244,109,67);
			retval[2] = new Rico.Color(253,174,97);
			retval[3] = new Rico.Color(254,224,139);
			retval[4] = new Rico.Color(255,255,191);
			retval[5] = new Rico.Color(230,245,152);
			retval[6] = new Rico.Color(171,221,164);
			retval[7] = new Rico.Color(102,194,165);
			retval[8] = new Rico.Color(50,136,189);
			break;
		case 10:
			retval[0] = new Rico.Color(158,1,66);
			retval[1] = new Rico.Color(213,62,79);
			retval[2] = new Rico.Color(244,109,67);
			retval[3] = new Rico.Color(253,174,97);
			retval[4] = new Rico.Color(254,224,139);
			retval[5] = new Rico.Color(230,245,152);
			retval[6] = new Rico.Color(171,221,164);
			retval[7] = new Rico.Color(102,194,165);
			retval[8] = new Rico.Color(50,136,189);
			retval[9] = new Rico.Color(94,79,162);
			break;
		}
	//let's adjust the saturation a bit
	for (var i = 0; i < retval.length; i++) {
		var hsbcolor = retval[i].asHSB();
		hsbcolor["s"] = Math.min(1, hsbcolor["s"] * 1.5);
		retval[i].rgb = Rico.Color.HSBtoRGB(hsbcolor.h, hsbcolor.s, hsbcolor.b);
	}
	return reverse? retval.reverse() : retval;
}

function createDivergingRYGColorScale(intervals, reverse, minc, maxc)
{
	/* a diverging Red Yellow Green (RYG, or RdYlGn) color scale, from http://colorbrewer.org */
	var retval = new Array(intervals.length);
	switch (intervals.length) {
		case 2:
			retval[0] = new Rico.Color(215,25,28);
			retval[1] = new Rico.Color(26,150,65);
//			retval[0] = new Rico.Color(252,141,89);
//			retval[1] = new Rico.Color(145,207,96);
			break;
		case 3:
			retval[0] = new Rico.Color(215,25,28);
			retval[1] = new Rico.Color(166,217,106);
			retval[2] = new Rico.Color(26,150,65);
//			retval[0] = new Rico.Color(252,141,89);
//			retval[1] = new Rico.Color(255,255,191);
//			retval[2] = new Rico.Color(145,207,96);
			break;
		case 4:
			retval[0] = new Rico.Color(215,25,28);
			retval[1] = new Rico.Color(253,174,97);
			retval[2] = new Rico.Color(166,217,106);
			retval[3] = new Rico.Color(26,150,65);
			break;
		case 5:
			retval[0] = new Rico.Color(215,25,28);
			retval[1] = new Rico.Color(253,174,97);
			retval[2] = new Rico.Color(255,255,191);
			retval[3] = new Rico.Color(166,217,106);
			retval[4] = new Rico.Color(26,150,65);
			break;
		case 6:
			retval[0] = new Rico.Color(215,48,39);
			retval[1] = new Rico.Color(252,141,89);
			retval[2] = new Rico.Color(254,224,139);
			retval[3] = new Rico.Color(217,239,139);
			retval[4] = new Rico.Color(145,207,96);
			retval[5] = new Rico.Color(26,152,80);
			break;
		case 7:
			retval[0] = new Rico.Color(215,48,39);
			retval[1] = new Rico.Color(252,141,89);
			retval[2] = new Rico.Color(254,224,139);
			retval[3] = new Rico.Color(255,255,191);
			retval[4] = new Rico.Color(217,239,139);
			retval[5] = new Rico.Color(145,207,96);
			retval[6] = new Rico.Color(26,152,80);
			break;
		case 8:
			retval[0] = new Rico.Color(215,48,39);
			retval[1] = new Rico.Color(244,109,67);
			retval[2] = new Rico.Color(253,174,97);
			retval[3] = new Rico.Color(254,224,139);
			retval[4] = new Rico.Color(217,239,139);
			retval[5] = new Rico.Color(166,217,106);
			retval[6] = new Rico.Color(102,189,99);
			retval[7] = new Rico.Color(26,152,80);
			break;
		case 9:
			retval[0] = new Rico.Color(215,48,39);
			retval[1] = new Rico.Color(244,109,67);
			retval[2] = new Rico.Color(253,174,97);
			retval[3] = new Rico.Color(254,224,139);
			retval[4] = new Rico.Color(255,255,191);
			retval[5] = new Rico.Color(217,239,139);
			retval[6] = new Rico.Color(166,217,106);
			retval[7] = new Rico.Color(102,189,99);
			retval[8] = new Rico.Color(26,152,80);
			break;
		case 10:
			retval[0] = new Rico.Color(165,0,38);
			retval[1] = new Rico.Color(215,48,39);
			retval[2] = new Rico.Color(244,109,67);
			retval[3] = new Rico.Color(253,174,97);
			retval[4] = new Rico.Color(254,224,139);
			retval[5] = new Rico.Color(217,239,139);
			retval[6] = new Rico.Color(166,217,106);
			retval[7] = new Rico.Color(102,189,99);
			retval[8] = new Rico.Color(26,152,80);
			retval[9] = new Rico.Color(0,104,55);
			break;
	}
	//let's adjust the colors a bit
	for (var i = 0; i < retval.length; i++) {
		var hsbcolor = retval[i].asHSB();
		hsbcolor["s"] = Math.min(1, hsbcolor["s"] * 1.5);
		hsbcolor["b"] = Math.min(1, hsbcolor["b"] * 1.2);
		//hsbcolor["s"] = 1;
		retval[i].rgb = Rico.Color.HSBtoRGB(hsbcolor.h, hsbcolor.s, hsbcolor.b);
	}
	return reverse? retval.reverse() : retval;
}

function createRSColorScale(intervals, reverse, minc, maxc)
{
	//create a color scale that looks good for Region Sjaelland
	//these colors are slightly modified versions of the colors in the Region Sjaelland web site color guide
	var mincolor = new Rico.Color( 226, 27, 35);
	var middlecolor = new Rico.Color( 223, 203, 47);
	var maxcolor = new Rico.Color( 40, 162, 66);
	
	var retval = new Array();
	switch (intervals.length) {
		case 2:
			retval.push(mincolor);
			retval.push(maxcolor);
			break;
		case 3:
			retval.push(mincolor);
			retval.push(middlecolor);
			retval.push(maxcolor);
			break;
		default:
			var firsthalf = intervals.slice(0, Math.floor(intervals.length/2)+1);
			var secondhalf = intervals.slice(Math.floor(intervals.length/2)); //also includes the last of the firsthalf array

			var firstscale = createRGBColorScale(firsthalf, false, mincolor, middlecolor);
			var secondscale = createRGBColorScale(secondhalf, false, middlecolor, maxcolor);
			//var firstscale = createHSBColorScale(firsthalf, mincolor, middlecolor);
			//var secondscale = createHSBColorScale(secondhalf, middlecolor, maxcolor);
			firstscale.pop(); 
			retval = retval.concat(firstscale);
			retval = retval.concat(secondscale);
			break;
	}
	//let's adjust the colors a bit
	for (var i = 0; i < retval.length; i++) {
		var hsbcolor = retval[i].asHSB();
		hsbcolor["s"] = Math.min(1, hsbcolor["s"] * 1.5);
		hsbcolor["b"] = Math.min(1, hsbcolor["b"] * 1.2);
		//hsbcolor["s"] = 1;
		retval[i].rgb = Rico.Color.HSBtoRGB(hsbcolor.h, hsbcolor.s, hsbcolor.b);
	}
	return reverse? retval.reverse() : retval;
}

function findIntervalIndexForValue(value, intervals)
{
	var i = 0;
	while (i < intervals.length-1 && value > intervals[i]) {
		i++;
	}
	return i;
}

function highlight_entity(entityid)
{
	var entity = einfo[entityid]; //find corresponding object in the entities
	if (entity != null) {
		for (var i = 0; i < entity.polys.length; i++) {
			var point = myXmlOverlay.getPointObject('pp' +  entity.polys[i]);
			point.graphics[0].stroke = 4;
			//point.graphics[0].bcolor = '#0000ff';
			point.graphics[0].draw(point); //redraw
		}
	}
}

function reset_entity(entityid)
{
	var entity = einfo[entityid]; //find corresponding object in the entities
	if (entity != null) {
		for (var i = 0; i < entity.polys.length; i++) {
			var point = myXmlOverlay.getPointObject('pp' +  entity.polys[i]);
			point.graphics[0].stroke = 1;
			//point.graphics[0].bcolor = '#000000';
			point.graphics[0].draw(point); //redraw
		}
	}
}

/*
	Objects for handling global settings
*/

function KapolymapSortInfo(col, desc) {
	this.column = col;
	this.descending = desc;
}

function KapolymapIntervalSettings(noi, iname, imethod, csname, csmethod, csreverse) {
	var numberOfIntervals = noi;
	var intervalName = iname;
	var intervalMethod = imethod;
	var colorscaleName = csname;
	var colorscaleMethod = csmethod;
	var colorscaleReverse = csreverse;
	var currentIntervals = null;
	
	this.getNumberOfIntervals = function() { return numberOfIntervals; }
	this.getIntervalName = function() { return intervalName; }
	this.getIntervalMethod = function() { return intervalMethod; }
	this.getColorscaleName = function() { return colorscaleName; }
	this.getColorscaleMethod = function() { return colorscaleMethod; }
	this.getReverseColors = function() { return colorscaleReverse; }
	this.getCurrentIntervals = function() { return currentIntervals; }
	this.setCurrentIntervals = function(intervals) { currentIntervals = intervals; }
}

function KapolymapGlobalSettings() {
	var self = this;
	this.mapName = null;
	this.datasetName = null;
	this.intervalSettings = null;
	this.defaultIntervalSettings = null;
	this.sortInfo = null;
	this.copyrightString = null;
	this.printBgMap = true;
	this.printValues = true;
	this.printValueIndex = -1;
	this.printBBox = null;
	this.decimalPlaces = 2;
	this.entityDetailPage = "";
	this.entitySpec = null;
	this.predefined = null;
	this.mapHeight = 0;
	this.mapWidth = 0;
	this.style = "";
	this.decimalseparator = ".";
	this.thousandseparator = "";
	this.tablelayoutGeoId = null;
	this.resetIntervalSettings = false;
	
	var currentValues = null;

	this.setSortInfo = function(newsortinfo) {
		this.sortInfo = newsortinfo;
		var valinfo = this.getCurrentValues();
		var decimalplaces = calcDecimalPlaces(valinfo);
		updateEntityList(false, decimalplaces);
	}

	this.getSortInfo = function() { return this.sortInfo; }

	this.setIntervalSettings = function(newsettings) {
		this.intervalSettings = newsettings;
		updateMap();
	}
	this.getIntervalSettings = function() { return this.intervalSettings; }
	this.onIntervalChange = null; //assign to this to be notified when the interval settings changes.
	
	this.getCurrentValues = function() { return currentValues; };
	this.setCurrentValues = function(newvalues) { currentValues = newvalues; };
}

/* status message handling */
var showProgress = function(info) { document.getElementById('progress').innerHTML = info; }
var showDebug = function(info) { /* empty until debug js is included */}
var myMouseMoved = function( eventID, position ) { /* empty until debug js is included */ }

/* GUI */
function ComboGroupHandler(editvarid, selhandler)
{
	var self = this;
	var selectelemlist = [];
	var containerlist = [];
	var origoptionslist = [];
	var currentselectionhierarchy = [];

	function getGroupName(selectelem) 
	{
		for (var i = 0; i < selectelemlist.length; i++) {
			if (selectelemlist[i].elem == selectelem) {
				return selectelemlist[i].groupname;
			}
		}
		return "";
	}

	function updateSelects(selectelem, index)
	{
		if (index > 0) {
			Element.removeClassName(selectelem, "nooption");
		} else {
			Element.addClassName(selectelem, "nooption");
		}
		
		//reset all the other select elements
		for (var i = 0; i < selectelemlist.length; i++) {
			if (selectelemlist[i].elem != selectelem) {
				selectelemlist[i].elem.selectedIndex = 0;
				Element.addClassName(selectelemlist[i].elem, "nooption");
			}
		}
	}
	
	this.addSelect = function(selectelem, groupname, containerelem)
	{
		selectelemlist.push({"elem": selectelem, "groupname" : groupname});
		selectelem.onchange = self.handleChange;
		containerlist.push(containerelem);
		var optionslist = [];
		//store original options list (because ie cannot simply hide an option ... argh)
		for (var i = 0; i < selectelem.childNodes.length; i++) {
			var cnode = selectelem.childNodes[i];
			if (cnode.tagName == "OPTGROUP") {
				optionslist.push({"optgroup" : cnode.getAttribute("label"), "classname" : cnode.className});
				for (var j = 0; j < cnode.childNodes.length; j++) {
					var ccnode = cnode.childNodes[j];
					if (ccnode.tagName == "OPTION") {
						optionslist.push({"value" : ccnode.value, "text" : ccnode.innerHTML, "classname" : ccnode.className});
					}
				}
			} else if (cnode.tagName == "OPTION") {
				optionslist.push({"value" : cnode.value, "text" : cnode.innerHTML, "classname" : cnode.className});
			}
		}
		origoptionslist.push(optionslist);
	}
	
	this.handleChange = function(ev)
	{
		if (typeof ev == "undefined") ev = window.event;
		var selectelem = this; //javascript is strange, this points to the sender of the event here

		var groupname = getGroupName(selectelem);
		var optionelem = selectelem.options[selectelem.selectedIndex];
		if (optionelem.parentNode.tagName == "OPTGROUP") {
			currentselectionhierarchy = [groupname, optionelem.parentNode.label, optionelem.innerHTML];
		} else {
			currentselectionhierarchy = [groupname, optionelem.innerHTML];
		}
		
		selhandler.updateSelection(editvarid, optionelem.value);
		updateSelects(selectelem, selectelem.selectedIndex);
	}
	
	this.filterToList = function(filterlist)
	{
		for (var i = 0; i < selectelemlist.length; i++) {
			var elem = selectelemlist[i].elem;
			deleteAllChildNodes(elem);
			var optionslist = origoptionslist[i];
			var optiongroup = null;
			var displaynum = 0;
			for (var j = 0; j < optionslist.length; j++) {
				if (typeof optionslist[j].optgroup != "undefined") {
					if (optiongroup != null) {
						elem.appendChild(optiongroup);
					}
					optiongroup = document.createElement("optgroup");
					optiongroup.setAttribute("label", optionslist[j].optgroup);					   optiongroup.className = optionslist[j].classname; 
				} else if (j == 0 || filterlist.indexOf(optionslist[j].value) > -1) {
					var optionselem = document.createElement("option");
					optionselem.value = optionslist[j].value; 
					optionselem.innerHTML = optionslist[j].text;
					optionselem.className = optionslist[j].classname; 
					if (optiongroup != null) {
						optiongroup.appendChild(optionselem);
					} else {
						elem.appendChild(optionselem);
					}
					if (j > 0) displaynum++;
				}
			}
			if (optiongroup != null) {
				elem.appendChild(optiongroup);
			}
			
			var celem = containerlist[i];
			if (displaynum > 0) {
				celem.style.display = 'block';
			} else {
				celem.style.display = 'none';
			}
		}
	}
	
	this.selectFirstValidOption = function()
	{
		for (var i = 0; i < selectelemlist.length; i++) {
			var elem = selectelemlist[i].elem;
			if (elem.length > 1) {
				elem.selectedIndex = 1;
			
				var groupname = selectelemlist[i].groupname
				var optionelem = elem.options[elem.selectedIndex];
				if (optionelem.parentNode.tagName == "OPTGROUP") {
					currentselectionhierarchy = [groupname, optionelem.parentNode.label, optionelem.innerHTML];
				} else {
					currentselectionhierarchy = [groupname, optionelem.innerHTML];
				}

				updateSelects(elem, 1);
				return elem.options[1].value;
			}
		}
	}
	
	this.selectThisValue = function(value)
	{
		for (var i = 0; i < selectelemlist.length; i++) {
			var elem = selectelemlist[i].elem;
			for (var j = 0; j < elem.length; j++) {
				var optionelem = elem[j];
				if (optionelem.value == value) {
					elem.selectedIndex = optionelem.index;

					var groupname = selectelemlist[i].groupname
					var optionelem = elem.options[elem.selectedIndex];
					if (optionelem.parentNode.tagName == "OPTGROUP") {
						currentselectionhierarchy = [groupname, optionelem.parentNode.label, optionelem.innerHTML];
					} else {
						currentselectionhierarchy = [groupname, optionelem.innerHTML];
					}

					updateSelects(elem, optionelem.index);
				}
			}
		}
	}

	this.hideCombos = function()
	{
		for (var i = 0; i < containerlist.length; i++) {
			var elem = containerlist[i];
			elem.style.display = 'none';
		}
	}
	
	this.getSelectionHierarchy = function() 
	{
		return currentselectionhierarchy;
	
	}
}

function getVarComboFilterList(varcomboinfo, prefix)
{
	var retval = [];
	var plength = prefix.length;
	for (var varcombo in varcomboinfo) {
		if (plength == 0 || varcombo.substr(0, plength+1) == (prefix+",")) {
			var validid = varcombo.substr(plength == 0 ? 0 : plength+1).split(",")[0];
			if (retval.indexOf(validid) == -1 && validid != "") {
				retval.push(validid);
			}
		}
	}
	return retval;
}

function DataSelectionHandler(datasetinfo, choiceelem)
{
	var self = this;
	var selections = {};
	var selectionprefix = "";
	var combohandlers = {};
	var editvars = Object.keys(datasetinfo.variableinfo);
	for (var i = 0; i < editvars.length; i++) {
		selections[editvars[i]] = "";
		combohandlers[editvars[i]] = null;
	}
	
	
	function allHasValue() {
		for (var i in selections) {
			if (selections[i] == "") return false;
		}
		return true;
	}
	
	this.updateSelection = function(editvarid, value)
	{
		if (selections[editvarid] == value) {
			return;
		}
		
		var changeidx = editvars.indexOf(editvarid);
		
		if (value == "") {
			//user selected one of the no-ops, don't allow that, so revert
			var origvalue = selections[editvarid];
			combohandlers[editvars[changeidx]].selectThisValue(origvalue);
			return;
		}
		
		selections[editvarid] = value;

		selectionprefix = "";
		for (var i = 0; i < editvars.length; i++) {
			if (i > changeidx) {
				var flist = getVarComboFilterList(datasetinfo.varcomboinfo, selectionprefix);
				combohandlers[editvars[i]].filterToList(flist);
				if (selections[editvars[i]] == "" || flist.indexOf(selections[editvars[i]]) < 0) {
					//get the first option possible for this group
					selections[editvars[i]] = combohandlers[editvars[i]].selectFirstValidOption();
				} else {
					combohandlers[editvars[i]].selectThisValue(selections[editvars[i]]);
				}
			}
			selectionprefix += (i == 0 ? "" : ",") + selections[editvars[i]];
		}
		//show the name of the current selection
		var levelclasses = ["currentselection_level1", "currentselection_level2", "currentselection_level3"];
		var currentselectionelem = document.getElementById('currentselection');
		deleteAllChildNodes(currentselectionelem);
		for (var ch in combohandlers) {
			var chandler = combohandlers[ch];
			var sh = chandler.getSelectionHierarchy();
			for (var i = 0; i < sh.length; i++) {
				if (i > 0) {
					var spacerelem = document.createTextNode(i == 1 ? ": " : " » ");
					currentselectionelem.appendChild(spacerelem);
				}
				var spanelem = document.createElement("span")
				spanelem.className = i == 1 && sh.length == 2 ? levelclasses[2] : levelclasses[i];
				spanelem.innerHTML = sh[i];
				currentselectionelem.appendChild(spanelem);
			}
			currentselectionelem.appendChild(document.createTextNode(". "));
		}
		
		//load the data
		loadData(globalSettings.datasetName, datasetinfo.varcomboinfo[selectionprefix]+1);
	}
	
	this.addComboHandler = function(editvarid, combohandler) 
	{
		combohandlers[editvarid] = combohandler;
	}
	
	this.selectFirstDataset = function()
	{
		//load initial data
		var firstvalue = combohandlers[editvars[0]].selectFirstValidOption();
		self.updateSelection(editvars[0], firstvalue);
		choiceelem.style.display = "block";
	}
	
	this.filterFirstGroup = function()
	{
		var flist = getVarComboFilterList(datasetinfo.varcomboinfo, "");
		combohandlers[editvars[0]].filterToList(flist);
	}
	
	this.getSelections = function()
	{
		return selections;
	}
	
	this.getDataIndex = function()
	{
		return datasetinfo.varcomboinfo[selectionprefix]+1;
	}

}

function buildChoiceGUI(choiceelemid, datasetinfo)
{
	var choiceelem = document.getElementById(choiceelemid);
	deleteAllChildNodes(choiceelem);
	choiceelem.style.display = "none";
	dataselectionhandler = new DataSelectionHandler(datasetinfo, choiceelem);
	
	var numvars = 0;
	for (var varid in datasetinfo.variableinfo) {
		var combohandler = new ComboGroupHandler(varid, dataselectionhandler);
		var varinfo = datasetinfo.variableinfo[varid];
		var choicediv = document.createElement("div");
		choicediv.className = "choicecollection";
		choicediv.onmouseover = function(ev) { 
									if (typeof ev == "undefined") ev = window.event;
									this.style.backgroundColor = "rgb(230,230,230)";
								};
		choicediv.onmouseout = function(ev) { 
									if (typeof ev == "undefined") ev = window.event;
									this.style.backgroundColor = "";
								};
		var containerfloat = "none";
		var containerclear = "both";
		if (varinfo.layout == "horizontal") {
			containerfloat = "left";
			containerclear = "none";
		} 

		for (var groupid in varinfo.groups) {
			var groupinfo = varinfo.groups[groupid];
			//container
			var containerelem = document.createElement("div");
			containerelem.className = "choicebox";
			containerelem.style.cssFloat = containerfloat;
			containerelem.style.styleFloat = containerfloat;
			containerelem.style.clear = containerclear;
			//header 
			var headerelem = document.createElement("span");
			headerelem.className = "choiceheader";
			headerelem.appendChild(document.createTextNode(groupinfo.name));
			//combobox
			var selectelem = document.createElement("select");
			selectelem.className = "choiceselect nooption";
			var noselectionelem = document.createElement("option");
			noselectionelem.className = "nooption";
			noselectionelem.value = "";
			noselectionelem.innerHTML = "Andre data -- klik her";
			selectelem.appendChild(noselectionelem);
			var optiongroup = null;
			for (var i = 0; i < groupinfo.values.length; i++) {
				var valueinfo = groupinfo.values[i];
				if (valueinfo.type == "separator") {
					if (optiongroup != null) {
						selectelem.appendChild(optiongroup);
					}
					optiongroup = document.createElement("optgroup");
					optiongroup.setAttribute("label", valueinfo.name);
				} else {
					var optionelem = document.createElement("option");
					optionelem.className = "option";
					optionelem.value = valueinfo.code
					optionelem.innerHTML = valueinfo.name;
					if (optiongroup != null) {
						optiongroup.appendChild(optionelem);
					} else {
						selectelem.appendChild(optionelem);
					}
				}
			}
			if (optiongroup != null) {
				selectelem.appendChild(optiongroup);
			}
			//assemble
			containerelem.appendChild(headerelem);
			containerelem.appendChild(document.createElement("br"));
			containerelem.appendChild(selectelem);
			choicediv.appendChild(containerelem);
			//handle
			combohandler.addSelect(selectelem, groupinfo.name, containerelem);
		}
		dataselectionhandler.addComboHandler(varid, combohandler);
		choiceelem.appendChild(choicediv);
		numvars++;
	}
	dataselectionhandler.filterFirstGroup();
}

function buildPrintChoiceGUI(choiceelemid, datasetinfo, varchoiceindices)
{
	var choiceelem = document.getElementById(choiceelemid);
	deleteAllChildNodes(choiceelem);
	
	document.title = datasetinfo.tableinfo.desc;
	document.getElementById('headerarea').innerHTML = datasetinfo.tableinfo.desc;
	
	var varchoiceidx = 0;
	for (var varid in datasetinfo.variableinfo) {
		var varinfo = datasetinfo.variableinfo[varid];
		var valuecode = varchoiceindices[varchoiceidx];
		var varselectionhierarchy = [];
		for (var groupid in varinfo.groups) {
			var groupinfo = varinfo.groups[groupid];
			var separator = null;
			for (var i = 0; i < groupinfo.values.length; i++) {
				var valueinfo = groupinfo.values[i];
				if (valueinfo.code == valuecode) {
					//found the one we are looking for, good
					if (separator != null) {
						varselectionhierarchy = [groupinfo.name, separator.name, valueinfo.name];
					} else {
						varselectionhierarchy = [groupinfo.name, valueinfo.name];
					}
					break;
				}
				if (valueinfo.type == "separator") {
					separator = valueinfo;
				}
			}
		}
		//print the selection hierarchy for the current variable
		var levelclasses = ["printselection_level1", "printselection_level2", "printselection_level3"];
		var varchoiceelem = document.createElement("div");
		varchoiceelem.className = "printvarchoice";
		for (var i = 0; i < varselectionhierarchy.length; i++) {
			var divelem = document.createElement("div")
			divelem.className = i == 1 && varselectionhierarchy.length == 2 ? levelclasses[2] : levelclasses[i];
			divelem.innerHTML = varselectionhierarchy[i];
			varchoiceelem.appendChild(divelem);
		}
		choiceelem.appendChild(varchoiceelem);
		varchoiceidx++;
	}
}


/*****************************************
	GUI code from old comboboxlayout
******************************************/
function showdiv(divname)
{
	var div = document.getElementById(divname);
	div.style.display = 'block';
}
function hidediv(divname)
{
	var div = document.getElementById(divname);
	div.style.display = 'none';
}

function roundToDecimalPlaces(value, decimalplaces)
{
	var numparts = value.toFixed(decimalplaces).split('.');
	var sign = "";
	var integerpart = numparts[0];
	if (integerpart.split('')[0] == "-") {
		sign = "-";
		integerpart = integerpart.slice(1);
	}
	var fractionpart = numparts[1];
	//insert thousand separators
	var s = integerpart.split('').reverse().join(''); //this is how you reverse a string in javascript! argh!
	var r = ''; 
	for (var i = 0; i < s.length; i++) {
		r += (i > 0 && i % 3 == 0 ? globalSettings.thousandseparator : '') + s.charAt(i);
	}
	var integerres = sign + r.split('').reverse().join('');
	return integerres + (typeof fractionpart != "undefined"? globalSettings.decimalseparator + fractionpart : "");
}

function getLegendHTML(intervals, colorscale, membercount, forprint, decimalplaces)
{
	var retval = "<b>Signaturforklaring:</b><div style='margin: 3px;'>";
	var minvaluestep = 1/Math.pow(10, decimalplaces);
	for (var i = 0; i < intervals.length; i++) {
		var legendtext = "";
		switch (i) {
			case 0:
				legendtext = "<= " + roundToDecimalPlaces(intervals[i], decimalplaces);
				break;	
			case intervals.length - 1:
				legendtext = "> " + roundToDecimalPlaces(intervals[i-1], decimalplaces);
				break;
			default:
				legendtext = roundToDecimalPlaces(intervals[i-1] + minvaluestep, decimalplaces) + " - " + roundToDecimalPlaces(intervals[i], decimalplaces);
				break;
		}
		var color = colorscale[i];
		var colorhtml = "";
		if (forprint) {
			//for printing, we generate an image with the correct color, because background colors won't print (at least not with default settings)
			colorhtml = "<img style='vertical-align: middle; margin: 0px; padding: 0px;' src='colorimagegen.php?color=" + escape(color.asHex()) + "&opacity=0.7&height=15&width=18'>&nbsp;";
		} else {
			//for screen rendering, we use a span with a background color
			colorhtml = "<span style='opacity: 0.7; filter: alpha(opacity = 70); zoom: 1; border: solid black 1px; background-color:" + color.asRGB() + "'>&nbsp;&nbsp;&nbsp;&nbsp;</span>";
		}
		retval += "<div style='vertical-align: middle; margin: 0px; margin-bottom: 0px;'>" + colorhtml + "<span>&nbsp;" + legendtext + "</span>" + " (" + membercount[i] + ")" + "</div>";
	}
	return retval + "</div>";
}
function getEntityListHTML(geovariableinfo, entityinfo, valinfo, sortinfo, tmpid, forprint, entitydetailpage, decimalplaces)
{
	var entityarray = [];
	var otherarray = [];
	for (var gv in geovariableinfo) {
		if (typeof entityinfo[gv] != "undefined") {
			//value exists as an entity on the map
			entityarray.push({eid: gv, ename: geovariableinfo[gv], evalue: valinfo[gv]}); 
		} else {
			//value is only meant for the entity list
			otherarray.push({eid: gv, ename: geovariableinfo[gv], evalue: valinfo[gv]}); 
		}
	}
	switch (sortinfo.column) {
		case "name":
			if (sortinfo.descending) {
				//sort descending
				entityarray.sort( function(a,b) { return (b.ename <= a.ename? (b.ename == a.ename? 0 : -1) : 1); } );
			} else {
				//sort ascending
				entityarray.sort( function(a,b) { return (a.ename <= b.ename? (a.ename == b.ename? 0 : -1) : 1); } );
			}
			break;
		case "value":
			if (sortinfo.descending) {
				//sort descending
				entityarray.sort( function(a,b) { return b.evalue - a.evalue; } );
			} else {
				//sort ascending
				entityarray.sort( function(a,b) { return a.evalue - b.evalue; } );
			}
			break;
	}

	//output
	var namesortdesc = (sortinfo.column == "name" && sortinfo.descending == false) ? "true": "false";
	var valuesortdesc = (sortinfo.column == "value" && sortinfo.descending == false) ? "true": "false";
	var nameheading = "";
	var valueheading = "";
	if (forprint) {
		nameheading = "Kommune";
		valueheading = "Værdi";
	} else {
		nameheading = "<a class='clickable' title='Klik for at sortere listen' href='javascript:globalSettings.setSortInfo(new KapolymapSortInfo(\"name\", " + namesortdesc + "));'>Kommune</a>"
		valueheading = "<a class='clickable' title='Klik for at sortere listen' href='javascript:globalSettings.setSortInfo(new KapolymapSortInfo(\"value\", " + valuesortdesc + "));'>Værdi</a>";
	}
	var retval = "<table><tr><th class='entityname'>" + nameheading + "</th><th class='entityvalue'>" + valueheading + "</th></tr>";
	for (var i = 0; i < entityarray.length; i++) {
		var entityname = "";
		if (forprint ) {
			entityname = entityarray[i].ename;
		} else {
			var detaillink = "";
			if (entitydetailpage != "") {
				//special case when using generic layout, bacause we generate the page using javascript
				if (entitydetailpage == "tablelayout/genericlayout.html") {
					detaillink = "title='Klik for at se detaljer for området' onclick='openTableLayoutWindow(\"" + entityarray[i].eid + "\");' href='javascript:void(0);'";
				} else {
					detaillink = "title='Klik for at se alle tal for kommunen' href='" + entitydetailpage + "?entityid=" + entityarray[i].eid + "&entityname=" + entityarray[i].ename + "&tmpid="+ tmpid + "' target='_blank'";
				}
			}
			entityname = "<a class='clickable' " + detaillink + " onmouseover='javascript:highlight_entity(" + entityarray[i].eid + ");' onmouseout='javascript:reset_entity(" + entityarray[i].eid + ");'>" + entityarray[i].ename + "</a>";
		}
		retval += "<tr><td class='entityname'>" + entityname + "</td><td class='entityvalue'>" + roundToDecimalPlaces(entityarray[i].evalue, decimalplaces) + "</td></tr>";
	}
	//Other values
	retval += "<tr></tr>";
	for (var i = 0; i < otherarray.length; i++) {
		if (typeof otherarray[i].evalue == "number") {
			var entityname = "";
			if (forprint ) {
				entityname = otherarray[i].ename;
			} else {
				var detaillink = "";
				if (entitydetailpage != "") {
					//special case when using generic layout, bacause we generate the page using javascript
					if (entitydetailpage == "tablelayout/genericlayout.html") {
						detaillink = "title='Klik for at se detaljer for området' onclick='openTableLayoutWindow(\"" + otherarray[i].eid + "\");' href='javascript:void(0);'";
					} else {
						detaillink = "title='Klik for at se alle tal for kommunen' href='" + entitydetailpage + "?entityid=" + otherarray[i].eid + "&entityname=" + otherarray[i].ename + "&tmpid="+ tmpid + "' target='_blank'";
					}
				}
				entityname = "<a class='clickable' " + detaillink + ">" + otherarray[i].ename + "</a>";
			}
			retval += "<tr><td class='entityname'>" + entityname + "</td><td class='entityvalue'>" + roundToDecimalPlaces(otherarray[i].evalue, decimalplaces) + "</td></tr>";
		}
	}

	return retval + "</table>";
}

function openUrl(name, url)
{
	var win = window.open(url, name, "");
}

function openPrintWin(urlCallback)
{
	var cb = eval(urlCallback);
	var printbgmap = $('printbgmapcheckbox').checked;
	var printvalues = $('printvaluescheckbox').checked;
	var url = cb(printbgmap, printvalues);
	openUrl("_blank", url);
}

function getPrintHTML(urlCallback)
{
	var retval = "<a class='clickable' href='javascript:showdiv(\"printoptions\");'>Udskriv</a><div id='printoptions'><input checked='1' type='checkbox' id='printbgmapcheckbox'>Med baggrundskort</input><br><input checked='1' type='checkbox' id='printvaluescheckbox'>Vis værdier på kort</input><br><br><div style='text-align: center;'><a class='button' href='javascript:openPrintWin(" + urlCallback + "); hidediv(\"printoptions\");'>OK</a>&nbsp;<a class='button' href='javascript:hidediv(\"printoptions\")'>Cancel</a></div><br></div>";
	return retval;
}

function syncVariableSelections(choices)
{
	for (var i = 0; i < choices.length; i++) {
		var elem = $('choice_' + i);
		elem.selectedIndex = findOptionIndexByValue(elem, choices[i]); 
	}
}

function findOptionIndexByText(elem, text)
{
	var idx = -1;
	for (var i = 0; i < elem.options.length; i++) {
		if (elem.options[i].text == text) {
			idx = i;
			break;
		}
	}
	return idx;
}

function findOptionIndexByValue(elem, value)
{
	var idx = -1;
	for (var i = 0; i < elem.options.length; i++) {
		if (elem.options[i].value == value) {
			idx = i;
			break;
		}
	}
	return idx;
}

function findValueInfo(variableinfo, valueid)
{
	for (gid in variableinfo.groups) {
		var groupinfo = variableinfo.groups[gid];
		for (var i = 0; i < groupinfo.values.length; i++) {
			if (valueid == groupinfo.values[i].code) {
				//good, something was found
				return groupinfo.values[i];
			}
		}
	}
	return false;
}

function ShowTableRowListHandler(elemlist, showinitial, expanderelem, expandhtml, contracthtml)
{
	var isshown = showinitial;
	do_toggle();

	function do_toggle()
	{
		for (var i = 0; i < elemlist.length; i++) {
			elemlist[i].style.display = (isshown ? (document.all ? "block" : "table-row") : "none"); //TODO lazy!!
		}
		expanderelem.innerHTML = (isshown ? contracthtml : expandhtml);
	}

	this.toggleshow = function()
	{
		isshown = !isshown;
		do_toggle();
	}
}

function generateTableLayout(callerwindow, titleelemid, layouttableid)
{
	var geoid = globalSettings.tablelayoutGeoId;
	if (geoid == null) {
		return false;
	}
	var windocument = callerwindow.document;
	var titleelem = windocument.getElementById(titleelemid);
	var tblelem = windocument.getElementById(layouttableid);

	//set title
	windocument.title = datasetinfo.tableinfo.desc + " - " + datasetinfo.geovariableinfo[geoid];
	titleelem.innerHTML = datasetinfo.tableinfo.desc + " - " +  datasetinfo.geovariableinfo[geoid];
	
	//sanity check, we only support two variables currently for the table layout
	var varkeys = Object.keys(datasetinfo.variableinfo)
	if (varkeys.length != 2) {
		alert("Unsupported number of variables");
		return false;
	}
	
	var varinfo = datasetinfo.variableinfo[varkeys[0]];
	var varinfo2 = datasetinfo.variableinfo[varkeys[1]]
	var groupsinrow = 0;
	var numgroup = 0;
	var currenttrelem = null;
	var dataindextovalueelemlist = {};
	//iterate over groups in first variable
	for (var gid in varinfo.groups) {
		var groupinfo = varinfo.groups[gid];
		if (groupsinrow == 0) {
			if (currenttrelem != null) {
				tblelem.appendChild(currenttrelem);
			}
			currenttrelem = windocument.createElement("tr");
		}
		var grouptdelem = windocument.createElement("td");
		grouptdelem.className = "tablelayoutgroupcolumn " + (numgroup % 2 == 0 ? "tablelayoutevengroup" : "tablelayoutoddgroup");
		//group header
		var groupheaderelem = windocument.createElement("div");
		groupheaderelem.className = "tablelayoutgroupheader";
		groupheaderelem.innerHTML = groupinfo.name;

		//nested table for values in group
		var valuetableelem = windocument.createElement("table");
		valuetableelem.setAttribute("cellspacing", "0");
		valuetableelem.setAttribute("cellpadding", "0");
		var valuetbodyelem = windocument.createElement("tbody");
		for (var i = 0; i < groupinfo.values.length; i++) {
			var valinfo = groupinfo.values[i];
			var comboprefix = groupinfo.values[i].code + ",";
			if (valinfo.type == "separator") {
				var valuerowelem = windocument.createElement("tr");
				valuerowelem.className = "tablelayoutseparatorrow";
				var separatorelem = windocument.createElement("td");
				separatorelem.className = "tablelayoutseparator";
				separatorelem.setAttribute("colspan", 3);
				separatorelem.innerHTML = valinfo.name;
				valuerowelem.appendChild(separatorelem);
				valuetbodyelem.appendChild(valuerowelem);
			} else {
				var vlist = getVarComboFilterList(datasetinfo.varcomboinfo, valinfo.code);
				if (vlist.length > 0) {
					var firstrow = null;
					var firstrowexpanderelem = null;
					var otherrows = [];
					for (var j = 0; j < vlist.length; j++) {
						var val2info = findValueInfo(varinfo2, vlist[j]);
						var valnameelem = windocument.createElement("td");
						valnameelem.className = "tablelayoutvaluename";
						var rowelem = windocument.createElement("tr");
						rowelem.className = "tablelayoutvaluerow" + (j == vlist.length - 1 && j > 0 ? " tablelayoutlastvaluerow" : "");
						var subvalnameelem = windocument.createElement("td");
						subvalnameelem.className = "tablelayoutsubvaluename";
						var subvalvalueelem = windocument.createElement("td");
						subvalvalueelem.className = "tablelayoutsubvaluevalue";
						subvalnameelem.innerHTML = "(" + val2info.name + ")";
						var expanderelem = windocument.createElement("span");
						expanderelem.className = (j == 0 && vlist.length > 1 ? "tablelayoutexpandsign" : "tablelayoutexpandnosign");
						expanderelem.innerHTML = (j == 0 && vlist.length > 1 ? "&darr;" : "&nbsp;");
						subvalnameelem.appendChild(expanderelem);
						subvalvalueelem.innerHTML = "&nbsp;";
						
						var combo = comboprefix + vlist[j];
						subvalvalueelem.id = "valueplaceholder_" + datasetinfo.varcomboinfo[combo];
						dataindextovalueelemlist[datasetinfo.varcomboinfo[combo]] = subvalvalueelem;

						//assemble
						if (j == 0) {
							valnameelem.innerHTML = valinfo.name;
							firstrow = rowelem;
							firstrowexpanderelem = expanderelem;
						} else {
							valnameelem.innerHTML = "";
							otherrows.push(rowelem);
						}
						rowelem.appendChild(valnameelem);
						rowelem.appendChild(subvalvalueelem);
						rowelem.appendChild(subvalnameelem);
						valuetbodyelem.appendChild(rowelem);
					}
					//hide other rows here, and set up js to display it
					if (otherrows.length > 0) {
						var showelementlisthandler = new ShowTableRowListHandler(otherrows, false, firstrowexpanderelem, "&darr;", "&uarr;");
						firstrow.onclick = showelementlisthandler.toggleshow;
						firstrow.className += " tablelayoutclickable";
					}
				}
			}
		}
		
		//assemble
		grouptdelem.appendChild(groupheaderelem);
		valuetableelem.appendChild(valuetbodyelem);
		grouptdelem.appendChild(valuetableelem);
		currenttrelem.appendChild(grouptdelem);

		groupsinrow++;
		numgroup++;
		if (groupsinrow == 4) {
			groupsinrow	= 0;
		}
	}
	if (currenttrelem != null) {
		tblelem.appendChild(currenttrelem);
	}
	
	//now, load the data
	loadEntityData(geoid, globalSettings.datasetName, dataindextovalueelemlist, windocument);
}

function EntityLoadDataHandler(indextoelemlist, doc)
{
	this.onsuccess = function(transport)
	{
		var entityvalues = eval('(' + transport.responseText + ')');
		for (var i = 0; i < entityvalues.length; i++) {
			if (typeof entityvalues[i] != "undefined") {
				var elem = indextoelemlist[i];
				var decimalplaces = calcDecimalPlaces({i : entityvalues[i]});
				elem.innerHTML = roundToDecimalPlaces(entityvalues[i], decimalplaces);
			}
		}
	}
	
	this.onexception = function(transport)
	{
		//alert("Exception:" + transport + " " + transport.responseText);
	}
	
	this.onfailure = function(transport)
	{
		alert("failure: " + transport.responseText);
	}
}
function loadEntityData(geoid, xdsname, indextoelemlist, doc) 
{
	var ehandler = new EntityLoadDataHandler(indextoelemlist, doc);
	
    var myAjax = new Ajax.Request((document.all? '' : '../') + 'datajs/entityvalues.php',
	{
		method: 'get',
		parameters: { dsname: xdsname, eid: geoid },
		onSuccess: ehandler.onsuccess,
		onException: ehandler.onexception,
		onFailure: ehandler.onfailure
	});
}

function openTableLayoutWindow(geoid)
{
	globalSettings.tablelayoutGeoId = geoid; //ugly, put we need to pass a specific geoid to generateTableLayout.
	var win = window.open("tablelayout/genericlayout.html", "detailwindow_" + geoid, "");
}

function getDatasetInfo()
{
	return datasetinfo;
}