function RouteDraw( map, descriptiondiv ){

	/* Store the map */
	this.Map = map;

	/* Linked list of the raw route, with inner routes */
	this.FirstPoint = null;
	this.LastPoint = null;

	/* Polyline of the raw route */
	this.Polyline = null;

	this.DescriptionDiv = descriptiondiv;

	this.Editable = false;
	this.Newable = false;
	
	this.MapDistance = null;
  this.SetMapDistance();
	var self = this;
	GEvent.addListener( this.Map, 'zoomend', function(){ self.SetMapDistance(); } );
}

RouteDraw.Settings = {
	UseGoogleDirections: true,
	WaypointRoute: {
	  Color: '#FF0000',
  	Width: 2,
	  Opacity: 0.25
	},
	RealRoute: {
  	Color: '#0000BB',
	  Width: 3,
  	Opacity: 1
	}
};

/* this method is called when waypoints are added or changed */
RouteDraw.prototype.OnWaypointUpdate = function(){}
/* this method is called when distance changes */
RouteDraw.prototype.OnDistanceUpdate = function(){}


/* Get the list of waypoints */
RouteDraw.prototype.GetWaypoints = function(){
	var waypoints = [];
	var point = this.FirstPoint;
	while( point ){
		if ( point.Previous ){
			for( var i=0; i < point.Connection.length; i++ ){
				var p = {
					lat: point.Connection[ i ].lat(),
					lng: point.Connection[ i ].lng()};
				if ( i ==0 ){
					p.height = point.Height;
				}
				waypoints.push( p );
			}
		} else {
			waypoints.push( {
				lat: point.Position.lat(), 
				lng: point.Position.lng(), 
				height: point.Height 
			});
		}
		point = point.Next;
	}
	return waypoints;
}

/* Get the cumulation of all seperated distances */
RouteDraw.prototype.GetDistance = function(){
	var distance = 0;
	var node = this.FirstPoint;
	while( node != null ){
		if ( node.Distance != null ){
			distance += Math.floor( node.Distance );
		}
		node = node.Next;
	}
	return distance;
}

RouteDraw.prototype.SaveRoute = function(){
	if ( this.FirstPoint == null || this.FirstPoint.Next == null ){
		return false;
  }

	this.Pause();

	/* Get all info */
  var Info = {
    Distance: this.GetDistance(),
		StartInfo: this.FirstPoint.StartInfo,
		Route: []
  };
  var node = this.FirstPoint;
	while( node != null ){
		if ( node.Connection != null ){
			/* Skip the first point which is in the previous point */
			for( var i = 1; i < node.Connection.length; i ++ ){
				var lat = node.Connection[i].lat();
		    var lng = node.Connection[i].lng();
				var point = {lat: lat, lng: lng}
				if ( i == 1 && node.Previous.Previous == null ){
					/* this is the second node */
					var point1 = {
						lat: node.Connection[0].lat(),
						lng: node.Connection[0].lng(),
						height: node.Previous.Height
					};
					Info.Route.push( point1 );
				}
				if ( i == node.Connection.length - 1 ){
					point.height = node.Height;
				}
				Info.Route.push( point );
			}
		}
		node = node.Next;
	}
	return this.SaveRouteForm( Info );
}

RouteDraw.prototype.SaveRouteForm = function( Info ){};

/* Delete last point. 2 points always remain */
RouteDraw.prototype.DeleteLastPoint = function(){
	if ( this.Polyline.getVertexCount() > 2 ){
    this.LastPoint = this.LastPoint.Previous;
		this.LastPoint.Next.Delete();
		this.Polyline.deleteVertex( this.Polyline.getVertexCount() - 1 );
	}
}

/* Set the distance used in the map. This is usefull for calculation the space needed for points of the road */
RouteDraw.prototype.SetMapDistance = function(){
	var bounds = this.Map.getBounds();
	this.MapDistance = bounds.getSouthWest().distanceFrom( bounds.getNorthEast() );
}

/* Init the route */
RouteDraw.prototype.Init = function(){
	if ( this.Polyline != null ){
    this.Map.removeOverlay( this.Polyline );
    delete this.Polyline;
  }
	
	var point = this.LastPoint;
  while( point != null ){
    var previous = point.Previous;
    point.Delete();
    point = previous;
  }

  this.FirstPoint = null;
  this.LastPoint = null;
}

RouteDraw.prototype.Start = function(  ){
	this.Init();

	this.DrawPolyLine( );

	this.Polyline.enableDrawing();
	this.Newable = true;
}

RouteDraw.prototype.DrawPolyLine = function( ){
	var list = [];

  this.Polyline = new GPolyline(
    [],
   	RouteDraw.Settings.WaypointRoute.Color,
    RouteDraw.Settings.WaypointRoute.Width,
    RouteDraw.Settings.WaypointRoute.Opacity
  );

  /* Events handling */
  var self = this;
  GEvent.addListener( this.Polyline, 'lineupdated', function(){
    self.PointsChanged();
  });
  GEvent.addListener( this.Map, 'singlerightclick', function(){
    self.Pause()
  });
  GEvent.addListener( this.Polyline, 'mouseover', function(){
    self.Editable = true;
    self.SetPolyline();
  });
  GEvent.addListener( this.Polyline, 'mouseout', function(){
    self.Editable = false;
    self.SetPolyline();
  });
  GEvent.addListener( this.Polyline, 'click', function( point ){
    self.Newable = true;
    self.SetPolyline();
  });

  this.Map.addOverlay( this.Polyline );
}

RouteDraw.prototype.PointsChanged = function(){
	var polylineLength = this.Polyline.getVertexCount();
	var pointsLength = this.PointsLength();

	this.SetPolyline();

	if ( polylineLength == pointsLength ){
		this.Movement();
	} else {
		var lastVertex = this.Polyline.getVertex( polylineLength - 1 );
		if ( this.LastPoint == null || ! this.LastPoint.Position.equals( lastVertex ) ){
			this.AddNewLastPoint( lastVertex );
		} else {
			this.AddNewInsertPoint( lastVertex );
		}
	}
	this.OnWaypointUpdate();
}

/* Movement, one of the nodes moved, find out which */
RouteDraw.prototype.Movement = function(){
	var point = this.LastPoint;
	var teller = this.Polyline.getVertexCount() - 1;
	while( point != null ){
		if ( ! point.Position.equals( this.Polyline.getVertex( teller ) ) ){
			point.SetPosition( this.Polyline.getVertex( teller ) );
			return true;
		}
		point = point.Previous;
		teller --;
	}
	return false;
}

/* Add a new node at the end of the list */
RouteDraw.prototype.AddNewLastPoint = function( newVertex ){
	if ( this.LastPoint == null ){
		this.FirstPoint = this.LastPoint = new RouteDraw_Point( this, null, newVertex );
	} else {
		this.LastPoint = new RouteDraw_Point( this, this.LastPoint, newVertex );
		this.LastPoint.InitRoute();
	}
}

/* A node has been inserted in the list, find out which */
RouteDraw.prototype.AddNewInsertPoint = function( ){
	var point = this.LastPoint.Previous;
  var teller = this.Polyline.getVertexCount() - 2;
  while( point != null ){
    if ( ! point.Position.equals( this.Polyline.getVertex( teller ) ) ){
			var next = point.Next;
			var newPoint = new RouteDraw_Point( this, point, this.Polyline.getVertex( teller ) );
			newPoint.Next = next;
			next.Previous = newPoint;
			newPoint.InitRoute();
      return true;
    }
    point = point.Previous;
    teller --;
  }
  return false;
}

RouteDraw.prototype.Pause = function(){
	this.Newable = false;
	this.SetPolyline();
}

RouteDraw.prototype.PointsLength = function(){
	var point = this.LastPoint;
	var teller = 0;
	while( point != null ){
		teller ++;
		point = point.Previous;
	}
	return teller;
}

RouteDraw.prototype.SetPolyline = function(){
	if ( ! this.Editable ){
		this.Polyline.disableEditing();
	}
	if ( this.Editable && this.Polyline.getVertexCount() > 1 ){
		this.Polyline.enableEditing();
	}
	if ( this.Newable ){
    this.Polyline.enableDrawing();
  }
}

function RouteDraw_Point( route, previous, position ){
	this.Route = route;

	/* Position */
	this.Position = position;

	/* Possible connections */
	this.QuickConnection = null;
	this.GoogleConnection = null;
	this.Connection = null;

	/* Last drawn connection */
	this.LastDrawnConnection = null;

	/* Google Tool to calculate distance */
	this.DirectionTool = null;

	/* Derogation of the waypoint to the last known Google Route point */
	this.MuchDerogation = false;

	/* Cache for distance */
	this.Distance = null;

  /* Linked list */
  this.Previous = previous;
  this.Next = null;
	if ( this.Previous != null ){
		this.Previous.Next = this;
	}

	this.Height = null;

	this.getHeight();

	this.StartInfo = null;
	if ( this.Previous == null ){
    this.GetStartInfo();
  }
}

RouteDraw_Point.prototype.GetGoogleRoute = function(){
  var self = this;
	this.DirectionTool = new GDirections( null, this.Route.DescriptionDiv );

  GEvent.addListener( this.DirectionTool, 'error', function(){
		alert( this.DirectionTool.getStatus().code );
  });

  GEvent.addListener( this.DirectionTool, 'load', function(){
		self.GoogleRouteReady();
  });

  this.DirectionTool.loadFromWaypoints( this.QuickConnection, {
    travelMode:   G_TRAVEL_MODE_WALKING, /* Currently beta! */
    getPolyline:  true
  });
}

RouteDraw_Point.prototype.GoogleRouteReady = function(){
	var polyline = this.DirectionTool.getPolyline();
  this.GoogleDistance = this.DirectionTool.getDistance();
	if ( polyline && this.GoogleDistance != null ){
		this.GoogleDistance = this.GoogleDistance.meters;
	  this.GoogleConnection = new Array();
  	for( var i = 0; i < polyline.getVertexCount(); i ++ ){
	    this.GoogleConnection.push( polyline.getVertex( i ) );
  	}
	  var derogation = this.Position.distanceFrom( polyline.getVertex( polyline.getVertexCount() - 1 ) ) / this.Route.MapDistance;
  	this.MuchDerogation = derogation > 0.02; // Meer dan 3% afwijking
	  this.CalculateBestRoute();
  	this.DrawRoute();
	}
	if ( this.Next ){
    this.Next.InitRoute();
	}
}

RouteDraw_Point.prototype.SetPosition = function( position ){
	this.Position = position;
	this.InitRoute();
	this.getHeight();
  if ( this.Previous == null ){
    this.GetStartInfo();
  }
}

RouteDraw_Point.prototype.getHeight = function(){
	this.Height = null;
	var self = this;
	setTimeout( function(){
		ClientHeightRequest.GetHeight( self.Position.lat(), self.Position.lng(), function( height ){
			self.Height = height;
			self.OnHeightResponse();
		} );
	}, 50 );
}

RouteDraw_Point.prototype.InitRoute = function(){
	if ( this.Previous ){
		this.GoogleDistance = null;
		this.GoogleConnection = null;
		this.MuchDerogation = false;
		this.ClearRoute();
    this.QuickConnection = [ this.Previous.GetLastPoint(), this.Position ];
	  this.CalculateBestRoute();
		var self = this;
		if ( RouteDraw.Settings.UseGoogleDirections ){
			setTimeout( function(){
				self.GetGoogleRoute();
			}, 5 );
		}
		/* Google usually answers in 100ms. At no answer, or delayed answer, draw default lines */
		setTimeout( function(){
			self.CalculateBestRoute();
			self.DrawRoute();
		}, 400 );		
  } else { 
		/* at movement of the first node */
		if ( this.Next ){
			this.Next.InitRoute();
		}
	}
}

RouteDraw_Point.prototype.GetLastPoint = function(){
	if ( this.Connection != null ){
		return this.Connection[ this.Connection.length - 1 ];
	}
	return this.Position;
}

RouteDraw_Point.prototype.CalculateBestRoute = function(){
	var bestRoute = null;	
	var GoogleConnectionAllowed = null;
	if ( this.Previous ){
		if ( this.GoogleConnection != null ){
			/* Zelf weinig afwijking, de vorige wel veel, dan 'terug naar de weg' */
			if ( ! this.MuchDerogation && this.Previous.MuchDerogation ){
				return[ this.Previous.GetLastPoint(), this.GoogleConnection[ this.GoogleConnection.length - 1 ] ];
      }

			var GoogleConnectionAllowed = true;

			/* Als je te ver afwijkt van de weg */
			if ( this.MuchDerogation ){
				GoogleConnectionAllowed = false;
			}

			/* Als de absolute route korter een factor korter is dan de route via Google */
			if ( this.Previous.Position.distanceFrom( this.Position ) / this.GoogleDistance < 0.3 ){
				GoogleConnectionAllowed = false;
			}

			if ( GoogleConnectionAllowed ){
				bestRoute = this.GoogleConnection;
				this.Distance = this.GoogleDistance;
			}
		}
		if ( ! bestRoute ){
			this.Distance = this.QuickConnection[ 0 ].distanceFrom( this.QuickConnection[ 1 ] );
			bestRoute = this.QuickConnection;
		}
	}
	if ( this.ConnectionUpdated( bestRoute ) ){
		this.Connection = bestRoute;
		this.Route.OnDistanceUpdate();
		return true;
	}
	return false;
}

RouteDraw_Point.prototype.ClearRoute = function(){
	if ( this.Polyline ){
		this.Route.Map.removeOverlay( this.Polyline );
		this.LastDrawnConnection = null;
	}
}

RouteDraw_Point.prototype.Nr = function(){
	var point = this.Previous;
	while( point != null && point.Previous != null ){
		point = point.Previous;
	}
	var teller = 0;
	while( point != null && ! this.Position.equals( point.Position )  ){
		teller++;
		point = point.Next;
	}
	return teller;
}

RouteDraw_Point.prototype.DrawRoute = function(){
	if ( this.Connection != null ){
		if ( this.ConnectionUpdated( this.LastDrawnConnection ) ){
		  this.ClearRoute();
			this.Polyline = new GPolyline(
  		  this.Connection,
				RouteDraw.Settings.RealRoute.Color,
				RouteDraw.Settings.RealRoute.Width,
				RouteDraw.Settings.RealRoute.Opacity
	  	);
			this.Route.Map.addOverlay( this.Polyline );
			this.LastDrawnConnection = this.Connection
		}
	}
}

RouteDraw_Point.prototype.Delete = function(){
	this.ClearRoute();
	if ( this.Previous ){
		this.Previous.Next = this.Next;
	}
	if ( this.Next ){
		this.Next.Previous = this.Previous;
	}
	this.Next = null;
	this.Previous = null;
}

RouteDraw_Point.prototype.GetStartInfo = function(){
	var self = this;
	self.StartInfo = {
		Zipcode: null,
		Place: null,
		Region: null,
		CountyCode: null
	};
	var geocoder = new GClientGeocoder();
	var latlng = this.Position;
  geocoder.getLocations(this.Position, function(addresses) {
    if(addresses.Status.code != 200) {
      alert("reverse geocoder failed to find an address for" + latlng.toUrlValue());
    }
    else {
			/* first correct zipcode is good enough */
			self.StartInfo.Zipcode = self.StartInfo.Place = self.StartInfo.Region = self.StartInfo.CountryCode = "";
			for(var i=0;i<addresses.Placemark.length;i++){
				if ( addresses.Placemark[i].AddressDetails.Accuracy == 5 ){
					var details = addresses.Placemark[i].AddressDetails.Country;
					self.StartInfo.Zipcode = details.AdministrativeArea.Locality.PostalCode.PostalCodeNumber;
					self.StartInfo.Place = details.AdministrativeArea.Locality.LocalityName;
					self.StartInfo.Region = details.AdministrativeArea.AdministrativeAreaName;
					self.StartInfo.CountryCode = details.CountryNameCode;
					return;
				}
			}
		}
	});
}
var Locallll = null;

RouteDraw_Point.prototype.ConnectionUpdated = function( comparedConnection ){
	if ( comparedConnection == null || this.Connection == null ){
		return true;
	}
	if ( comparedConnection.length != this.Connection.length ){
		return true;
	}
	for( var i = this.Connection.length - 1; i >= 0; i-- ){
		if ( ! this.Connection[ i ].equals( comparedConnection[ i ] ) ){
			return true;
		}
	}
	return false;
}

/* this method is called when distance changes */
RouteDraw_Point.prototype.OnHeightResponse = function(){}
