﻿var x = 0;
var y = 0;
// http://koti.welho.com/saberg/maps/
// ABQIAAAAYr0aoo6SfUD_92fi3wDYaxSUoraaxghnFvugbTXzg7-i5QyokRRu5fmzqGWvtn7eCwXksdXxFrPjlg
// http://localhost/
// ABQIAAAAYr0aoo6SfUD_92fi3wDYaxT2yXp_ZAY8_ufC3CFXhHIE1NvwkxQF90aIn_VJM_xP4eUsFCIu5VRH3g
var map = 0;

var globalId;

var currPath = 0;

var allPaths = new Array();
var allBoats = new Array();

var Mode = {
  NORMAL : 0,
  MEASURE : 1,
  ZOOM : 2
};

var currentMode = Mode.NORMAL;
var modeHandler = 0;
var moveHandler = 0;
var modeControl;
var trackingId;
var distUnit = 1; 
var distUnitMark = "km"; 
var currentShownDist = 0;
var latLngFormat = 1;
var spdUnitMark = "km/h";
var windSpeedUnit = 1; 
var windSpeedUnitMark = "m/s"; 
var polyOptions = {geodesic:true};

var latLngDisplay;

var windGrid = 0;
var windGridList = [];
var windAreaBounds = 0;
var lastUserBoundZoom = 0;

var KNTS = 1.852;
var MS_TO_KNTS = (3.6 / KNTS);

var sailingMode = true;
var currentLineHit = 0;

var foundPath;
var foundPointIndex;

function round(v, dec) {
    v = Math.round(v * Math.pow(10,dec)) / Math.pow(10,dec);
    return v;
}

function rDist(d) {
    var f = 3;
    if (d > 2) f = 2;
    if (d > 10) f = 1;
    if (d > 100) f = 0;
    return round(d, f)
}

function rSpd(d) {
    return round(d / distUnit, 1)
}

function rPoint(d) {
    return round(d, 5)
}

function showDist(s) {
    currentShownDist = s;
    document.getElementById('distance').innerHTML = rDist(s / distUnit);
}

function showPathInfo(p) {
    if (p) {
        document.getElementById('path_name').innerHTML = p.name;
        document.getElementById('num_points').innerHTML = p.line.length;
    } else {
        document.getElementById('path_name').innerHTML = "";
        document.getElementById('num_points').innerHTML = "0";
    }
}

function showPathMove(dist1, course1, dist2, course2) {
    document.getElementById('distance_unit').innerHTML = distUnitMark;

    document.getElementById('from_prev_dist').innerHTML = rDist(dist1 / distUnit);
    document.getElementById('from_prev_dir').innerHTML = course1 + "&deg;";

    document.getElementById('to_next_dist').innerHTML = rDist(dist2 / distUnit);
    document.getElementById('to_next_dir').innerHTML = course2 + "&deg;";
}

function showSailTime(sailTimeIn, sailTimeOut) {
    document.getElementById('sail_time_in').innerHTML = formatTime(sailTimeIn);
    document.getElementById('sail_time_out').innerHTML = formatTime(sailTimeOut);
    document.getElementById('sail_time').innerHTML = formatTime(sailTimeIn + sailTimeOut);
}

function radToDeg(r) {
    r = r / Math.PI * 180;
    if (r < 0) r += 360;
    return round(r, 0);
}

function changeDistanceUnit(v) {
    distUnit = v;
    showDist(currentShownDist);
    if (distUnit > 1.8) {
	spdUnitMark = "knt";
        windSpeedUnitMark = "knt"; 
        windSpeedUnit = MS_TO_KNTS;
        distUnitMark = "Nm";
    } else {
        distUnitMark = "km";
	spdUnitMark = "km/h";
        windSpeedUnitMark = "m/s"; 
        windSpeedUnit = 1;
    }
}

function changeLatLngFormat(v) {
    latLngFormat = v;
}

function toggleLatLngFormat(v) {
    if (latLngFormat == 1) {
        latLngFormat = 2;
    } else {
        latLngFormat = 1;
    }
    updateCoords(0);
}

function debug(s) {
  GLog.write(s);
}

function hideOverlays() {
  for (var i = 0; i < allPaths.length; i++) {
    allPaths[i].show(false);
  }
}

function showOverlays() {
  for (var i = 0; i < allPaths.length; i++) {
    allPaths[i].show(true);
  }
}

var measureCallback = function(marker, point, overlaypoint) {
  if (!marker || marker != currPath.lineOverlay) {
    if (marker)
      point = overlaypoint;
    modeControl.update();// update clear button
    currPath.add(snapPoint(point));
  } else
    if (marker == currPath.lineOverlay) {
      currPath.insert(snapPoint(overlaypoint));
    }
}

var pointInfoCallback = function(marker, point) {
  if (!marker) {
    foundPointIndex = -1;
    foundPath = 0;
    var foundPoint;
    var minDist = 100000;
    for (var i = 0; i < allPaths.length; i++) {
      if (allPaths[i].visible) {
        for (var l = 0; l < allPaths[i].line.length; l++) {
          var d = distHaversine(allPaths[i].line[l], point);
          if (d < minDist) {
            minDist = d;
            foundPath = allPaths[i];
            foundPointIndex = l;
            foundPoint = allPaths[i].line[l];
          }
        }
      }
    }
    if (foundPointIndex > -1) {
        // don't open info win if we are far from the point in pixel coords.
        var p1 = map.fromLatLngToContainerPixel(foundPoint);
        var p2 = map.fromLatLngToContainerPixel(point);
        if (Math.sqrt((p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y)) > 25) {
            foundPointIndex = -1;
        }
    }
    if (foundPointIndex > -1) {
      map.openInfoWindowHtml(foundPoint, infoWin(foundPath, foundPointIndex));
    }

  }
}

function deleteCurrentPoint() {
    map.closeInfoWindow();
    if (!foundPath || foundPointIndex < 0) {
        debug("no point to delete");
        return;
    }
    foundPath.deletePoint(foundPointIndex);
}

function parseLL(str) {
  if (str.search(":") > 0) {
    var deg_min = str.split(":");
    var sign = 1;
    if (deg_min[0]*1.0 < 0) {
        sign = -1;
        deg_min[0] = Math.abs(deg_min[0]);
    }
    var v = (deg_min[0] * 1.0 + deg_min[1] / 60.0) * sign;
    //    debug(str + "/" + deg_min[0] + "/" + deg_min[1] + "/" + v);
    return v;
  } else {
    return str;
  }
}

function changePoint(str) {
  if (!foundPath || foundPointIndex < 0) {
    debug("no point to change");
    return;
  }

  var vals = str.split(" ");
  if (vals.length < 3) {
    debug("wrong lat long string " + str);
    return;
  }
  var lat = parseLL(vals[1]);
  var lng = parseLL(vals[2]);
  foundPath.changeWithMarker(foundPointIndex, new GLatLng(lat, lng));
}

function roundLL(val) {
    if (latLngFormat == 1) {
        var dms = toDegMinSec(val);
        dms[1] = round(dms[1]/10, 0) * 10; // Round minutes to 10th
        var sign = 1;
        if (dms[0] < 0) {
            sign = -1;
            dms[0] = Math.abs(dms[0]);
        }
        return (dms[0] + dms[1] / 60.0) * sign; // back to float
    } else {
        return round(val, 1);
    }
}

function snapPoint(point) {
    var lat = point.lat();
    var lng = point.lng();
    //if (document.getElementById('snap_lat_to_grid').checked) {
    //    lat = roundLL(lat);
    //}
   // if (document.getElementById('snap_lng_to_grid').checked) {
   //     lng = roundLL(lng);
   // }
    return new GLatLng(lat, lng);
}

var geocoder;

function trim(str) {
    var i = 0;
    var j = str.length - 1;
    while (i < str.length && str.charAt(i) == ' ') i++;
    while (j >= 0  && str.charAt(j) == ' ') j--;
    return str.substring(i, j+1);
}

function addBoat(str) {
//    var boats = str.split(',');
//    var first = (allBoats.length == 0);
//    for (var i = 0; i < boats.length; i++) {
//        var b = trim(boats[i]);
//        allBoats.push(new Boat(b));
//    }
    //alert(str);
	allBoats.push(new Boat(str));
//    if (first) {
        //setInterval("refreshAllBoats();", 10*60*1000); // every 10 mins
//    }
}

function showAddress(address) {
  if (address.search("ll: ") == 0) {
    changePoint(address);
    return;
  }
  if (address.search("boat:") == 0) {
      addBoat(address.substring(address.indexOf(":")+1));
    return;
  }
  if (!geocoder) {
    geocoder = new GClientGeocoder();
  }
  geocoder.getLatLng(address,
                     function(point) {
    if (!point) {
      alert(address + " not found");
    } else {
      map.setCenter(point, 6);
      var newmarker = new GMarker(point);
      map.addOverlay(newmarker);
      GEvent.addListener(newmarker, "click", function() {
        map.removeOverlay(newmarker);
      });
    }
  }
                     );
}

function printAddress(s) {
  document.getElementById('Address').innerHTML = s;
}

function reverseGeocode(marker, point) {
    if (marker)
	return;
  if (!geocoder) {
    geocoder = new GClientGeocoder();
  }
  geocoder.getLocations(point,
			function(response) {
			    if (response.Status.code == 200) {
				printAddress(response.Placemark[0].address);
			    } else {
				printAddress("no address found: " + response.Status.code);
			    }
			});
}

function selectCallback(path) {
  if (!path.selected) {
    for (var i = 0; i < allPaths.length; i++) {
      if (allPaths[i] == path) {
        allPaths[i].select(true);
        currPath = path;
        showDist(path.dist());
        showPathInfo(path);
      } else {
        allPaths[i].select(false);
        allPaths[i].hideLastSegment();
      }
    }
  }
}

var myWidth = 0, myHeight = 0;

function getSize() {
  if( typeof( window.innerWidth ) == 'number' ) {
    myWidth = window.innerWidth; myHeight = window.innerHeight;
  } else if( document.documentElement && ( document.documentElement.clientWidth ||document.documentElement.clientHeight ) ) {
    myWidth = document.documentElement.clientWidth; myHeight = document.documentElement.clientHeight;
  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    myWidth = document.body.clientWidth; myHeight = document.body.clientHeight;
  }
  //  window.alert( 'Width = ' + myWidth + ' and height = ' + myHeight );
}

var colors =     ["#0000a0", "#00a000", "#a00000", "#000000", "#a0a000", "#a0a0a0", "#a000a0", "#a0a010", "#000000", "#808010"];
var iconColors = ["blue",    "green",   "red",     "black",   "yellow",  "gray",    "purple", "orange", "black", "brown"];
var markerIcons = [];

function initMarkerIcons() {
  for (var i = 0; i < iconColors.length; i++) {
    var ic = new GIcon();
    ic.image = "http://labs.google.com/ridefinder/images/mm_20_" + iconColors[i] + ".png";
    ic.shadow = "http://labs.google.com/ridefinder/images/mm_20_shadow.png";
    ic.iconSize = new GSize(12, 20);
    ic.shadowSize = new GSize(22, 20);
    ic.iconAnchor = new GPoint(6, 20);
    ic.infoWindowAnchor = new GPoint(5, 1);
    markerIcons.push(ic);
  }
}


function newPath(sel, name, vis, serverUser, userDrawn) {
  var ci;
  if (colors.length > allPaths.length) {
    ci = allPaths.length;
  } else {
    ci = 0;
  }
  if (name == "") {
      name = "Path" + allPaths.length;
  }
  var newp = new Path(map, name, serverUser, colors[ci], markerIcons[ci], userDrawn);
  allPaths.push(newp);
  if (vis) {
      newp.setVisible(true);
  }
  if (sel) {
    selectCallback(newp);
  }
}

function windowResized() {
  getSize();
  var mapdiv = document.getElementById("map");
  if (myWidth > 400) {
    mapdiv.style.width = (myWidth - 210) + "px";
  } else {
    mapdiv.style.width = myWidth + "px";
  }
  if (myHeight > 300) {
    mapdiv.style.height = (myHeight - 120) + "px";
  } else {
    mapdiv.style.height = myHeight + "px";
  }
}

//Start in Qingdao 	35°45'N 120°25'E
//New Zealand gate 	37°39'S 178°15'E ( Leave to starboard )
// West pacific gate 	47°00'S 147°30'W ( Leave to starboard )
// East pacific gate 	45°00'S 112°30'W ( Leave to starboard )
// Cape Horn 	56°12'S 67°16'W ( Leave to port )
// Finish in Rio de Janeiro* 	23°00'S 43°08'W

var gates = [[new GLatLng(35 + 45.0/60, 120+25.0/60), "Gindao<br/>Start"],
             [new GLatLng(-37 - 39.0/60, 178+15.0/60), "New Zealand<br/>Leave to starboard"],
             [new GLatLng(-47, -147-30.0/60), "West pacific gate<br/>Leave to starboard"],
             [new GLatLng(-45, -112-30.0/60), "East pacific gate<br/>Leave to starboard"],
             [new GLatLng(-56-12/60, -67-16.0/60), "Cape Horn<br/>Leave to port"],
             [new GLatLng(-23-0/60, -43-8.0/60), "Rio de Janeiro<br/>Finish"]
             ];

function createGateMark(point, info) {
    var gatemark = new GMarker(point, {draggable: false, icon: markerIcons[4]});
    map.addOverlay(gatemark);
    info += "<br/>";
    var lat = formatDegMinSec(point.lat());
    var lng = formatDegMinSec(point.lng());
    info += lat + "/" + lng;
    var thiz = gatemark;
    GEvent.addListener(gatemark, "click", function() {
            thiz.openInfoWindowHtml(info);
        });
}

function showVORGGates() {
    for (var i = 0; i < gates.length; i++) {
        createGateMark(gates[i][0], gates[i][1]);
    }
}


function load(id) {
    try {
    document.getElementById('wind_info').style.display = "none";
    document.getElementById('point_move').style.display = "none";
    document.getElementById('sail_time_table').style.display = "none";
    document.getElementById('segment_sail_info_table').style.display = "none";
    globalId = id
    if (GBrowserIsCompatible()) {
      initMarkerIcons();

      windowResized();

      var mapdiv = document.getElementById("map");

      window.onresize = windowResized;

      map = new GMap2(mapdiv);

      //      showAddress("Gisborne New Zealand");
      map.setCenter(new GLatLng(-40, -150), 4);

      if (myHeight < 300) {
        map.addControl(new GSmallZoomControl());
      } else {
        map.addControl(new GLargeMapControl());
      }
      map.addControl(new GMapTypeControl());
      map.enableDoubleClickZoom();
      modeControl = new ModeControl();
      map.addControl(modeControl);

      latLngDisplay = new LatLngDisplay()
      map.addControl(latLngDisplay);

      newPath(true, "", true, "", true);

      if (id != 'noid') {
	  loadPathList(id);
	  new PointOfInterests("Private", id, "user");
	  new PointOfInterests("User speedcams", "public", "user");
	  new PointOfInterests("Imported speedcams", "public", "gps-waypoints");
      }
      
      setNormalMode();

      //      GEvent.addListener(map, "movestart", hideOverlays);
      //      GEvent.addListener(map, "moveend", showOverlays);
      GEvent.addListener(map, "mousemove", updateCoords);
      GEvent.addListener(map, "click", reverseGeocode);

      //      var x=document.getElementById("latlng_format").selectedIndex;
      //      latLngFormat = document.getElementsByTagName("option")[x].value;
      //      x=document.getElementById("dist_unit").selectedIndex;
      //      distUnit = document.getElementsByTagName("option")[x].value;

      loadWindSelections();
      changeDistanceUnit(document.getElementById('dist_unit').value);
      //      changeLatLngFormat(document.getElementById('latlng_format').value);


      //setTimeout(showVORGGates, 2*1000);
      
    } else {
      debug("Browser not supported");
    }
  } catch (e) {
        debug("Exception: " + e);
    }
}

function toDegMinSec(v) {
    var res = new Array(3);
    var neg = 1;
    if (v < 0) {
	v = -v;
	neg = -1;
    }
    var deg = Math.floor(v);
    var min = (v - deg) * 60 + 0.0000001;
    var sec = (min - Math.floor(min)) * 60;
    min = Math.floor(min);
    res[0] = deg * neg;
    res[1] = min;
    res[2] = sec;
    return res;
}

function pad(v, n) {
    var sv = "" + v;
    while (v.length < n)
	v = "0" + v;
    return v;
}

function formatDegMinSec(v) {
    if (latLngFormat == 1) {
        var l = toDegMinSec(v);
	return pad(sprintf("%.2d", l[0]), 2) + "°" + pad(sprintf("%.2d", l[1]), 2) + "'" + pad(sprintf("%.2f", round(l[2],2)), 5) + '"';
    }
    return pad(sprintf("%.6f", round(v, 6)), 9);
}

var prevPoint;
function updateCoords(point) {
    if (!point) {
        point = prevPoint;
    }
    point = snapPoint(point);
    prevPoint = point;
    var lat = formatDegMinSec(point.lat());
    var lng = formatDegMinSec(point.lng());
    latLngDisplay.show(lat, lng);
    if (currentLineHit) {
      currentLineHit.lineHit(point);
    }
}

function loadPathList(id) {
    GDownloadUrl("/paths?cmd=list&id=" + id,
                 function(data, responseCode) {
                     if (responseCode == 200) {
                         var xml = GXml.parse(data);
                         var paths = xml.documentElement.getElementsByTagName("path");
                         for (var i = 0; i < paths.length; i++) {
                             newPath(false, paths[i].getAttribute("name"), false, globalId, false);
                         }
                     }
                 }
                 );
}

function setMeasureMode() {
  currentMode = Mode.MEASURE;
  if (modeHandler)
    GEvent.removeListener(modeHandler);
  modeHandler = GEvent.addListener(map, "click", measureCallback);
  if (moveHandler)
    GEvent.removeListener(moveHandler);
  
  moveHandler = GEvent.addListener(map, "mousemove", function(point) {
    if (currPath.size() > 0) {
      showDist(currPath.addDist(point));
      currPath.showSailInfoAtPoint(currPath.line[currPath.line.length-1], point, point);
      if (currentLineHit != currPath) // dont show last segment when we hit self and should add a point in between
        currPath.showLastSegment(point);
    }
  });
  modeControl.update();
}

function setNormalMode() {
  currentMode = Mode.NORMAL;
  modeControl.update();
  showDist(currPath.dist());
  if (moveHandler)
    GEvent.removeListener(moveHandler);
  if (modeHandler)
    GEvent.removeListener(modeHandler);
  modeHandler = GEvent.addListener(map, "click", pointInfoCallback);
}

function toggleZoomMode() {
  if (currentMode == Mode.ZOOM) {
    setNormalMode();
  } else {
    setZoomMode();
  }
}

function toggleMeasureMode() {
  if (currentMode == Mode.MEASURE) {
    setNormalMode();
    document.getElementById('segment_sail_info_table').style.display = "none";
    document.getElementById('point_move').style.display = "none";
    if (currPath) 
      currPath.hideLastSegment();
  } else {
    setMeasureMode();
  }
}

function setZoomMode() {
  currentMode = Mode.ZOOM;
  if (modeHandler)
    GEvent.removeListener(modeHandler);
  var zoomRect;
  var p = 0;
  var sw;
  var ne;
  var rect;
  if (moveHandler)
    GEvent.removeListener(moveHandler);
  modeHandler = GEvent.addListener(map, "click", function(marker, point) {
    if (!marker) {
      if (p == 0) {
        sw = ne = point;
        p = 1;
        rect = new Rectangle(new GLatLngBounds(sw, ne))
        map.addOverlay(rect);
        moveHandler = GEvent.addListener(map, "mousemove", function(point) {
          ne = point;
          map.removeOverlay(rect);
          rect = new Rectangle(new GLatLngBounds(sw, ne))
          map.addOverlay(rect);
        });
      } else if (p == 1) {
        ne = point;
        p = 0;
        map.removeOverlay(rect);
        GEvent.removeListener(moveHandler);
        lastUserBoundZoom = setBoundsZoom(new GLatLngBounds(sw, ne));
      }
    }
  });

  modeControl.update();
  showDist(currPath.dist());
}

function setBoundsZoom(bounds) {
    var p;
    if (Math.min(bounds.getSouthWest().lng(), bounds.getNorthEast().lng()) < -90 &&
        Math.max(bounds.getSouthWest().lng(), bounds.getNorthEast().lng()) > 90) { // crossing 180 meridian

        p = new GLatLngBounds(new GLatLng(Math.min(bounds.getSouthWest().lat(), bounds.getNorthEast().lat()),
                                          Math.max(bounds.getSouthWest().lng(), bounds.getNorthEast().lng())),
                              new GLatLng(Math.max(bounds.getSouthWest().lat(), bounds.getNorthEast().lat()),
                                          Math.min(bounds.getSouthWest().lng(), bounds.getNorthEast().lng())));
        var centerLng = p.getSouthWest().lng() + (p.getNorthEast().lng()+360 - p.getSouthWest().lng())/2;
        if (centerLng > 180) centerLng = centerLng - 360;
        map.setCenter(new GLatLng(p.getSouthWest().lat() + (p.getNorthEast().lat() - p.getSouthWest().lat())/2,
                                  centerLng));
    } else {
        p = new GLatLngBounds(new GLatLng(Math.min(bounds.getSouthWest().lat(), bounds.getNorthEast().lat()),
                                          Math.min(bounds.getSouthWest().lng(), bounds.getNorthEast().lng())),
                              new GLatLng(Math.max(bounds.getSouthWest().lat(), bounds.getNorthEast().lat()),
                                          Math.max(bounds.getSouthWest().lng(), bounds.getNorthEast().lng())));
                            
        map.setCenter(new GLatLng(p.getSouthWest().lat() + (p.getNorthEast().lat() - p.getSouthWest().lat())/2,
                                  p.getSouthWest().lng() + (p.getNorthEast().lng() - p.getSouthWest().lng())/2 ));
    }
    map.setZoom(map.getBoundsZoomLevel(p));
    return p;
}

function zoomToFit() {

  var minLat = 360;
  var maxLat = -360;
  var maxLng = -360;
  var minLng = 360;
  var found = 0;

  for (var i = 0; i < allPaths.length; i++) {
    if (allPaths[i].visible) {
      for (var l = 0; l < allPaths[i].line.length; l++) {
        found++;
        minLat = Math.min(allPaths[i].line[l].lat(), minLat);
        maxLat = Math.max(allPaths[i].line[l].lat(), maxLat);
        minLng = Math.min(allPaths[i].line[l].lng(), minLng);
        maxLng = Math.max(allPaths[i].line[l].lng(), maxLng);
      }
    }
  }
  if (found > 0) {
    setBoundsZoom(new GLatLngBounds(new GLatLng(minLat, minLng),
                                    new GLatLng(maxLat, maxLng)))
  }
}

function infoWin(obj, ind) {
  var alt = 0;
  var speed = 0;
  var time = 0;
  if (ind < obj.alts.length) {
    alt = obj.alts[ind];
  }
  if (ind < obj.speeds.length) {
    speed = obj.speeds[ind];
  }
  if (ind < obj.times.length) {
    time = obj.times[ind];
  }
  if (sailingMode && windGrid) {
      var course;
      if (ind < obj.line.length - 1) {
          course = bearing(obj.line[ind], obj.line[ind+1]);
      } else {
          course = bearing(obj.line[ind-1], obj.line[ind]);
      }
      course = course / Math.PI * 180;
      if (course < 0) course += 360;
      var wd = windGrid.windAtPoint(obj.line[ind]);
      //      debug("" + round(wd.dir, 0)  +  " " + round(course, 0) +  " " + round(wd.speed, 1));
      speed = calcBoatSpeed(wd.dir, course, wd.speed * MS_TO_KNTS /* wnd speed to knts*/) * 1.852 / 3.6; // Conv to m/s
  }
  var info = '<font size="-1"><table cellspacing=0 cellpadding=2 border=1>';
  info += ' <tr> <td></td> <td>Lat</td><td>Lon</td><td>Alt</td><td>Speed</td><td>Time</td></tr>';
  info += "<tr><td>Point</td>";
  info += "<td>" + formatDegMinSec(obj.line[ind].lat()) + "</td>";
  info += "<td>" + formatDegMinSec(obj.line[ind].lng()) + "</td>";
  info += "<td>" + rDist(alt) + "</td>";
  info += "<td>" + rSpd(3.6*speed) + spdUnitMark  + "</td>";
  info += "<td>" + time + "</td>";
  info += "</tr>";
  info += ' <tr> <td></td> <td>';
  info += distUnitMark;
  info += '</td><td>Course</td><td>Speed</td>';
  if (sailingMode && windGrid) {
      info += '<td></td><td>Time</td>';
  }
  info += '</tr>';
  if (ind < obj.line.length - 1) {
    info += "<tr><td>End</td>";
    var e = 0;
    for (i = ind; i < obj.line.length -1; i++) {
      e += distHaversine(obj.line[i], obj.line[i+1])
        }
    info += "<td>" + rDist(e / distUnit) + "</td>";
    info += "<td>" + radToBrng(bearing(obj.line[ind], obj.line[ind+1])) + "</td>";
    info += "<td>";
    if (obj.times.length == obj.line.length && obj.times[0] > 0) {
        info += rSpd(3.6*e*1000/(obj.times[obj.times.length-1] - obj.times[ind])) + spdUnitMark;
    }
    info += "</td>";
    info += "<td></td><td>";
    if (sailingMode && windGrid) {
        info += formatTime(calcSailingTime(obj, ind, -1));
    }
    info += "</td>";
    info += "</tr>";
  }
   if (ind < obj.line.length - 2) {
     info += "<tr><td>Next</td>";
     info += "<td>" + rDist(distHaversine(obj.line[ind+1], obj.line[ind]) / distUnit) + "</td>";
     info += "<td>" + radToBrng(bearing(obj.line[ind], obj.line[ind+1])) + "</td>";
     info += "<td></td>";
     info += "<td></td><td>";
     if (sailingMode && windGrid) {
         info += formatTime(calcSailingTime(obj, ind, ind + 2));
     }
     info += "</td>";
     info += "</tr>";
   }
  if (ind > 0) {
    info += "<tr><td>Start</td>";
    var e = 0;
    for (i = 0; i < ind; i++) {
        e += distHaversine(obj.line[i], obj.line[i+1]);
    }
    info += "<td>" + rDist(e / distUnit) + "</td>";
    info += "<td>" + radToBrng(bearing(obj.line[ind], obj.line[0])) + "</td>";
    if (obj.times.length == obj.line.length && obj.times[0] > 0) {
        info += "<td>" + rSpd(3.6*e*1000/(obj.times[ind] - obj.times[0])) + spdUnitMark + "</td>";
    }
    info += "</tr>";
  }
//   if (ind > 1) {
//     info += "<tr><td>Prev</td>";
//     info += "<td>" + rDist(distHaversine(obj.line[ind], obj.line[ind-1]) / distUnit) + "</td>";
//     info += "<td>" + radToBrng(bearing(obj.line[ind], obj.line[ind-1])) + "</td>";
//     info += "</tr>";
//   }
  if (ind > 0 && ind < obj.line.length - 1) {
    info += "<tr><td>Total</td>";
    var e = 0;
    for (i = 0; i < obj.line.length - 1; i++) {
      e += distHaversine(obj.line[i], obj.line[i+1]);
    }
    info += "<td>" + rDist(e / distUnit) + "</td>";
    info += "<td>" + radToBrng(bearing(obj.line[0], obj.line[obj.line.length-1])) + "</td>";
    info += "</tr>";
  }
  info += "</table>";
  info += '<a onclick="deleteCurrentPoint();">Delete point</a>';
  info += "</font>";
  return info;
}

function formatTime(s) {
    var h = Math.floor(s / 3600);
    s -= h * 3600;
    var m = Math.floor(s / 60);
    s -= m * 60;
    var d = Math.floor(h / 24);
    h -= d * 24;
    var str = "";
    if (d > 0) {
        if (d < 10) d = "0" + d;
        str = d + "d ";
    }
    if (h < 10) h = "0" + h;
    if (m < 10) m = "0" + m;
    if (s < 10) s = "0" + s;
    str += h + ":" + m + ":" + s;
    return str;
}

function endReached(ex, x, ey, y, xi, yi) {
  if (xi > 0 && x >= ex)
    return true;
  if (xi < 0 && x <= ex)
    return true;
  if (yi > 0 && y >= ey)
    return true;
  if (yi < 0 && y <= ey)
    return true;
  return false;
}

function calcSailingTime(obj, ind1, ind2) {
    var i;
    var time = 0;
    var INCR = 0.1;
    var EPSILON = 0.00001;
    var BIGNUM = 1000000;
    if (ind2 == -1 || ind2 > obj.line.length) {
        ind2 = obj.line.length;
    }
    if (ind1 < 0) {
        ind1 = 0;
    }
    for (i = ind1; i < ind2 - 1; i++) {
      var ps = obj.line[i];
      var pe = obj.line[i+1];
      var course = bearing(ps, pe);
      course = course / Math.PI * 180;
      if (course < 0) course += 360;
      var ps_lng = ps.lng();
      var pe_lng = pe.lng();
      if (ps_lng > 90 && pe_lng < -90) {
          pe_lng = pe_lng + 360;
      } else if (ps_lng < -90 && pe_lng > 90) {
          ps_lng = ps_lng + 360;
      } 
      var latInc = 0;
      var lngInc = 0;
      if (Math.abs(pe_lng - ps_lng) < EPSILON) {
        if ((pe.lat() - ps.lat()) < 0)
          latInc = -BIGNUM;
        else 
          latInc = BIGNUM;
      } else 
        if (Math.abs(pe.lat() - ps.lat()) < EPSILON) {
          if ((pe_lng - ps_lng) < 0)
            lngInc = -BIGNUM;
          else 
            lngInc = BIGNUM;
        } else {
          latInc = (pe.lat() - ps.lat()) / Math.abs(pe_lng - ps_lng);
          lngInc = (pe_lng - ps_lng) / Math.abs(pe.lat() - ps.lat());
        }
      if (Math.abs(latInc) > Math.abs(lngInc)) {
        if (lngInc < 0) {
          lngInc = Math.abs(INCR / latInc) * -1;
        } else {
          lngInc = Math.abs(INCR / latInc);
        }
        if (latInc < 0)
          latInc = -INCR;
        else
          latInc = INCR;
      } else {
        if (latInc < 0) {
          latInc = Math.abs(INCR / lngInc) * -1;
        } else {
          latInc = Math.abs(INCR / lngInc);
        }
        if (lngInc < 0)
          lngInc = -INCR;
        else
          lngInc = INCR;
      }
      //      debug("ps: " + ps.lat() + "/" + ps.lng() + " pe: " + pe.lat() + "/" + pe.lng() +
      //            " latinc: " + latInc + " lngInc: " + lngInc);

      var p1 = ps;
      var cont;
      var pc;
      var ws = windGrid.windAtPoint(p1);
      cont = true;
      pc = p1;
      while (cont) {
        var p2 = new GLatLng(pc.lat() + latInc, pc.lng() + lngInc);
        if (endReached(pe.lat(), p2.lat(), pe.lng(), p2.lng(), latInc, lngInc)) {
          p2 = pe;
          cont = false;
        }
        var we = windGrid.windAtPoint(p2);
        if (ws != we || !cont) {

          // calc sailing between p1 and p2
          var speed = calcBoatSpeed(ws.dir, course, ws.speed * MS_TO_KNTS);
          speed = speed * 1.852 / 3.6; // was in knots. conf to m/s
          var dist = distHaversine(p1, p2) * 1000; // conv to m
          time += dist / speed;
          /*
          var m = new GMarker(p1);
          GEvent.addListener(m, "click", function() {
              this.openInfoWindowHtml("<b>" + speed + "</b>");
            });
          map.addOverlay(m);
          */

          p1 = p2;
          ws = we;
        }
        pc = p2;
      }
      /*
      if (sailingMode && windGrid) {
        var wd = windGrid.windAtPoint(p1);
        var speed = calcBoatSpeed(wd.dir, course, wd.speed * MS_TO_KNTS);
        speed = speed * 1.852 / 3.6; // was in knots. conf to m/s
        var dist = distHaversine(p1, p2) * 1000; // conv to m
        time += dist / speed;
      }
      */
      //

      //      debug("spd: " + speed + " dist " + dist + " len " + obj.line.length);
    }
    return round(time, 0);
}

// class Path start

function Path(map, name, userid, color, icon, userDrawn) {
  this.line = new Array();
  this.markers = new Array();
  this.alts = new Array();
  this.speeds = new Array();
  this.times = new Array();
  this.map = map;
  this.lineOverlay = 0;
  this.lastLineSegment = 0; // during mouse move in measure mode
  this.name = name;
  this.userid = userid;
  this.markersOnlyAtEnds = !userDrawn;
  this.userDrawn = userDrawn;
  if (userid == "") {
    this.loaded = true;
  } else {
    this.loaded = false;
  }
  this.selector = new PathSelector(this, this.name, userid);
  this.visible = false;
  this.selector.check(this.visible);
  this.selector.eventHandler(this);
  this.selected = false;
  this.shown = false;
  this.color = color;
  this.icon = icon;
  this.dirty = false;
  this.timerId = 0;
}

Path.prototype.size = function() {
  return this.line.length;
}

Path.prototype.select = function(s) {
  this.selected = s;
  this.selector.select(this.selected);
}

Path.prototype.dist = function() {
  var e = 0;
  for (i = 0; i < this.line.length - 1; i++) {
    e += distHaversine(this.line[i], this.line[i+1]);
  }
  return e;
}

Path.prototype.addDist = function(point) {
  return this.dist() + distHaversine(this.line[this.line.length-1],point);
}

Path.prototype.showLastSegment = function(point) {
  if (!this.visible || this.line.length == 0) return;
  if (this.lastLineSegment)
    this.map.removeOverlay(this.lastLineSegment);
  var lastSeg = [];
  lastSeg.push(this.line[this.line.length - 1]);
  lastSeg.push(point);
  this.lastLineSegment = new GPolyline(lastSeg, this.color, 4, 1, polyOptions);
  this.map.addOverlay(this.lastLineSegment);

  var dist1 = distHaversine(this.line[this.line.length-1], point);
  var course1 = radToDeg(bearing(this.line[this.line.length-1], point));
  document.getElementById('point_move').style.display = "block";
  showPathMove(dist1, course1, 0, 0);
}

Path.prototype.hideLastSegment = function() {
  if (!this.visible) return;
  if (this.lastLineSegment)
    this.map.removeOverlay(this.lastLineSegment);
}

function autoSave(p) {
    if (p.dirty && p.userDrawn && p.userid) {
        if (p.timerId) {
            clearTimeout(p.timerId);
            p.timerId = 0;
        }
        p.save(false);
    }
}

Path.prototype.mkdirty = function() {
    this.dirty = true;
    if (this.userDrawn && this.userid && !this.timerId) {
        var thiz = this;
        //this.timerId = setTimeout(function() { autoSave(thiz); }, 10*60*1000); // 20min
    }
}

Path.prototype.add = function(point) {
  if (!this.visible) return;
  this.mkdirty();
  this.line.push(point);
  if (this.lineOverlay) {
    this.map.removeOverlay(this.lineOverlay);
    this.lineOverlay = 0;
  }
  if (this.line.length > 1) {
    showDist(this.dist());
    showPathInfo(this);
  }
  this.createPolyline();
  if (this.markers.length > 1 && this.markersOnlyAtEnds) {
    this.map.removeOverlay(this.markers[1]);
    this.markers.pop();
  }
  this.addMarker(this.line.length - 1);
}

Path.prototype.findHitSegment = function(point) {
  var insertAfter = -1;
  var pc = map.fromLatLngToDivPixel(point);
  for (var i = 0; i < this.line.length - 1; i++) {
    var p1 = map.fromLatLngToDivPixel(this.line[i]);
    var p2 = map.fromLatLngToDivPixel(this.line[i+1]);
    var a1 = Math.atan2(pc.x - p1.x, pc.y - p1.y) / Math.PI * 180;
    var a2 = Math.atan2(pc.x - p2.x, pc.y - p2.y) / Math.PI * 180;
    a2 += 180;
    if (a1 < 0) a1 += 360;
    if (a2 < 0) a2 += 360;
    if (a1 >= 360) a1 -= 360;
    if (a2 >= 360) a2 -= 360;
    if (Math.abs(a1 - a2) < 20) {
      //      debug(p1.x + " " + p1.y + " " +  pc.x + " " +  pc.y + " " +  p2.x + " " +  p2.y +
      //            "al : " + a1 + " a2: " + a2 );
      insertAfter = i;
      break;
    }
  }
  return insertAfter;
}
    
Path.prototype.insert = function(point) {
  if (!this.visible) return;
  //
  var insertAfter = this.findHitSegment(point);
  if (insertAfter < 0) {
    debug("index to insert after not found");
    return;
  }
  if (this.lineOverlay) {
    this.map.removeOverlay(this.lineOverlay);
    this.lineOverlay = 0;
  }
  this.mkdirty();
  this.line.push(point); // just add one item
  var i;
  for (i = this.line.length - 1; i > insertAfter + 1; i--) {
    this.line[i] = this.line[i-1];
  }
  this.line[i] = point;
  this.createPolyline();

  for (i = 0; i < this.markers.length; i++) {
    this.map.removeOverlay(this.markers[i]);
  }
  this.addMarker(0);
  this.addMarker(this.line.length-1);
  if (!this.markersOnlyAtEnds) {
    for (i = 1; i < this.line.length - 1; i++) {
      this.addMarker(i);
    }
  }
}

Path.prototype.addPoint = function(point, alt, speed, time) {
  this.mkdirty();
  this.line.push(point);
  this.alts.push(alt);
  this.speeds.push(speed);
  this.times.push(time);
}

Path.prototype.clear = function() {
  this.line = new Array();
  this.lineOverlay = 0;
}

Path.prototype.setVisible = function(v) {
  if (this.visible != v) {
    if (!v) {
      this.show(v);
      this.visible = v;
      if (this.loaded && this.userid) {
          autoSave(this);
          this.loaded = false; // reload path from other source from server every time
          this.line = new Array(); // reset points
      }
    } else {
      this.visible = v;
      if (!this.loaded && this.userid) {
        this.loadFile();
      } else {
          this.show(v);
      }
    }
    this.selector.checkit(v);
  }
}

Path.prototype.loadFile = function() {
  var thiz = this;
  if (!this.userid) return;
  GDownloadUrl("/paths?cmd=get&id=" + this.userid + "&name=" + this.name,
               function(data, responseCode) {
                 if (responseCode == 200) {
                     //                     debug("got " + data);
                   var xml = GXml.parse(data);
                   var path = xml.documentElement.getElementsByTagName("path");
                   thiz.userDrawn = parseInt(path[0].getAttribute("manualdraw"));
                   thiz.markersOnlyAtEnds = !thiz.userDrawn;
                   var points = xml.documentElement.getElementsByTagName("point");
                   for (var i = 0; i < points.length; i++) {
                     var point = new GLatLng(parseFloat(points[i].getAttribute("lat")),
                                             parseFloat(points[i].getAttribute("lng")));
                     var alt = 0;
                     var speed = 0;
                     var time = 0;
                     if (points[i].getAttribute("alt")) {
                         alt = parseFloat(points[i].getAttribute("alt"));
                     }
                     if (points[i].getAttribute("speed") && points[i].getAttribute("speed") != "NaN") {
                         speed = parseFloat(points[i].getAttribute("speed"));
                     }
                     if (points[i].getAttribute("time")) {
                         time = Math.round(parseInt(points[i].getAttribute("time")) / 1000);
                     }
                     thiz.addPoint(point, alt, speed, time);
                   }
                   thiz.loaded = true;
                   thiz.show(true);
                   thiz.dirty = false;
                 } else {
                     debug(data);
                 }
               }
               );
}

Path.prototype.show = function(v) {
  if (this.visible && this.shown != v) {
    this.shown = v;
    if (v) {

      if (this.line.length > 0) {
	this.createPolyline();
        this.addMarker(0);
        this.addMarker(this.line.length-1);
        if (!this.markersOnlyAtEnds) {
            for (i = 1; i < this.line.length - 1; i++) {
                this.addMarker(i);
            }
        }
      }
    } else {
      if (this.lineOverlay) {
        this.map.removeOverlay(this.lineOverlay);
        this.lineOverlay = 0;
      }
      for (i = 0; i < this.markers.length; i++) {
        this.map.removeOverlay(this.markers[i]);
      }
    }
  }
}

Path.prototype.addMarker = function(ind) {
  var newmarker = new GMarker(this.line[ind], {draggable: true, icon: this.icon});
  this.map.addOverlay(newmarker);
  this.markers.push(newmarker);
  var obj = this;
  GEvent.addListener(newmarker, "click", function() {
                       foundPath = obj; foundPointIndex = ind;
                       newmarker.openInfoWindowHtml(infoWin(obj, ind));
                     });
  GEvent.addListener(newmarker, "dragend", function() {
                       obj.change(ind, newmarker.getLatLng());
                       newmarker.setLatLng(snapPoint(newmarker.getLatLng()));
                       document.getElementById('point_move').style.display = "none";
                       document.getElementById('sail_time_table').style.display = "none";
                       GEvent.addListener(map, "mousemove", updateCoords);
                     });

  GEvent.addListener(newmarker, "dragstart", function(p) {
                       document.getElementById('point_move').style.display = "block";
                       if (sailingMode && windGrid)
                           document.getElementById('sail_time_table').style.display = "block";
                       GEvent.removeListener(updateCoords);
                     });
  GEvent.addListener(newmarker, "drag", function(p) {
                       obj.update(ind, p);
                       updateCoords(p);
                     });

}

Path.prototype.lineHit = function(point) {
    if (sailingMode && windGrid) {
      var segment = this.findHitSegment(point);
      if (segment >= 0)
          this.showSailInfoAtSegmentPoint(point, segment);
    }
}

Path.prototype.showSailInfoAtSegmentPoint = function(point, segment) {
    if (sailingMode && windGrid) {
      this.showSailInfoAtPoint(this.line[segment], point, this.line[segment+1]);
    }
}

Path.prototype.showSailInfoAtPoint = function(p1, point, p2) {
    if (sailingMode && windGrid) {
      document.getElementById('segment_sail_info_table').style.display = "block";
      var course = bearing(p1, p2);
      course = course / Math.PI * 180;
      if (course < 0) course += 360;
      var ws = windGrid.windAtPoint(point);
      var rel_wind = Math.abs(ws.dir - course);
      if (rel_wind > 180)
        rel_wind = 360 - rel_wind;
      var dir = course - ws.dir;
      var boat_speed = calcBoatSpeed(ws.dir, course, ws.speed * MS_TO_KNTS);

      document.getElementById('wind_speed_at_point').innerHTML = round(ws.speed*windSpeedUnit, 1) + windSpeedUnitMark;
      document.getElementById('wind_dir_at_point').innerHTML = round(ws.dir, 0) + "&deg";
      document.getElementById('rel_wind_dir_at_point').innerHTML = round(rel_wind, 0) + "&deg";
      if (distUnit == 1) { // boat speed calc gives knots so conversion here is the "wrong way"
        boat_speed *= KNTS;
      }
      document.getElementById('boat_speed_at_point').innerHTML = round(boat_speed, 1) + spdUnitMark;
    }
}

Path.prototype.createPolyline = function() {
  this.lineOverlay = new GPolyline(this.line, this.color, 4, 1, polyOptions);
  var thiz = this;
  GEvent.addListener(this.lineOverlay, "mouseover", function() {
      currentLineHit = thiz;
      if (currentLineHit == currPath)
        thiz.hideLastSegment(); // hide last segment when we hit self and should add a point
      //      debug("mouseover on line");
    });
  GEvent.addListener(this.lineOverlay, "mouseout", function() {
          //      debug("mouseout on line");
      currentLineHit = 0;
      document.getElementById('segment_sail_info_table').style.display = "none";
    });
  this.map.addOverlay(this.lineOverlay);
}

Path.prototype.changeWithMarker = function(ind, point) {
  this.change(ind, point);
  foundPath.show(false);
  foundPath.show(true);
}
  
Path.prototype.change = function(ind, point) {
    this.mkdirty();
    point = snapPoint(point);
    if (this.lineOverlay) {
        this.map.removeOverlay(this.lineOverlay);
        this.lineOverlay = 0;
    }
    this.line[ind] = point;

    this.createPolyline();
    showDist(this.dist());
}

Path.prototype.update = function(ind, point) {
    this.mkdirty();
    point = snapPoint(point);
    var dist1 = 0;
    var course1 = 0;
    var dist2 = 0;
    var course2 = 0;
    if (ind > 0) {
        dist1 = distHaversine(this.line[ind-1], point);
        course1 = radToDeg(bearing(this.line[ind-1], point));
    }
    if (ind < this.line.length - 1) {
        dist2 = distHaversine(point, this.line[ind+1]);
        course2 = radToDeg(bearing(point, this.line[ind+1]));
    }
    if (sailingMode && windGrid) {
        showSailTime(calcSailingTime(this, ind - 1, ind + 1),
                     calcSailingTime(this, ind, ind + 2));
    }
    showPathInfo(this);
    showPathMove(dist1, course1, dist2, course2);
    
    this.change(ind, point);
}

Path.prototype.deletePoint = function(ind) {
    this.mkdirty();
    if (this.lineOverlay) {
        this.map.removeOverlay(this.lineOverlay);
        this.lineOverlay = 0;
    }
    for (var i = ind; i < this.line.length - 1; i++) {
        this.line[i] = this.line[i+1];
    }
    this.line.pop();
    this.createPolyline();
    showDist(this.dist());
    this.show(false);
    this.show(true);
}

function allspace(str) {
    var i;
    for (i = 0; i < str.length && (str.charAt(i) == ' ' || str.charAt(i) == '\t'); i++) {
    }
    return i == str.length;
}

function saveCurrentPath() {
    if (globalId == 'noid') {
        window.alert("Must be logged in to save!");
    } else {
        var name = document.getElementById('save_name').value;
        if (name == "" || allspace(name)) {
            alert("Enter a name for the path to save as");
        } else {
            currPath.rename(document.getElementById('save_name').value);
            currPath.save(true);
        }
    }
}

Path.prototype.rename = function(newname) {
    if (newname != "") {
        this.name = newname;
        this.selector.rename(newname);
    }
}

Path.prototype.save = function(confirm) {
    var RUN = 10;
    var responses = "";
    var failed = false;
    for (var start = 0; start < this.line.length; start += RUN) {
        var end = start + RUN;
        var sb = "";
        sb += "cmd=upload&manualdraw=1&";
        sb += "name=" + this.name + "&";
        this.userid = globalId;
        this.selector.userid = globalId;
        sb += "id=" + this.userid + "&";
        if (start == 0 && end < this.line.length) {
            sb += "part=first&";
        } else if (start > 0) {
            if (end < this.line.length) {
                sb += "part=mid&";
            } else {
                sb += "part=last&";
            }
        }
        for (var i = start; i < this.line.length && i < end; i++) {
            var loc = this.line[i];
            if (i > start)
                sb += "&";
            sb += "p=" + 
                loc.lat() + "," + 
                loc.lng() + "," + 
                "0" + "," + // alt
                "0"  + "," +  // speed
                "0"; // time stamp
        }
        //        debug(sb);
        var objHTTP;
        objHTTP = GXmlHttp.create();
        objHTTP.open('POST',"/paths",false);
        objHTTP.setRequestHeader('Content-Type',
                                 'application/x-www-form-urlencoded');
        objHTTP.send(sb);
        responses += objHTTP.responseText;
        responses += "Status:" + objHTTP.status;
        if (objHTTP.status != 200) {
            failed = true;
        }
    }
    if (failed) {
        debug(responses);
        window.alert("Save failed!");
    } else {
        this.dirty = false;
        if (confirm) {
            window.alert("Save OK\nThis path will be autosaved every 10 minutes\nand when hidden");
        }
        //        debug("saved");
    }
}

Path.prototype.remove = function() {
    //    debug("deleting " + this.name);
    this.setVisible(false);
    this.selector.remove();
    if (this.userid) {
        GDownloadUrl("/paths?cmd=del&id=" + this.userid + "&name=" + this.name,
                     function(data, responseCode) {
                         if (responseCode != 200) {
                             debug(data);
                             alert("Problem deleting from server! " + responseCode);
                         }
                     });
    }
    
}

// Path end

// class Boat start

function refreshAllBoats() {
    for (var i = 0; i < allBoats.length; i++) {
        if (allBoats[i].visible) {
            allBoats[i].loadBoatInfo();
        }
    }
}

function removeBoat(name) 
{
    for (var i = 0; i < allBoats.length; i++) 
	{
        if (allBoats[i].name == trim(name)) 
		{
            allBoats[i].visible = false;
			allBoats[i].removeLines();
        }
    }
	refreshAllBoats();
}


function Boat(name) {
  this.name = name;
  this.line = new Array();
  this.markers = new Array();
  this.lineOverlay = 0;
  this.marker = 0;

  this.xmlInfo = '';
  this.selector = new BoatSelector(this, this.name);
  this.color = "#00ff00";

  this.setVisible(true);
}

Boat.prototype.size = function() {
  return this.line.length;
}

Boat.prototype.removeLines = function() {
    if (this.lineOverlay) {
        map.removeOverlay(this.lineOverlay);
        map.removeOverlay(this.marker);
        this.lineOverlay = 0;
        this.marker = 0;
        this.line = [];
    }
}

Boat.prototype.setVisible = function(v) {
    this.visible = v;
    this.selector.check(this.visible);
    if (v) {
        this.loadBoatInfo();
    } else {
        this.removeLines();
    }
}

Boat.prototype.infoWin = function() {
    var sailedDist = 0;
    for (var i = 0; i < this.line.length - 1; i++) {
        sailedDist += distHaversine(this.line[i], this.line[i+1]);
    }

    var position = this.xmlInfo.documentElement.getElementsByTagName("position")[0];
    var lat = position.getElementsByTagName("latitude")[0].firstChild.nodeValue;
    var lng = position.getElementsByTagName("longitude")[0].firstChild.nodeValue;

    var course = position.getElementsByTagName("cap")[0].firstChild.nodeValue;
    var speed = position.getElementsByTagName("vitesse")[0].firstChild.nodeValue;
    var wind_speed = position.getElementsByTagName("wind_speed")[0].firstChild.nodeValue;
    var wind_angle = position.getElementsByTagName("wind_angle")[0].firstChild.nodeValue;
    var rank = position.getElementsByTagName("classement")[0].firstChild.nodeValue;

    var info = '<font size="-1">' + this.name + '<table cellspacing=0 cellpadding=2 border=1>';
    info += ' <tr> <td>Lat/Lng</td> <td>' + formatDegMinSec(lat) + '</td><td>' + formatDegMinSec(lng) + '</td></tr>';
    info += ' <tr> <td>Course</td> <td>' + course + '</td></tr>';
    info += ' <tr> <td>Speed</td> <td>' + speed + '</td></tr>';
    info += ' <tr> <td>Wind Speed</td> <td>' + wind_speed + '</td></tr>';
    info += ' <tr> <td>Wind Angle</td> <td>' + wind_angle + '</td></tr>';
    info += ' <tr> <td>Rank</td> <td>' + rank + '</td></tr>';
    info += ' <tr> <td>Dist sailed</td> <td>' + rDist(sailedDist/distUnit) + " " + distUnitMark + '</td></tr>';
    info += "</table></font>";

    return info;
}

/*
   method : POST/GET
   url    : Call url
   func   : custom function which is used to process returned data,
            take only one parameter
*/
function CDownloadUrl(method, url, func) {
   var httpObj;
   var browser = navigator.appName;
   if(browser.indexOf("Microsoft") > -1)
      httpObj = new ActiveXObject("Microsoft.XMLHTTP");
   else
      httpObj = new XMLHttpRequest();
 
   httpObj.open(method, url, true);
   httpObj.onreadystatechange = function() {
      if(httpObj.readyState == 4){
         if (httpObj.status == 200) {
            var contenttype = httpObj.getResponseHeader('Content-Type');
            if (contenttype.indexOf('xml')>-1) {
               func(httpObj.responseXML);
            } else {
               func(httpObj.responseText);
            }
         } else {
            func('Error: '+httpObj.status);
         }
      }
   };
   httpObj.send(null);
}

Boat.prototype.loadBoatInfo = function() {
  var thiz = this;

  //GDownloadUrl("http://volvogame.virtualregatta.com/get_user.php?pseudo=" + this.name + "&rnd=" + Math.random(),
  GDownloadUrl("get_user.php?pseudo=" + this.name + "&rnd=" + Math.random(),
               function(data, responseCode) {
                 if (responseCode == 200) {
                     thiz.removeLines();

                     //                     debug("got " + data);
                     thiz.xmlInfo = GXml.parse(data);

                     if (data.match("error msg=") || data.match("ERROR:")) {
                         alert("error: " + data + " name: " + escape(thiz.name));
                         return;
                     }

                     var trajectoire = thiz.xmlInfo.documentElement.getElementsByTagName("trajectoire");
                     var valStr = trajectoire[0].childNodes[0].nodeValue;
                     var points = valStr.split(";");
                     for (var i = 0; i < points.length; i++) {
                         var lng_lat = points[i].split("!");
                         if (lng_lat.length == 2) {
                             thiz.line.push(new GLatLng(parseFloat(lng_lat[1]), parseFloat(lng_lat[0])));
                         }
                     }
                     var position = thiz.xmlInfo.documentElement.getElementsByTagName("position")[0];
                     var lat = position.getElementsByTagName("latitude")[0].firstChild.nodeValue;
                     var lng = position.getElementsByTagName("longitude")[0].firstChild.nodeValue;

                     
                     thiz.line.push(new GLatLng(parseFloat(lat), parseFloat(lng)));

                     thiz.lineOverlay = new GPolyline(thiz.line, thiz.color, 1, 1);
                     map.addOverlay(thiz.lineOverlay);
                     //thiz.marker = new GMarker(thiz.line[thiz.line.length-1], {draggable: false, icon: markerIcons[1]});
                     //GEvent.addListener(thiz.marker, "click", function() {
                     //        thiz.marker.openInfoWindowHtml(thiz.infoWin());
                     //    });

                     //map.addOverlay(thiz.marker);
                 } else {
                     debug(data);
                 }
               }
               );
}

// Boat end

// Rectangle start

// A Rectangle is a simple overlay that outlines a lat/lng bounds on the
// map. It has a border of the given weight and color and can optionally
// have a semi-transparent background color.
function Rectangle(bounds, opt_weight, opt_color) {
  this.bounds_ = bounds;
  this.weight_ = opt_weight || 1;
  this.color_ = opt_color || "#888888";
}
Rectangle.prototype = new GOverlay();

// Creates the DIV representing this rectangle.
Rectangle.prototype.initialize = function(map) {
  // Create the DIV representing our rectangle
  var div = document.createElement("div");
  div.style.border = this.weight_ + "px solid " + this.color_;
  div.style.position = "absolute";

  // Our rectangle is flat against the map, so we add our selves to the
  // MAP_PANE pane, which is at the same z-index as the map itself (i.e.,
  // below the marker shadows)
  map.getPane(G_MAP_MAP_PANE).appendChild(div);

  this.map_ = map;
  this.div_ = div;
}

// Remove the main DIV from the map pane
Rectangle.prototype.remove = function() {
  this.div_.parentNode.removeChild(this.div_);
}

// Copy our data to a new Rectangle
Rectangle.prototype.copy = function() {
  return new Rectangle(this.bounds_, this.weight_, this.color_,
                       this.backgroundColor_, this.opacity_);
}

// Redraw the rectangle based on the current projection and zoom level
Rectangle.prototype.redraw = function(force) {
  // We only need to redraw if the coordinate system has changed
  if (!force) return;

  // Calculate the DIV coordinates of two opposite corners of our bounds to
  // get the size and position of our rectangle
  var c1 = this.map_.fromLatLngToDivPixel(this.bounds_.getSouthWest());
  var c2 = this.map_.fromLatLngToDivPixel(this.bounds_.getNorthEast());

  // Now position our DIV based on the DIV coordinates of our bounds
  this.div_.style.width = Math.abs(c2.x - c1.x) + "px";
  this.div_.style.height = Math.abs(c2.y - c1.y) + "px";
  this.div_.style.left = (Math.min(c2.x, c1.x) - this.weight_) + "px";
  this.div_.style.top = (Math.min(c2.y, c1.y) - this.weight_) + "px";
}
  
Rectangle.prototype.setBounds = function(bounds) {
  this.bounds = bounds;
}

// Rectangle end

// LatLngDisplay start    
function LatLngDisplay() {
    this.latDisplay = 0;
    this.lngDisplay = 0;
}

LatLngDisplay.prototype = new GControl();

LatLngDisplay.prototype.initialize = function(map) {
  var container = document.createElement("div");
  container.style.font = "small Courier";
  container.setAttribute("title", "Click to change lat/lng display mode");

  container.onclick = function() { toggleLatLngFormat(); }

  container.appendChild(document.createTextNode("Lat "));
  this.latDisplay = document.createTextNode("0")
  container.appendChild(this.latDisplay);
  container.appendChild(document.createTextNode(" Lng "));
  this.lngDisplay = document.createTextNode("0")
  container.appendChild(this.lngDisplay);
  map.getContainer().appendChild(container);
  return container;
}

LatLngDisplay.prototype.show = function(lat, lng) {
    this.latDisplay.data = lat;
    this.lngDisplay.data = lng;
}

LatLngDisplay.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(65, 7));
}

// LatLngDisplay end

// ModeControl start
function ModeControl() {
}
ModeControl.prototype = new GControl();

// Creates a one DIV for each of the buttons and places them in a container
// DIV which is returned as our control element. We add the control to
// to the map container and return the element for the map class to
// position properly.


ModeControl.prototype.initialize = function(map) {
  var container = document.createElement("div");

  this.measureDiv = document.createElement("div");
  this.setButtonStyle_(this.measureDiv);
  container.appendChild(this.measureDiv);
  this.measureDiv.appendChild(document.createTextNode("Add points"));
  GEvent.addDomListener(this.measureDiv, "click", function() {
                          toggleMeasureMode();
  });

  this.rectZoomDiv = document.createElement("div");
  this.rectZoomDiv.setAttribute("title", "Select for rectanlge zoom mode. Select two points on the map to zoom to the area.");
  this.setButtonStyle_(this.rectZoomDiv);
  container.appendChild(this.rectZoomDiv);
  this.rectZoomDiv.appendChild(document.createTextNode("Zoom"));
  GEvent.addDomListener(this.rectZoomDiv, "click", function() {
                          toggleZoomMode();
  });

  map.getContainer().appendChild(container);
  return container;
}

// By default, the control will appear in the top left corner of the
// map with 7 pixels of padding.
ModeControl.prototype.getDefaultPosition = function() {
  return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 34));
}

// Sets the proper CSS for the given button element.
ModeControl.prototype.setButtonStyle_ = function(button) {
  //  button.style.textDecoration = "underline";
  button.style.color = "#000000";
  button.style.backgroundColor = "white";
  button.style.font = "small Arial";
  button.style.border = "1px solid black";
  button.style.padding = "1px";
  button.style.marginBottom = "2px";
  button.style.textAlign = "center";
  button.style.width = "6em";
  button.style.cursor = "pointer";
}

ModeControl.prototype.update = function() {
  if (currentMode == Mode.NORMAL) {
    this.measureDiv.style.backgroundColor = "white";
    this.rectZoomDiv.style.backgroundColor = "white";
  } else if (currentMode == Mode.MEASURE) {
    this.measureDiv.style.backgroundColor = "gray";
    this.rectZoomDiv.style.backgroundColor = "white";
  } else if (currentMode == Mode.ZOOM) {
    this.measureDiv.style.backgroundColor = "white";
    this.rectZoomDiv.style.backgroundColor = "gray";
  }

}

// ModeControl

// class PathSelector start

function deletePath(path) {
    var a = confirm("Really delete " + path.name);
    //    debug(a);
    if (!a) {
        return;
    }
    var found = false;
    for (var i = 0; i < allPaths.length; i++) {
        if (path == allPaths[i]) {
            found = true;
            //            debug("removing path " + i);
        }
        if (found && i < allPaths.length - 1) {
            allPaths[i] = allPaths[i+1];
        }
    }
    if (found) {
        allPaths.pop();
    }
    path.remove();
}

function PathSelector(path, name, userid) {
  this.div = document.getElementById("paths");
  this.path = path;
  this.text = document.createElement("font");
  this.input = document.createElement('INPUT');
  this.input.type = "checkbox";
  this.input.id = "check_" + name;
  this.div.appendChild(this.input);
  this.name = document.createTextNode(name);
  this.text.appendChild(this.name);
  if (userid) {
      var a = document.createElement('A');
      a.href = "/paths?cmd=getkml&id=" + userid + "&name=" + name;
      a.appendChild(document.createTextNode(" (kml) "));
      this.text.appendChild(a);

  }
  var a = document.createElement('A');
  a.onclick = function() { deletePath(path); }
  a.appendChild(document.createTextNode(" Delete"));
  this.text.appendChild(a);

  this.text.appendChild(document.createElement('BR'));
  this.div.appendChild(this.text);
}


PathSelector.prototype.rename = function(name) {
    this.name.nodeValue = name;
}

PathSelector.prototype.check = function(c) {
  this.input.checked = c;
}

PathSelector.prototype.select = function(s) {
  if (s) {
    this.text.style.backgroundColor = "#a0a0a0";
  } else {
    this.text.style.backgroundColor = "white";
  }
}
  
PathSelector.prototype.checkit = function(c) {
    this.input.checked = c;
}

PathSelector.prototype.remove = function(c) {
    this.div.removeChild(this.input);
    this.div.removeChild(this.text);
}

PathSelector.prototype.eventHandler = function(c) {
  var i = this.input;
  this.input.onclick = function() {
    c.setVisible(i.checked);
  }
  this.text.onclick = function() {
    selectCallback(c);
  }
}

// class PathSelector end

// class BoatSelector start

function BoatSelector(callback, name) {
  var div = document.getElementById("boats");
  this.text = document.createElement("font");
  this.input = document.createElement('INPUT');
  this.input.type = "checkbox";
  this.input.id = "check_" + name;
  var thiz = this;
  var i = this.input;
  this.input.onclick = function() {
      callback.setVisible(i.checked);
  }
  //div.appendChild(this.input);
  this.name = document.createTextNode(name);
  this.text.appendChild(this.name);
  this.text.appendChild(document.createElement('BR'));
  //div.appendChild(this.text);
}

BoatSelector.prototype.check = function(c) {
  this.input.checked = c;
}
  
BoatSelector.prototype.checkit = function(c) {
    this.input.checked = c;
}

// class BoatSelector end

// class PointOfInterests start 

function PointOfInterests(name, userid, source) {
  var div = document.getElementById("pois");
  this.userid = userid;
  this.source = source;
  this.text = document.createElement("font");
  this.input = document.createElement('INPUT');
  this.input.type = "checkbox";
  this.input.id = "check_" + name;
  div.appendChild(this.input);
  this.text.appendChild(document.createTextNode(name));
  this.text.appendChild(document.createElement('BR'));
  div.appendChild(this.text);
  this.overlay = 0;
  var i = this.input;
  var id = this.userid;
  var thiz = this;
  this.input.onclick = function() {
      if (i.checked) {
	  var url = "http://pathtrack.appspot.com/poi?cmd=list&kml=t&id=" + id + "&source=" + thiz.source;
	  thiz.overlay = new GGeoXml(url);
	  GEvent.addListener(thiz.overlay, "load", function() {
		  if (!thiz.overlay.loadedCorrectly())
		      debug("failed to load" );
	      });
	  map.addOverlay(thiz.overlay);
      } else {
	  map.removeOverlay(thiz.overlay);
      }
  }
}

// class PointOfInterests end

var _i = 10;

function loadWindSelections() {
    GDownloadUrl("/wproxy?ifile=gribs.txt" + "&nocache=" + _i++,
                 function(data, responseCode) {
                     if (responseCode == 200) {
                         createWindSelector(data);
                     } else {
                         debug("response code" + responseCode);
                         debug(data);
                     }
                 });
}

function showWinds(ind) {
  var first = true;
  if (windGrid) {
    windGrid.show(false);
    first = false;
  }
  if (ind >= 0) {
    windGrid = windGridList[ind];
    if (windGrid.loaded == false) {
        windGrid.loadFile(first);
    } else {
        windGrid.show(true);
    }
  } else if (ind == -2) {
      reloadWinds();
  }
}

function reloadWinds() {
    var div = document.getElementById("winds");
    while (div.childNodes.length > 0) {
        div.removeChild(div.childNodes[0]);
    }
    windGrid = 0;
    windGridList = [];
    loadWindSelections();
}

function createWindSelector(str) {

    var lines = str.split('\n');
    var div = document.getElementById("winds");
    var select = document.createElement("select");
    select.onchange=function() { showWinds(this.value); }
    var opt = document.createElement("option");
    var text = document.createTextNode("-- select wind --");
    opt.value = -1;
    opt.appendChild(text);
    select.add(opt, null);

    text = document.createTextNode("-- reload --");
    opt = document.createElement("option");
    opt.value = -2;
    opt.appendChild(text);
    select.add(opt, null);

    for (var i = 0; i < lines.length; i++) {
        var fields = lines[i].split(':');
        if (fields.length > 2) {
            opt = document.createElement("option");
            opt.value = i;
            text = document.createTextNode(fields[1] + "/" + fields[3]);
            opt.appendChild(text);
            select.add(opt, null);
            windGridList.push(new WindGrid(fields[0]));
        }
    }

    div.appendChild(select);
}

// class WindGrid start

function GridDef(start, end, num, incr, isLng) {
    this.start = start;
    this.end = end;
    this.num = num;
    if (isLng) {
        if (start > 0 && end < 0) {
            end = 360 + end;
        }
    } else {
        if (start > end && incr > 0)
            incr *= -1;
    }
    this.incr = incr;
}

function WindDef(dir, speed) {
     // File tells wind dir where it is blowing to
    // convert to from
    dir += 180;
    if (dir > 360) dir -= 360;
    this.dir = dir;
    this.speed = speed;
    this.flag = 0;
}

function WindGrid(file) {
    this.file = file;
    this.latGrid = 0;
    this.lngGrid = 0;
    this.gridVals = 0;
    this.visible = false;
    this.loaded = false;
}

WindGrid.prototype.loadFile = function(first) {
  var thiz = this;
  if (first) {
      if (lastUserBoundZoom) {
          windAreaBounds = lastUserBoundZoom;
          lastUserBoundZoom = 0;
      } else {
          windAreaBounds = map.getBounds();
      }
  }
  // wind area bounds are point NW and SE while map is SW and NE
  var boundsParam = "nwpnt=" +
  round(windAreaBounds.getNorthEast().lat(),0) + "," +
  round(windAreaBounds.getSouthWest().lng(),0) +
  "&sepnt=" +
  round(windAreaBounds.getSouthWest().lat(),0) + "," +
  round(windAreaBounds.getNorthEast().lng(),0);

  //  debug(boundsParam);

  GDownloadUrl("/wproxy?" + boundsParam + "&ifile=" + this.file + "&nocache=" + _i++,
               function(data, responseCode) {
                 if (responseCode == 200) {
                   var lines = data.split('\n');
                   var lat = lines[0].split(' ');
                   var lng = lines[1].split(' ');
                   var size = lines[2].split(' ');
                   thiz.latGrid = new GridDef(parseFloat(lat[0]), parseFloat(lat[1]),
                                              parseFloat(size[1]), parseFloat(lat[2]), false);
                   thiz.lngGrid = new GridDef(parseFloat(lng[0]), parseFloat(lng[1]),
                                              parseFloat(size[0]), parseFloat(lng[2]), true);
                   thiz.gridVals = new Array();
                   var i;
                   for (i = 3; i < lines.length; i++) {
                     var wnd = lines[i].split(' ');
                     if (wnd.length == 2) {
                         thiz.gridVals.push(new WindDef(parseFloat(wnd[1]), parseFloat(wnd[0])));
                     }
                   }
                   thiz.loaded = true;
                   //                   if (first) {
                   //                     thiz.zoomTo();
                   //                   }
                   thiz.show(true);
                 } else {
                     debug("response code" + responseCode);
                     debug(data);
                 }
               });
}

WindGrid.prototype.windAtPoint = function(point) {
    var lng = point.lng();
    if (this.lngGrid.start > 0 && lng < 0) {
        lng += 360;
    }
    var lat_i = round((point.lat() - this.latGrid.start) / this.latGrid.incr, 0);
    var lng_i = round((lng - this.lngGrid.start) / this.lngGrid.incr, 0);
    var i = lat_i * this.lngGrid.num + lng_i;
    if (i >= this.gridVals.length)
        i = this.gridVals.length - 1;
    if (i < 0)
        i = 0;
    return this.gridVals[i];
}
    
WindGrid.prototype.zoomTo = function() {
    var sw = new GLatLng(this.latGrid.start, this.lngGrid.start);
    var ne = new GLatLng(this.latGrid.end, this.lngGrid.end);
    setBoundsZoom(new GLatLngBounds(sw, ne));
}

WindGrid.prototype.show = function(v) {
  if (v == this.visible)
    return;
  this.visible = v;
  var i, j;
  if (this.visible) {
    var k = 0;
    for (i = 0; i < this.latGrid.num; i++) {
      for (j = 0; j < this.lngGrid.num; j++, k++) {
        if (this.gridVals[k].flag == 0) {
          this.gridVals[k].flag = windFlag(new GLatLng(i * this.latGrid.incr + this.latGrid.start,
                                                       j * this.lngGrid.incr + this.lngGrid.start),
                                           this.gridVals[k].dir, this.gridVals[k].speed);
        }
        map.addOverlay(this.gridVals[k].flag);
      }
    }
  } else {
    for (i = 0; i < this.gridVals.length; i++) {
      map.removeOverlay(this.gridVals[i].flag);
    }
  }
}

// class WindGrid end

function windFlag(pos, dir, speed) {
    var poleL = 0.3; // in lats
    var flagL = 10;  // part of poleL
    var center = map.getCenter();
    var cp = map.fromLatLngToContainerPixel(center);
    var p1 = map.fromLatLngToContainerPixel(new GLatLng(center.lat() + poleL/2, center.lng()));
    var p2 = map.fromLatLngToContainerPixel(new GLatLng(center.lat() - poleL/2, center.lng()));
    poleL = Math.abs(p1.y - p2.y);
    flagL = poleL / flagL;
    var flgs = Math.max(numBft(speed), 2);

    var line = new Array();
    var x;
    var y;
    x = 0; y = poleL / 2;
    line.push(new GPoint(x, y));
    y -= poleL;
    line.push(new GPoint(x, y));

    x += flagL*flgs/2 + flagL/4;
    line.push(new GPoint(x, y));
    /*
    while (flgs > 0) {
        x += flagL*Math.min(flgs, 4);
        line.push(new GPoint(x, y));
        y += flagL / 2;
        x = 0;
        line.push(new GPoint(x, y));
        flgs -= 4;
    }
    */
    var rad = dir * Math.PI / 180;
    var m00 = Math.cos(rad);
    var m11 = m00;
    var m10 = -1 * Math.sin(rad);
    var m01 = Math.sin(rad);
    for (i = 0; i < line.length; i++) {
        line[i] = new GPoint(line[i].x * m00 + line[i].y * m10,
                             line[i].x * m01 + line[i].y * m11);
    }
    
    var i;
    for (i = 0; i < line.length; i++) {
        line[i] = map.fromContainerPixelToLatLng(new GPoint(line[i].x + cp.x, line[i].y + cp.y));
    }
    for (i = 0; i < line.length; i++) {
        line[i] = new GLatLng(line[i].lat() - center.lat() + pos.lat(),
                              line[i].lng() - center.lng() + pos.lng());
    }
    var width = numBft(speed) / 2;
    if (width < 1) width = 1;
    var p = new GPolyline(line, spdToCol(speed), width);
    GEvent.addListener(p, "mouseover", function() {
            document.getElementById('wind_info').style.display = "block";
            showWind(dir, speed);
        });
    GEvent.addListener(p, "mouseout", function() {
            document.getElementById('wind_info').style.display = "none";
        });
    
    return p;
}

function showWind(dir, speed) {
    document.getElementById('wind_dir').innerHTML = round(dir,0) + "&deg;";
    document.getElementById('wind_speed').innerHTML = round(speed*windSpeedUnit,1) + windSpeedUnitMark;
}

//var spdColors = [ "#FEFFFF", "#FEE7E7", "#FEDBDB", "#FEC3C3", "#FEB7B7", "#FE9F9F",     "#FE9393", "#FE7B7B",     "#FE6F6F",     "#FE6363",     "#FE5757",     "#FE4B4B",     "#FE3F3F",     "#FE3333",     "#FE2727",     "#FE1B1B",     "#FE0F0F",     "#FE0303"];
var spdColors = [ "#A0FFFF", "#017EFF", "#0000FE", "#00F00A", "#FFFF00", "#FEA900", "#FF5300", "#FD0100", "#FF009E", "#FF00FC"];

function spdToCol(spd) {
    spd = numBft(spd);
    spd = round(spd, 0);
    if (spd >= spdColors.length) spd = spdColors.length - 1;
    return spdColors[spd];

}

function numBft(sp) {
    sp *= 2; // to knots
    if (sp < 1) return 0;
    if (sp < 3) return 1;
    if (sp < 7) return 2;
    if (sp < 11) return 3;
    if (sp < 16) return 4;
    if (sp < 21) return 5;
    if (sp < 27) return 6;
    if (sp < 34) return 7;
    if (sp < 41) return 8;
    if (sp < 48) return 9;
    if (sp < 56) return 10;
    if (sp < 64) return 11;
    return 12;
}


/**
*
*  Javascript sprintf
*  http://www.webtoolkit.info/
*
*
**/

sprintfWrapper = {

	init : function () {

		if (typeof arguments == 'undefined') { return null; }
		if (arguments.length < 1) { return null; }
		if (typeof arguments[0] != 'string') { return null; }
		if (typeof RegExp == 'undefined') { return null; }

		var string = arguments[0];
		var exp = new RegExp(/(%([%]|(\-)?(\+|\x20)?(0)?(\d+)?(\.(\d)?)?([bcdfosxX])))/g);
		var matches = new Array();
		var strings = new Array();
		var convCount = 0;
		var stringPosStart = 0;
		var stringPosEnd = 0;
		var matchPosEnd = 0;
		var newString = '';
		var match = null;

		while (match = exp.exec(string)) {
			if (match[9]) { convCount += 1; }

			stringPosStart = matchPosEnd;
			stringPosEnd = exp.lastIndex - match[0].length;
			strings[strings.length] = string.substring(stringPosStart, stringPosEnd);

			matchPosEnd = exp.lastIndex;
			matches[matches.length] = {
				match: match[0],
				left: match[3] ? true : false,
				sign: match[4] || '',
				pad: match[5] || ' ',
				min: match[6] || 0,
				precision: match[8],
				code: match[9] || '%',
				negative: parseInt(arguments[convCount]) < 0 ? true : false,
				argument: String(arguments[convCount])
			};
		}
		strings[strings.length] = string.substring(matchPosEnd);

		if (matches.length == 0) { return string; }
		if ((arguments.length - 1) < convCount) { return null; }

		var code = null;
		var match = null;
		var i = null;

		for (i=0; i<matches.length; i++) {

			if (matches[i].code == '%') { substitution = '%' }
			else if (matches[i].code == 'b') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(2));
				substitution = sprintfWrapper.convert(matches[i], true);
			}
			else if (matches[i].code == 'c') {
				matches[i].argument = String(String.fromCharCode(parseInt(Math.abs(parseInt(matches[i].argument)))));
				substitution = sprintfWrapper.convert(matches[i], true);
			}
			else if (matches[i].code == 'd') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)));
				substitution = sprintfWrapper.convert(matches[i]);
			}
			else if (matches[i].code == 'f') {
				matches[i].argument = String(Math.abs(parseFloat(matches[i].argument)).toFixed(matches[i].precision ? matches[i].precision : 6));
				substitution = sprintfWrapper.convert(matches[i]);
			}
			else if (matches[i].code == 'o') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(8));
				substitution = sprintfWrapper.convert(matches[i]);
			}
			else if (matches[i].code == 's') {
				matches[i].argument = matches[i].argument.substring(0, matches[i].precision ? matches[i].precision : matches[i].argument.length)
				substitution = sprintfWrapper.convert(matches[i], true);
			}
			else if (matches[i].code == 'x') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
				substitution = sprintfWrapper.convert(matches[i]);
			}
			else if (matches[i].code == 'X') {
				matches[i].argument = String(Math.abs(parseInt(matches[i].argument)).toString(16));
				substitution = sprintfWrapper.convert(matches[i]).toUpperCase();
			}
			else {
				substitution = matches[i].match;
			}

			newString += strings[i];
			newString += substitution;

		}
		newString += strings[i];

		return newString;

	},

	convert : function(match, nosign){
		if (nosign) {
			match.sign = '';
		} else {
			match.sign = match.negative ? '-' : match.sign;
		}
		var l = match.min - match.argument.length + 1 - match.sign.length;
		var pad = new Array(l < 0 ? 0 : l).join(match.pad);
		if (!match.left) {
			if (match.pad == '0' || nosign) {
				return match.sign + pad + match.argument;
			} else {
				return pad + match.sign + match.argument;
			}
		} else {
			if (match.pad == '0' || nosign) {
				return match.sign + match.argument + pad.replace(/0/g, ' ');
			} else {
				return match.sign + match.argument + pad;
			}
		}
	}
}

sprintf = sprintfWrapper.init;