﻿/*
 * Represents a path point. An array of these is held in the global PathPoints, 
 * which is central to representing a course on the map.
 *      Members:
 *          - Location (get, set)
 *          - Note (get, set)
 */
function PathPoint(LatLng,Note)
{
    this.Location = LatLng;
    this.Note = Note;
}

PathPoint.prototype =
{
    /*
     * Returns the location of this path point as a GLatLng.
     */
    getLocation: function()
    {
        return this.Location;
    },

    /*
     * Sets the location of this path point. value should be a GLatLng.
     */
    setLocation: function(value)
    {
        this.Location = value;
    },
        
    /*
     * Gets the note text for this path point
     */
    getNote: function()
    {
        if(this.Note==null)
            return "";
        return this.Note;
    },
    
    /*
     * Sets the note text for this path point
     */
    setNote: function(value)
    {
        this.Note = value;
    },
    
    /*
     * Returns a duplicate copy of this path point, not a reference to the same path point
     */
    Copy: function(value)
    {
        return new PathPoint(this.getLocation(),this.getNote());
    }
}

// Array of course points
var PathPoints = new Array();
var PathPointNotes = new Array(); // just an array of strings
var AdjustedPoint = null;
var AdjustedMarker = null;

// Just call RefreshMap
var PathPointLinesOnMap = new Array();

var PathLineColor="#00dd00";
var PathLineThickness=4;

// We need to know if a drag is in progress for when determining whether or not to hide non-closest markers
var DragIsInProgress=false;

/*
 * Most drag action goes on in the move handler. We just need to mark a drag as in progress at the start.
 */
function Marker_OnDragStart()
{
    // We don't want to process this event if there is an info window being displayed
    if(InfoWindowBeingDisplayed)
        return;

    DragIsInProgress=true;
}

/*
 * Most drag action goes on in the move handler. We just need to mark a drag as not in progress at the end.
 */
function Marker_OnDragEnd()
{
    DragIsInProgress=false;
    
    // In case we're in snap-to-road mode and the lines have been repositioned but the marker for a
    // path point has not.
    
    if(PathPoints[PathPoints.length-1] != undefined) {
        var Previous = PathPoints[PathPoints.length-1];
        var DistanceInMeters = PathPoints[PathPoints.length-1].getLocation().distanceFrom(this.getLatLng());
        var DistanceInKilometers=parseFloat(DistanceInMeters)/1000;
        var DistanceInMiles=DistanceInMeters*0.000621371192; // 1 meter = 0.000621371192 miles
        if(DistanceInMiles > 100) {
            alert("Your point is too far from previous point.");
            PathPoints[AdjustedMarker].setLocation(AdjustedPoint);
            RefreshMap();
            AdjustedMarker = null;
            AdjustedPoint = null;
        }else{
            RefreshMap(this.getLatLng());  
        }
    }    
   
}

/*
 * Event handler for a sprite drag handler
 *      - Each sprite will call this drag handler. Reference this to get the Marker map object
 */
function Marker_OnDrag()
{
    // We don't want to process this event if there is an info window being displayed
    if(InfoWindowBeingDisplayed)
        return;

    var LatLng=this.getLatLng();
    
    if(AdjustedMarker == null) {
        AdjustedMarker = IndexOfPathPointMarkerClosestToTheCursor;
        AdjustedPoint = PathPoints[AdjustedMarker].getLocation();
    }
    
    AdjustMarkerPosition(IndexOfPathPointMarkerClosestToTheCursor,LatLng);
    if(SnapToRoadEnabled)
        SnapLatLngToRoad(IndexOfPathPointMarkerClosestToTheCursor,LatLng);

    UpdateDistanceDisplay();
}

function AdjustMarkerPosition(MarkerIndex,LatLng)
{
    // Update our course point
    PathPoints[MarkerIndex].setLocation(LatLng);
    
    // Adjust the polyline before this point   
    if(MarkerIndex>0)
    {
        // Remove the existing line from the map
        map.removeOverlay(PathPointLinesOnMap[MarkerIndex-1]);
        
        // Create a new polyline and add the replacement to the map
        PathPointLinesOnMap[MarkerIndex-1]=new GPolyline(
            [PathPoints[MarkerIndex-1].getLocation(),PathPoints[MarkerIndex].getLocation()],
            PathLineColor, PathLineThickness);
        map.addOverlay(PathPointLinesOnMap[MarkerIndex-1]);
    }
    
    // Adjust the polyline after this point
    if(MarkerIndex<PathPointLinesOnMap.length)
    {
        // Remove the existing line from the map
        map.removeOverlay(PathPointLinesOnMap[MarkerIndex]);
        
        // Create a new polyline and add the replacement to the map
        PathPointLinesOnMap[MarkerIndex]=new GPolyline(
            [PathPoints[MarkerIndex].getLocation(),PathPoints[MarkerIndex+1].getLocation()],
            PathLineColor, PathLineThickness);
        
        map.addOverlay(PathPointLinesOnMap[MarkerIndex]);
    }
}

// Remembers the index of the course point marker the last time the mouse moved
// This is altered by ShowOnlyThePathPointMarkerClosestToTheCursor and is required because we don't want to
// be removing objects from the map when we're dragging them.
var IndexOfPathPointMarkerClosestToTheCursor=0;
/*
 * This is called by Map_OnMouseMove to show only one draggable middle course point marker
 */
function ShowOnlyThePathPointMarkerClosestToTheCursor(LatLng)
{
    // No need to sweat the small stuff
    if(PathPoints.length==0)
        return;

    // Find the course point closest to the cursor
    var PathPointClosestToTheCursor=GetIndexOfPathPointClosestToLatLng(LatLng);

    if(IndexOfPathPointMarkerClosestToTheCursor!=PathPointClosestToTheCursor)
    {
        // Refresh the map to show the closest marker
        RefreshMap(LatLng);
    
        // Remember that we've already refreshed the map
        IndexOfPathPointMarkerClosestToTheCursor=PathPointClosestToTheCursor;
    }
}

/*
 * The drag handler for an existing course point should only be shown if it is the closest one to the
 * cursor, just to keep things tidy.
 *      - CursorPosition is a GLatLng
 */
function GetIndexOfPathPointClosestToLatLng(CursorPosition)
{
    // If we don't have any markers on the map, don't even bother
    if(PathPoints.length==0||CursorPosition==null)
        return 0;
        
    // Determine which marker on the map is the closest
    var ClosestPathPointLocation=PathPoints[0].getLocation(); // Default to the first course point
    var DistanceBetweenCursorAndClosestPathPoint=ClosestPathPointLocation.distanceFrom(CursorPosition);
    for(var i=1;i<PathPoints.length;i++)
    {
        if(PathPoints[i]==null)
            continue;
    
        // Get the course point for this iteration
        var ThePathPointLocation=PathPoints[i].getLocation();
        
        // Figure out how far the current course point is from the cursor
        var DistanceBetweenThisPathPointAndTheCursor = ThePathPointLocation.distanceFrom(CursorPosition);
        
        // If this PathPoint turns out to be closer than the point previously thought to be the point closest
        // to the cursor, set it as the new closest point.
        if(DistanceBetweenThisPathPointAndTheCursor < DistanceBetweenCursorAndClosestPathPoint)
        {
            ClosestPathPointLocation=ThePathPointLocation;
            DistanceBetweenCursorAndClosestPathPoint=DistanceBetweenThisPathPointAndTheCursor;
        }
    }
    
    // Now we know which marker is the closest and will have to re-iterate over the markers and
    // set all markers to hidden except the one we selected
    for(i=0;i<PathPoints.length;i++)
    {
        if(PathPoints[i].getLocation()==ClosestPathPointLocation)
            return i;
    }
    
    // Close the info window
    MapPointInfoWindow_OnClose();
    RefreshMapPathPointsAndLines(LastCursorPosition);
}

/*
 * Leaves edit mode, the last point on the path will be considered the finish point
 */
function FinishPath()
{
    MapMode=ReadyMode;
    RefreshMap(LastCursorPosition);
}

/*
 * Walks the course backwards, adding points along the way
 */
function MakePathRoundTrip()
{
    // Make the path round trip by walking backwards through the array of path points
    for(var i=PathPoints.length-2;i>=0;i--)
        PathPoints[PathPoints.length]=PathPoints[i].Copy();
    FinishPath();
}

/*
 * Finishes the path by adding one more point that is the same as the start point, thus going directly
 * there from the last point that was added.
 */
function FinishPathAtStartPoint()
{
    // Make a new path point that is the same as the start
    PathPoints[PathPoints.length]=PathPoints[0].Copy();
    FinishPath();
}


/*
 * Refreshes all map path points and lines.
 * This should only be called by RefreshMap()
 */
function RefreshMapPathPointsAndLines(LatLng)
{
    // Draw lines for the course ponts on the map
    PathPointLinesOnMap = new Array();    
    for(var i=0;i<PathPoints.length;i++)
    {
        // Create a new line. A green one
        if(i<PathPoints.length-1)
        {
            var polyline = new GPolyline([PathPoints[i].getLocation(),PathPoints[i+1].getLocation()], PathLineColor, PathLineThickness);
	        map.addOverlay(polyline);
	        PathPointLinesOnMap[i]=polyline;
	    }

	    var NewIcon = new GIcon();
        NewIcon.shadowSize = new GSize(0, 0);
        NewIcon.infoWindowAnchor = new GPoint(9, 2);
        NewIcon.infoShadowAnchor = new GPoint(18, 25);
	    
	    // Stop right here if we're not adding any markers to the map
	    if(!ShowMapMarkers)
	        continue;
	    
	    // In just a moment we're going to add a maker for the vertice. We'll need to know which type
	    // of marker icon to use. We figure this out like so:
	    //      start   - first element in the array
	    //      end     - last element in the array IF we're in MAP EDIT MODE (when MapMode == PathEditMode)
	    //      middle  - default, just a middle point
	    var VerticeMarkerIcon="css/app/path/";
	    if(i==0)
	    {
            VerticeMarkerIcon += "start.png";
            NewIcon.iconSize = new GSize(28,28);	       
            NewIcon.iconAnchor = new GPoint(14,26);
	    }
	    else if(i==PathPoints.length-1)
	    {
            VerticeMarkerIcon += "stop.png";
            NewIcon.iconSize = new GSize(28,28);	       
            NewIcon.iconAnchor = new GPoint(14,26);
	    }
	    else
	    {
	        if(PathPoints[i].getNote().length>0)
	        {
    	        VerticeMarkerIcon += 'map_point_w_notes.png';
                NewIcon.iconSize = new GSize(28,28);
                NewIcon.iconAnchor = new GPoint(2,28);
    	    }
    	    else
    	    {
    	        VerticeMarkerIcon += 'map_point.png';
                NewIcon.iconSize = new GSize(13,13);
                NewIcon.iconAnchor = new GPoint(6.5,12);
    	    }
	        
	        // But wait, because we only really want to show middle markers if the cursor is near we'll need to
    	    // check ourselves here. If the point has a note, however, we will display a marker for
    	    // it regardless of whether or not it is the closest point to the cursor.    	    
            if(PathPoints[i].getNote().length==0 &&
            (i!=GetIndexOfPathPointClosestToLatLng(LatLng) || MapMode != PathEditMode))
                continue;
	    }
	    
        NewIcon.image = VerticeMarkerIcon;
	    
	    if(VerticeMarkerIcon==G_DEFAULT_ICON)
	        NewIcon=null;
	    
	    // Add a marker for the vertice
	    var marker = new GMarker(PathPoints[i].getLocation(),
	        {
	            draggable: true,
	            bouncy: true,
	            clickable: true,
	            icon: NewIcon
	        });
	    if(MapMode==PathEditMode)
    	    marker.enableDragging();
    	else
    	    marker.disableDragging();

        // Set up an event handler for the marker's drag event
        GEvent.addListener(marker, "drag", Marker_OnDrag);
        GEvent.addListener(marker, "dragstart", Marker_OnDragStart);
        GEvent.addListener(marker, "dragend", Marker_OnDragEnd);
        if(MapMode==PathEditMode)
            GEvent.addListener(marker, "click", Marker_OnClick);
        else
            GEvent.addListener(marker, "click", function()
                { 
                    alert('If you want to modify the course or add notes to points along the path, click the "Edit course" link in the big yellow box.');
                });

        // Add the marker to the map
        map.addOverlay(marker);
    }
}

/*
 * Event handler for the click event of one of the course point markers
 * We take this opportunity to pop up an info window.
 */
function Marker_OnClick()
{
    // We don't want to process this event if there is an info window being displayed
    if(InfoWindowBeingDisplayed)
        return;
        
    // Find path point at a given pixel on the map div
    var Index = GetIndexOfPathPointClosestToLatLng(this.getLatLng());
    var Html='<div style="padding-top: 5px;">You can move this point around by dragging<br />the marker to a different location on the map.</div>';
    Html += '<br />';
    Html += '<b>Notes:</b><br />';
    Html += '<textarea id="txtNote" cols="30" rows="3" style="border: solid black 1px;" onchange="SaveTextFromMapPointInfoPopup();">'
    Html += PathPoints[Index].getNote();
    Html += '</textarea><br />';
    Html += '<a href="" onclick="RemovePathPointAtIndex(' + Index + '); return false;">';
    Html += 'Remove this point</a>';
    Html += '<br />';
    if(Index>0&&Index<PathPoints.length)
    {
        Html += '<a href="" onclick="BisectPath(' + "'" + (Index-1) + "','" + Index + "'" + '); return false;">';
        Html += 'Insert point before this</a>';
        Html += '<br />';
    }
    if(Index>=0&&Index<PathPoints.length-1)
    {
        Html += '<a href="" onclick="BisectPath(' + Index + ',' + (Index+1) + '); return false;">';
        Html += 'Insert point after this</a>';
        Html += '<br />';
    }
    Html += '<a href="" onclick="map.closeInfoWindow(); return false;">Close this popup</a>';
    InfoWindowBeingDisplayed=true;
    map.openInfoWindowHtml(this.getLatLng(),Html,{onCloseFn: function() { eval('MapPointInfoWindow_OnClose('+Index+');'); }, noCloseOnClick: true });
    setTimeout("$get('txtNote').focus();",500);
}

/*
 * Inserts a point between two existing path points
 */
function BisectPath(FirstPathPointIndex,SecondPathPointIndex)
{
    // Later on in this routine we need the first path point index to be less than the
    // second path point index so if they were passed in the wrong order, we need to put
    // them in order now
    if(FirstPathPointIndex<SecondPathPointIndex)
    {
        var OldFirstPathPointIndex = FirstPathPointIndex;
        FirstPathPointIndex=SecondPathPointIndex;
        SecondPathPointIndex=OldFirstPathPointIndex;
    }

    // Build a polyline out of the two points we 
    var LatLngsForLineSegmentToBeBisected = new Array();
    LatLngsForLineSegmentToBeBisected.push(PathPoints[FirstPathPointIndex].getLocation());
    LatLngsForLineSegmentToBeBisected.push(PathPoints[SecondPathPointIndex].getLocation());
    var Polyline = new GPolyline(LatLngsForLineSegmentToBeBisected);
    
    // Figure out the distance between the two points, put the new point at the bisect
    var DistanceBetweenTheTwoPoints = Polyline.Distance();
    var PositionOfNewPathPoint =  Polyline.GetPointAtDistance(DistanceBetweenTheTwoPoints/2);    
    var NewPathPoint = new PathPoint(PositionOfNewPathPoint,'')
    
    // Copy over any elements of the array before the insert, then copy the insert over,
    // then copy over any that are remaining
    var NewPathPoints = new Array();
    for(var i=0;i<FirstPathPointIndex;i++)
        NewPathPoints.push(PathPoints[i]);
    NewPathPoints.push(NewPathPoint);
    for(var i=FirstPathPointIndex;i<PathPoints.length;i++)
        NewPathPoints.push(PathPoints[i]);
    PathPoints=NewPathPoints;
    
    CloseInfoWindowProperly();
}

/*
 * Removes a path point from the list of path points
 *      - index is the index in PathPoints of the element to be removed
 */
function RemovePathPointAtIndex(index)
{
    // Create a new list of path points
    var NewPathPoints = new Array();
    
    // Add all path points EXCEPT the one specified at index
    for(var i=0;i<PathPoints.length;i++)
        if(i!=index)
            NewPathPoints.push(PathPoints[i]);
    PathPoints = NewPathPoints;
    
    // Refresh the map
    RefreshMap(LastCursorPosition);
}

/*
 * Called from the onchange handler of the textarea
 */
function SaveTextFromMapPointInfoPopup()
{
    if(InfoWindowBeingDisplayed==false)
        return;

    var NoteHadTextBeforeThis = PathPoints[IndexOfPathPointMarkerClosestToTheCursor].getNote().length>0;

    var NoteText = $get('txtNote').value;
    PathPoints[IndexOfPathPointMarkerClosestToTheCursor].setNote(NoteText);
    
    // If the map had text before this and now doesn't, OR if the map didn't have text before and now does,
    // refresh the map
    if((NoteText.length>0)!=NoteHadTextBeforeThis)
    {
        // Because the Map_OnMouseMove event handler doesn't run the block that updates the variable that determines
        // which point on the path is closest to the cursor when we have an info window is displayed we need to force
        // it to show the path point closest to the cursor before refreshin the map
        CloseInfoWindowProperly();
    }
}

/*
 * Event handler for the close event of the info window for path markers
 */
function MapPointInfoWindow_OnClose()
{
    CloseInfoWindowProperly();
}

function CloseInfoWindowProperly()
{
    setTimeout("InfoWindowBeingDisplayed=false;",200);
    map.closeInfoWindow();
    RefreshMap(LastCursorPosition);
}

var InfoWindowBeingDisplayed = false;

