//*     cGmHelper() - jurk@webit.de
//*     cOverlay() - jurk@webit.de
//*     cCallbackHelper() - jurk@webit.de


    //* cGmHelper() ist eine Hilfsklasse zum laden und zeichnen von 
    //* Overlaytypen der Google Maps Api
    
    //~ oGmHelper = new cGmHelper(); 
    
    // * ODER optional mapvariable übergeben (map zb nicht global gesetzt ist)
    //~ oGmHelper = new cGmHelper({map:map}); 
    
    //* Laden und Zeichnen aus XML Dateien (icon, marker, polygon, polyline)
    //* optionaler Parameter mit callback Funktionen (als Array oder einzeln), da 
    //* das Laden asynchron erfolgt
    
    //~ oGmHelper.get_xml("./data.xml",['icon','polyline'],profil);
    
    //* oder aus JSON (Struktur entspeicht der XML)(siehe gmhelper.xml)
    
    //~ oGmHelper.load_from_options({
      //~ polyline:{
        //~ kml:'./test.kml',
        //~ color:'#ff0000',
        //~ info: {textContent:"Hallo Welt"}
        //~ outline:'1',
      //~ }
    //~ },profil);
    
    //* Zugriff auf die geladenen Elemente:
    
    //~ oGmHelper.icon['grün']  //* oder google.maps.Icon Object
    //~ oGmHelper.polyline[0] //* google.maps.Polygon Object
    //~ oGmHelper.polyline[0].__outline //* google.maps.Polygon Object
    //~ oGmHelper.polyline[0].__altitudes //* Array mit den Höhendaten
    
    //~ oGmHelper.toggle_polyline('radwege') //* alle Polylines der Gruppe "radwege" ein/ausblenden
    //~ oGmHelper.toggle_maker('bierstuben')
    
    //~ oGmHelper.fit_to_marker() //* Kartenansicht auf die aktuell geladenen marker setzen
    //~ fit_to_polyline() / fit_to_polygon() sind noch nicht implementiert
    
    //* Wird die Datei marker_cluster.js geladen und somit die Klasse cMarkerCluster erstellt werden die Icons(Marker) geclustert
    //* (zusammengefasst um die Kartenbelasung zu reduzieren).
    //* Das Icon mit dem Namen "clusterIcon" kann als Clustericon definiert werden.

function cGmHelper(options) {

  options = options || {};
  
  this.oMap = options.map || map;
  if(typeof this.oMap == 'undefined'){ throw ("cGmHelper(options) - Keine map Variable angegeben.");}
  
  // soll geclustert werden?
  if(typeof cMarkerCluster == 'function') {
    this._markerCluster = true;
    this._clusterOptions = options.clusterOptions ? options.clusterOptions : {};
  } else {
    this._markerCluster = false; 
    this._clusterOptions = {};
  }
  
  // ist CustomInfo geladen?
  if(typeof CustomInfo == 'function') { this.CustomInfo = true; CustomInfo(); }
  
  this.defaultCenter = options.defaultCenter ? new google.maps.LatLng(options.center[0],options.center[1]) : this.oMap.getCenter();
  this.defaultZoomLevel = options.defaultZoomLevel ? options.defaultZoomLevel : this.oMap.getZoom();
  
  this.oOverlay = new cOverlay(this.oMap.getContainer());
  
  //~ Dinge auf die zugegriffen werden kann, zb oGmHelper.icon['grün'] oder oGmHelper.polylines[0], oGmHelper.maker[0]
  this.polygon = [];
  this.polyline = [];
  this.marker = [];
  this.icon = {}; // assoziativ für einfacheren zugriff
  
  //~ wird getezt wenn xml / json ein "infos" objekt mit textinhalt enthält
  this.requestInfos = '';
  
  //~ Objekte ohne Namen bekommen eine fortlaufende ID , bz oGmHelper.icon['icon10']
  this._object_id = 0;
  
  // aus den verschiedenen Typen werden Gruppen gebildet (die in der legende erscheinen) (Attrib category)
  this.polygonCategories = {};
  this.polylineCategories = {};
  this.markerCategories = {};
  
  this.oCallbackHelper = new cCallbackHelper(function() { return false; });
  
  this.create_polygon = function(o) {
    var polylines = [];
    for (var l in o.polyline) {
      if(isNaN(l)){ continue; }
      polylines.push({
        color: o.strokeColor,
        weight: o.strokeWeight,
        opacity: o.strokeOpacity,
        points: o.polyline[l].points.textContent,
        levels: o.polyline[l].levels.textContent,
        zoomFactor: parseInt(o.polyline[l].zoomFactor),
        numLevels: parseInt(o.polyline[l].numLevels)
      });
    } // ende polgon linien
    
    var polygon = new google.maps.Polygon.fromEncoded({
      polylines: polylines,
      color: o.fillColor,
      opacity: o.fillOpacity,
      outline: parseInt(o.strokeWeight) ? true : false,
      fill: o.fillColor ? true : false
    });
    
    
    polygon.__category = o.category || 'default';
    
    this.polygon.push(polygon);
    
    if(typeof this.polygonCategories[polygon.__category] == "undefined") {
      this.polygonCategories[polygon.__category] = {
        color:polygon.color,
        outline:parseInt(o.strokeWeight) ? parseInt(o.strokeWeight) : null,
        outlinecolor:parseInt(o.strokeWeight) ? o.strokeColor : null,
        visible:o.hide != 'true',
        count: 1
      };
      if(o.hide == 'true'){ polygon.hide(); }
    } else{ 
      this.polygonCategories[polygon.__category].count++;
      if(!this.polygonCategories[polygon.__category].visible){ polygon.hide(); }
    }
    
    polygon.__visible = this.polygonCategories[polygon.__category].visible;
    
    this.oMap.addOverlay(polygon);
    
    return polygon;
  };
  
  this.create_icon = function(o) {
    var icon = new google.maps.Icon(G_DEFAULT_ICON);
    if(o.image) {
    
      icon.image = o.image;
      
      o.iconSize = o.iconSize.split('x');
      icon.iconSize = new google.maps.Size(parseInt(o.iconSize[0]),parseInt(o.iconSize[1]));
      
      o.iconAnchor = o.iconAnchor.split(',');
      icon.iconAnchor = new google.maps.Point(parseInt(o.iconAnchor[0]),parseInt(o.iconAnchor[1]));

      o.infoWindowAnchor = o.infoWindowAnchor.split(',');
      icon.infoWindowAnchor = new google.maps.Point(parseInt(o.infoWindowAnchor[0]),parseInt(o.infoWindowAnchor[1]));
      
      if(o.shadow !== '') {
        o.shadowSize = o.shadowSize.split('x');
        icon.shadow = o.shadow;
        icon.shadowSize = new google.maps.Size(parseInt(o.shadowSize[0]),parseInt(o.shadowSize[1]));
      }
      
    }
    this.icon[o.name || "icon"+this._object_id++] = icon;
    return icon;
  };
  
  this.create_marker = function(o) {
    var icon = this.icon;
    
    o.clickable = o.info ? true : false;
    o.latlng = o.latlng ? o.latlng.split(',') : [90,0]; // nordpol ;)
    
    var marker = new google.maps.Marker(
      new google.maps.LatLng(parseFloat(o.latlng[0]),parseFloat(o.latlng[1])),{
      icon: icon[o.icon || o.category || 'default'] || null,
      clickable: o.clickable,
      title: o.title || ''
      }
    );
    
    marker.__category = o.category || 'default';

    // gruppen erstellen (ist der erste marker der category nicht sichtbar werden alle marker auf nicht sichtbar gestellt)
    if(typeof this.markerCategories[marker.__category] == "undefined"){
      this.markerCategories[marker.__category] = {icon:icon[o.icon || o.category || 'default'], visible: o.hide != 'true', count:1};
      if(o.hide == 'true'){ marker.hide(); }
    } else {
      this.markerCategories[marker.__category].count++;
      if(!this.markerCategories[marker.__category].visible){ marker.hide(); }
    }
      
    marker.__visible = this.markerCategories[marker.__category].visible;

    if(o.clickable) { // wenn clickabel dann auch html da...
      google.maps.Event.addListener(marker, 'click', function(){
        this.openInfoWindowHtml(o.info.textContent);
      });
    }

    if(o.mouseover && icon[o.mouseover]) { // wenn mouseover icon gesetzt ist dann event binden
      google.maps.Event.addListener(marker, 'mouseover',function(){
        this.setImage(icon[o.mouseover].image);
      });
      google.maps.Event.addListener(marker, 'mouseout',function(){
        this.setImage(icon[o.icon || o.category].image);
      });
    }
  
    // instanz archivieren
    this.marker.push(marker);
    
    if(!this._markerCluster) { this.oMap.addOverlay(marker); marker.__visible ? marker.show() : marker.hide(); }
    
    
    return marker;
  };
  
  this.create_polyline = function(o) {
  
    if(o.coordinates) { // wir haben uncodierte coordinaten gegeben
      var polyline = new google.maps.Polyline(
        o.coordinates.latlng, // latlngs
        (o.color || '#ff0000'), // farbe
        parseInt(o.weight || 2), // stärke
        parseFloat(o.opacity || 1) // deckkraft
      );
      polyline.__altitudes = o.coordinates.altitudes;
      
    } else {
      var polyline = new google.maps.Polyline.fromEncoded({
        color:o.color || '#ff0000',
        weight:parseInt(o.weight || 2),
        opacity:parseFloat(o.opacity || 1),
        points:(o.points ? o.points.textContent : ''), //* Das wird dann ein leeres Polylineobjekt
        levels:(o.levels ? o.levels.textContent : ''),
        zoomFactor:parseInt(o.zoomFactor),
        numLevels:parseInt(o.numLevels)
      });
      
      polyline.__altitudes = o.altitudes ? o.altitudes.textContent.split(',') : [];
      for(var k in polyline.__altitudes) {
        if(isNaN(k)){ continue; }
        polyline.__altitudes[k] = parseInt(polyline.__altitudes[k]); // erstmal nur ganze höhenmeter
      }
    }
    
    //~ // Kopie der linie erstellen für Umriss
    if(o.outline) {
      polyline.__outline = polyline.copy();
      polyline.__outline.color = o.outlinecolor || '#000';
      polyline.__outline.weight = parseInt(o.outline)*2+parseInt(o.weight || 2);
      polyline.__outline.opacity = parseFloat(o.opacity || 1);
    }

    var self = this;
    if(o.mouseover){
      google.maps.Event.addListener(polyline, "mouseover", function(){
        self.oMap.getDragObject().setDraggableCursor("pointer");
        polyline.color = o.mouseover;
        polyline.redraw(true); 
      });
      google.maps.Event.addListener(polyline, "mouseout", function(){
        self.oMap.getDragObject().setDraggableCursor(google.maps.DraggableObject.getDraggableCursor());
        polyline.color = o.color;
        polyline.redraw(true);
      });
    }
    
    if(o.info){
      google.maps.Event.addListener(polyline, "click", function(point){
        self.oMap.openInfoWindowHtml(point,o.info.textContent);
      });
    }
    
    // gruppen erstellen
    // legendendarstellung entspricht den eigenschaften der ersten polylinie einer category
    polyline.__category = o.category || 'default';
    
    if(typeof this.polylineCategories[polyline.__category] == "undefined"){
      this.polylineCategories[polyline.__category] = {
        weight:polyline.weight,
        color:polyline.color,
        outline:polyline.__outline ? o.outline:null,
        outlinecolor:polyline.__outline ? polyline.__outline.color : null,
        visible:o.hide != 'true',
        count:1
      };
      if(o.hide == 'true'){ if(polyline.__outline){ polyline.__outline.hide(); } polyline.hide(); }
    } else {
      this.polylineCategories[polyline.__category].count++ ;
      if(!this.polylineCategories[polyline.__category].visible) { if(polyline.__outline){ polyline.__outline.hide(); } polyline.hide(); }
    }
    
    polyline.__visible = this.polylineCategories[polyline.__category].visible;
    
    // linie Zeichnen
    if(polyline.__outline){ this.oMap.addOverlay(polyline.__outline); }
    this.oMap.addOverlay(polyline);
    
    this.polyline.push(polyline);
    return polyline;
  };
  
  this.get_xml = function(url,load,callback) {
  
    //this.oCallbackHelper = new cCallbackHelper(callback);
    this.oCallbackHelper.add(callback);

    this.oOverlay.set("Lade Kartendaten");
    this.oCallbackHelper.add("clear",this.oOverlay);

    try{
      //~ xml laden und die entsprechen knoten anzeigen
      this.oCallbackHelper.trigger();
      var self = this;
      google.maps.DownloadUrl(url,function(data){
      
        self.call_functions(data.xml2json(),load);
        //~ zusätzlich angegebene funktionen ausführen Object übergeben
        self.oCallbackHelper.check();
      });
    }catch(e) {
      google.maps.Log.write(e);
      this.oOverlay.clear();
    }
  };
  
  this.load_from_options = function(json,callback) {

    //this.oCallbackHelper = new cCallbackHelper(callback);
    this.oCallbackHelper.add(callback);
    this.oCallbackHelper.add("clear",this.oOverlay);
    this.oOverlay.set("Lade Kartendaten");
    
    this.oCallbackHelper.trigger();
    
    this.call_functions(json);
    
    this.oCallbackHelper.check();
    
  };
  
  this.call_functions = function(json,load) {
    load = load || ['polygon','polyline','icon','marker'];
    for (var l in load) {
      if(isNaN(l)){ continue; }
      var caller = load[l];
      if(json[caller]) { this['remove_'+caller](); }
      if(json[caller] instanceof Array) {
        //~ mehrere Objekte
        for(var v in json[caller]) {
          if(isNaN(v)){ continue; }
          if(json[caller][v].kml  && caller == 'polyline'){ this.load_kml(json[caller][v],caller); }
          else { this['create_'+caller](json[caller][v]); }
        }
      } else if (json[caller]) {
        //~ ein Objekt zu Zeichnen
        if(json[caller].kml && caller == 'polyline'){ this.load_kml(json[caller],caller); } // erstmal nur für polyline implementiert (da )
        else{ this['create_'+caller](json[caller]); }
      }
      
      if(caller == 'marker' && this._markerCluster) {
        this._clusterOptions.helper = this;
        new cMarkerCluster(this._clusterOptions);
      }
      if(json['infos']){ this.requestInfos = json['infos'].textContent; }
    }
  };
  
  this.load_kml = function(json,caller) {
    this.oCallbackHelper.trigger();
    var self = this;
    google.maps.DownloadUrl(json.kml,function(kml){
      try {
        json.coordinates = self["parse_kml_"+caller](kml);
        self['create_'+caller](json);
      } catch(e) {
        google.maps.Log.write(e);
      }
      self.oCallbackHelper.check();
    });
  };
  
  this.toggle_polyline = function(category) {
  category = category || 'default';
  this.polylineCategories[category].visible = this.polylineCategories[category].visible ? false : true;
    for(var p in this.polyline) {
      if(isNaN(p)){ continue; }
      if(this.polyline[p].__category == category) {
        if (!this.polyline[p].__visible) {
          if(this.polyline[p].__outline){ this.polyline[p].__outline.show(); }
          this.polyline[p].show();
          this.polyline[p].__visible = true;
        } else {
          if(this.polyline[p].__outline){ this.polyline[p].__outline.hide(); }
          this.polyline[p].hide();
          this.polyline[p].__visible = false;
        }
      }
    }
    this.oMap.closeInfoWindow();
  };
  
  this.remove_polyline = function() {
    for (var p in this.polyline) {
      if(isNaN(p)){ continue; }
      this.oMap.removeOverlay(this.polygon[p]);
      google.maps.Event.clearInstanceListeners(this.polygon[p]);
      this.polygonCategories[this.polygon[p].__category].count--;
    }
    this.polyline = [];
    this.oMap.closeInfoWindow();
  };
  
  this.toggle_marker = function(category) {
    category = category || 'default';
    this.markerCategories[category].visible = this.markerCategories[category].visible ? false : true;
    for (var m in this.marker) {
      if(isNaN(m)){ continue; }
      if (this.marker[m].__category == category) {
        this.marker[m].__visible = this.marker[m].__visible ? false : true;
        if (!this._markerCluster){
          this.marker[m].__visible ? this.marker[m].show() : this.marker[m].hide();
        }
      }
    }
    if (this._markerCluster){ google.maps.Event.trigger(this.oMap, '__refresh_cluster'); }
    this.oMap.closeInfoWindow();
  };
  
  this.remove_marker = function() {
    for (var m in this.marker) {
      if(isNaN(m)){ continue; }
      this.oMap.removeOverlay(this.marker[m]);
      this.markerCategories[this.marker[m].__category].count--;
      google.maps.Event.clearInstanceListeners(this.marker[m]);
    }
    if(this._markerCluster){ google.maps.Event.trigger(this.oMap, '__destroy_cluster'); }
    this.marker = [];
    this.oMap.closeInfoWindow();
  };
  
  this.fit_to_marker=function(){
    var bounds=new google.maps.LatLngBounds();
    var count = 0;
    for(var m in this.marker) {
      if(isNaN(m)){ continue; }
      if(this.marker[m].__visible) {bounds.extend(this.marker[m].getLatLng()); count++;}
    }
    var zoomLevel=this.oMap.getBoundsZoomLevel(bounds);
    if(count > 0) { //nur anpassen wenn auch marker vorhanden sind
      this.oMap.setCenter(bounds.getCenter(), zoomLevel);
    } else {
      this.oMap.setCenter(this.defaultCenter, this.defaultZoomLevel);
    }
    if (this._markerCluster){ google.maps.Event.trigger(this.oMap, '__refresh_cluster'); }
  };
  
  this.remove_icon = function() { this.remove_marker(); this.icon = {}; };
  
  this.toggle_polygon = function(category) {
    category = category || 'default';
    this.polygonCategories[category].visible = this.polygonCategories[category].visible ? false : true;
    
    for(var p in this.polygon) {
      if(isNaN(p)){ continue; }
      if(this.polygon[p].__category == category) {
        if (!this.polygon[p].__visible) { this.polygon[p].show(); this.polygon[p].__visible = true; }
        else {this.polygon[p].hide(); this.polygon[p].__visible = false;}
      }
    }
    this.oMap.closeInfoWindow();
  };
  
  this.remove_polygon = function() {
    for (var p in this.polygon) {
      if(isNaN(p)){ continue; }
      this.oMap.removeOverlay(this.polygon[p]);
      google.maps.Event.clearInstanceListeners(this.polygon[p]);
      this.polygonCategories[this.polygon[p].__category].count--;
    }
    this.polygon = [];
    this.oMap.closeInfoWindow();
  };
  
  this.parse_kml_polyline = function(data) {
    var xml = google.maps.Xml.parse(data);
    
    if(!xml.documentElement.nodeName == 'kml'){ throw("cGmHelper() - Fehlerhafte KML"); }
    
    // die erst besten coordinaten holen, mehr wird nicht supportet ;)
    var coord_element  = xml.documentElement.getElementsByTagName('coordinates')[0];
    if(!coord_element){ throw("cGmHelper() - Fehlerhafte KML"); }
    var coord_string = "";
    for (var n = 0; n < coord_element.childNodes.length; n++) {
      coord_string += coord_element.childNodes[n].nodeType == 3 ? coord_element.childNodes[n].nodeValue : '';
    }
    var coord_array = coord_string.replace(/\n/g,' ').split(' ');
    var coordinates = {latlng:[],altitudes:[]};
    for(var c in coord_array) {
      if(isNaN(c)){ continue; }
      var values = coord_array[c].split(',');
      coordinates.latlng.push(new google.maps.LatLng(values[1],values[0]));
      coordinates.altitudes.push(parseInt(values[2]));
    }
    return coordinates;
  };

}

//* http://www.acme.com/javascript/OverlayMessage.js
function cOverlay(container) {

  this.container = container;
  if(typeof this.container == 'undefined'){ throw ("cOverlay(container) - Keine container variable angegeben."); }
  
  
  this.set = function (message){
    this.overlay.innerHTML = message;
    this.overlay.style.display = '';
  };
  
  this.clear = function(){
    this.overlay.style.display = 'none';
  };
  
  this._getStyle = function (styleProp) {
    if (this.container.currentStyle) {
      var y = this.container.currentStyle[styleProp];
    } else if (window.getComputedStyle) {
      var y = document.defaultView.getComputedStyle(this.container,null).getPropertyValue(styleProp);
    }
    return y;
  };
  
  this._init = function(){
    var parent = this.container.parentNode;
    var wrapper = document.createElement('div');
    wrapper.style.width = this._getStyle('width');
    wrapper.style.height = this._getStyle('height');
    parent.insertBefore(wrapper,this.container);
    parent.removeChild(this.container);
    wrapper.appendChild(this.container);
    this.container.style.cssText = 'position: relative; width: 100%; height: 100%;';
    this.overlay = document.createElement('div');

    var options = {border:'1px solid #222',backgroundColor:'#eee',display:'none',position:'relative',top:'-55%',width:'40%',textAlign:'center',marginLeft:'auto',marginRight:'auto',padding:'2em',zIndex:'100',opacity:'.7',filter:'alpha(opacity=75)'};
    for(var k in options){this.overlay.style[k] = options[k];}
    wrapper.appendChild(this.overlay);
  };
  
  this._init();
  
}

//* parsed mittels Browser-DOM XML und baut ein einfaches JSON Objekt
String.prototype.xml2json = function(){
  var xml = google.maps.Xml.parse(this);
  
  if(xml.documentElement.nodeName == 'parsererror'){ return {}; }
  
  var parse = function(xml,json) {
    for (var j = 0; j < xml.attributes.length; j++) {
      var attribut = xml.attributes[j];
      if(typeof json[attribut.nodeName] == 'undefined') { json[attribut.nodeName] = attribut.nodeValue; }
    }
    for (var i = 0; i < xml.childNodes.length; i++) {
      var child = xml.childNodes[i];
      switch(child.nodeType) {
        case 1: // normaler tag
          if(typeof json[child.nodeName] == 'undefined') {
            json[child.nodeName] = {};
            json[child.nodeName] = parse(child,json[child.nodeName]);
          } else {
            if(!(json[child.nodeName] instanceof Array)) { json[child.nodeName] = [json[child.nodeName]]; }
            var last = json[child.nodeName].push({})-1;
            json[child.nodeName][last] = parse(child,json[child.nodeName][last]);
          }
          break;
        case 4: // CDATA
          json.textContent = child.nodeValue;
          break;
      }
    }
    return json;
  };
  return parse(xml.documentElement,{});
};


// einfacher trigger um asynchrone aufrufe zu verarbeiten
function cCallbackHelper(callback,object){
  
  this._trigger= 0;
  this._callback = [];
  
  this.add = function(callback,object) {
    if(callback instanceof Array) {
      for(var c in callback) {
        if(isNaN(c)){ continue; }
        this._callback.push({callback:callback[c]});
      }
    } else if(callback instanceof Function){  this._callback.push({callback:callback}); }
    else if(object instanceof Object){ this._callback.push({callback:callback,object:object}); }
  };
  
  this.check = function() {
    this._trigger--;
    if(this._trigger <= 0) {
      for(var func in this._callback) {
        if(isNaN(func)){ continue; }
        try {
          if(this._callback[func].object){ this._callback[func].object[this._callback[func].callback](); }
          else{ this._callback[func].callback(); }
        } catch (e) {
          google.maps.Log.write("Fehler beim ausführen der Funktion: "+this._callback[func].callback.toString().replace(/\n/g,'').replace(/^function (\w+)\(\) \{.*$/g,'$1')+"() Fehlermeldung:  "+e);
        }
      }
    }
  };
  
  this.trigger =  function() { this._trigger++; };
  
  this.add(callback,object);

}


