/*
    geoXML3.js
    Renders KML on the Google Maps JavaScript API Version 3
    http://code.google.com/p/geoxml3/
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see .
*/
// Extend the global String with a method to remove leading and trailing whitespace
if (!String.prototype.trim) {
  String.prototype.trim = function () {
    return this.replace(/^\s+|\s+$/g, '');
  };
}
// Declare namespace
geoXML3 = window.geoXML3 || {};
// Constructor for the root KML parser object
geoXML3.parser = function (options) {
  // Private variables
  var parserOptions = geoXML3.combineOptions(options, {
    singleInfoWindow: false,
    processStyles: true,
    zoom: true
  });
  var docs = []; // Individual KML documents
  var lastMarker;
  // Private methods
  var parse = function (urls) {
    // Process one or more KML documents
    if (typeof urls === 'string') {
      // Single KML document
      urls = [urls];
    }
    // Internal values for the set of documents as a whole
    var internals = {
      docSet: [],
      remaining: urls.length,
      parserOnly: !parserOptions.afterParse
    };
    var thisDoc;
    for (var i = 0; i < urls.length; i++) {
      thisDoc = {
        url: urls[i],
        internals: internals
      };
      internals.docSet.push(thisDoc);
      geoXML3.fetchXML(thisDoc.url, function (responseXML) {render(responseXML, thisDoc);});
    }
  };
  var hideDocument = function (doc) {
    // Hide the map objects associated with a document
    var i;
    for (i = 0; i < doc.markers.length; i++) {
      this.markers[i].set_visible(false);
    }
    for (i = 0; i < doc.overlays.length; i++) {
      doc.overlays[i].setOpacity(0);
    }
  };
  var showDocument = function (doc) {
    // Show the map objects associated with a document
    var i;
    for (i = 0; i < doc.markers.length; i++) {
      doc.markers[i].set_visible(true);
    }
    for (i = 0; i < doc.overlays.length; i++) {
      doc.overlays[i].setOpacity(doc.overlays[i].percentOpacity_);
    }
  };
  var render = function (responseXML, doc) {
    // Callback for retrieving a KML document: parse the KML and display it on the map
    if (!responseXML) {
      // Error retrieving the data
      geoXML3.log('Unable to retrieve ' + doc.url);
      if (parserOptions.failedParse) {
        parserOptions.failedParse(doc);
      }
    } else if (!doc) {
      throw 'geoXML3 internal error: render called with null document';
    } else {
      doc.styles = {};
      doc.placemarks = [];
      doc.groundOverlays = [];
      if (parserOptions.zoom && !!parserOptions.map)
        doc.bounds = new google.maps.LatLngBounds();
      // Parse styles
      var styleID, iconNodes, i;
      var styleNodes = responseXML.getElementsByTagName('Style');
      for (i = 0; i < styleNodes.length; i++) {
        styleID   = styleNodes[i].getAttribute('id');
        iconNodes = styleNodes[i].getElementsByTagName('Icon');
        if (!!iconNodes.length) {
          doc.styles['#' + styleID] = {
            href: geoXML3.nodeValue(iconNodes[0].getElementsByTagName('href')[0])
          };
        }
      }
      if (!!parserOptions.processStyles || !parserOptions.createMarker) {
        // Convert parsed styles into GMaps equivalents
        processStyles(doc);
      }
      // Parse placemarks
      var placemark, node, coords, path;
      var placemarkNodes = responseXML.getElementsByTagName('Placemark');
      for (i = 0; i < placemarkNodes.length; i++) {
        // Init the placemark object
        node = placemarkNodes[i];
        placemark = {
          name:  geoXML3.nodeValue(node.getElementsByTagName('name')[0]),
          description: geoXML3.nodeValue(node.getElementsByTagName('description')[0]),
          styleUrl: geoXML3.nodeValue(node.getElementsByTagName('styleUrl')[0])
        };
        placemark.style = doc.styles[placemark.styleUrl] || {};
        if (/^https?:\/\//.test(placemark.description)) {
          placemark.description = '' + placemark.description + '';
        }
        // Extract the coordinates
        coords = geoXML3.nodeValue(node.getElementsByTagName('coordinates')[0]).trim();
        coords = coords.replace(/\s+/g, ' ').replace(/, /g, ',');
        path = coords.split(' ');
        // What sort of placemark?
        if (path.length === 1) {
          // Polygons/lines not supported in v3, so only plot markers
          coords = path[0].split(',');
          placemark.point = {
            lat: parseFloat(coords[1]),
            lng: parseFloat(coords[0]),
            alt: parseFloat(coords[2])
          };
          if (!!doc.bounds) {
            doc.bounds.extend(new google.maps.LatLng(placemark.point.lat, placemark.point.lng));
          }
          // Call the appropriate function to create the marker
          if (!!parserOptions.createMarker) {
            parserOptions.createMarker(placemark, doc);
          } else {
            createMarker(placemark, doc);
          }
        }
      }
      // Parse ground overlays
      var groundOverlay, color, transparency;
      var groundNodes = responseXML.getElementsByTagName('GroundOverlay');
      for (i = 0; i < groundNodes.length; i++) {
        node = groundNodes[i];
        // Init the ground overlay object
        groundOverlay = {
          name:        geoXML3.nodeValue(node.getElementsByTagName('name')[0]),
          description: geoXML3.nodeValue(node.getElementsByTagName('description')[0]),
          icon: {href: geoXML3.nodeValue(node.getElementsByTagName('href')[0])},
          latLonBox: {
            north: parseFloat(geoXML3.nodeValue(node.getElementsByTagName('north')[0])),
            east:  parseFloat(geoXML3.nodeValue(node.getElementsByTagName('east')[0])),
            south: parseFloat(geoXML3.nodeValue(node.getElementsByTagName('south')[0])),
            west:  parseFloat(geoXML3.nodeValue(node.getElementsByTagName('west')[0]))
          }
        };
        if (!!doc.bounds) {
          doc.bounds.union(new google.maps.LatLngBounds(
            new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
            new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
          ));
        }
        // Opacity is encoded in the color node
        color = geoXML3.nodeValue(node.getElementsByTagName('color')[0]);
        if ((color !== '') && (color.length == 8)) {
          transparency = parseInt(color.substring(0, 2), 16);
          groundOverlay.opacity = Math.round((255 - transparency) / 2.55);
        } else {
          groundOverlay.opacity = 100;
        }
        // Call the appropriate function to create the overlay
        if (!!parserOptions.createOverlay) {
          parserOptions.createOverlay(groundOverlay, doc);
        } else {
          createOverlay(groundOverlay, doc);
        }
      }
      if (!!doc.bounds) {
        doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds();
        doc.internals.bounds.union(doc.bounds);
      }
      if (!!doc.styles || !!doc.markers || !!doc.overlays) {
        doc.internals.parserOnly = false;
      }
      doc.internals.remaining -= 1;
      if (doc.internals.remaining === 0) {
        // We're done processing this set of KML documents
        // Options that get invoked after parsing completes
        if (!!doc.internals.bounds) {
          parserOptions.map.fitBounds(doc.internals.bounds);
        }
        if (parserOptions.afterParse) {
          parserOptions.afterParse(doc.internals.docSet);
        }
        if (!doc.internals.parserOnly) {
          // geoXML3 is not being used only as a real-time parser, so keep the parsed documents around
          docs.concat(doc.internals.docSet);
        }
      }
    }
  };
  var processStyles = function (doc) {
    var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/;
    for (var styleID in doc.styles) {
      if (!!doc.styles[styleID].href) {
        // Init the style object with a standard KML icon
        doc.styles[styleID].icon =  new google.maps.MarkerImage(
          doc.styles[styleID].href,
          new google.maps.Size(32, 32),
          new google.maps.Point(0, 0),
          new google.maps.Point(16, 12)
        );
        // Look for a predictable shadow
        if (stdRegEx.test(doc.styles[styleID].href)) {
          // A standard GMap-style marker icon
          doc.styles[styleID].shadow = new google.maps.MarkerImage(
              'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png',
              new google.maps.Size(59, 32),
              new google.maps.Point(0, 0),
              new google.maps.Point(16, 12));
        } else if (doc.styles[styleID].href.indexOf('-pushpin.png') > -1) {
          // Pushpin marker icon
          doc.styles[styleID].shadow = new google.maps.MarkerImage(
            'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png',
            new google.maps.Size(59, 32),
            new google.maps.Point(0, 0),
            new google.maps.Point(16, 12));
        } else {
          // Other MyMaps KML standard icon
          doc.styles[styleID].shadow = new google.maps.MarkerImage(
            doc.styles[styleID].href.replace('.png', '.shadow.png'),
            new google.maps.Size(59, 32),
            new google.maps.Point(0, 0),
            new google.maps.Point(16, 12));
        }
      }
    }
  };
  var createMarker = function (placemark, doc) {
    // create a Marker to the map from a placemark KML object
    // Load basic marker properties
    var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, {
      map:      parserOptions.map,
      position: new google.maps.LatLng(placemark.point.lat, placemark.point.lng),
      title:    placemark.name,
      zIndex:   Math.round(-placemark.point.lat * 100000),
      icon:     placemark.style.icon,
      shadow:   placemark.style.shadow
    });
    // Create the marker on the map
    var marker = new google.maps.Marker(markerOptions);
    // Set up and create the infowindow
    var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, {
      content: '
' + placemark.name +
               '
' + placemark.description + '