// (C) Copyright 2007 Adam Nowacki

function GetElementOffset(e) {
	if (e.offsetParent) {
		var v = GetElementOffset(e.offsetParent);
		return { x: e.offsetLeft - e.scrollLeft + v.x, y: e.offsetTop - e.scrollTop + v.y };
	} else return { x: e.offsetLeft - e.scrollLeft, y: e.offsetTop - e.scrollTop };
}

function TestBoxIntersection(a, b) {
    return ((b.x1 - a.x0) * (b.x0 - a.x1) <= 0) && ((b.y1 - a.y0) * (b.y0 - a.y1) <= 0);
}

function Log(msg) {
    var log = document.getElementById("logContainer");
    if (log) {
    	var div = document.createElement("div");
		div.innerHTML = msg;
		log.appendChild(div);
    }
}

function MapList() {
	this.total = 0;
	this.front = null;
	this.back = null;
}

MapList.prototype.AddBack = function(o) {
	var cont = { o: o, prev: this.back, next: null };
	if (this.back) this.back.next = cont;
	else this.front = cont;
	this.back = cont;
	++this.total;
	return cont;
}

MapList.prototype.Remove = function(cont) {
	if (cont.prev) cont.prev.next = cont.next;
	else this.front = cont.next;
	if (cont.next) cont.next.prev = cont.prev;
	else this.back = cont.prev;
	--this.total;
}

MapList.prototype.Kill = function() {
	var cont, next;
	for (cont = this.front; cont; cont = next) {
		next = cont.next;
		cont.o = null;
		cont.prev = null;
		cont.next = null;
	}
	this.total = 0;
}

function Map(args) {
	this.container_id = args.container_id;
	this.container = document.getElementById(this.container_id);

	this.want_status_update = true;
	this.custom_status = "";

	this.zoom_factor = 1.25992105;
	this.zoom_factor = 2.0;
	this.view = {
		position: { x: 0.0, y: 0.0 },
		scale: { x: 512.0, y: 512.0 }
	};
	this.layers = { };
	this.active_layers = { };

    var div = document.createElement("div");
    div.style.position = "absolute";
    div.style.left = 0;
    div.style.bottom = 0;
    div.style.width = "100%";
    div.style.height = "auto";
    div.style.zIndex = 9;
    div.style.backgroundColor = "#DDDDDD";
    div.innerHTML = "&nbsp;";
    this.status_div = div;

    var div = document.createElement("div");
    div.style.position = "absolute";
    div.style.left = 0;
    div.style.top = 0;
    div.style.width = "100%";
    div.style.height = "100%";
    div.style.zIndex = 1;
    div.style.backgroundColor = "#EEEEEE";
    this.window0_div = div;

    var div = document.createElement("div");
    div.style.position = "absolute";
    div.style.right = 20;
    div.style.bottom = 40;
    div.style.width = "20%";
    div.style.height = "20%";
    div.style.zIndex = 3;
    div.style.backgroundColor = "#EEEEEE";
    div.style.border = "2px solid #999999";
    this.window1_div = div;

	this.download_manager = new MapDownloadManager();
	this.window0 = new MapWindow(this, { container: this.window0_div });
	this.window0.AddControls();
	this.window1 = new MapWindow(this, { container: this.window1_div, local_scale: { x: 0.125, y: 0.125 } });
	this.window1.AddControlsMini();

	this.container.appendChild(this.window0_div);
	this.container.appendChild(this.window1_div);
	this.container.appendChild(this.status_div);

	this.copyright_div = this.window0.copyright_div;
	this.copyright_manager = new MapCopyrightManager(this.copyright_div);

    var div = document.createElement("div");
    div.style.position = "absolute";
    div.style.right = 0;
    div.style.top = 0;
    div.style.width = "300";
    div.style.height = "50%";
    div.style.zIndex = 11;
    div.style.backgroundColor = "#DDDDDD";
	div.style.overflow = "scroll";
	this.layer_manager_div = div;
    this.layer_manager = new MapLayerManager(this, { container: div });
    this.visible_layer_manager = false;
}

Map.prototype.ShowLayerManager = function() {
	this.visible_layer_manager = true;
	this.container.appendChild(this.layer_manager_div);
}

Map.prototype.HideLayerManager = function() {
	this.visible_layer_manager = false;
	this.container.removeChild(this.layer_manager_div);
}

Map.prototype.ToggleLayerManager = function() {
	if (this.visible_layer_manager) this.HideLayerManager();
	else this.ShowLayerManager();
}

Map.prototype.AddLayer = function(id, layer) {
	this.layers[id] = layer;
}

Map.prototype.RemoveLayer = function(id) {
	delete this.layers[id];
}

Map.prototype.EnableLayer = function(id) {
	this.active_layers[id] = true;
	this.window0.EnableLayer(id, this.layers[id]);
	this.window1.EnableLayer(id, this.layers[id]);
}

Map.prototype.DisableLayer = function(id) {
	delete this.active_layers[id];
	this.window0.DisableLayer(id, this.layers[id]);
	this.window1.DisableLayer(id, this.layers[id]);
}

Map.prototype.Tick = function() {
	this.copyright_manager.Tick0();
	this.window0.Tick0();
	this.window1.Tick0();
	this.download_manager.Tick();
	this.window0.Tick1();
	this.window1.Tick1();
	this.copyright_manager.Tick1();
	if (this.want_status_update) {
		this.UpdateStatus();
		this.want_status_update = false;
	}
}

Map.prototype.UpdateStatus = function() {
	var s = "";
	var layers = "";
	for (var id in this.active_layers) layers += id + ",";
	s += "<div style=\"float: right; margin-right: 5px;\"><a href=\"?x=" + this.view.position.x + "&y=" + this.view.position.y 
		+ "&sx=" + this.view.scale.x + "&sy=" + this.view.scale.y 
		+ "&layers=" + layers 
		+ "&layer_manager=" + (this.visible_layer_manager ? "1" : "0")
		+ "\">Link to this map</a></div>";
	s += "<div style=\"margin-left: 5px;\">";
	s += " center: " + (90 - this.view.position.y * 360).toFixed(6) + "&deg;N, " + (-180 + this.view.position.x * 360).toFixed(6) + "&deg;E";
//	s += " zoom: " + (this.view.scale.x) + ", " + (this.view.scale.y);
	if (this.mouse_world_position && this.mouse_world_scale) {
		s += " mouse: " + (90 - this.mouse_world_position.y * 360).toFixed(6) + "&deg;N, " + (-180 + this.mouse_world_position.x * 360).toFixed(6) + "&deg;E";
//			+ " scale: " + this.mouse_world_scale.x + ", " + this.mouse_world_scale.y;
	}
	s+= "</div>";
	this.status_div.innerHTML = s;
}

Map.prototype.ViewMove = function(x, y) {
	this.view.position.x += x;
	this.view.position.y += y;
}

Map.prototype.ViewScale = function(x, y, scale_x, scale_y) {
    this.view.position.x += (x - this.view.position.x) * (scale_x - 1.0) / scale_x;
    this.view.position.y += (y - this.view.position.y) * (scale_y - 1.0) / scale_y;
    this.view.scale.x *= scale_x;
    this.view.scale.y *= scale_y;
}

function MapWindow(map, args) {
	this.map = map;
	this.container = args.container;

	this.view = this.map.view;
	this.local_view = {
			position: { x: 0.0, y: 0.0 },
			scale: (args.local_scale ? args.local_scale : { x: 1.0, y: 1.0 })
		};
    this.mouse = {
    		inside: false,
    		down: false,
    		out: false, 
    		local_pos: { x: 0, y: 0 },
    		screen_pos: { x: 0, y: 0 },
    		world_pos: { x: 0, y: 0 },
    		last_screen_pos: { x: 0, y: 0 },
    		out_pos: { x: 0, y: 0}
    	};

	this.InitializeGlass();
	this.InitializeEvents();
	
	this.map_display = new MapDisplay(this.map, this, { container: this.display_div });
}

MapWindow.prototype.AddControls = function() {
    var div = document.createElement("div");
    div.style.position = "absolute";
    div.style.left = 10;
    div.style.top = 10;
    div.style.zIndex = 20;
	this.controls_div = div;	
	this.container.appendChild(this.controls_div);
	
	var _this = this;
	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 18;
	img.style.top = 0;
	img.style.zIndex = 10;
	img.onclick = function(ev) { _this.Move(0, -100); };
	img.src = "img/arrow_n.png";
	this.controls_div.appendChild(img);

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 0;
	img.style.top = 18;
	img.style.zIndex = 10;
	img.onclick = function(ev) { _this.Move(-100, 0); };
	img.src = "img/arrow_w.png";
	this.controls_div.appendChild(img);

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 36;
	img.style.top = 18;
	img.style.zIndex = 10;
	img.onclick = function(ev) { _this.Move(100, 0); };
	img.src = "img/arrow_e.png";
	this.controls_div.appendChild(img);

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 18;
	img.style.top = 36;
	img.style.zIndex = 10;
	img.onclick = function(ev) { _this.Move(0, 100); };
	img.src = "img/arrow_s.png";
	this.controls_div.appendChild(img);

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 0;
	img.style.top = 0;
	img.style.zIndex = 10;
	img.onclick = function(ev) { _this.Scale(_this.view.position.x, _this.view.position.y, _this.map.zoom_factor, _this.map.zoom_factor); };
	img.src = "img/plus.png";
	this.controls_div.appendChild(img);

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 0;
	img.style.top = 36;
	img.style.zIndex = 10;
	img.onclick = function(ev) { _this.Scale(_this.view.position.x, _this.view.position.y, 1.0 / _this.map.zoom_factor, 1.0 / _this.map.zoom_factor); };
	img.src = "img/minus.png";
	this.controls_div.appendChild(img);

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 18;
	img.style.top = 18;
	img.style.zIndex = 10;
	img.onclick = function(ev) { _this.map.ToggleLayerManager(); };
	img.src = "img/layers.png";
	this.controls_div.appendChild(img);

	var a = document.createElement("a");
	a.href = "http://www.madmappers.com/map_help.html";
	a.target = "_blank";
	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 36;
	img.style.top = 0;
	img.style.zIndex = 10;
	img.style.border = "0";
	img.src = "img/help.png";
	a.appendChild(img);
	this.controls_div.appendChild(a);
}

MapWindow.prototype.AddControlsMini = function() {
    var div = document.createElement("div");
    div.style.position = "absolute";
    div.style.left = 5;
    div.style.top = 5;
    div.style.zIndex = 20;
	this.controls_div = div;	
	this.container.appendChild(this.controls_div);
	
	var _this = this;
	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 0;
	img.style.top = 0;
	img.style.zIndex = 10;
	img.onclick = function(ev) { _this.local_view.scale.x *= _this.map.zoom_factor; _this.local_view.scale.y *= _this.map.zoom_factor; };
	img.src = "img/plus_mini.png";
	this.controls_div.appendChild(img);

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 0;
	img.style.top = 18;
	img.style.zIndex = 10;
	img.onclick = function(ev) { _this.local_view.scale.x /= _this.map.zoom_factor; _this.local_view.scale.y /= _this.map.zoom_factor; };
	img.src = "img/minus_mini.png";
	this.controls_div.appendChild(img);
}

MapWindow.prototype.Move = function(x, y) {
	this.map.ViewMove(x / (this.view.scale.x * this.local_view.scale.x), 
		y / (this.view.scale.y * this.local_view.scale.y));
	this.map.want_status_update = true;
}

MapWindow.prototype.Tick0 = function() {
	var view = this.GetMyView();
	this.map_display.Tick0(view);
}

MapWindow.prototype.Tick1 = function() {
	var view = this.GetMyView();
	this.map_display.Tick1(view);
}

MapWindow.prototype.InitializeGlass = function() {
	var div = document.createElement("div");
	div.style.position = "absolute";
	div.style.bottom = 25;
	div.style.width = "100%";
	div.style.height = "auto";
	div.style.zIndex = 5;
	this.copyright_div = div;

    var div = document.createElement("div");
    div.style.position = "absolute";
    div.style.left = 0;
    div.style.top = 0;
    div.style.width = "100%";
    div.style.height = "100%";
    div.style.zIndex = 1;
    div.style.overflow = "hidden";
	this.display_div = div;

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.position = "absolute";
	img.style.left = 0;
	img.style.top = 0;
	img.style.width = "100%";
	img.style.height = "100%";
	img.style.zIndex = 10;
	img.style.cursor = "crosshair";
	img.src = "img/transparent.gif";
	this.glass_img = img;

    var div = document.createElement("div");
    div.style.position = "absolute";
    div.style.left = 0;
    div.style.top = 0;
    div.style.width = "100%";
    div.style.height = "100%";
    div.style.zIndex = 10;
	div.style.cursor = "crosshair";
    this.glass_div = div;

	this.container.appendChild(this.display_div);
	this.container.appendChild(this.copyright_div);
	this.container.appendChild(this.glass_img);
	this.container.appendChild(this.glass_div);
}

MapWindow.prototype.InitializeEvents = function() {
    var _this = this;
    this.onGlassMouseDownCaller = function(ev) { if (!ev) ev = window.event; if (ev.preventDefault) ev.preventDefault(); _this.onGlassMouseDown(ev); };
    this.onGlassMouseMoveCaller = function(ev) { if (!ev) ev = window.event; _this.onGlassMouseMove(ev); };
    this.onGlassMouseOutCaller = function(ev) { if (!ev) ev = window.event; _this.onGlassMouseOut(ev); };
    this.onGlassMouseOverCaller = function(ev) { if (!ev) ev = window.event; _this.onGlassMouseOver(ev); };
    this.onGlassMouseWheelCaller = function(ev) {
		var d = 0;
		if (!ev) ev = window.event;
 		if (ev.wheelDelta) {
			d = ev.wheelDelta / 120;
			if (window.opera) d = -d;
		} else if (ev.detail) {
			d = -ev.detail / 3;
		}
		if (d) _this.onGlassMouseWheel(ev, d);
		if (ev.preventDefault) ev.preventDefault();
		ev.returnValue = false;
	};

    this.onGlobalMouseUpCaller = function(ev) { if (!ev) ev = window.event; _this.onGlobalMouseUp(ev); };
    this.onGlobalMouseMoveCaller = function(ev) { if (!ev) ev = window.event; _this.onGlobalMouseMove(ev); };
    this.onGlobalMouseOutCaller = function(ev) { if (!ev) ev = window.event; _this.onGlobalMouseOut(ev); };
    this.onGlobalMouseOverCaller = function(ev) { if (!ev) ev = window.event; _this.onGlobalMouseOver(ev); };

	this.glass_img.onmousedown = this.onGlassMouseDownCaller;
	this.glass_div.onmousedown = this.onGlassMouseDownCaller;
	this.glass_img.onmousemove = this.onGlassMouseMoveCaller;
	this.glass_div.onmousemove = this.onGlassMouseMoveCaller;
	this.glass_img.onmouseout = this.onGlassMouseOutCaller
	this.glass_div.onmouseout = this.onGlassMouseOutCaller
	if (this.glass_img.addEventListener) {
		this.glass_img.addEventListener('DOMMouseScroll', this.onGlassMouseWheelCaller, false);
	} else {
		this.glass_img.onmousewheel = this.onGlassMouseWheelCaller;
	}
	if (this.glass_div.addEventListener) {
		this.glass_div.addEventListener('DOMMouseScroll', this.onGlassMouseWheelCaller, false);
	} else {
		this.glass_div.onmousewheel = this.onGlassMouseWheelCaller;
	}
}

MapWindow.prototype.onGlobalMouseUp = function(ev) {
	if (this.mouse.down) {
		this.mouse.down = false;
		document.onmouseup = null;
		document.onmousemove = null;
		document.onmouseout = null;
	}
}

MapWindow.prototype.onGlobalMouseMove = function(ev) {
	this.mouse.screen_pos.x = ev.screenX;
	this.mouse.screen_pos.y = ev.screenY;
	if (this.mouse.down) {
		var delta = {
				x: this.mouse.last_screen_pos.x - ev.screenX,
				y: this.mouse.last_screen_pos.y - ev.screenY
			};
		this.map.ViewMove(delta.x / (this.view.scale.x * this.local_view.scale.x), 
			delta.y / (this.view.scale.y * this.local_view.scale.y));
		this.mouse.last_screen_pos = { x: ev.screenX, y: ev.screenY };
		this.map.want_status_update = true;
	}
}

MapWindow.prototype.onGlobalMouseOut = function(ev) {
}

MapWindow.prototype.onGlobalMouseOver = function(ev) {
}

MapWindow.prototype.onGlassMouseDown = function(ev) {
	if (ev.stopPropagation) ev.stopPropagation();
	ev.cancelBubble = true;

	var offset = GetElementOffset(this.container);
//	Log("onGlassMouseDown(" + ev.clientX + ", " + ev.clientY + ", " + (ev.clientX - offset.x) + ", " + (ev.clientY - offset.y) + ")");

	this.mouse.inside = true;	
	if (ev.ctrlKey) this.Scale(this.map.mouse_world_position.x, this.map.mouse_world_position.y, 2, 2);
	else if (ev.shiftKey) this.Scale(this.map.mouse_world_position.x, this.map.mouse_world_position.y, 0.5, 0.5);
	else if (!this.mouse.down) {
		document.onmouseup = this.onGlobalMouseUpCaller;
		document.onmousemove = this.onGlobalMouseMoveCaller;
		document.onmouseout = this.onGlobalMouseOutCaller;
		this.mouse.down = true;
		this.mouse.last_screen_pos = { x: ev.screenX, y: ev.screenY };
	}
	this.want_status_update = true;
}

MapWindow.prototype.onGlassMouseMove = function(ev) {
//	if (ev.stopPropagation) ev.stopPropagation();
//	ev.cancelBubble = true;

	var offset = GetElementOffset(this.container);
	this.mouse.inside = true;
	this.mouse.screen_pos.x = ev.screenX;
	this.mouse.screen_pos.y = ev.screenY;
	this.mouse.local_pos.x = ev.clientX - offset.x;
	this.mouse.local_pos.y = ev.clientY - offset.y;

	var view = this.GetMyView();
	this.map.mouse_world_position = {
		x: view.world_box.x0 + this.mouse.local_pos.x / view.scale.x,
		y: view.world_box.y0 + this.mouse.local_pos.y / view.scale.y
	};
	this.map.mouse_world_scale = {
		x: view.scale.x,
		y: view.scale.y
	};
	this.map.want_status_update = true;
}

MapWindow.prototype.onGlassMouseOut = function(ev) {
	if (ev.stopPropagation) ev.stopPropagation();
	ev.cancelBubble = true;

	this.mouse.inside = false;
	this.map.mouse_world_position = null;
	this.map.mouse_world_scale = null;
	this.map.want_status_update = true;
}

MapWindow.prototype.onGlassMouseOver = function(ev) {
//	if (ev.stopPropagation) ev.stopPropagation();
//	ev.cancelBubble = true;
	this.mouse.inside = true;
}

MapWindow.prototype.onGlassMouseWheel = function(ev, d) {
	if (ev.stopPropagation) ev.stopPropagation();
	ev.cancelBubble = true;

	if (this.map.mouse_world_position) {
		if (d > 0) this.Scale(this.map.mouse_world_position.x, this.map.mouse_world_position.y, this.map.zoom_factor, this.map.zoom_factor);
		else this.Scale(this.map.mouse_world_position.x, this.map.mouse_world_position.y, 1.0 / this.map.zoom_factor, 1.0 / this.map.zoom_factor);

		var view = this.GetMyView();
		this.map.mouse_world_scale = {
			x: view.scale.x,
			y: view.scale.y
		};
		this.map.want_status_update = true;
	}
}

MapWindow.prototype.Scale = function(x, y, scale_x, scale_y) {
    this.view.position.x += (x - this.view.position.x) * (scale_x - 1) / scale_x;
    this.view.position.y += (y - this.view.position.y) * (scale_y - 1) / scale_y;
    this.view.scale.x *= scale_x;
    this.view.scale.y *= scale_y;
}

MapWindow.prototype.GetMyView = function() {
	var size = {
		x: this.display_div.offsetWidth,
		y: this.display_div.offsetHeight
	};
	var position = {
		x: this.view.position.x + this.local_view.position.x,
		y: this.view.position.y + this.local_view.position.y
	};
	var scale = {
		x: this.view.scale.x * this.local_view.scale.x,
		y: this.view.scale.y * this.local_view.scale.y
	}; 
	var world_box = {
		x0: position.x - 0.5 * size.x / scale.x,
		y0: position.y - 0.5 * size.y / scale.y,
		x1: position.x + 0.5 * size.x / scale.x,
		y1: position.y + 0.5 * size.y / scale.y
	};
	return {
		position: position,
		scale: scale,
		world_box: world_box,
		size: size
	};
}

MapWindow.prototype.EnableLayer = function(id, layer) {
	this.map_display.EnableLayer(id, layer);
}

MapWindow.prototype.DisableLayer = function(id, layer) {
	this.map_display.DisableLayer(id, layer);
}

function MapDisplay(map, map_window, args) {
	this.map = map;
	this.map_window = map_window;
	this.container = args.container;
	this.layer_instances = { };
}

MapDisplay.prototype.EnableLayer = function(id, layer) {
	this.layer_instances[id] = layer.NewInstance(this.map, this);
	this.container.appendChild(this.layer_instances[id].GetContainer());
}

MapDisplay.prototype.DisableLayer = function(id, layer) {
	this.container.removeChild(this.layer_instances[id].GetContainer());
	this.layer_instances[id].Kill();
	delete this.layer_instances[id];
}

MapDisplay.prototype.Tick0 = function(view) {
	for (var id in this.layer_instances) {
		this.layer_instances[id].Tick0(view);
	}
}

MapDisplay.prototype.Tick1 = function(view) {
	for (var id in this.layer_instances) {
		this.layer_instances[id].Tick1(view);
	}
}

function MapTileLayer(args) {
	if (args) this.MapTileLayerInitialize(args);
}

MapTileLayer.prototype.MapTileLayerInitialize = function(args) {
	this.start_point = args.start_point;
	this.scale = args.scale;
	this.tile_size = args.tile_size;
	this.tile_scale = { x: this.scale.x * this.tile_size.x, y: this.scale.y * this.tile_size.y };
	this.world_box = args.world_box;
	this.max_level = args.max_level;
	this.min_display_level = args.min_display_level;
	this.max_display_level = args.max_display_level;
	this.load_band = (args.load_band != null) ? args.load_band : 500;
	this.keep_band = (args.keep_band != null) ? args.keep_band : 1200;
	this.opacity = (args.opacity != null) ? args.opacity : null;
	this.copyright = (args.copyright != null) ? args.copyright : null;
	this.inv_y_base = (args.inv_y_base != null) ? args.inv_y_base : 1;
	this.z_index = (args.z_index != null) ? args.z_index : 1;
    this.url = args.url;
}

MapTileLayer.prototype.NewInstance = function(map, display) {
	return new MapTileLayerInstance(map, this, display);
}

MapTileLayer.prototype.GetImageSrc = function(level, x, y) {
	var url = this.url;
	url = url.replace(/\$\{level\}/, level);
	url = url.replace(/\$\{x\}/, x);
	url = url.replace(/\$\{y\}/, y);
	url = url.replace(/\$\{inv_y\}/, this.inv_y_base * Math.pow(2, level) - y - 1);
	url = url.replace(/\$\{server_2x2\}/, (x % 2) + (y % 2) * 2);
	url = url.replace(/\$\{server_3x3\}/, (x % 3) + (y % 3) * 3);
	url = url.replace(/\$\{server_4x4\}/, (x % 4) + (y % 4) * 4);
	return url;
}

function MapTileLayerWMS(args) {
	this.MapTileLayerInitialize(args);
	if (args.ext) this.ext = args.ext;
	else this.ext = { x0: -180, y0: 90, x1: 180, y1: -270 };
}

MapTileLayerWMS.prototype = new MapTileLayer();

MapTileLayerWMS.prototype.GetImageSrc = function(level, x, y) {
	var url = this.url;
	var sx = (this.ext.x1 - this.ext.x0) * this.scale.x * this.tile_size.x * Math.pow(0.5, level);
	var sy = (this.ext.y1 - this.ext.y0) * this.scale.y * this.tile_size.y * Math.pow(0.5, level);
	var bbox = { x0: sx * x + this.ext.x0, y0: sy * y + this.ext.y0, x1: sx * (x + 1) + this.ext.x0, y1: sy * (y + 1) + this.ext.y0 };
	var bbox = Math.min(bbox.x0, bbox.x1) + "," + Math.min(bbox.y0, bbox.y1) + "," + Math.max(bbox.x0, bbox.x1) + "," + Math.max(bbox.y0, bbox.y1);
	url = url.replace(/\$\{bbox\}/, bbox);
	return url;
}

function MapTileLayerInstance(map, layer, display, args) {
	this.map = map;
	this.layer = layer;
	this.display = display;

	if (args && args.min_display_level)	this.min_display_level = args.min_display_level;
	else if (this.layer.min_display_level) this.min_display_level = this.layer.min_display_level;
	else this.min_display_level = 0;
	if (args && args.max_display_level) this.max_display_level = args.max_display_level;
	else if (this.layer.max_display_level) this.max_display_level = this.layer.max_display_level;
	else this.max_display_level = this.layer.max_level;

	this.root = new Object();
	this.tiles = new Array();

    var div = document.createElement("div");
	div.style.position = "absolute";
	div.style.left = 0;
	div.style.top = 0;
	div.style.width = "100%";
	div.style.height = "100%";
	div.style.zIndex = this.layer.z_index;
	this.tiles_div = div;

	this.copyright = this.layer.copyright;
}

MapTileLayerInstance.prototype.Kill = function() {
	for (var i in this.tiles) {
		for (var tid in this.tiles[i]) {
			var tile = this.tiles[i][tid];
			tile.state = tile.s_delete;
		}
	}
	for (var i in this.tiles) {
		for (var tid in this.tiles[i]) {
			var tile = this.tiles[i][tid];
			this.TryDeleteTile(tile);
		}
	}
}

MapTileLayerInstance.prototype.AppendChild = function(e) {
	this.tiles_div.appendChild(e);
}

MapTileLayerInstance.prototype.RemoveChild = function(e) {
	this.tiles_div.removeChild(e);
}

MapTileLayerInstance.prototype.GetContainer = function() {
	return this.tiles_div;
}

MapTileLayerInstance.prototype.NewTile = function(level, x, y) {
	var src = this.layer.GetImageSrc(level, x, y);
	var tile = new MapImageTile(this.map, this, level, x, y, { src: src, opacity: this.layer.opacity });
	if (!this.tiles[level]) this.tiles[level] = new Object();
	this.tiles[level][x + ":" + y] = tile;
	if (level > 0) {
		if (!this.tiles[level - 1]) this.tiles[level - 1] = new Object();
		tile.parent = this.tiles[level - 1][(x >> 1) + ":" + (y >> 1)];
		if (!tile.parent) {
			tile.parent = this.NewTile(level - 1, x >> 1, y >> 1);
		}
		tile.parent.child[tile.child_to_parent] = tile;
	} else {
		tile.parent = null;
		this.root[x + ":" + y] = tile;
	}
	return tile;
}

MapTileLayerInstance.prototype.DeleteTile = function(tile) {
    tile.Kill();
    for (var i = 0; i < 4; ++i) if (tile.child[i]) this.DeleteTile(tile.child[i]);
    if (tile.parent) tile.parent.child[tile.childToParent] = null;
    else this.root[tile.x + ":" + tile.y] = null;
    delete this.tiles[tile.level][tile.x + ":" + tile.y];
}

MapTileLayerInstance.prototype.TryDeleteTile = function(tile) {
    if (!tile.child[0] && !tile.child[1] && !tile.child[2] && !tile.child[3]) {
		this.DeleteTile(tile);
		if (tile.parent && (tile.parent.state == tile.parent.s_delete)) this.TryDeleteTile(tile.parent);
    }
}

MapTileLayerInstance.prototype.GetTile = function(level, x, y) {
	if (this.tiles[level]) return this.tiles[level][x + ":" + y];
	else return null;
}

MapTileLayerInstance.prototype.UpdateCTX = function(view) {
	var real_level = Math.ceil(Math.log(0.5 * this.layer.scale.x * view.scale.x + 0.5 * this.layer.scale.y * view.scale.y) 
		/ Math.log(2) - 0.4);
	var level;
	if (real_level < 0) level = 0;
	else if (real_level > this.layer.max_level) level = this.layer.max_level;
	else level = real_level;
	
	var display;
	if ((real_level >= this.min_display_level) && (real_level <= this.max_display_level)) display = true;
	else display = false;

	var world_tile_size = {
		x: this.layer.tile_scale.x * Math.pow(0.5, level), 
		y: this.layer.tile_scale.y * Math.pow(0.5, level)
	};

	var display_box = {
		x0: Math.max(view.world_box.x0, this.layer.world_box.x0),
		y0: Math.max(view.world_box.y0, this.layer.world_box.y0),
		x1: Math.min(view.world_box.x1, this.layer.world_box.x1),
		y1: Math.min(view.world_box.y1, this.layer.world_box.y1)
	};
	var display_tile_box = {
		x0: Math.max(0, Math.floor((display_box.x0 - this.layer.start_point.x) / world_tile_size.x)),
		y0: Math.max(0, Math.floor((display_box.y0 - this.layer.start_point.y) / world_tile_size.y)),
		x1: Math.ceil((display_box.x1 - this.layer.start_point.x) / world_tile_size.x),
		y1: Math.ceil((display_box.y1 - this.layer.start_point.y) / world_tile_size.y)
	};

	var e = { 
		x: this.layer.load_band / view.scale.x, 
		y: this.layer.load_band / view.scale.y
	};
	var load_box = {
		x0: Math.max(view.world_box.x0 - e.x, this.layer.world_box.x0),
		y0: Math.max(view.world_box.y0 - e.y, this.layer.world_box.y0),
		x1: Math.min(view.world_box.x1 + e.x, this.layer.world_box.x1),
		y1: Math.min(view.world_box.y1 + e.y, this.layer.world_box.y1)
	};
	var load_tile_box = {
   		x0: Math.max(0, Math.floor((load_box.x0 - this.layer.start_point.x) / world_tile_size.x)),
		y0: Math.max(0, Math.floor((load_box.y0 - this.layer.start_point.y) / world_tile_size.y)),
		x1: Math.ceil((load_box.x1 - this.layer.start_point.x) / world_tile_size.x),
		y1: Math.ceil((load_box.y1 - this.layer.start_point.y) / world_tile_size.y)
	};

	var e = { 
		x: this.layer.keep_band / view.scale.x, 
		y: this.layer.keep_band / view.scale.y
	};
	var keep_box = {
		x0: Math.max(view.world_box.x0 - e.x, this.layer.world_box.x0),
		y0: Math.max(view.world_box.y0 - e.y, this.layer.world_box.y0),
		x1: Math.min(view.world_box.x1 + e.x, this.layer.world_box.x1),
		y1: Math.min(view.world_box.y1 + e.y, this.layer.world_box.y1)
	};
	var keep_tile_box = {
    	x0: Math.max(0, Math.floor((keep_box.x0 - this.layer.start_point.x) / world_tile_size.x)),
		y0: Math.max(0, Math.floor((keep_box.y0 - this.layer.start_point.y) / world_tile_size.y)),
		x1: Math.ceil((keep_box.x1 - this.layer.start_point.x) / world_tile_size.x),
		y1: Math.ceil((keep_box.y1 - this.layer.start_point.y) / world_tile_size.y)
	};
	var keep_min_level = Math.max(0, level - 2);
	var keep_max_level = level;

	this.ctx = {
		display: display,
		level: level,
		display_box: display_box,
		display_tile_box: display_tile_box,
		load_box: load_box,
		load_tile_box: load_tile_box,
		keep_box: keep_box,
		keep_tile_box: keep_tile_box,
		keep_min_level: keep_min_level,
		keep_max_level: keep_max_level
	};
	
	this.map.custom_status = " " + this.ctx.display + ", L" + real_level;
}

MapTileLayerInstance.prototype.Tick0 = function(view) {
	this.UpdateCTX(view);
	var ctx = this.ctx;

	if (ctx.display) {
		for (var i in this.tiles) {
			for (var tid in this.tiles[i]) {
				var tile = this.tiles[i][tid];
				if ((tile.level <= ctx.keep_max_level) && (tile.level >= ctx.keep_min_level) 
					&& TestBoxIntersection(tile.world_box, ctx.keep_box)) tile.state = tile.s_keep;
				else tile.state = tile.s_delete;
			}
	    }
		for (var y = ctx.display_tile_box.y0; y < ctx.display_tile_box.y1; ++y) {
			for (var x = ctx.display_tile_box.x0; x < ctx.display_tile_box.x1; ++x) {
				var tile = this.GetTile(ctx.level, x, y);
				if (!tile) tile = this.NewTile(ctx.level, x, y);
				tile.state = tile.s_visible;
				if ((tile.image_state == tile.is_empty) || (tile.image_state == tile.is_loading)) {
					var found = false;
					for (var ptile = tile.parent; ptile && (ptile.level >= ctx.keep_min_level); ptile = ptile.parent) {
						if (ptile.image_state == ptile.is_complete) {
							ptile.state = ptile.s_visible;
							found = true;
							break;
						}
					}
					if (!found) this.TryChildren(tile, ctx.display_box, ctx.level + 3);
				}
			}
		}
		for (var y = ctx.load_tile_box.y0; y < ctx.load_tile_box.y1; ++y) {
			for (var x = ctx.load_tile_box.x0; x < ctx.load_tile_box.x1; ++x) {
				var tile = this.GetTile(ctx.level, x, y);
				if (!tile) tile = this.NewTile(ctx.level, x, y);
				if ((tile.state == tile.s_delete) || (tile.state == tile.s_keep))
					tile.state = tile.s_load;
			}
		}
	} else {
		for (var i in this.tiles) {
			for (var tid in this.tiles[i]) {
				var tile = this.tiles[i][tid];
				tile.state = tile.s_delete;
			}
	    }
	}
	for (var i in this.tiles) {
		for (var tid in this.tiles[i]) {
			var tile = this.tiles[i][tid];
			tile.Tick0();
			if (tile.state == tile.s_delete) this.TryDeleteTile(tile);
		}
	}
}

MapTileLayerInstance.prototype.Tick1 = function(view) {
	var tile_view = {
		start_point: { x: view.world_box.x0, y: view.world_box.y0 },
		scale: { x: view.scale.x, y: view.scale.y }
	};
	var display_copyright = false;
	for (var i in this.tiles) {
		for (var tid in this.tiles[i]) {
			var tile = this.tiles[i][tid];
			tile.Tick1(tile_view);
			if ((tile.state == tile.s_visible) && ((tile.image_state == tile.is_loading) || (tile.image_state == tile.is_complete))) display_copyright = true;
		}
	}
	if ((this.copyright != null) && display_copyright) {
		this.map.copyright_manager.ShowCopyright(this.copyright);
	}
}

MapTileLayerInstance.prototype.TryChildren = function(tile, world_box, max_level) {
//	Log("TryChildren(" + tile.level + ", " + tile.x + ", " + tile.y);
	for (var i = 0; i < 4; ++i) {
		var ctile = tile.child[i];
		if (ctile && TestBoxIntersection(ctile.world_box, world_box)) {
			if (ctile.image_state == ctile.is_complete) {
				ctile.state = ctile.s_visible;
			} else if (ctile.level < max_level) {
				this.TryChildren(ctile, world_box, max_level);
			}
		}
	}
}

function MapImageTile(map, layer_instance, level, x, y, args) {
	this.map = map;
	this.download_manager = this.map.download_manager;
	this.layer_instance = layer_instance;
	this.level = level;
	this.x = x;
	this.y = y;
	this.src = args.src;
	this.stop_src = "img/transparent.gif";
	this.opacity = (args.opacity != null) ? args.opacity : null;

	this.image = null;
	this.base_priority = 0;
	this.old_priority = 0;
	this.in_download_manager = false;
	this.displaying = false;
	
	this.s_none = 0;
	this.s_visible = 1;
	this.s_load = 2;
	this.s_keep = 3;
	this.s_delete = 4;
	this.state = this.s_none;
	
	this.is_empty = 0;
	this.is_loading = 1;
	this.is_complete = 2;
	this.is_error = 3;
	this.image_state = this.is_empty;
	this.retry_count = 0;
	this.max_retries = 0;

	var layer = this.layer_instance.layer;
	this.scale = {
		x: layer.scale.x * Math.pow(0.5, level), 
		y: layer.scale.y * Math.pow(0.5, level)
	};
	this.tile_size = layer.tile_size;
	this.world_tile_size = {
		x: this.scale.x * this.tile_size.x,
		y: this.scale.y * this.tile_size.y
	};
	this.start_point = {
		x: layer.start_point.x + x * this.world_tile_size.x,
		y: layer.start_point.y + y * this.world_tile_size.y
	};
    this.world_box = {
		x0: this.start_point.x,
		y0: this.start_point.y,
		x1: this.start_point.x + this.world_tile_size.x,
		y1: this.start_point.y + this.world_tile_size.y
   	};

	this.parent = null;
	this.child_to_parent = (x & 1) + (y & 1) * 2;
	this.child = new Array();
	for (var i = 0; i < 4; ++i) this.child[i] = null;
	
	var _this = this;
	this.OnImageLoadCaller = function(ev) { if (!ev) ev = event; _this.OnImageLoad(ev); };
    this.OnImageErrorCaller = function(ev) { if (!ev) ev = event; _this.OnImageError(ev); };
}

MapImageTile.prototype.Kill = function() {
//	Log("KILL " + this.level + ", " + this.x + ", " + this.y);
	if (this.in_download_manager) {
		this.in_download_manager = false;
		this.download_manager.RemoveDownload(this);
	}
	if (this.image_state == this.is_loading) {
		this.StopDownload();
	}
	if (this.displaying) {
		this.displaying = false;
		this.layer_instance.RemoveChild(this.image);
	}
	this.image = null;
}

MapImageTile.prototype.OnImageLoad = function(ev) {
	if (this.image_state == this.is_loading) {
		this.image_state = this.is_complete;
		this.image.onload = null;
		this.image.onerror = null;
		if (this.in_download_manager) {
			this.in_download_manager = false;
			this.download_manager.RemoveDownload(this);
		}
    }
}

MapImageTile.prototype.OnImageError = function(ev) {
//	Log("ImageError");
    if (this.image_state == this.is_loading) {
    	++this.retry_count;
		if (this.retry_count <= this.max_retries) {
			this.image.src = this.src + "&retry=" + this.retry_count;
		} else {
			this.image_state = this.is_error;
			this.image.onload = null;
			this.image.onerror = null;
			this.image.src = this.stop_src;
			if (this.in_download_manager) {
				this.in_download_manager = false;
				this.download_manager.RemoveDownload(this);
			}
		}
	}
}

MapImageTile.prototype.CreateImage = function() {
	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.style.visibility = "visible";
	img.style.position = "absolute";
	img.style.zIndex = this.level + 100;
	if (this.opacity != null) {
		img.style.opacity = this.opacity;
		img.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=" + (this.opacity * 100) + ")";
	}

	return img;
}

MapImageTile.prototype.DestroyImage = function(image) {
}

MapImageTile.prototype.StartDownload = function() {
	if (this.image_state == this.is_empty) {
		this.image_state = this.is_loading;
		if (!this.image) this.image = this.CreateImage();
		this.image.onload = this.OnImageLoadCaller;
		this.image.onerror = this.OnImageErrorCaller;
		this.image.src = this.src;
	}
}

MapImageTile.prototype.StopDownload = function() {
	if (this.image_state == this.is_loading) {
		this.image_state = this.is_empty;
		this.image.onload = null;
		this.image.onerror = null;
		this.image.src = this.stop_src;
	}
}

MapImageTile.prototype.Tick0 = function() {
	if ((this.state == this.s_visible) || (this.state == this.s_load)) {
		var priority = this.base_priority;
		if (this.state == this.s_load) ++priority;
		if ((this.image_state == this.is_empty) && !this.in_download_manager) {
			this.in_download_manager = true;
			this.old_priority = priority;
			this.download_manager.AddDownload(this, priority);
		}
		if (this.in_download_manager && (this.old_priority != priority)) {
			this.old_priority = priority;
			this.download_manager.ChangePriority(this, priority);
		}
	} else {
		if (this.in_download_manager) {
			this.in_download_manager = false;
			this.download_manager.RemoveDownload(this);
		}
		if (this.image_state == this.is_loading) {
			this.StopDownload();
		}
	}
}
	
MapImageTile.prototype.Tick1 = function(view) {
	if ((this.state == this.s_visible) && ((this.image_state == this.is_loading) 
			|| (this.image_state == this.is_complete))) {
		if (this.image) {
			this.UpdatePosition(view);
			if (!this.displaying) {
				this.displaying = true;
				this.layer_instance.AppendChild(this.image);
			}
		}
	} else {
		if (this.displaying) {
			this.displaying = false;
			this.layer_instance.RemoveChild(this.image);
		}
	}
	if ((this.image_state == this.is_error) && this.image) {
		this.image = null;
	}
}

MapImageTile.prototype.UpdatePosition = function(view) {
	var pos = {
		x0: Math.round((this.world_box.x0 - view.start_point.x) * view.scale.x),
		y0: Math.round((this.world_box.y0 - view.start_point.y) * view.scale.y),
		x1: Math.round((this.world_box.x1 - view.start_point.x) * view.scale.x),
		y1: Math.round((this.world_box.y1 - view.start_point.y) * view.scale.y)
	};
	this.image.style.left = pos.x0;
	this.image.style.top = pos.y0;
	this.image.width = pos.x1 - pos.x0;
	this.image.height = pos.y1 - pos.y0;
}

function MapDownloadManager() {
	this.max_priority = 9;
	this.max_running_downloads = 80;
	this.max_start_downloads = 60;
	this.min_running_downloads = 10;
	this.running_downloads = 0;
	this.total_downloads = 0;
	this.queues = new Array();
	for (var i = 0; i <= this.max_priority; ++i)
		this.queues[i] = { running: 0, total: 0, first: null, last: null };
}

MapDownloadManager.prototype.AddDownload = function(download, priority) {
	var q = this.queues[priority];
	download.download_priority = priority;
	download.download_running = false;
	download.download_next = null;
	download.download_prev = q.last;
	if (q.last) q.last.download_next = download;
	else q.first = download;
	q.last = download;
	++this.total_downloads;
	++q.total;
}

MapDownloadManager.prototype.ChangePriority = function(download, priority) {
	var q = this.queues[download.download_priority];
	if (download.download_prev) download.download_prev.download_next = download.download_next;
	else q.first = download.download_next;
	if (download.download_next) download.download_next.download_prev = download.download_prev;
	else q.last = download.download_prev;
	--q.total;

	var q = this.queues[priority];
	download.download_priority = priority;
	download.download_next = null;
	download.download_prev = q.last;
	if (q.last) q.last.download_next = download;
	else q.first = download;
	q.last = download;
	++q.total;
}

MapDownloadManager.prototype.RemoveDownload = function(download) {
	var q = this.queues[download.download_priority];
	if (download.download_prev) download.download_prev.download_next = download.download_next;
	else q.first = download.download_next;
	if (download.download_next) download.download_next.download_prev = download.download_prev;
	else q.last = download.download_prev;
	download.download_next = null;
	download.download_prev = null;
	--this.total_downloads;
	--q.total;
}

MapDownloadManager.prototype.Tick = function() {
	var start_left = this.max_start_downloads;
	var running_left = this.max_running_downloads;
	var running = 0;
	for (var i = 0; i <= this.max_priority; ++i) {
		var q = this.queues[i];
		for (var d = q.first; d; d = d.download_next) {
			if (d.download_running) {
				if (running_left <= 0) {
					d.StopDownload();
					d.download_running = false;
				}
			} else {
				if (start_left > 0) {
					d.StartDownload();
					d.download_running = true;
				}
			}
			if (d.download_running) {
				--start_left;
				--running_left;
				++running;
			}
		}
		if (running > 0) {
			start_left = this.min_running_downloads - running;
			running_left = this.min_running_downloads - running;
		}
	}
	return {
		total: this.total_downloads,
		running: running
	};
}

function MapLayerManager(map, args) {
	this.map = map;
	this.container = args.container;
	this.layers = { };
	this.layer_manager = this;
	this.visible = true;

    var div = document.createElement("table");
    div.style.width = "100%";
    div.cellPadding = 0;
    div.cellSpacing = 0;
    this.table = div;

    this.container.appendChild(this.table);
}

MapLayerManager.prototype.NewGroup = function(name) {
	var group = { 
		map: this.map,
		layer_manager: this.layer_manager
	};
	group.Expand = function() {
		group.ce_img.src = "img/lm_minus.png";
		group.ce_img.onclick = this.onCollapseClick;
		group.inner_table_row.style.height = "auto";
		group.inner_table_row.style.visibility = "visible";
		group.inner_table_container.appendChild(this.inner_table);
	};
	group.Collapse = function() {
		group.ce_img.src = "img/lm_plus.png";
		group.ce_img.onclick = this.onExpandClick;
		group.inner_table_row.style.height = "0";
		group.inner_table_row.style.visibility = "hidden";
		group.inner_table_container.removeChild(this.inner_table);
	};
	group.onCollapseClick = function(ev) { group.Collapse(); };
	group.onExpandClick = function(ev) { group.Expand(); };
}

function MapLayerManagerGroup(parent, args) {
	this.map = parent.map;
	this.layer_manager = parent.layer_manager;

    var _this = this;
    this.onCollapseClick = function(ev) { _this.Collapse(); };
    this.onExpandClick = function(ev) { _this.Expand(); };

	var inner_table = document.createElement("table");
	inner_table.style.width = "100%";
	inner_table.cellPadding = 0;
	inner_table.cellSpacing = 0;
	this.inner_table = inner_table;
	this.table = inner_table;
	
	var table = parent.table;

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.src = "img/lm_plus.png";
	img.onclick = this.onExpandClick;
	this.ce_img = img;

	var row = table.insertRow(-1);

	var cell = row.insertCell(-1);
	cell.style.paddingLeft = "3";
	cell.style.paddingRight = "3";
	cell.appendChild(this.ce_img);

	var cell = row.insertCell(-1);
	cell.style.width = "100%";
	cell.innerHTML = args.name;

	var row = table.insertRow(-1);
	this.inner_table_row = row;
	var cell = row.insertCell(-1);
	var cell = row.insertCell(-1);
	cell.style.width = "100%";
	this.inner_table_container = cell;

	this.inner_table_row.style.height = "0";
	this.inner_table_row.style.visibility = "hidden";
}

MapLayerManagerGroup.prototype.Collapse = function() {
	this.ce_img.src = "img/lm_plus.png";
	this.ce_img.onclick = this.onExpandClick;
	this.inner_table_row.style.height = "0";
	this.inner_table_row.style.visibility = "hidden";
	this.inner_table_container.removeChild(this.inner_table);
}

MapLayerManagerGroup.prototype.Expand = function() {
	this.ce_img.src = "img/lm_minus.png";
	this.ce_img.onclick = this.onCollapseClick;
	this.inner_table_row.style.height = "auto";
	this.inner_table_row.style.visibility = "visible";
	this.inner_table_container.appendChild(this.inner_table);
}

function MapLayerManagerNode(parent, args) {
	this.map = parent.map;
	this.layer_id = args.layer_id;
	this.layer_manager = parent.layer_manager;
	this.layer_manager.layers[this.layer_id] = this;

    var _this = this;
    this.onEnableClick = function(ev) { _this.Enable(); };
    this.onDisableClick = function(ev) { _this.Disable(); };

	var table = parent.table;

	var img = document.createElement("img");
	img.unselectable = true;
	img.galleryImg = "no";
	img.src = "img/lm_disabled.png";
	img.onclick = this.onEnableClick;
	this.ce_img = img;

	var row = table.insertRow(-1);

	var cell = row.insertCell(-1);
	cell.style.paddingLeft = "3";
	cell.style.paddingRight = "3";
	cell.appendChild(this.ce_img);

	var cell = row.insertCell(-1);
	cell.style.width = "100%";
	cell.innerHTML = args.name;
}

MapLayerManagerNode.prototype.Enable = function() {
	this.ce_img.src = "img/lm_enabled.png";
	this.ce_img.onclick = this.onDisableClick;
	this.map.EnableLayer(this.layer_id);
}

MapLayerManagerNode.prototype.Disable = function() {
	this.ce_img.src = "img/lm_disabled.png";
	this.ce_img.onclick = this.onEnableClick;
	this.map.DisableLayer(this.layer_id);
}

function MapContextMenu(map, args) {
	this.map = map;
	this.container = args.container;

	var table = document.createElement("table");
	table.style.width = "100";
	table.style.height = "100";
	table.style.backgroundColor = "100";
	table.style.position = "absolute";
	table.cellPadding = 0;
	table.cellSpacing = 0;
	this.table = table;
}

MapContextMenu.prototype.Show = function(position) {
	this.table.style.left = position.x;
	this.table.style.top = position.y;
	this.container.appendChild(this.table);
}

MapContextMenu.prototype.Hide = function() {
	this.container.removeChild(this.table);
}

function MapContextMenuNode() {
}

function MapCopyrightManager(container) {
	this.container = container;
	this.copyrights = { };
}

MapCopyrightManager.prototype.AddCopyright = function(copyright) {
	this.copyrights[copyright.id] = copyright;
}

MapCopyrightManager.prototype.ShowCopyright = function(id) {
	var o = this.copyrights[id];
	o.show = true;
}

MapCopyrightManager.prototype.Tick0 = function() {
	for (id in this.copyrights) {
		this.copyrights[id].show = false;
	}
}

MapCopyrightManager.prototype.Tick1 = function() {
	for (id in this.copyrights) {
		var o = this.copyrights[id];
		if (o.show) {
			if (!o.visible) {
				o.visible = true;
				this.container.appendChild(o.copyright_div);
			}
		} else {
			if (o.visible) {
				o.visible = false;
				this.container.removeChild(o.copyright_div);
			}
		}
	}
}

function MapCopyrightNotice(id, text) {
	this.id = id;
	this.visible = false;

	var div = document.createElement("div");
	div.style.textAlign = "center";
	div.innerHTML = text;
	this.copyright_div = div;
}
