/*
##################################################
#
# map.js; JS-Funktionen für den Stadtplan
# Autor: DI Michael Berg, Fit4Net GbR
# Datum: 1.6.2006
# Version: 1.0
# Copyright: 2006 by Fit4Net GbR
#
##################################################

##################################################
# Änderungen
##################################################
 1.) images/" + l.dpi + "-pc/tile" + l.dpi + "_" + count + ".gif --> Angabe der Zoomstufen in %

*/
//functions ending with _md mean "mode dependent", means will have different meaning whether in admin or normal mode

//path to the root the map-application
var f4n_pathToRoot = "../stadtplan/";

//full url to the php script handling the communication with the server
var f4n_serverURL = "http://www.badelster-info.de/stadtplan/map-server.php";

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//general code
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
function f4n_require(condF, where, msg){  //where and msg can be ommited
  if (!where) where = "";
  if (typeof condF == "function" && !condF()) {
    var condition = condF.toString().match(/function\s*\(\)\s*\{\s*return\s*([^\}]+)\}/)[1];
    if (!condition) condition = condF.toString();
    if (msg) alert(msg);
    else alert ("in "+where+ " require: Bedingung ("+condition+") war nicht erfüllt!");
    throw new Error("required condition was not fulfilled");
  }
}
function f4n_require(condF, where, msg){}

//simple function which trims blanks in the beginning of a string
function f4n_trim(s){
  var removeToIndex = 0;
  for (var i = 0;i<s.length; i++)
    if (s.charAt(i) == " ") removeToIndex = i + 1;
    else break;
  return s.substring(removeToIndex, s.length);
}

//symbol size on map and in alert mode
var f4n_onMapSymbolSize = {w: 30, h: 30};
var f4n_onAlertSymbolSizeFactor = 1.2; //{w: 40, h: 40}
//var f4n_onAlertSymbolSize = f4n_onMapSymbolSize;

//this is the map we drag around
var f4n_map;
var f4n_tiles;
//and the peephole we see the map through
var f4n_peephole;

//array holding the currently on the map available objects
//an associative array, key = objectId, value: {mapObject: the_on_map_image, id: object_id, name: name, top_pos: y, left_pos: x, zoomlevelNo: 0}
var f4n_objects = new Array();
//Dummy-Objekt, um über externen Link eine Markierung zu positionieren!
var f4n_dummy_object = {top: 0, left: 0, zoomLevelNo: 0, symbolName: "dot.gif", alertSymbolName: "empty.gif", name: "gesuchtes Objekt!", id: "999"};
var f4n_quickNavObjects = new Array();

//here we put all available symbols - means we don't make them changeable right now
//if we add a new symbol - we have to add it here and put the files to the location of all other symbols
//s : Symbol, as: alertSymbol, w: Width, h: Height  --> originale Größe width und height beziehen sich auf "s"!
var f4n_symbols = [
    {s: "dot.gif", as: "empty.gif", w: "23", h: "23"},
    {s: "veranstaltungen.gif", as: "empty.gif", w: "47", h: "30"},
    {s: "Ticket-Shop-und-Info.gif", as: "empty.gif", w: "34", h: "34"},
    {s: "Kurverwaltung-Gaesteservice.gif", as: "empty.gif", w: "45", h: "28"},
    {s: "Klinik.gif", as: "empty.gif", w: "51", h: "42"},
    {s: "Aerztehaus.gif", as: "empty.gif", w: "38", h: "40"},
    {s: "Apotheke.gif", as: "empty.gif", w: "29", h: "29"},
    {s: "Elsterado.gif", as: "empty.gif", w: "37", h: "37"},
    {s: "Bademuseum.gif", as: "empty.gif", w: "40", h: "40"},
    {s: "Stadtbibliothek.gif", as: "empty.gif", w: "36", h: "32"},
    {s: "Postargentur.gif", as: "empty.gif", w: "33", h: "28"},
    {s: "Kirche.gif", as: "empty.gif", w: "29", h: "30"},
    {s: "Parkplatz.gif", as: "empty.gif", w: "37", h: "37"},
    {s: "Bushaltestelle.gif", as: "empty.gif", w: "41", h: "40"},
    {s: "Hotels.gif", as: "empty.gif", w: "35", h: "40"},
    {s: "Bank.gif", as: "empty.gif", w: "33", h: "34"},
    {s: "Park.gif", as: "empty.gif", w: "45", h: "29"},
    {s: "Parkweg.gif", as: "empty.gif", w: "45", h: "28"},
    {s: "Sonst-Gebaeude.gif", as: "empty.gif", w: "45", h: "29"},
    {s: "Marienquelle.gif", as: "empty.gif", w: "35", h: "32"},
    {s: "Salzquelle.gif", as: "empty.gif", w: "31", h: "36"},
    {s: "Moritzquelle.gif", as: "empty.gif", w: "35", h: "37"},
    {s: "Sprudelquelle.gif", as: "empty.gif", w: "31", h: "36"}
]

//array for the objecttypes
//associative array, key = type_name, value: db_type
var f4n_objectTypes = new Array();

function f4n_init(){
  agent.call(f4n_serverURL, "getObjectTypes", "f4n_receivedObjectTypes");
  agent.call(f4n_serverURL, "getAllQuickNavElements", "f4n_receivedQuickNavElements");

  f4n_init_md();

  //capture key down events in order to know when one of the zoom keys gets pressed
  document.onkeydown = f4n_handleZoomKeyDown;
  document.onkeyup = f4n_handleZoomKeyUp;

  //get the map
  f4n_map = document.getElementById("f4n_map");
  f4n_tiles = document.getElementById("f4n_tiles");
  if (f4n_map){
    f4n_map.onmousedown = f4n_mouseDownHandler;
    f4n_map.onmouseup = f4n_mouseUpHandler;
  }
  else return;

  f4n_peephole = document.getElementById("f4n_ph");
  if (!f4n_peephole) return;

  //init the map arrays
  for(var i = 0; i < f4n_mapLevels.numberOfLevels; i++){
    var count = 0;
    var l = f4n_mapLevels[i];
    for(var top = 0; top < l.rows; top++){
      l[top] = new Array();
      for(var left = 0; left < l.cols; left++){
        var src = f4n_pathToRoot + "images/" + l.dpi + "-pc/tile" + l.dpi + "_" + count + ".gif";
        l[top][left] = new f4n_MapTile (top * l.tileHeight, left * l.tileWidth, src);
        count++;
      }
    }
  }

  //if we got an initial position via url parameters, go there
  var args = new Object();
  var argString = location.search.substring(1);
  var pairs = argString.split("&");
  for(var i = 0; i < pairs.length; i++){
    var pos = pairs[i].indexOf("=");
    if (pos == -1) continue;
    var argName = pairs[i].substring(0, pos);
    args[argName] = parseInt(pairs[i].substring(pos + 1));
  }

  //setzt die Höhe, Breite und den Zoomlevel
  set_position_data_for_dummy_object(args);

  //check for paramter correctness, and try to center on the given position as far as possible
  //zoomlevel
  if(!args["zl"] || args["zl"] > f4n_mapLevels.numberOfLevels - 1 || args["zl"] < 0)
    args["zl"] = f4n_mapLevels.currentLevelNo;
  //position from top
  if(!args["t"] || args["t"] > f4n_mapLevels[args["zl"]].height || args["t"] < 0)
    args["t"] = 0;
  else {
    if((args["t"] + (f4n_ph.height / 2)) > f4n_mapLevels[args["zl"]].height)
      args["t"] = f4n_mapLevels[args["zl"]].height - f4n_ph.height;
    else if((args["t"] - (f4n_ph.height / 2)) < 0)
      args["t"] = 0;
    else
      args["t"] = args["t"] - (f4n_ph.height / 2);
  }
  //position from left
  if(!args["l"] || args["l"] > f4n_mapLevels[args["zl"]].width || args["l"] < 0)
    args["l"] = 0;
  else {
    if((args["l"] + (f4n_ph.width / 2)) > f4n_mapLevels[args["zl"]].width)
      args["l"] = f4n_mapLevels[args["zl"]].width - f4n_ph.width;
    else if((args["l"] - (f4n_ph.width / 2)) < 0)
      args["l"] = 0;
    else
      args["l"] = args["l"] - (f4n_ph.width / 2);
  }

  if(args["oid"]) agent.call(f4n_serverURL, "getObjects", "f4n_startObjectReceived", null, "o.id="+args["oid"]);

  //bei direktem Aufruf, Punkt anzeigen oder nicht?
  if (args["dot"] == 1) {

   //Aufruf der Funktion zum Anzeigen des Objekts! --> erstmal ohne den eigentlichen Array-Weg!
   var DummyObjectArray = new Array(f4n_dummy_object);
   f4n_showObjectsOnMap(DummyObjectArray);
   f4n_alertObject(f4n_dummy_object);
   //dieser Eintrag sorgt dafür, dass das Objekt bei unterschiedlichen Zoomleveln scaliert wird!
   f4n_objects[f4n_dummy_object.id] = f4n_dummy_object;
  }

  //initialize the map with the correct coordinates
  f4n_initMap(args["zl"], args["t"], args["l"]);

}

function set_position_data_for_dummy_object(args) {
 //setzt die Höhe, Breite und den Zoomlevel
 if (args["dot"] == 1) {
  f4n_dummy_object.top = args["t"];
  f4n_dummy_object.left = args["l"];
  f4n_dummy_object.zoomLevelNo = args["zl"];
 }
}

function f4n_startObjectReceived (objects){
  if(objects.length == 1){
    f4n_receivedSearchResponse(objects);
    f4n_alertObject(objects[0]);
    f4n_goto(objects[0].zoomLevelNo, objects[0].top, objects[0].left);
  }
}

function f4n_init_md(){}

function f4n_goto(zoomLevelNo, top, left){
  var targetLevel = f4n_mapLevels[zoomLevelNo];
  //calculate the correct position of the peephole
  top = top - (f4n_ph.height / 2);
  if (top < 0) top = 0; if (top + f4n_ph.height > targetLevel.height) top = targetLevel.height - f4n_ph.height;
  left = left - (f4n_ph.width / 2);
  if (left < 0) left = 0; if (left + f4n_ph.width > targetLevel.width) left = targetLevel.width - f4n_ph.width;

  if (zoomLevelNo != f4n_mapLevels.currentLevelNo)
    f4n_initMap(zoomLevelNo, top, left);
  else {
    //set the new peephole position
    f4n_ph.top = top;
    f4n_ph.left = left;

    //we position the map as if it would be loaded anew, because if we would just
    //"move it", the extendBuffer function would load all images between the old and the new position which might
    //be bad with very large maps
    //the drawback is here, that extendBuffer will cover potentially already known regions again, but
    //displayImageRange won't load them, but the checks will nevertheless happen which could impact the performance
    //--> this should be fixed somewhen
    var eb = f4n_extendBuffer;
    eb.top = f4n_px2indexTL (f4n_ph.top, targetLevel.tileHeight);
    eb.left = f4n_px2indexTL (f4n_ph.left, targetLevel.tileWidth);
    eb.bottom = f4n_px2indexBR (f4n_ph.bottom(), targetLevel.tileHeight);
    eb.right = f4n_px2indexBR (f4n_ph.right(), targetLevel.tileWidth);
    eb.buffered = {all: 0, top: false, left: false, bottom: false, right: false};
    //display the peephole area
    f4n_displayImageRange (zoomLevelNo, eb.top, eb.bottom, eb.left, eb.right);
    //display the buffer around
    f4n_extendBuffer (zoomLevelNo);

    //position the map relative to the peephole - thus get the coordinates of the upper left map corner in peephole coordinates
    f4n_map.style.left = "" + f4n_map2phLeft (0) + "px";
    f4n_map.style.top = "" + f4n_map2phTop (0) + "px";
  }
}

function f4n_debugObject(o){
  var s = "";
  var count = 0;
  for (m in o){
    count++;
    if (count > 15){alert(s); s = ""; count = 0;}
    s += "\n" + m + "=" + o[m];
  }
  alert(s);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//code related to managing the map itself
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

//this function can be used to turn the DBC off
//function require(condition,msg,name){}

var f4n_mapLevels = new Array();
f4n_mapLevels.currentLevelNo = 0;

//the columns and rows should be even numbers
var mapData = new Array();
mapData[0] = {cols: 5, rows: 5, tw: 100, th: 100, dpi: 100};
mapData[1] = {cols: 10, rows: 10, tw: 75, th: 75, dpi: 150};
mapData[2] = {cols: 10, rows: 10, tw: 100, th: 100, dpi: 200};
mapData[3] = {cols: 25, rows: 25, tw: 50, th: 50, dpi: 250};
mapData[4] = {cols: 15, rows: 15, tw: 100, th: 100, dpi: 300};
mapData[5] = {cols: 25, rows: 25, tw: 70, th: 70, dpi: 350};
//mapData[4] = {cols: 44, rows: 42, tw: 164, th: 151, dpi: 225};

for (var i = 0; i < mapData.length; i++){
  var f4n_ml = new Array();
  f4n_ml.cols = mapData[i].cols; f4n_ml.rows = mapData[i].rows;
  f4n_ml.tileWidth = mapData[i].tw; f4n_ml.tileHeight = mapData[i].th;
  f4n_ml.width= f4n_ml.cols * f4n_ml.tileWidth; f4n_ml.height = f4n_ml.rows * f4n_ml.tileHeight;
  f4n_ml.dpi = mapData[i].dpi;
  f4n_mapLevels[i] = f4n_ml;
  f4n_mapLevels.numberOfLevels = i + 1;
}

//define the object which hold all information about a tile of the map to be shown
function f4n_MapTile(top,left,imageSource){
  this.top = top;
  this.left = left;
  this.src = imageSource;
  this.onDisplay = false;  //indicator whether tile is currently on display
}

//defining the peep hole object, this means right now the peep hole starts at (0,0)
var f4n_ph = new Object();
f4n_ph.width = 470;
f4n_ph.height = 470;
f4n_ph.top = 0;  //the position of the peephole top on the map
f4n_ph.left = 0; //the position of the peephole left on the map
f4n_ph.right = function (){return this.left + this.width};
f4n_ph.bottom = function (){return this.top + this.height};

//for now this function is a bit handmade and hand initialized
//usually there should be some way to get the data for the array in oder to initialize the zoom levels
function f4n_initMap(zoomLevelNo, startTop, startLeft){
  //var here = "f4n_initMap";
  //f4n_require(function(){return startTop >= 0}, here);
  //f4n_require(function(){return startLeft >= 0}, here);

  var level = f4n_mapLevels[zoomLevelNo];
  //set the current zoom level
  f4n_mapLevels.currentLevelNo = zoomLevelNo;
  //this is the positon of the peephole top,left on the map
  f4n_ph.top = startTop;
  f4n_ph.left = startLeft;

  //clear a possibly old map
  f4n_tiles.innerHTML = "";

  //clear the display attribute of the map to be loaded
  for (var top = 0; top < level.rows; top++)
    for(var left = 0; left < level.cols; left++)
      level[top][left].onDisplay = false;

  //put all objects onto the right position at the new maplevel
  for (var i in f4n_objects){  //because array is sparsely populated, we have to iterate over the properties of the object
    var o = f4n_objects[i];
    if (o != null){
      var omo = o.onMapObject;
      if (omo){
        omo.style.left = (f4n_l2lW(o.left, o.zoomLevelNo, zoomLevelNo) - 0.5 * f4n_onMapSymbolSize.w) + "px";
        omo.style.top = (f4n_l2lH(o.top, o.zoomLevelNo, zoomLevelNo) - 0.5 * omo.height) + "px";
      }
    }
  }
  f4n_initMap_md(zoomLevelNo);

  //same for quicknav objects, even though just one should be visible at a time
  for (var i in f4n_quickNavObjects){  //because array is sparsely populated, we have to iterate over the properties of the object
    var o = f4n_quickNavObjects[i];
    if (o != null){
      var omo = o.onMapObject;
      if (omo){
        omo.style.left = "" + (f4n_l2lW(o.left, o.zoomLevelNo, zoomLevelNo) - 0.5 * f4n_onMapSymbolSize.w) + "px";
        omo.style.top = "" +  (f4n_l2lH(o.top, o.zoomLevelNo, zoomLevelNo) - 0.5 * omo.height) + "px";
      }
    }
  }

  //set the buffer index coordinates
  var eb = f4n_extendBuffer;
  eb.top = f4n_px2indexTL (f4n_ph.top, level.tileHeight);
  eb.left = f4n_px2indexTL (f4n_ph.left, level.tileWidth);
  eb.bottom = f4n_px2indexBR (f4n_ph.bottom(), level.tileHeight);
  eb.right = f4n_px2indexBR (f4n_ph.right(), level.tileWidth);
  eb.buffered = {all: 0, top: false, left: false, bottom: false, right: false};
  //display the peephole area
  f4n_displayImageRange (zoomLevelNo, eb.top, eb.bottom, eb.left, eb.right);
  //display the buffer around
  f4n_extendBuffer (zoomLevelNo);

  //position the map relative to the peephole - thus get the coordinates of the upper left map corner in peephole coordinates
  f4n_map.style.left = "" + f4n_map2phLeft (0) + "px";
  f4n_map.style.top = "" + f4n_map2phTop (0) + "px";
}

function f4n_initMap_md(zoomLevelNo){}

//these vars remember where we filled the buffer already
f4n_extendBuffer.top = 0;
f4n_extendBuffer.left = 0;
f4n_extendBuffer.bottom = 0;
f4n_extendBuffer.right = 0;
f4n_extendBuffer.buffered = {all: 0, top: false, left: false, bottom: false, right: false}; //if all = 4, all sides are buffered
//fill and extend the buffer around the peephole based on the current peephole position on the map
function f4n_extendBuffer(zoomLevelNo){
  var level = f4n_mapLevels[zoomLevelNo];
  //shortcut
  var eb = f4n_extendBuffer;  //just a shortcut to save all the checks if not necessary anymore
  if (eb.buffered.all == 4) return;

  //get current outer buffer bounds
  var top = f4n_px2indexTL (f4n_ph.top - (1 * level.tileHeight), level.tileHeight);
  var left = f4n_px2indexTL (f4n_ph.left - (1 * level.tileWidth), level.tileWidth);
  var bottom = f4n_px2indexBR (f4n_ph.bottom() + (1 * level.tileHeight), level.tileHeight);
  var right = f4n_px2indexBR (f4n_ph.right() + (1 * level.tileWidth), level.tileWidth);

  if (!eb.buffered.top && top < eb.top){
    if (top >= 0){
      f4n_displayImageRange (zoomLevelNo, top, eb.top, eb.left, eb.right);
      eb.top = top;
    } else {
      f4n_displayImageRange (zoomLevelNo, 0, eb.top, eb.left, eb.right);
      eb.top = 0; eb.buffered.top = true; eb.buffered.all++;
    }
  }
  if (!eb.buffered.left && left < eb.left){
    if (left >= 0){
      f4n_displayImageRange (zoomLevelNo, eb.top, eb.bottom, left, eb.left);
      eb.left = left;
    } else {
      f4n_displayImageRange (zoomLevelNo, eb.top, eb.bottom, 0, eb.left);
      eb.left = 0; eb.buffered.left = true; eb.buffered.all++;
    }
  }
  if (!eb.buffered.bottom && bottom > eb.bottom){
    if(bottom <= level.rows - 1){
      f4n_displayImageRange (zoomLevelNo, eb.bottom, bottom, eb.left, eb.right);
      eb.bottom = bottom;
    } else {
      f4n_displayImageRange (zoomLevelNo, eb.bottom, level.rows - 1, eb.left, eb.right);
      eb.bottom = level.rows - 1; eb.buffered.bottom = true; eb.buffered.all++;
    }
  }
  if (!eb.buffered.right && right > eb.right){
    if(right <= level.cols - 1){
      f4n_displayImageRange (zoomLevelNo, eb.top, eb.bottom, eb.right, right);
      eb.right = right;
    } else {
      f4n_displayImageRange (zoomLevelNo, eb.top, eb.bottom, eb.right, level.cols - 1);
      eb.right = level.cols - 1; eb.buffered.right = true; eb.buffered.all++;
    }
  }
}

//calculate the index value of a top left corner value
function f4n_px2indexTL(pxValue, tileSize){
  return Math.floor (pxValue / tileSize);
}

//calculate the index value of a top left corner value
function f4n_px2indexBR(pxValue, tileSize){
  return Math.ceil (pxValue / tileSize) - 1;
}

function f4n_map2phTop(v){return v - f4n_ph.top;}
function f4n_map2phLeft(v){return v - f4n_ph.left;}
function f4n_ph2mapTop(v){return f4n_ph.top + v;}
function f4n_ph2mapLeft(v){return f4n_ph.left + v;}

//this function is only used during building up a previously empty map as it will only insert new elements and not reuse old elements
function f4n_displayImageRange(zoomLevelNo, fromTop, toTop, fromLeft, toLeft){
  var level = f4n_mapLevels[zoomLevelNo];

  //don't try to load images out of the map borders
  //var here = "f4n_displayImageRange";
  //f4n_require(function(){return zoomLevelNo >= 0 && zoomLevelNo <= f4n_mapLevels.numberOfLevels}, here);
  //f4n_require(function(){return fromTop >= 0 && fromTop <= toTop}, here);
  //f4n_require(function(){return toTop <= level.height}, here);
  //f4n_require(function(){return fromLeft >= 0 && fromLeft <= toLeft}, here);
  //f4n_require(function(){return toLeft <= level.width}, here);

  for(var top = fromTop; top <= toTop; top++){
    for(var left = fromLeft; left <= toLeft; left++){
      var tile = level[top][left];
      if (!tile.onDisplay){ //only load image if not done before
        var img = document.createElement("img");
        //now set the images values
        img.style.zIndex = "1";
        img.style.position = "absolute";
        img.style.left = "" + tile.left + "px";
        img.style.top = "" + tile.top + "px";
        img.src = tile.src;
        img.id = "f4n_i_t" + tile.top + "_l" + tile.left;
        img.alt = "map tile (" + tile.top + "," + tile.left + ")";
        img.width = level.tileWidth;
        img.height = level.tileHeight;
        tile.onDisplay = true;
        f4n_tiles.appendChild(img);
      }
    }
  }
}

//convert height and width values from one map to the other
function f4n_l2lH(value, fromLevelNo, toLevelNo){
  return Math.round(f4n_mapLevels[toLevelNo].height / f4n_mapLevels[fromLevelNo].height * value);
}
function f4n_l2lW(value, fromLevelNo, toLevelNo){
  return Math.round(f4n_mapLevels[toLevelNo].width / f4n_mapLevels[fromLevelNo].width * value);
}

var f4n_isZoomInKeyDown = false;
var f4n_isZoomOutKeyDown = false;

function f4n_mouseDownHandler(event){
  //use mouseclick to zoom in
  if (!f4n_isZoomInKeyDown && !f4n_isZoomOutKeyDown) f4n_startDragMap(event);
}

//this handler is used to capture the zoom events
function f4n_mouseUpHandler(event){
  if (!event) event = window.event; //for IE

  if (!f4n_isZoomInKeyDown && !f4n_isZoomOutKeyDown) return;

  f4n_map.onmousedown = f4n_mouseDownHandler;

  if (event.srcElement && !event.target) event.target = event.srcElement;  //if IE set a target attribute like in DOM Level 2

  //the only consistent behavior we get across browsers is the clicked at element - here one of the image-tiles
  //so we get the coordinates relative to the tiles upper left corner
  if (event.layerX){ //netscape, firefox - doesn't work as it takes not the parent element, but the image as the relative position
    var imgDeltaLeft = event.layerX;
    var imgDeltaTop = event.layerY;
  }
  else {  //IE and Opera
    var imgDeltaLeft = event.offsetX;
    var imgDeltaTop = event.offsetY;
  }

  //calculate the position where the user clicked on the map
  var deltaLeft = parseInt(event.target.style.left) + imgDeltaLeft;
  var deltaTop = parseInt(event.target.style.top) + imgDeltaTop;

  //ok now zoom in - or out
  if (f4n_isZoomInKeyDown)
    f4n_zoomIn(deltaTop, deltaLeft);
  else if (f4n_isZoomOutKeyDown)
    f4n_zoomOut(deltaTop, deltaLeft);
}

function f4n_handleZoomKeyUp(event){
  if (!event) event = window.event; //get IE event object

  if (event.which){  //netscape
    switch (event.which){
    case 16: //shift keycode
      f4n_isZoomInKeyDown = false;
      f4n_map.style.cursor = "move";
      break;
    case 17: //ctrl keycode
      f4n_isZoomOutKeyDown = false;
      f4n_map.style.cursor = "move";
      break;
    }
  }
  else { //IE or current browsers (event.which is also supported by Opera and Firefox)
    switch (event.keyCode){
    case 16: //shift keycode
      f4n_isZoomInKeyDown = false;
      f4n_map.style.cursor = "move";
      break;
    case 17: //ctrl keycode
      f4n_isZoomOutKeyDown = false;
      f4n_map.style.cursor = "move";
      break;
    }
  }
}

function f4n_handleZoomKeyDown(event){
  if (!event) event = window.event; //get IE event object

  if (event.modifiers){  //netscape
    if (event.mofifiers & Event.SHIFT_MASK){
      if (!f4n_isZoomOutKeyDown) f4n_isZoomInKeyDown = true;
      f4n_map.style.cursor = "pointer";
    }
    else if (event.modifiers & Event.CONTROL_MASK){
      if (!f4n_isZoomInKeyDown) f4n_isZoomOutKeyDown = true;
      f4n_map.style.cursor = "pointer";
    }
  }
  else {
    if (event.shiftKey){
      if (!f4n_isZoomOutKeyDown) f4n_isZoomInKeyDown = true;
      f4n_map.style.cursor = "pointer";
    }
    else if (event.ctrlKey){
      if (!f4n_isZoomInKeyDown) f4n_isZoomOutKeyDown = true;
      f4n_map.style.cursor = "pointer";
    }
  }
}

function f4n_zoomIn(inToTopPos, inToLeftPos){
  if (!inToTopPos) inToTopPos =  f4n_ph2mapTop(f4n_ph.height / 2);
  if (!inToLeftPos) inToLeftPos = f4n_ph2mapLeft(f4n_ph.width / 2);

  var prevLevelNo = f4n_mapLevels.currentLevelNo;
  var curLevel = f4n_mapLevels[++f4n_mapLevels.currentLevelNo];
  if (f4n_mapLevels.currentLevelNo > f4n_mapLevels.numberOfLevels - 1){
    --f4n_mapLevels.currentLevelNo;
    return;  //stay in the current level
  }
  else {
    //try to center the zoomed-in map on the point clicked
    var top = f4n_l2lH(inToTopPos, prevLevelNo, f4n_mapLevels.currentLevelNo) - (f4n_ph.height / 2);
    if (top < 0) top = 0; if (top + f4n_ph.height > curLevel.height) top = curLevel.height - f4n_ph.height;
    var left = f4n_l2lW(inToLeftPos, prevLevelNo, f4n_mapLevels.currentLevelNo) - (f4n_ph.width / 2);
    if (left < 0) left = 0; if (left + f4n_ph.width > curLevel.width) left = curLevel.width - f4n_ph.width;
    f4n_initMap(f4n_mapLevels.currentLevelNo, top, left);
  }
}

function f4n_zoomOut(inToTopPos, inToLeftPos){
  if (!inToTopPos) inToTopPos =  f4n_ph2mapTop(f4n_ph.height / 2);
  if (!inToLeftPos) inToLeftPos = f4n_ph2mapLeft(f4n_ph.width / 2);

  var prevLevelNo = f4n_mapLevels.currentLevelNo;
  var curLevel = f4n_mapLevels[--f4n_mapLevels.currentLevelNo];
  if (f4n_mapLevels.currentLevelNo < 0){
    ++f4n_mapLevels.currentLevelNo;
    return;  //stay in the level we are
  }
  else {
    var top = f4n_l2lH(inToTopPos, prevLevelNo, f4n_mapLevels.currentLevelNo) - (f4n_ph.height / 2);
    if (top < 0) top = 0; if (top + f4n_ph.height > curLevel.height) top = curLevel.height - f4n_ph.height;
    var left = f4n_l2lW(inToLeftPos, prevLevelNo, f4n_mapLevels.currentLevelNo) - (f4n_ph.width / 2);
    if (left < 0) left = 0; if (left + f4n_ph.width > curLevel.width) left = curLevel.width - f4n_ph.width;
    f4n_initMap(f4n_mapLevels.currentLevelNo, top, left);
  }
}

f4n_startDragMap.lastLeft = 0;
f4n_startDragMap.lastTop = 0;
function f4n_startDragMap(event) {
  if (!event) event = window.event; //get IE event object

  //remember the last position in order to calculate deltas for the move-events
  f4n_startDragMap.lastLeft = event.clientX;
  f4n_startDragMap.lastTop = event.clientY;

  //register the event handlers
  if (document.addEventListener){ //DOM Level 2 available
    document.addEventListener("mousemove", moveHandler, true);
    document.addEventListener("mouseup", upHandler, true);
  }
  else if (document.attachEvent) { //IE 5+
    document.attachEvent("onmousemove", moveHandler);
    document.attachEvent("onmouseup", upHandler);
  }
  else { //IE 4
    //vars are used in the uphandler via the functioninvocations environment (closure)
    var oldmovehandler = document.onmousemove;
    var olduphandler = document.onmouseup;
    document.onmousemove = moveHandler;
    document.onmouseup = upHandler;
  }

  if (event.stopPropagation) event.stopPropagation(); //dom level 2
  else event.cancelBubble = true; //IE

  if (event.preventDefault) event.preventDefault(); //dom level 2
  else event.returnValue = false;

  //show the urlparams in the admin mode
  f4n_startDragMap_md(event);

  function moveHandler(event){
    if (!event) event = window.event;  //for IE

    var level = f4n_mapLevels[f4n_mapLevels.currentLevelNo];
    var deltaLeft = event.clientX - f4n_startDragMap.lastLeft;
    var deltaTop = event.clientY - f4n_startDragMap.lastTop;

    //if possible, move the peephole (in reality the map)
    if (f4n_ph.top - deltaTop >= 0 && f4n_ph.bottom() - deltaTop <= level.height) f4n_ph.top -= deltaTop;
    if (f4n_ph.left - deltaLeft >= 0 && f4n_ph.right() - deltaLeft <= level.width) f4n_ph.left -= deltaLeft;
    //extend the buffer to show new areas
    f4n_extendBuffer(f4n_mapLevels.currentLevelNo);
    f4n_map.style.left = "" + f4n_map2phLeft(0) + "px";
    f4n_map.style.top = "" + f4n_map2phTop(0) + "px";

    f4n_startDragMap.lastLeft = event.clientX;
    f4n_startDragMap.lastTop = event.clientY;

    if (event.stopPropagation) event.stopPropagation(); //dom level 2
    else event.cancelBubble = true; //IE
  }

  function upHandler(event) {
    if (!event) event = window.event; //for IE

    if (document.removeEventListener){  //dom level 2
      document.removeEventListener("mouseup", upHandler, true);
      document.removeEventListener("mousemove", moveHandler, true);
    }
    else if (document.detachEvent){  //IE 5+
      document.detachEvent("onmouseup", upHandler);
      document.detachEvent("onmousemove", moveHandler);
    }
    else {  //IE 4
      document.onmouseup = olduphandler;
      document.onmousemove = null;//oldmovehandler;
    }

    if (event.stopPropagation) event.stopPropagation(); //dom level 2
    else event.cancelBubble = true; //IE
  }
}

function f4n_startDragMap_md(event){}

//this move function is used for the coarse grained moving via the buttons
function f4n_moveMap(direction){
  var dh = 100;
  var dw = 100;

  var deltaTop = 0;
  var deltaLeft = 0;

  var level = f4n_mapLevels[f4n_mapLevels.currentLevelNo];

  switch (direction){
  case "up-left":
    deltaTop = dh;
    deltaLeft = dw;
    break;
  case "up":
    deltaTop = dh;
    break;
  case "up-right":
    deltaTop = dh;
    deltaLeft = -dw;
    break;
  case "left":
    deltaLeft = dw;
    break;
  case "right":
    deltaLeft = -dw;
    break;
  case "down-left":
    deltaTop = -dh;
    deltaLeft = dw;
    break;
  case "down":
    deltaTop = -dh;
    break;
  case "down-right":
    deltaTop = -dh;
    deltaLeft = -dw;
    break;
  }

  if (f4n_ph.top - deltaTop >= 0 && f4n_ph.bottom() - deltaTop <= level.height)
    f4n_ph.top -= deltaTop; //the normal case
  else {
    if (f4n_ph.top - deltaTop < 0) f4n_ph.top = 0;  //jump above top of map
    if (f4n_ph.bottom() - deltaTop > level.height) f4n_ph.top = level.height - f4n_ph.height; //jump below bottom of map
  }
  if (f4n_ph.left - deltaLeft >= 0 && f4n_ph.right() - deltaLeft <= level.width)
    f4n_ph.left -= deltaLeft;  //normal case
  else {
    if (f4n_ph.left - deltaLeft < 0) f4n_ph.left = 0; //jump beside left of map
    if (f4n_ph.right() - deltaLeft > level.width) f4n_ph.left = level.width - f4n_ph.width; //jump beside right of map
  }

  //extend the buffer to show new areas
  f4n_extendBuffer(f4n_mapLevels.currentLevelNo);
  f4n_map.style.left = "" + f4n_map2phLeft(0) + "px";
  f4n_map.style.top = "" + f4n_map2phTop(0) + "px";
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//code related to the quicknavigation
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

function f4n_insertQuickNavElementIntoPage(qne){
  //remember the container for the quicknavigation elements
  if (!f4n_insertQuickNavElementIntoPage.quickNavContainer){
    f4n_insertQuickNavElementIntoPage.quickNavContainer = document.getElementById("f4n_quicknav_container");
    var qnc = f4n_insertQuickNavElementIntoPage.quickNavContainer;
  }
  var qnc = f4n_insertQuickNavElementIntoPage.quickNavContainer;
  var a = document.createElement("a");
  a.href = "#";
  a.className = "map_quicknav";
  a.onclick = function (){f4n_goto(qne.zoomLevelNo, qne.top, qne.left); return false;};  //do not follow link
  a.style.marginRight = "5px";
  a.innerHTML = qne.name;
  qnc.appendChild(a);
  qnc.appendChild(document.createTextNode(" "));
}

//get all the quick navigation elements
function f4n_receivedQuickNavElements(qnes){
  for (var i = 0; i < qnes.length; i++){
    f4n_quickNavObjects[qnes[i].id] = qnes[i]; //store key=id, value=object
    f4n_insertQuickNavElementIntoPage(qnes[i]);
  }
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//code related to managing the on-map objects
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

var f4n_searchForObjectType = null; //null means for all or depending on the query itself

//the object type will be an array with the name of the object type as key and the database id as the value
function f4n_receivedObjectTypes(ots){
  //get the container element for the search object types
  var sotc = document.getElementById("f4n_searchObjectTypeContainer");

  for (var i = 0; i < ots.length; i++){
    f4n_objectTypes[ots[i].name] = ots[i];
    if (ots[i].name != "Schnell-Navigation"){
      var a = document.createElement("a");
      a.href = "#";
      a.className = "map_search_objects";
      a.style.marginRight = "5px";
      a.onclick = function (){f4n_markSelectedObjectType(this); return false;};  //do not follow link
      a.innerHTML = ots[i].name;
      sotc.appendChild(a);
      sotc.appendChild(document.createTextNode(" "));
    }
  }
}

function f4n_markSelectedObjectType(element){
  //clear the old marked one (if there is one)
  if (f4n_searchForObjectType == null) document.getElementById("f4n_searchForAll").style.color = "";
  else f4n_searchForObjectType.style.color = "";

  element.style.color = "red";

  if (element.innerHTML == "ALLE") f4n_searchForObjectType = null;
  else f4n_searchForObjectType = element;
}

//shortcut for showing many objects after a search
function f4n_showObjectsOnMap(objects){
  for (var i = 0; i < objects.length; i++) {
   //bevor das Object übergeben wird, noch die originale Höhe und Breite zuweisen
   for (var j = 0; j < f4n_symbols.length; j++) {
     if (f4n_symbols[j].s == objects[i].symbolName) {
      objects[i].OriginWidth = f4n_symbols[j].w;
      objects[i].OriginHeight = f4n_symbols[j].h;
      break;
     }
   }
   f4n_showObjectOnMap(objects[i]);
  }
}

//show an object at a certain position
function f4n_showObjectOnMap(object){
  if (!object.onMapObject){
    object.onMapObject = document.createElement("img");
    var curZoomLevelNo = f4n_mapLevels.currentLevelNo;
    //now set the images values
    var omo = object.onMapObject;
    omo.src = f4n_pathToRoot + "symbols/" + object.symbolName;
    omo.OriginWidth = object.OriginWidth;
    omo.OriginHeight = object.OriginHeight;
    omo.height = object.OriginHeight * (f4n_onMapSymbolSize.w / object.OriginHeight);
    omo.width = f4n_onMapSymbolSize.w;
    omo.style.zIndex = "10";
    omo.style.position = "absolute";
    omo.style.left = (f4n_l2lW(object.left, object.zoomLevelNo, curZoomLevelNo) - 0.5 * f4n_onMapSymbolSize.w) + "px";
    omo.style.top = (f4n_l2lH(object.top, object.zoomLevelNo, curZoomLevelNo) - 0.5 * omo.height) + "px";
    omo.id = "f4n_i_oid_" + object.id;
    omo.alt = object.name;
    omo.title = object.name;

    //do some more stuff for the admin mode objects
    f4n_showObjectOnMap_md(object);

    f4n_map.appendChild(omo);

  }
}

function f4n_showObjectOnMap_md(object){}

function f4n_removeObjectsFromMap(objects){
  for (var i in objects) if (objects[i].onMapObject) f4n_removeObjectFromMap(objects[i]);
}

function f4n_removeObjectFromMap(object){
  //f4n_require(function(){return object.onMapObject != null}, "removeObjectFromMap");
  f4n_map.removeChild(object.onMapObject);
}


//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//code related to the search functionality
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------

function f4n_doSearch(searchField){
  var query = searchField.value;

  //preprocess the query
  var parts = query.split(/\s+/);
  var orList = new Array();
  var andList = new Array();
  var litOrAnd = true;
  for (var i = 0; i < parts.length; i++){
    var part = parts[i];
    switch (part.toUpperCase()){
    case "UND":
      if (litOrAnd){ //is a literal UND and should be used as a pattern
        andList.push("UND");
        litOrAnd = false;
      }
      else litOrAnd = true;  //as this was no literal UND, the next might be one
      break;
    case "ODER":
      if (litOrAnd){
        andList.push("ODER");
        litOrAnd = false;
      } else {
        orList.push(andList);
        andList = new Array();
        litOrAnd = true;
      }
      break;
    default:
      andList.push(part);
      litOrAnd = false;
      break;
    }
  }

  //get the selected type
  if (f4n_searchForObjectType == null) var typeName = null;
  else var typeName = f4n_searchForObjectType.innerHTML;

  if (andList.length > 0) orList.push(andList);
  //ok now we can call the server search with the correct parameters
  agent.call(f4n_serverURL, "getObjectsBySearch", "f4n_receivedSearchResponse", typeName, orList);
}

function f4n_doSearchByFirstChar(firstChar){
  //get the selected type
  if (f4n_searchForObjectType == null) var typeName = null;
  else var typeName = f4n_searchForObjectType.innerHTML;

  agent.call(f4n_serverURL, "getObjectsBySearchForFirstChar", "f4n_receivedSearchResponse", typeName, firstChar);
}

function f4n_receivedSearchResponse(objects){
  //remove still visible objects from the map and clear the objects array
  f4n_removeObjectsFromMap(f4n_objects);
  //clear the objects array
  f4n_objects = new Array();

  //create the search output
  for (var i = 0; i < objects.length; i++) f4n_objects[objects[i].id] = objects[i]; //store key=id, value=object

  //for admin mode do some more
  f4n_receivedSearchResponse_md(objects);

  f4n_showObjectsOnMap(objects);
}

function f4n_receivedSearchResponse_md(objects){
  //first remove already existing search result rows
  var srrc = document.getElementById("f4n_searchResultRowContainer");
  if (window.event || window.event === null) srrc.innerText = "";  //for IE
  else srrc.innerHTML = "";

  var setTrOnClick = function (tr, o){
    tr.onclick = function (){
      f4n_alertObject(o); //make object more visible
      f4n_goto(o.zoomLevelNo, o.top, o.left);
      window.location.href = "#f4n_mapAnchor";
    };
  };

  //now add new one
  for (var i = 0; i < objects.length; i++){
    var o = objects[i];
    var tr = document.createElement("tr");
    tr.vAlign = "top";
    tr.style.cursor = "pointer";
    setTrOnClick(tr, o);
    if (i % 2 == 0) tr.className = "map_search";
    var tdt = document.createElement("td");
    tdt.innerHTML = o.typeName;
    var tda = document.createElement("td");
    if (o.link != null){
      var a = document.createElement("a");
      a.className = "map_search_link_ext";
      a.href = "http://" + o.link; a.innerHTML = o.name;
      tda.appendChild(a);
    }
    else tda.innerHTML = o.name;
    var tdd = document.createElement("td");
    tdd.setAttribute("colSpan",2);
    if (o.desc == null) tdd.innerHTML = " ";
    else tdd.innerHTML = o.desc;

    tr.appendChild(tdt);
    tr.appendChild(tda);
    tr.appendChild(tdd);
    srrc.appendChild(tr);
  }
}

var f4n_alertIntervalId;
var f4n_currentAlertedObject;
var f4n_oldOnclick; //exists because IE seams to have problems in saving arbitrary data to an object

function f4n_alertObject(object){
  if (f4n_currentAlertedObject) f4n_clearAlertMode();
  f4n_currentAlertedObject = object;
  f4n_alertIntervalId = window.setInterval("f4n_alertObjectHandler()", 500);
  var omo = object.onMapObject;
  var dl = f4n_onMapSymbolSize.w * (f4n_onAlertSymbolSizeFactor - 1);
  var dt = omo.height * (f4n_onAlertSymbolSizeFactor - 1);
  omo.style.left = (parseInt(omo.style.left) - 0.5 * dl) + "px";
  omo.style.top = (parseInt(omo.style.top) - 0.5 * dt) + "px";
  omo.style.zIndex = "999";
  f4n_oldOnclick = object.onMapObject.onclick;
  omo.onclick = f4n_clearAlertMode;
  omo.width = f4n_onMapSymbolSize.w * f4n_onAlertSymbolSizeFactor;
  omo.height = omo.height * f4n_onAlertSymbolSizeFactor;
}

function f4n_clearAlertMode(){
  var o = f4n_currentAlertedObject;
  var omo = o.onMapObject;
  var dl = f4n_onMapSymbolSize.w * (f4n_onAlertSymbolSizeFactor - 1);
  var dt = (omo.OriginHeight * (f4n_onMapSymbolSize.w / omo.OriginWidth)) * (f4n_onAlertSymbolSizeFactor - 1);
  omo.style.left = (parseInt(omo.style.left) + 0.5 * dl) + "px";
  omo.style.top = (parseInt(omo.style.top) + 0.5 * dt) + "px";
  omo.style.zIndex = "10";
  omo.onclick = f4n_oldOnclick; f4n_oldOnclick = null;
  omo.src = f4n_pathToRoot + "symbols/" + o.symbolName;
  omo.width = f4n_onMapSymbolSize.w;
  omo.height = (omo.OriginHeight * (f4n_onMapSymbolSize.w / omo.OriginWidth));
  window.clearInterval(f4n_alertIntervalId);
  f4n_currentAlertedObject = null;
}

f4n_alertObjectHandler.alternate = false;
function f4n_alertObjectHandler(){
  var o = f4n_currentAlertedObject;
  f4n_alertObjectHandler.alternate = !f4n_alertObjectHandler.alternate;
  if (f4n_alertObjectHandler.alternate) o.onMapObject.src = f4n_pathToRoot + "symbols/" + o.alertSymbolName;
  else o.onMapObject.src = f4n_pathToRoot + "symbols/" + o.symbolName;
}