// var for encodedPolylines functions
small0 = 1;

// The global map object
var map = null

// array to hold markerIndexes sorted by location dates
var locationDates = new Array();

// array to hold points: lat, lon, zoom
var points = [];

// Custom icon array
var customIcons = [];

// configure icons
var activeIcon = new GIcon();        
activeIcon.image = "icons/mapHead.png";        
activeIcon.iconSize = new GSize(40, 36);        
activeIcon.iconAnchor = new GPoint(27, 17);        
activeIcon.infoWindowAnchor = new GPoint(5, 1);        
activeIcon.infoShadowAnchor = new GPoint(6, 20); 

var smallIconNote = new GIcon();        
smallIconNote.image = "icons/mapPin_note.png";        
smallIconNote.shadow = "icons/mapPin_shadow.png";        
smallIconNote.iconSize = new GSize(32, 33);        
smallIconNote.shadowSize = new GSize(32, 33);        
smallIconNote.iconAnchor = new GPoint(8, 30);        
smallIconNote.infoWindowAnchor = new GPoint(7, 1);        
smallIconNote.infoShadowAnchor = new GPoint(8, 20); 

var icon = new GIcon();        
icon.image = "icons/mapPin.png";        
icon.shadow = "icons/mapPin_shadow.png";        
icon.iconSize = new GSize(32, 33);        
icon.shadowSize = new GSize(32, 33);        
icon.iconAnchor = new GPoint(5, 20);        
icon.infoWindowAnchor = new GPoint(7, 1);        
icon.infoShadowAnchor = new GPoint(8, 20);




// Configured defaults
var defaultIconName = null
var defaultImageSource = null
var defaultImageWidth = null
var defaultImageHeight = null

// Load an XML file and call the given method with the result
function loadXml(name, method)
{
  var handler = function(data, result) 
  {
    if (result == 200)
    {
      var xml = GXml.parse(data)
      method(xml.documentElement)     
    }
    else
    {
      alert("Error loading map: " + name)
    }
  }
    
  GDownloadUrl(name, handler)
}
// Loads map data from the file '<name>.xml'
function loadMap(name, callbackLoaded, callbackJumped)
{  
  map = new GMap2(document.getElementById("map"))
  map.name = name
  map.callbackLoaded = callbackLoaded
  map.callbackJumped = callbackJumped
  
 
  if (GBrowserIsCompatible())
  {   
    loadXml("xml/" + name + ".xml", processMap)
  }
  else
  {
    alert("For one reason or another, Google Maps isn't going to work on your browser")
  }
}

// Jump to the given bookmark
function jumpTo(name)
{
  if (map && map.bookmarks)
  {
    var xml = map.bookmarks[name]
    
    if (xml)
    {
      map.setCenter(
        new GLatLng(parseFloat(xml.getAttribute("lat")), parseFloat(xml.getAttribute("lon"))), 
        parseInt(xml.getAttribute("zoom"))
      )
      
      if (map.callbackJumped)
      {
        map.callbackJumped(name, xml)
      }
    }
    else
    {
      alert("Failed to find bookmark [" + name + "] (case sensitive search)")
    }
  }
}

// Process the given map XML data
function processMap(root)
{
  map.addControl(new GLargeMapControl())
  map.addControl(new GMapTypeControl())
  
  //GEvent.addListener(map, "click", function(overlay, point) {
  //  toClip('<marker lat="' + point.y + '" lon="'+ point.x + '">')
  //})
  
  // Load bookmarks
  map.bookmarks = new Object()
  var bookmarksXml = root.getElementsByTagName("bookmark")
  
  if (bookmarksXml)
  {
    for (var i = 0; i < bookmarksXml.length; ++i)
    {
      var xml = bookmarksXml[i]
      var name = xml.getAttribute("name")
      map.bookmarks[name] = xml
    }
  }
  
  if (map.callbackLoaded)
  {
    map.callbackLoaded(map.name, root, map.bookmarks)
  }
    
  jumpTo(root.getAttribute("start"))
  
  map.enableDoubleClickZoom()
  map.enableContinuousZoom() 
  
  switch (root.getAttribute("maptype"))
  {
    case "normal":
      map.setMapType(G_NORMAL_MAP)
      break
    case "satellite":
      map.setMapType(G_SATELLITE_MAP)
      break
    case "hybrid":
      map.setMapType(G_HYBRID_MAP)
      break
  }
  
  var markers = root.getElementsByTagName("marker")
  var lineColor = "#FF0018"
  var lineWidth = 1
  var lineOpacity = 0.5
  var currentDate = new Date();    var layer1Markers = [];  var layer2Markers = [];  var layer3Markers = [];  var pointsToEncode = [];  var levelsString = "";  var marker;      //GEvent.addListener(map, 'click', function() {alert(map.getZoom());});       //what's the current location? also sets up the locationDates array, excludes dates in the future
  var currentLocation = getLocationDates(root); 
  
  // sort locationDates by visitdate ASC - only dealing with dates before or equal to today
  locationDates.sort(sortByLocationDates);
 
  // iterate through sorted locations by visitdate, collect points for encoded polyline, collect markerLayers for MarkerManager 
  for (var x = 0; x < locationDates.length; x++)
  {
    var mi = locationDates[x].MarkerIndex;
    var markerXML = markers[mi];
    var point = new GLatLng(parseFloat(markers[mi].getAttribute("lat")), parseFloat(markers[mi].getAttribute("lon")));
    var info = markers[mi].getElementsByTagName("info");
    var minZoom = markers[mi].getAttribute("minZoom");
    
    points.push(new pointToEncode(markers[mi].getAttribute("lat"), markers[mi].getAttribute("lon"), "unset"));
    
    // setup marker, apply to layer based on minZoom level
    if(x == locationDates.length-1)
    {
        levelsString+="P";
        if(info.length > 0)
        {
            if(tripInCurrentYear(markers[mi].getAttribute("visitdate")))
                marker = createMarker(point, new GIcon(activeIcon), info[0].getAttribute("title"), info[0]);
            else
                marker = createMarker(point, new GIcon(smallIconNote), info[0].getAttribute("title"), info[0]);
        }
        else
        {
            if(tripInCurrentYear(markers[mi].getAttribute("visitdate")))
                marker = createMarker(point, new GIcon(activeIcon), null, null);
            else
                marker = createMarker(point, new GIcon(icon), null, null); 
        }
        layer1Markers.push(marker);
    } 
     else if(minZoom >= 0 && minZoom <= 4)
     {
        levelsString+="P";
        if(info.length > 0)
        {   
            marker = createMarker(point, smallIconNote, info[0].getAttribute("title"), info[0]); 
        }
        else
            marker = createMarker(point, new GIcon(icon), null, null); 
        
        layer1Markers.push(marker);
     }
     else if(minZoom >= 5 && minZoom <= 18)
     {   
        levelsString+="K";
        if(info.length > 0)
            marker = createMarker(point, new GIcon(smallIconNote), info[0].getAttribute("title"), info[0]); 
        else
            marker = createMarker(point, new GIcon(icon), null, null); 
        layer2Markers.push(marker);
     }   
       
  }//for
 
    // if we have location dates apply marker layers to MarkerManager
    if(locationDates.length > 0)
    {
        var mgr = new GMarkerManager(map);
        mgr.addMarkers(layer1Markers, 1);
        mgr.addMarkers(layer2Markers, 5);

        // set encoded polyline
        setLevels(points);
        var result = createEncodings(points);
        var encodedPolyline = new GPolyline.fromEncoded({
            color: "#FF0018",
            weight: 3,
            opacity: 0.6,
            points: result.PointString,
            levels: levelsString,
            zoomFactor: 2,
            numLevels: 18
            });

        var zoomLevel;
        
        //2007
        if(tripInCurrentYear('1/1/' + map.name.substring(2)))
        {    
            zoomLevel = 3;
            point = new GLatLng(parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lat")), parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lon")));    
        }
        else if(map.name == "2004")
        {
            zoomLevel = 2;
            point = new GLatLng(35.460670, -42.363281);
        }
        else if(map.name == "2005")
        {
            zoomLevel = 3;
            point = new GLatLng(41.112469, -98.437500);
        }
        else if(map.name == "2006")
        {
            zoomLevel = 2;
            point = new GLatLng(-24.846565, -134.296875);
            //point = new GLatLng(parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lat")), parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lon")));    
        }
        else if(map.name == "2003")
        {
            zoomLevel = 3;
            point = new GLatLng(parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lat")), parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lon")));    
        }
        else
        {
            zoomLevel = 1;
            point = new GLatLng(parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lat")), parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lon")));    
        }
            
            
      
        // setup and display map
        map.setMapType(G_HYBRID_MAP);
        //point = new GLatLng(parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lat")), parseFloat(markers[locationDates[locationDates.length-1].MarkerIndex].getAttribute("lon")));
        map.setCenter(point, zoomLevel);
        map.addOverlay(encodedPolyline);
        mgr.refresh();
    }
    else
    {
        // set map to hybrid
        map.setMapType(G_HYBRID_MAP);
    }
}


// Read an attribute, or use given value if it does not exist
function readAttribute(element, name, value)
{
  var v = element.getAttribute(name)
  return (v) ? v : value
}


// Creates a single map marker
function createMarker(point, icon, title, info)
{
    if(icon.image != "icons/mapHead.png" && info != null)
    {
          var links = info.getElementsByTagName("text");
          var hasLink = false;
          for (var j = 0; j < links.length; j++)
          {
            if(links[j].getAttribute("href") != null)
                hasLink = true;
          }
          
          if(hasLink)
          {
              icon = new GIcon();        
              icon.image = "icons/mapPin_note.png";        
              icon.shadow = "icons/mapPin_shadow.png";        
              icon.iconSize = new GSize(32, 33);        
              icon.shadowSize = new GSize(32, 33);        
              icon.iconAnchor = new GPoint(5, 20);        
              icon.infoWindowAnchor = new GPoint(7, 1);        
              icon.infoShadowAnchor = new GPoint(8, 20);
              var marker = new GMarker(point, 
                    { 
                      icon: icon,
                      title: title
                    }
                  )
          }
          else
          {
                icon = new GIcon();        
                icon.image = "icons/mapPin.png";        
                icon.shadow = "icons/mapPin_shadow.png";        
                icon.iconSize = new GSize(32, 33);        
                icon.shadowSize = new GSize(32, 33);        
                icon.iconAnchor = new GPoint(5, 20);        
                icon.infoWindowAnchor = new GPoint(7, 1);        
                icon.infoShadowAnchor = new GPoint(8, 20);
                var marker = new GMarker(point, 
                    { 
                      icon: icon,
                      title: title
                    }
                  )
          }
          
          GEvent.addListener(
            marker, "click",
            function()
            {
              var html = generateInfoWindowHtml(info) + "<br><br>"
              marker.openInfoWindowHtml(html)
            }
          )     
          
     }     
     else
     {
        var marker = new GMarker(point, icon);
        GEvent.addListener(
            marker, "click",
            function()
            {
              var html = generateInfoWindowHtml(info) + "<br><br>"
              marker.openInfoWindowHtml(html)
            }
          )     
     }
  
  return marker;
}

function toClip(text)
{
  if (window.clipboardData)
  {
    window.clipboardData.setData("Text", text)
    return true
  }

  return false
}

function generateInfoWindowHtml(xml)
{
  var result = "<div class='info'><div class='infoitem'>"
  
  result += generateInfoWindowHtmlTitle(xml)

  for (var i = 0; i < xml.childNodes.length; ++i)
  {
    var node = xml.childNodes[i]
    
    switch (node.nodeName)
    {
      case "text":
        result += generateInfoWindowHtmlText(node)
        break
      case "image":
        result += generateInfoWindowHtmlImage(xml, node)
        break
    }
  }
  
  result += "</div></div>"
  
  return result
}

function generateInfoWindowHtmlTitle(xml)
{
  var href = xml.getAttribute("href")
  var title = readAttribute(xml, "title", "")
  
  if (href) 
  {
    return "<br><a class='infotitle' href='" + href + "' target='_blank' style='position:absolute; text-indent:0px; overflow:visible; cursor:pointer; z-index:2050; padding-top: 15px;'>" + title + "</a>"
  }
  else
  {
    return "<span class='infotitle'>" + title + "</span>"
  }
}

function generateInfoWindowHtmlText(xml)
{
  var result = "<div class='infoitem'>"
  
  var href = xml.getAttribute("href")
  var className = readAttribute(xml, "class", "")

  if (href)
  {
    result += "<br><a href='" + href + "' target='_blank' style='position:absolute; text-indent:0px; overflow:visible; cursor:pointer; z-index:2050; padding-top: 15px;'>" + xml.firstChild.nodeValue + "</a>"
  }
  else
  {
    result += "<span class='info" + className + "'>" + xml.firstChild.nodeValue + "</span>"
  }
  
  result += "</div>"
  
  return result
}

function generateInfoWindowHtmlImage(root, xml)
{
  var result = "<div class='infoitem'>"
  
  var href = xml.getAttribute("href")
  
  if (!href)
  {
    href = root.getAttribute("href")
  }

  if (href)
  {// class='info'
    result += "<br><a href='" + href + "' target='_blank' style='position:absolute; text-indent:0px; overflow:visible; cursor:pointer; z-index:2050; padding-top: 15px;'>" + generateInfoWindowHtmlImageContent(xml) + "</a>"
  }
  else
  {
    result += generateInfoWindowHtmlImageContent(xml)
  }
  
  result += "</div>"
  
  return result
}

function generateInfoWindowHtmlImageContent(xml)
{
  var result = "<img class='info' border='1' src='"
 
  if ((xml.firstChild.nodeValue.toUpperCase().indexOf("HTTP") != -1) || (xml.getAttribute("mode") == "direct"))
  {
    result += xml.firstChild.nodeValue
  }
  else
  {
    if (defaultImageSource)
    {
      result += defaultImageSource + "/" + xml.firstChild.nodeValue
    }
    else
    {
      result += "images/" + xml.firstChild.nodeValue
    }
  }

  result += "'"
  
  var width = xml.getAttribute("width")
  var height = xml.getAttribute("height")
    
  if (width)
  {
    result += " width='" + width + "'"
  }
  else
  
  if (defaultImageWidth)
  {
    result += " width='" + defaultImageWidth + "'"
  }
  
  if (height)
  {
    result += " height='" + height + "'"
  }
  else
  
  if (defaultImageHeight)
  {
    result += " height='" + defaultImageHeight + "'"
  }
  
  result += " />"
  
  return result
}





function getLocationDates(root)
{
    var markers = root.getElementsByTagName("marker");
    var curLocation;
    var leastDaysFromNow = null;
    var thisLocationDaysFromNow;    
    for (var i = 0; i < markers.length; i++)    {        if(markers[i].getAttribute("visitdate") != null)
        {  
            thisLocationDaysFromNow = daysFromNow(markers[i].getAttribute("visitdate"));
            
            if(thisLocationDaysFromNow <= 0)
            {
                if((thisLocationDaysFromNow <= leastDaysFromNow) || leastDaysFromNow == null)
                {
                    leastDaysFromNow = thisLocationDaysFromNow;
                    curLocation = i;
                }
                locationDates[locationDates.length++] = new locationDate(thisLocationDaysFromNow, i);
             
            }
         }
    }
    
    return curLocation;
}

function daysFromNow(visitDate)
{
    var a = visitDate.split("/");
    eventDate = new Date();    
    eventDate.setYear("20"+a[2]);
    eventDate.setMonth(a[0]-1);
    eventDate.setDate(a[1]);
    now = new Date();
    msEachDay = 24 * 60 * 60 * 1000;
    daysRemaining = (eventDate.getTime() - now.getTime()) / msEachDay;
    daysRemaining = Math.round(daysRemaining);
    //if neg, then date in past
    //if pos, then date in future
    return daysRemaining;
}


function tripInCurrentYear(visitDate)
{
    // compare current trip year to current year
    var a = visitDate.split("/");
    
    // if trip year == current year, return true
    if(a.length > 0)
    {
        var eventDate = new Date();    
        eventDate.setYear("20"+a[2]);
        eventDate.setMonth(a[0]-1);
        eventDate.setDate(a[1]);
        var now = new Date();
        if(eventDate.getYear() == now.getYear())
            return true;
        else
            return false;
    }
    else
        return false;
}
  
function sortByLocationDates(a, b) {
	var x = a.DaysFromNow;
	var y = b.DaysFromNow;
	return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}


function locationDate(daysAgo, markerIndex) {
    this.DaysFromNow = parseInt(daysAgo);
    this.MarkerIndex = parseInt(markerIndex);
}function pointToEncode(lat, lon, lev) {
    this.lat = parseFloat(lat);
    this.lon = parseFloat(lon);
    this.lev = lev;
}

// The createEncodings function is taken almost verbatim from
// http://www.google.com/apis/maps/documentation/polyline.js
// The only difference is the technique for passing data in and out.

function createEncodings(points) {
  var i = 0;

  var plat = 0;
  var plng = 0;

  var encoded_points = "";
  var encoded_levels = "";

  for(i = 0; i < points.length; ++i) {
    var point = points[i];
    var lat = point.lat;
    var lng = point.lon;
    var level = point.lev;

    var late5 = Math.floor(lat * 1e5);
    var lnge5 = Math.floor(lng * 1e5);

    dlat = late5 - plat;
    dlng = lnge5 - plng;

    plat = late5;
    plng = lnge5;

    encoded_points += encodeSignedNumber(dlat) + encodeSignedNumber(dlng);
    encoded_levels += encodeNumber(level);
  }
  return {
    PointString: encoded_points,
    ZoomString:  encoded_levels
  }
}


 
// The next two functions are taken verbatim from
// http://www.google.com/apis/maps/documentation/polyline.js
function encodeSignedNumber(num) {
  var sgn_num = num << 1;

  if (num < 0) {
    sgn_num = ~(sgn_num);
  }

  return(encodeNumber(sgn_num));
}

function encodeNumber(num) {
  var encodeString = "";
  var nextValue;

  while (num >= 0x20) {
    nextValue = (0x20 | (num & 0x1f)) + 63;
    if (nextValue == 92) {
      encodeString += (String.fromCharCode(nextValue));
    }
    encodeString += (String.fromCharCode(nextValue));
    num >>= 5;
  }

  finalValue = num + 63;
  if (finalValue == 92) {
    encodeString += (String.fromCharCode(finalValue));
  }
  encodeString += (String.fromCharCode(finalValue));
  return encodeString;
}


// === A function to put arrow heads at intermediate points
      function midArrows() {
        for (var i=1; i < points.length-1; i++) {  
          var p1=points[i-1];
          var p2=points[i+1];
          var dir = bearing(p1,p2);
          // == round it to a multiple of 3 and cast out 120s
          var dir = Math.round(dir/3) * 3;
          while (dir >= 120) {dir -= 120;}
          // == use the corresponding triangle marker 
          arrowIcon.image = "http://www.google.com/intl/en_ALL/mapfiles/dir_"+dir+".png";
          map.addOverlay(new GMarker(points[i], arrowIcon));
        }
      }


// === Returns the bearing in degrees between two points. ===
      // North = 0, East = 90, South = 180, West = 270.
      var degreesPerRadian = 180.0 / Math.PI;
      function bearing( from, to ) {
        // See T. Vincenty, Survey Review, 23, No 176, p 88-93,1975.
        // Convert to radians.
        var lat1 = from.latRadians();
        var lon1 = from.lngRadians();
        var lat2 = to.latRadians();
        var lon2 = to.lngRadians();

        // Compute the angle.
        var angle = - Math.atan2( Math.sin( lon1 - lon2 ) * Math.cos( lat2 ), Math.cos( lat1 ) * Math.sin( lat2 ) - Math.sin( lat1 ) * Math.cos( lat2 ) * Math.cos( lon1 - lon2 ) );
        if ( angle < 0.0 )
	 angle  += Math.PI * 2.0;

        // And convert result to degrees.
        angle = angle * degreesPerRadian;
        angle = angle.toFixed(1);

        return angle;
      }
      
      
// The next two functions set the level string,
// which is a bit trickier.

// dist(point1, point2) returns the distance between point1
// and point2 in meters.  Note that each point needs lat
// and lon properties.
function dist(point1, point2) {
  var deg = 0.0174532925199;
  
  return 12746004.5*Math.asin(Math.sqrt(
    Math.pow(Math.sin((point1.lat - point2.lat)*deg/2),2) + 
      Math.cos(point1.lat*deg)*Math.cos(point2.lat*deg)*
        Math.pow(Math.sin((point1.lon - point2.lon)*deg/2),2)));
}

// We set the levels here.
function setLevels(points) {
  var numLevels = 18;
  var small, i, j;
  var len = points.length;
  
  // Set the endpoints to show at all zoom levels
  points[0].lev = numLevels-1;
  points[len-1].lev = numLevels-1;
  
  // Starting at the largest possible level, we step down through
  // the levels labelling points which need to appear at
  // lower resolutions.
  for (level = numLevels-1; level >= 0; level--) {
    // Set "small" for the current level.
    // More generally, we might use
     small = small0*Math.pow(2.0, -18*(numLevels-level)/numLevels);
    //small = small0*Math.pow(2.0, level-18);
    
    // Figure out which points need to occur at this level,
    // the main criteria being that we've stepped a certain 
    // distance (called "small") from the previous set point.
    i = j = 1;
    while (i < len-1) {
      if (dist(points[i], points[j]) >= small && points[i].lev == "unset") {    
        points[i].lev = level;
        j = i;
      }
      if (points[i].lev != "unset") {
        j=i;
      }
      i++;
    }
  }
  // Set any unset points to level 0.
  for (i=0; i < len; i++) {
    if (points[i].lev == "unset") {
      points[i].lev = 0;
    }
  }
}