/**
 * DenkXweb, Version 1.0.0
 * Copyright (c) 2005 rjm business solutions GmbH
 * All rights reserved, license restrictions apply
 *
 * $Id$
 *
 * Hover-Effekt für die Kartenanzeige
 */



/*
 * Browser Hacks:
 * 
 * #1  IE 5.2 unter MacOS 10.3 positioniert das Tooltip nicht korrekt neu: Bei 
 *     zu schnellen Mausbewegungen werden Teile des darunterliegenden Bildes 
 *     nicht neu gezeichnet
 * #2  Opera >= 7.0 stellt den Tooltip-Schatten falsch dar, da die CSS-
 *     Positionsangaben nicht korrekt verarbeitet werden; daher Spezial-
 *     Fallunterscheidung mit opera-spezifischen CSS-Angaben
 *
 * Bekannte Inkompatibilitäten:
 *  * Opera < 7.0: Skript wird nicht ausgeführt, da DOM nicht oder inkompatibel
 *    implementiert
 */


/**
 * Class Tooltip
 *
 * Tooltip-Fenster, das Informationen über einen klickbaren Bereich anzeigt
 * 
 * @attr visible true, wenn das Tooltip-Fenster angezeigt wird, false sonst
 * @attr desc    Beschreibung des klickbaren Bereichs (z.B. Gebäudename)
 * @attr imgSrc  Bildquelle (komplette URI) für ein Thumbnail-Bild, das unter
 *               der Beschreibung angezeigt werden soll, oder undefined wenn
 *               kein Bild angezeigt werden soll
 * @attr div     div-HTML-Element, über das die Tooltip-Anzeige realisiert 
 *               wird; enthält den kompletten HTML-Code, um Beschreibung samt
 *               optionalem Bild zu rendern
 */

/**
 * Konstruktor für "Tooltip": Legt ein neues Tooltip-Fenster an, das jedoch
 * zunächst nicht angezeigt wird.
 * 
 * @param desc   Beschreibung des klickbaren Bereichs
 * @param imgSrc URI für die Bildquelle des Thumbnail-Bilds (optional)
 */
function Tooltip(desc, imgSrc) {
  this.visible = false;
  this.desc = desc;
  if (imgSrc) 
    this.imgSrc = imgSrc;


  /**
   * Legt das div-HTML-Element an, um dieses Tooltip zu rendern.
   * 
   * browser hack #2: für Opera >= 7.0 wird eine Extrawurst erzeugt, da
   * sonst entweder der Tooltip-Schatten oder der Tooltip-Rahmen nicht korrekt
   * gerendert wird (s. Hilfsfunktion initDivWithDiv) 
   */
  this.initDiv = function() {
    shadowWidth = 4;           // Schattenbreite 4 pixel
    shadowColor = '#6d6d6d';    // dunkles Grau
    shadowTransparency = 50;
    desc = this.desc;
    if (this.imgSrc)
      imgSrc = this.imgSrc;


    // Inhalt mit soft hyphens versehen, um möglichst kleine Tooltips
    // zu erhalten
    function addDescNode(owner, descText, splitChar1, splitChar2) {
      if (descText.split) {
        var descParts = descText.split(splitChar1);

        for (var i=0; i < descParts.length; i++) {
          var descPart = descParts[i];
          if (i < descParts.length-1)
            descPart = descPart+splitChar1;

          if (splitChar2) 
            addDescNode(owner, descPart, splitChar2);
          else 
            cell.appendChild(document.createTextNode(descPart));
          if (i < descParts.length-1)
            createChild(cell, 'wbr');
        }
      } else {
        cell.appendChild(document.createTextNode(desc));
      }
    }

    // HTML-Code erzeugen, um den eigentlichen Tooltip-Inhalt (Beschreibung
    // und evtl. Bild) zu rendern
    //
    function initContent(owner) {
      // Innere Tabelle zum Arrangement von Überschrift und Thumbnail
      var contentTable = createChild(owner, 'table');
      
      contentTable.cellPadding = 4;
      contentTable.cellSpacing = 4;
      
      var contentBody = createChild(contentTable, 'tbody');
      var row = createChild(contentBody, 'tr');
      cell = createChild(row, 'td');
      cell.style.fontFamily = 'Arial, Helvetica';
      cell.style.fontSize = '8pt';

      addDescNode(cell, desc, '-', '/');

      if (imgSrc) {
        row = createChild(contentBody, 'tr');
        cell = createChild(row, 'td');
        var img = createChild(cell, 'img');
        img.src = imgSrc;
      }
    }


    // Tooltip mit Hilfe von verschachtelten Tabellen aufbauen. Diese 
    // Vorgehensweise erlaubt es, den Schatten als Tabellen-Zellen-Hintergrund
    // zu realisieren, der wiederum getrennt vom eigentlichen Tooltip-Inhalt
    // mit Transparanz versehen werden kann.
    //
    // Wird korrekt gerendert von:
    //  - Firefox 0.9.3 (Windows)
    //  - Firefox 1.0.3 (Linux, MacOS X)
    //  - Mozilla 1.7 (Linux, Windows)
    //  - Internet Explorer 5.00 (Win 2K)
    //  - Internet Explorer 6.0 (Win 2K, Win XP)
    //
    // Wird *nicht* korrekt gerendert von:
    //  - Internet Explorer 5.2 für MacOS X (Höhe und Breite des Schattens
    //    rechts und unten falsch, Transparenz fehlt)
    //  - Konqueror 3.3.2 (Transparenz fehlt)
    //  - Safari 1.0, 1.2 (Höhe des Schattens rechts falsch, Transparenz fehlt)
    //  - Mozilla 1.0, 1.2 (Transparenz fehlt)
    //  - Mozilla 1.3-1.6 (Höhe und Breite des Schattens rechts und unten 
    //    falsch)
    //  - Opera 7.02, 7.10, 7.20, 8.0 (Höhe und Breite des Schattens falsch)
    //  
    function initDivWithTable(owner) {
      var table = createChild(owner, 'table');
      table.cellPadding = 0;
      table.cellSpacing = 0;

      layoutBody = createChild(table, 'tbody');
      var contentRow = createChild(layoutBody, 'tr');
      
      var cell = createChild(contentRow, 'td');
      cell.bgColor = '#fffee8';
      cell.style.backgroundColor = '#fffee8';
      cell.style.border = '1px solid black';
      
      // Innere Tabelle zum Arrangement von Überschrift und Thumbnail
      initContent(cell);
      // fertig mit innerer Tabelle
      
      // Tabelle rechts außen für Schatten
      cell = createChild(contentRow, 'td');
      cell.height = '100%';
      
      table = createChild(cell, 'table');
      table.cellPadding = 0;
      table.cellSpacing = 0;
      table.style.height = '100%';
      
      var shadowBody = createChild(table, 'tbody');
      row = createChild(shadowBody, 'tr');
      cell = createChild(row, 'td');
      img = createChild(cell, 'img');
      img.src = stockImgDir + 'clearpixel.gif';
      img.width = shadowWidth;
      img.height = shadowWidth;
      
      row = createChild(shadowBody, 'tr');
      row.style.height = '100%';
      cell = createChild(row, 'td');
      cell.style.backgroundColor = shadowColor;
      cell.bgColor = shadowColor;
      setTransparency(cell, shadowTransparency);
      img = createChild(cell, 'img');
      img.src = stockImgDir + 'clearpixel.gif';
      // fertig mit Tabelle rechts außen

      // Tabelle unten für Schatten
      row = createChild(layoutBody, 'tr');
      cell = createChild(row, 'td');
      cell.colSpan = 2;
      
      table = createChild(cell, 'table');
      table.cellPadding = 0;
      table.cellSpacing = 0;
      table.width = '100%';
      
      shadowBody = createChild(table, 'tbody');
      row = createChild(shadowBody, 'tr');
      cell = createChild(row, 'td');
      img = createChild(cell, 'img');
      img.src = stockImgDir + 'clearpixel.gif';
      img.width = shadowWidth;
      img.height = shadowWidth;
      
      cell = createChild(row, 'td');
      cell.style.backgroundColor = shadowColor;
      cell.bgColor = shadowColor;
      cell.width = '100%';
      setTransparency(cell, shadowTransparency);
      
      img = createChild(cell, 'img');
      img.src = stockImgDir + '/clearpixel.gif';
      // fertig mit Tabelle unten
    }


    // Tooltip über ein zum Inhalt relativ verschobenen <div>s mit Schatten
    // versehen. Damit ist es nicht mehr möglich, den Schatten transparent
    // darzustellen, da sonst das gesamte Tooltip transparent dargestellt 
    // wird.
    //
    // Wird korrekt gerendert von:
    //  - Firefox 0.9.3 (Windows)
    //  - Firefox 1.0.3 (Linux, MacOS X)
    //  - Internet Explorer 5.00 (Win 2K)
    //  - Internet Explorer 6.0 (Win 2K, Win XP)
    //  - Internet Explorer 5.2 für MacOS X
    //  - Konqueror 3.3.2
    //  - Safari 1.0, 1.2
    // 
    // Wird aufgrund browser detection korrekt gerendet von: 
    //  - Opera 7.20 (s. Browser Hack #2)
    //  
    function initDivWithDiv(owner) {
      // Tabelle um das div legen, um zu verhindern, dass im IE 5.2 für Mac 
      // das div die gesamte Dokumentbreite erbt
      var wrapperTable = createChild(owner, 'table');
      var wrapperCell = createChild(createChild(createChild(wrapperTable, 'tbody'), 'tr'), 'td');
      
      var shadow = createChild(wrapperCell, 'div');
      var shadowPx = shadowWidth + 'px';
      shadow.style.backgroundColor = '#bbbbbb';
      // browser hack #2
      if (isOpera) {
        shadow.style.paddingTop = shadowPx;
        shadow.style.paddingLeft = shadowPx;
        shadow.style.margin = shadowPx + ' -'+shadowPx + ' -'+shadowPx + ' ' + shadowPx;
      }
      
      var content = createChild(shadow, 'div');
      with (content.style) {
        backgroundColor = '#fffee8';
        border = '1px solid black';
        position = 'relative';
        // browser hack #2
        if (isOpera) {
          left = '-' + (shadowWidth * 2) + 'px';
          top = '-' + (shadowWidth * 2) + 'px';
        } else {
          bottom = shadowPx;
          right = shadowPx;
        }
      }

      initContent(content);
      // fertig mit innerer Tabelle
    }
     

    // weiter mit Tooltip.initDiv()
    //
    this.div = document.createElement('div');
    with (this.div) {
      style.maxWidth = '90px';
      style.position = 'absolute';
      style.width = '90px'; /* fuer Internet Explorer */
      
      style.display = 'none';
      style.visibility = 'hidden';

      // Art der Tooltip-Erzeugung in Abhängigkeit von
      // Transparenz-Unterstützung wählen: Die Methode mit
      // verschachtelten Tabellen ist nur sinnvoll, wenn Transparenz
      // überhaupt unterstützt wird
      if ((isGecko && (gecko_version >= 20040616)) ||  // Mozilla >= 1.7, Firefox >= 0.8
          (isIE && !isIEMac && (typeof(style.filter) != 'undefined')))  // IE/Win >= 5.0
        initDivWithTable(this.div);
      else
        initDivWithDiv(this.div);
    }

    document.body.appendChild(this.div);
  }

  /**
   * Gibt das div-HTML-Element wieder frei, das zum Rendern dieses Tooltips
   * verwendet wurde (falls eines bereits verwendet wurde.)
   */
  this.destroyDiv = function() {
      if (this.div) {
	  document.body.removeChild(this.div);
	  this.div = null;
      }
  }

  /**
   * Zeigt dieses Tooltip auf der aktuellen HTML-Seite an.
   * 
   * @param  pos  Position vom Typ Point, an der das Tooltip-Fenster angezeigt
   *              werden soll (absolut vom HTML-Seitenanfang gerechnet)
   */
  this.show = function(pos) {
    if (! this.div)
      this.initDiv();

    with (this.div) {
      style.left = pos.x + 'px';
      style.top = pos.y + 'px';
    
      if (! this.visible) {
        // unhide the tooltip
        style.display = '';
        style.visibility = 'visible';
      }
    }
    this.visible = true;
  }

  /**
   * Versteckt dieses Tooltip wieder, falls es auf der aktuellen HTML-Seite
   * angezeigt wird.
   * 
   * browser hack #1: Beim IE/Mac muss das Tooltip-Element auch gleich 
   * freigegeben werden, da sonst bei einer Neupositionierung über 
   * Tooltip.show() der Tooltip-Rahmen "verschmiert"
   */
  this.hide = function() {
    if (this.visible) {
      this.div.style.display = 'none';
      this.div.style.visibility = 'hidden';

      this.visible = false;

      // browser hack #1
      if (isIEMac) 
	  this.destroyDiv();

    }
  }
}

/**
 * Class Area
 *
 * Klickbarer Bereich auf der Karte
 *
 * @attr link     URI, die bei Klick in den Bereich geladen werden soll
 * @attr tooltip  Objekt vom Typ Tooltip zum Anzeigen eines Hinweisfensters
 *                beim Überfahren des klickbaren Bereichs
 * @attr poly     Objekt vom Typ Polygon zur Definition des klickbaren
 *                Bereichs
 */

/**
 * Konstruktor für "Area": Legt einen neuen klickbaren Bereich an
 * 
 * @param desc        Beschreibung des klickbaren Bereichs
 * @param imgSrc      URI für die Bildquelle des Thumbnail-Bilds, oder "null"
 *                    falls keines verfügbar ist
 * @param link        URI, die beim Klick in den Bereich geladen werden soll
 * @param outerRing   Äußere Begrenzung des klickbaren Bereichs (als 
 *                    Koordinaten-Array bezogen auf die darunterliegende 
 *                    Karte)
 * @param innerRings  Innere Begrenzugen (als Array von Koordinaten-Arrays, 
 *                    optional) zur Definition von "Löchern" in der äußeren
 *                    Begrenzung
 */
function Area(desc, imgSrc, link, outerRing, innerRings) {
  this.link = link;
  this.tooltip = new Tooltip(desc, imgSrc);
  this.poly = new Polygon(outerRing, innerRings);


  /**
   * Überprüft, ob sich dieser Bereich unter der angegebenen Position 
   * befindet.
   * 
   * @param  mapPos  Position vom Typ "Point" relativ zum Kartenursprung
   * @return true  wenn sich mapPos innerhalb der Bereichsbegrenzungen
   *         befindet, false sonst
   */
  this.test = function(mapPos) {
    return this.poly.contains(mapPos);
  }

  /**
   * Zeigt das Hinweisfenster bzgl. dieses Bereichs an der angegebenen 
   * Position an
   * 
   * @param mousePos  Position vom Typ "Point" für das Hinweisfenster 
   *        relativ zum HTML-Seitenursprung
   */
  this.showTooltip = function(mousePos) {
    // verhindern, dass das Tooltip vom Mauszeiger verdeckt wird
    mousePos.translate(10, 10);  

    // browser hack #1
    if (isIEMac)
      this.tooltip.hide();

    this.tooltip.show(mousePos);
    if (isIE && !isIEMac) {
      this.tooltip.hide();
      this.tooltip.show(mousePos);
    }
  }

  /**
   * Löscht das Tooltip von der Seitenanzeige und gibt damit verbundene
   * Ressourcen frei
   */
  this.destroyTooltip = function() {
    this.tooltip.hide();
    this.tooltip.destroyDiv();
  }
  
  /**
   * Schaltet die aktuell angezeigte HTML-Seite auf den Link um, der
   * diesem klickbaren Bereich zugewiesen wurde
   */
  this.redirect = function() {
    window.location.href = link;
  }

}


/**
 * Class AreaManager
 *
 * Factory-Klasse zum Verwalten der klickbaren Bereiche auf einer Karte
 *
 * @attr map       Objekt vom Typ "Map", das die hinter den klickbaren
 *                 Bereichen liegende Karte kapselt
 * @attr areas     Array von Objekten vom Typ Area, die alle klickbaren
 *                 Bereiche repräsentieren
 * @attr currArea  der zuletzt mit der Maus überfahrene klickbare Bereich,
 *                 oder null wenn kein Bereich zuvor überfahren wurde
 */


/**
 * Konstruktor für "MapManager": Initialisiert die Factory-Klasse zum 
 * Verwalten der klickbaren Bereiche
 *
 * @param map  Objekt vom Typ "Map" als Repräsentation der hinter den
 *        klickbaren Bereichen liegende Karte
 */
function AreaManager(map) {
  this.map = map;
  this.areas = new Array();
  this.currArea = null;


  /**
   * Erzeugt einen neuen klickbaren Bereich.
   * 
   * @param desc        Beschreibung des klickbaren Bereichs
   * @param imgSrc      URI für die Bildquelle des Thumbnail-Bilds, oder "null"
   *                    falls keines verfügbar ist
   * @param link        URI, die beim Klick in den Bereich geladen werden soll
   * @param outerRing   Äußere Begrenzung des klickbaren Bereichs (als 
   *                    Koordinaten-Array bezogen auf die darunterliegende 
   *                    Karte)
   * @param innerRings  Innere Begrenzugen (als Array von Koordinaten-Arrays, 
   *                    optional) zur Definition von "Löchern" in der äußeren
   *                    Begrenzung
   */
  this.addArea = function(desc, imgSrc, link, outerRing, innerRings) {
    this.areas[this.areas.length] = new Area(desc, imgSrc, link, outerRing, 
                                             innerRings || []);
  }


  /**
   * Sucht einen klickbaren Bereich anhand der Mauszeiger-Position während des
   * letzten Mausereignisses.
   *
   * @param  mouseEvent  Objekt vom Typ MouseEvent zum Kapseln des letzten 
   *         Mausereignisses
   * @return ein klickbarer Bereich vom Typ Area oder null falls kein Bereich
   *         unterhalb der Mauszeiger-Position existiert
   */
  this.findArea = function(mouseEvent) {
    var result = null;

    if (this.map.isMouseOver(mouseEvent)) {
      var mapPos = this.map.getMapPos(mouseEvent);
      if (this.map.isPosOnMap(mapPos))
        for (var i = 0; i < this.areas.length; i++) {
          if (this.areas[i].test(mapPos)) {
            result = this.areas[i];
            break;
          }
        }
    }
    return result;
  }
  


  /**
   * Aktualisiert die Tooltip-Anzeige gem. der aktuellen Mausposition:
   * Befindet sich der Mauszeiger über einem klickbaren Bereich, wird das
   * damit verbundene Tooltip-Fenster angezeigt, ansonsten wird ein ggf.
   * angezeigtes Tooltip-Fenster von der Anzeige genommen.
   * 
   * @param mouseEvent  Objekt vom Typ "MouseEvent", das in Reaktion zur
   *        letzten Mausbewegung erzeugt wurde
   */
  this.hover = function(mouseEvent) {
    var newArea = this.findArea(mouseEvent);
    var mapImg = this.map.getImg();
          
    if (this.currArea && (this.currArea != newArea))
      this.hideTooltip();
    
    this.currArea = newArea; 

    if (this.currArea) {
      this.map.setImgCursor("pointer");
      this.currArea.showTooltip(mouseEvent.getMousePt());
    }
  }

  
  /**
   * Verweist auf eine neue HTML-Seite gem. des Links, der dem klickbaren
   * Bereich unterhalb der aktuellen Mausposition zugewiesen wurde.
   *
   * @param mouseEvent  Objekt vom Typ "MouseEvent", das in Reaktion zum
   *        letzten Loslassen einer Maustaste erzeugt wurde
   */
  this.redirect = function(mouseEvent) {
    var area = this.findArea(mouseEvent);
    if (area != null)
      area.redirect();
  }
    

  this.hideTooltip = function() {
    if (this.currArea) {
      this.map.setImgCursor("default");
      this.currArea.destroyTooltip();
    }
  }


}


