var sidebar;
var mapDiv;
var map;
var tileXml;
var polyXml;
var zones = {};
var content;
var lastMinute = -1;
var clockStyle = 'digital';
var iconBase;
var mercator;
var isKmlParsed = false;
var lastSize;
var sizeChanged = false;


if (!window._include) {
  function _include(url) {
    document.write('<script type="text/javascript" src="' + url + '"><\/script>')
  };
}

var timeCorrection = 0;
var clientTime = Number(new Date());
var serverTime = clientTime; // default in case time server unreachable
_include('http://gad.getpla.net/include/current_utc.js.php?rand=' + Math.random());

if ((crossPlatform.api != 'igoogle') ||
    (crossPlatform.igoogle.host != 'gmaps')) {
  // Not running as a mapplet => a few additional HTML elements are required
  _include('http://gad.getpla.net/include/async.js');
  document.write('<div id="map"><\/div>');
}

/* Mapplets don't have the GMercatorProjection object, but it's required in order to convert
   between lat/lon and pixels without a big, asynchronous mess. So the following code
   is cribbed (and reduced) from an early version of the API - 2.67, actually. */
function rad2deg(a){return a*Math.PI/180}
function deg2rad(a){return a/(Math.PI/180)}
function limit(a,b,c){if(b!=null){a=Math.max(a,b)}if(c!=null){a=Math.min(a,c)}return a}
function mercatorProjection(a){var b=this;b.Ve=[];b.We=[];b.Te=[];b.Ue=[];var c=256;for(var d=0;d<a;d++){var e=c/2;b.Ve.push(c/360);b.We.push(c/(2*Math.PI));b.Te.push(new GPoint(e,e));b.Ue.push(c);c*=2}}
mercatorProjection.prototype.fromLatLngToPixel=function(a,b){var c=this,d=c.Te[b],e=Math.round(d.x+a.lng()*c.Ve[b]),f=limit(Math.sin(rad2deg(a.lat())),-0.9999,0.9999),g=Math.round(d.y+0.5*Math.log((1+f)/(1-f))*-c.We[b]);return new GPoint(e,g)};
mercatorProjection.prototype.fromPixelToLatLng=function(a,b,c){var d=this,e=d.Te[b],f=(a.x-e.x)/d.Ve[b],g=(a.y-e.y)/-d.We[b],h=deg2rad(2*Math.atan(Math.exp(g))-Math.PI/2);return new GLatLng(h,f,c)};

function attachHandler(element, shortEvent, handler) {
  if (window.addEventListener) {
    // W3C DOM Level 2 compliant
    element.addEventListener(shortEvent, handler, false);
  } else if (window.attachEvent) {
    // Most versions of IE
    element.attachEvent('on' + shortEvent, handler);
  }
};

// Sutherland-Hodgman clipping algorithm implemented for rectangular bounds
function polygonIntersection(vertices, bounds) {
  function computeIntersection(start, end, edgeType, edgeOrdinate) {
    switch (edgeType) {
      case 'N':
      case 'S':
        return {y: edgeOrdinate, x: start.x + (end.x - start.x) * 
                                    (edgeOrdinate - start.y) / (end.y - start.y)};
      default:
        return {y: start.y + (end.y - start.y) * 
                     (edgeOrdinate - start.x) / (end.x - start.x),
                x: edgeOrdinate};
    }
  };

  function clipPlaneContains(point, edgeType, edgeOrdinate)
  {
    switch (edgeType) 
    {
      case 'N':
        return (point.y <= edgeOrdinate);
      case 'S':
        return (point.y >= edgeOrdinate);
      case 'E':
        return (point.x <= edgeOrdinate);
      default:
        return (point.x >= edgeOrdinate);
    }
  };

  function SutherlandHodgman(vertices, edgeType, edgeOrdinate)
  {
    var S = vertices[vertices.length - 1];
    var P;
    var result = [];
    for (var j = 0; j < vertices.length; j = j + 1)
    {
      P = vertices[j];
      if (clipPlaneContains(P, edgeType, edgeOrdinate))
      {
        if (!clipPlaneContains(S, edgeType, edgeOrdinate))
          result.push(computeIntersection(S, P, edgeType, edgeOrdinate));
        result.push(P);
      }
      else if (clipPlaneContains(S, edgeType, edgeOrdinate))
        result.push(computeIntersection(P, S, edgeType, edgeOrdinate));
      S = P;
    }
    return result;
  };

  var result = SutherlandHodgman(vertices, 'S', bounds.getSouthWest().y);
  result = SutherlandHodgman(result, 'W', bounds.getSouthWest().x);
  result = SutherlandHodgman(result, 'N', bounds.getNorthEast().y);
  result = SutherlandHodgman(result, 'E', bounds.getNorthEast().x);
  return result;
};

attachHandler(window, 'load', loadMap);
function loadMap() {
  content = document.getElementById('list');
  if (((crossPlatform.api != 'igoogle') || (crossPlatform.igoogle.host != 'gmaps')) && 
    !GBrowserIsCompatible()) {
    content.innerHTML = 'Sorry, your browser isn\'t compatible with Google Maps.';
    return;
  }

  if (GMarker.prototype.setLatLng == null) {
    GMarker.prototype.setLatLng = function (coords) {
      this.setPoint(coords);
    };
  }

  sidebar = document.getElementById('sidebar');
  mapDiv = document.getElementById('map');
  map = new GMap2(mapDiv, {mapTypes: [G_NORMAL_MAP]});

  mercator = new mercatorProjection(25);
  iconBase = {analog: {iconSize: new GSize(31, 31),
                       iconAnchor: new GPoint(15, 15)},
              digital: {iconSize: new GSize(39, 18),
                        iconAnchor: new GPoint(18, 9)}};

  if ((crossPlatform.api == 'igoogle') &&
      (crossPlatform.igoogle.host == 'gmaps')) {
    // Mapplet-specific init
  } else {
    // Non-mapplet init

//    if (crossPlatform.api.indexOf('google') > -1)
//      mapDiv.style.height = '300px';
//    crossPlatform.adjustHeight();

    // Load & validate initial location
    var defaultLocation = [37.44, -98.44, 3];// [51.478, 0, 4];
    var prefLocation = crossPlatform.loadPref('location', defaultLocation.join('|')).split('|');
    if (prefLocation.length != 3) {
      prefLocation = defaultLocation;
    } else {
      for (var n = 0; n < prefLocation.length; n++) { 
        if (isNaN(parseFloat(prefLocation[n]))) {
          prefLocation = defaultLocation;
          break;
        }
      }
    }

    var initCoords = new GLatLng(parseFloat(prefLocation[0]), parseFloat(prefLocation[1]));
    var initZoom = parseInt(prefLocation[2]);
    map.setCenter(initCoords, initZoom);

    if (crossPlatform.getWidth() < 200)
      map.addControl(new GSmallZoomControl());
    else if (crossPlatform.getHeight() < 500)
      map.addControl(new GSmallMapControl());
    else
      map.addControl(new GLargeMapControl());
  }

  GEvent.addListener(map, 'moveend', mapMoveEnd);
  
  GEvent.addDomListener(window, 'resize', windowResize);
  windowResize();
  if ((crossPlatform.api == 'igoogle') &&
      (crossPlatform.igoogle.host == 'gmaps')) {
    getSize();
  }

  // Overlay the time zones (this is the easy part)
  var url = 'http://www.daylightmap.com/time/zones.kml?nocache=3';
  tileXml = new GGeoXml(url);
  map.addOverlay(tileXml);

  // Load & validate clock style
  var defaultStyle = 'digital';
  clockStyle = crossPlatform.loadPref('clock_style', defaultStyle);
  if ((clockStyle != 'digital') && (clockStyle != 'analog')) {
    clockStyle = defaultStyle;
  }

  var checkbox = document.getElementById(clockStyle);
  if (checkbox != null) {
    checkbox.checked = true;
  }
  
//url = 'http://dev.daylightmap.com/time/zones.kml?nocache=2';
  var options = {createpolygon: addZone, nozoom: true};
  polyXml = new EGeoXml(map, url, options);
  GEvent.addListener(polyXml, 'parsed', kmlParsed);
  polyXml.parse();

  var analyticsKey = '/timezones/' + crossPlatform.host + '/';
  if (crossPlatform.api == 'igoogle') {
    _IG_Analytics('UA-1556555-7', analyticsKey);
  } else if (!!window.pageTracker) {
    pageTracker._trackPageview(analyticsKey);
  }
};

function kmlParsed() {
  isKmlParsed = true;
  setInterval('updateTime()', 1000);
  GAsync(map, 'getZoom', function (zoom) {
    getBounds(zoom);
  });
};

function proxify(url) {
  if (crossPlatform.api == 'igoogle')
    return 'http://www.gmodules.com/gadgets/proxy?refresh=3600&url=' + escape(url); 
  else
    return url;
};

function getSize() {
  setTimeout("GAsync(map, 'getSize', checkSize)", 5000);
};

function checkSize(size) {
  if (!lastSize) {
    lastSize = size;
    getSize();
  } else if (!size.equals(lastSize)) {
    if (isKmlParsed) {
      mapMoveEnd();
    }
    lastSize = size;
  } else {
    getSize();
  }    
};

function windowResize() {
  if ((crossPlatform.api != 'igoogle') ||
      (crossPlatform.igoogle.host != 'gmaps')) {
    if (crossPlatform.getWidth() < 660) {
      sidebar.style.display = 'none';
      mapDiv.style.width = '100%';
    } else {
      mapDiv.style.width = 
        sidebar.style.left = '70%';
      sidebar.style.display = 'block';
    }
    map.checkResize();
  }

  if (isKmlParsed)
    mapMoveEnd();
};

function mapMoveEnd()
{
  //  mapMoveEnd: get the new map boundary coordinates to display visible zones
  if (crossPlatform.getHeight())
  {
    GAsync(map, 'getCenter', 'getZoom', function (center, zoom)
    {
      getBounds(zoom);
      crossPlatform.savePref('location', center.lat() + '|' + center.lng() + '|' + zoom);
    });
  }
};

function getBounds(zoom) {
  if ((crossPlatform.api != 'igoogle') ||
      (crossPlatform.igoogle.host != 'gmaps')) {
    // In theory GAsync should remove the need for this, but I ran into timing issues.
    setTimeout('afterGetBounds(map.getBounds(), ' + zoom + ')', 100);
  } else {
    map.getBoundsAsync(function (bounds) {afterGetBounds(bounds, zoom);});
  }
};

function highlight(name) {
  // Placeholder for future functionality
}

function afterGetBounds(mapBounds, zoom) {
  function fromLatLngToPixel(coords) {
    var point = mercator.fromLatLngToPixel(coords, zoom);
   
    point.x -= origin.x;
    point.y -= origin.y;
    return point;
  };

  function fromPixelToLatLng(point) {
    var coords = mercator.fromPixelToLatLng(new GPoint(point.x + origin.x, point.y + origin.y), zoom);
    return coords;
  };

  var html = '';
  var intersects;
  var intersection;
  var clockCoords;
  var sw = mapBounds.getSouthWest();
  var ne = mapBounds.getNorthEast();
  var origin = mercator.fromLatLngToPixel(sw, zoom);
  var v;

  if (sw.lng() > ne.lng()) {
    var splitBounds = [new GLatLngBounds(new GLatLng(sw.lat(), -179.99999999), ne),
                       new GLatLngBounds(sw, new GLatLng(ne.lat(), 179.99999999))];
  } else {
    var splitBounds = [];
  }
  
  for (var name in zones) {
    clockCoords = {s: ne.lat(), n: sw.lat()}; // Looks wrong, doesn't it? It's not, though.

    if (splitBounds.length) {
      intersects = zones[name].bounds.intersects(splitBounds[0]) ||
                   zones[name].bounds.intersects(splitBounds[1]);
    } else {
      intersects = (zones[name].bounds.intersects(mapBounds));
    }
    
    if (intersects && (zoom > 2)) {
      intersects = false;
      for (var p = 0; p < zones[name].polys.length; p++) {
        if (zones[name].polys[0][0].x == null) {
          var newPoly = [];
          for (var p = 0; p < zones[name].polys[0].length; p++) { 
            newPoly.push({x: zones[name].polys[0][p].lng(), 
                          y: zones[name].polys[0][p].lat()});
          }
          zones[name].polys[0] = newPoly;
        }

        if (splitBounds.length) {
          if (zones[name].polys[p][0].x < 0) {
            intersection = polygonIntersection(zones[name].polys[p], splitBounds[0]);
          } else {
            intersection = polygonIntersection(zones[name].polys[p], splitBounds[1]);
          }
        } else { 
          intersection = polygonIntersection(zones[name].polys[p], mapBounds);
        }
        
        if (intersection.length) {
          intersects = true;
          for (v = 0; v < intersection.length; v++) {
            clockCoords.n = Math.max(clockCoords.n, intersection[v].y);
            if (clockCoords.s > intersection[v].y) {
              clockCoords.s = Math.min(clockCoords.s, intersection[v].y);
              clockCoords.w = intersection[v].x;
              clockCoords.e = intersection[v].x;
            } else if (clockCoords.s == intersection[v].y) {
              clockCoords.w = Math.min(clockCoords.w, intersection[v].x);
              clockCoords.e = Math.max(clockCoords.e, intersection[v].x);
            }
          }
//if (zones[name].polys[p].line != null)
//  map.removeOverlay(zones[name].polys[p].line);
//zones[name].polys[p].line = new GPolyline(intersection, zones[name].color);
//map.addOverlay(zones[name].polys[p].line, 5, 0.8);
        }
      }
    }

    if (!intersects) {
      zones[name].visible = false;
    } else {
      zones[name].visible = true;
      if (zones[name].description == null) {
        zones[name].description = name;
        if (zones[name].abbrs != '') {
          zones[name].description = zones[name].description + ': ' + zones[name].abbrs;
        }
      }
      
      html = html + 
        '<p class="zone">' +
          '<span class="key" style="background-color: ' + zones[name].color + '" onclick="highlight(\'' + name + '\')"></span>' +
          '<strong class="time" id="' + name + '"></strong>' +
          ' (' + zones[name].description + ')</p>';

      if (zoom <= 2) {
        intersects = false;
      } else {
        clockCoords.left  = fromLatLngToPixel(new GLatLng(clockCoords.s, clockCoords.w));
        clockCoords.right = fromLatLngToPixel(new GLatLng(clockCoords.n, clockCoords.e));
  
        if (clockCoords.right.x - clockCoords.left.x < 30) {
          intersects = false;
        } else {
          clockCoords.x = (clockCoords.right.x + clockCoords.left.x) / 2; // midway between E & W edges
          if (clockCoords.x < 110) {
            clockCoords.y = clockCoords.left.y - 46;  // clear Google logo / scale
          } else {
            clockCoords.y = clockCoords.left.y - 24;  // clear copyrights
          }
          clockCoords.y = Math.max(clockCoords.y, clockCoords.right.y); // limit to the N edge of the zone
          
          zones[name].clockCoords = fromPixelToLatLng(new GPoint(clockCoords.x, clockCoords.y));
        }
      }
    }

    if (!intersects) {
      zones[name].clockCoords = null;
    }
  }

  content.innerHTML = html;

  lastMinute = -1;
  updateTime();

  if ((crossPlatform.api == 'igoogle') &&
      (crossPlatform.igoogle.host == 'gmaps')) {
    getSize();
  }
};

function addZone(points, color, width, opacity, fillcolor, fillopacity, bounds, name, description)
{
  if (zones[name] == null)
  {
    zones[name] = {offset: parseFloat(name.substr(3)) * 3600 * 1000, 
                   abbrs: description, 
                   color: fillcolor,
                   polys: [points],
//                   polyBounds: [bounds],
                   bounds: bounds,
                   visible: false};
  }
  else
  {
    zones[name].polys.push(points);
//    zones[name].polyBounds.push(bounds);
    zones[name].bounds.extend(bounds.getSouthWest());
    zones[name].bounds.extend(bounds.getNorthEast());
//    
//    if (description != '')
//      if (zones[name].abbrs == '')
//        zones[name].abbrs = description;
//      else
//        zones[name].abbrs = zones[name].abbrs + ', ' + description;
  }
};

function updateTime()
{
  if (timeCorrection == 0)
    timeCorrection = (serverTime - clientTime);
  var now = new Date(Number(new Date()) + timeCorrection);

  if (now.getUTCMinutes() != lastMinute)
  {
    lastMinute = now.getUTCMinutes();
  
    now = Number(now);
    for (var name in zones)
    {
      if (zones[name].visible)
      {
        var timeCell = document.getElementById(name);
        if (timeCell != null)
        {
          var newTime = new Date(now + zones[name].offset);
          timeCell.innerHTML = formatTime(newTime);
        }
  
        if (zones[name].clockCoords == null)
        {
          if (zones[name].clock != null)
            zones[name].clock.hide();
          continue;
        }

        var filename = ((newTime.getHours() + newTime.getTimezoneOffset() / 60) % 24) + 
                       '_' + zeroPad(newTime.getMinutes());
        filename = 'http://www.daylightmap.com/clock/' + clockStyle + '/' + filename + '.png';
        filename = proxify(filename);
  
        if (zones[name].clock == null)
        {
          var clockIcon = new GIcon();
          clockIcon.image      = filename;
          clockIcon.iconSize   = iconBase[clockStyle].iconSize;
          clockIcon.iconAnchor = iconBase[clockStyle].iconAnchor;
    
          zones[name].clock = new GMarker(zones[name].clockCoords,
                                          {icon: clockIcon, clickable: false,
                                           title: zones[name].description});
          map.addOverlay(zones[name].clock);
        }
        else
        {
          zones[name].clock.setImage(filename);
          zones[name].clock.setLatLng(zones[name].clockCoords);
          zones[name].clock.show();
        }
      }
      else
        if (zones[name].clock != null)
          zones[name].clock.hide();
    }
  }
};

function formatDate(time)
{
  var result = time.getUTCFullYear() + '/' + 
               ('0' + (time.getUTCMonth() + 1)).substr(-2) + '/' + 
               ('0' + time.getUTCDate()).substr(-2);
  
  return result;
};

function zeroPad(number)
{
  if (number < 10)
    return '0' + number;
  else
    return number.toString();
}

function formatTime(time)
{
//  return time.toLocaleTimeString();
  var hemi = '';
  var hour = time.getUTCHours();
  if (clockStyle == 'analog')
    if (hour >= 12)
    {
      hemi = ' pm';
      if (hour > 12)
        hour -= 12;
    }
    else
    {
      hemi = ' am';
      if (hour == 0)
        hour = 12;
    }
  
  var minute = zeroPad(time.getUTCMinutes());
  var result = hour + ':' + minute;

  var today = formatDate(new Date());
  var target = formatDate(time)
  if (target > today)
    result = '<em class="tomorrow" title="' + result + hemi + ' tomorrow: ' + 
             target + '">' + result + '<span>' + hemi + '</span>' + '</em>';
  else if (target < today)
    result = '<em class="yesterday" title="' + result + hemi + ' yesterday: ' + 
             target + '">' + result + '<span>' + hemi + '</span>' + '</em>';
  else 
    result = result + '<span>' + hemi + '</span>';

  return result;
};

function setStyle(newStyle)
{
  clockStyle = newStyle;
  crossPlatform.savePref('clock_style', clockStyle);

  for (var name in zones)
    if (zones[name].clock != null)
    {
//      var clockCoords = zones[name].clock.getLatLng();
      map.removeOverlay(zones[name].clock);
      zones[name].clock = null;
//
//      var clockIcon = new GIcon();
//      clockIcon.image      = '';
//      clockIcon.iconSize   = iconBase[clockStyle].iconSize;
//      clockIcon.iconAnchor = iconBase[clockStyle].iconAnchor;
//
//      zones[name].clock = new GMarker(clockCoords,
//                                      {icon: clockIcon, clickable: false});
//      map.addOverlay(zones[name].clock);
    }

  lastMinute = -1;
  updateTime();
};
