/******************************************************************************
 * @file    distancetool.js
 * @brief   This JavaScript file is used to create a tool
 *          that allows a user to measure distance.
 * @author  Kyle Overby
 *
 *  $Revision: YMmaps_Development_koverby/2 $
 *  $Date: 2008/12/05 01:58:49 $
 *  $LastEditedBy: koverby $
 *
 * $Copyright: 2007-2008, Southwest Research Institute.  All rights reserved. $
 *****************************************************************************/

/******************************************************************************
 *  $Log: distancetool.js $
 *  Revision YMmaps_Development_koverby/2 2008/12/05 01:58:49 koverby
 *     Added lables that show the distance of each
 *     segement of a measurement.
 *  
 *
 *****************************************************************************/

/******************************************************************************
 * This Prototype class is used to create a widget that allows a user to
 * measure distance.
 *
 * @author Kyle Overby
 * @since November 3rd, 2008
 * @version $Revision: YMmaps_Development_koverby/2 $
 *****************************************************************************/
DistanceTool = Class.create({

    /** Constructor */
    initialize:function(){

        /** If a MapTool passed in, associate it with Search Tool */
        if(arguments[0]) {
            this.map = arguments[0];
        }
        else this.map = null;

        this.distanceMarkers = new Array();

        this.distanceLines = new Array();

        this.distanceValues = new Array();

        this.distanceLabels = new Array();

        this.distanceEventListener = null;

        this.lastMarker = null;

        this.showLabels = true;

        this.useMetric = true;

        this.totalDistance = 0;

        this.distanceToolHeader = Builder.node("div", {className:"distanceToolHeader"});
        this.distanceToolHeader.innerHTML = "Distance Measurement Tool";

        this.resetDistanceToolButton = Builder.node("button", {type:"submit", style:"position:absolute; left:20px; font-size:14px;"});
        this.resetDistanceToolButton.innerHTML = "Reset Tool";

        this.removeLastSegmentButton = Builder.node("button", {type:"submit", style:"position:absolute; left:130px; font-size:14px;"});
        this.removeLastSegmentButton.innerHTML = "Remove Last Segment";

        this.toggleUnitsTypeButton = Builder.node("button", {type:"submit", style:"position:absolute; top:40px; left:20px; font-size:14px;"});
        this.toggleUnitsTypeButton.innerHTML = "Show: ft/mi";

        this.toggleLabelsButton = Builder.node("button", {type:"submit", style:"position:absolute; top:40px; left:120px; font-size:14px;"});
        this.toggleLabelsButton.innerHTML = "Hide Segment Distances";

        this.distanceControlsContainer = Builder.node("div", {className:"distanceControlsContainer"}, [this.resetDistanceToolButton, this.removeLastSegmentButton, this.toggleUnitsTypeButton, this.toggleLabelsButton]);

        this.distanceToolTotalDistanceLabel = Builder.node("div");
        this.distanceToolTotalDistanceLabel.innerHTML = "Total Distance:";

        this.distanceToolTotalDistanceValue = Builder.node("div", {style:"position:absolute; top:0px; left:120px;"});
        this.distanceToolTotalDistanceValue.innerHTML = "0.00m";

        this.distanceToolTotalDistance = Builder.node("div", {className:"distanceToolMeasurementsListItem"}, [this.distanceToolTotalDistanceLabel, this.distanceToolTotalDistanceValue]);

        this.distanceToolMeasurementsList = Builder.node("div", {className:"distanceToolMeasurementsList"}, [this.distanceToolTotalDistance]);

        this.distanceToolWindow = Builder.node("div", {className: "distanceToolWindow", style:"display:none;"}, [this.distanceToolHeader, this.distanceControlsContainer, this.distanceToolMeasurementsList]);

        this.resetDistanceToolButtonEvent = this.resetDistanceTool.bindAsEventListener(this);
        Event.observe(this.resetDistanceToolButton, "click", this.resetDistanceToolButtonEvent);

        this.removeLastSegmentButtonEvent = this.removeLastSegment.bindAsEventListener(this);
        Event.observe(this.removeLastSegmentButton, "click", this.removeLastSegmentButtonEvent);

        this.toggleUnitsTypeButtonEvent = this.toggleUnits.bindAsEventListener(this);
        Event.observe(this.toggleUnitsTypeButton, "click", this.toggleUnitsTypeButtonEvent);

        this.toggleLabelsButtonButtonEvent = this.toggleLabels.bindAsEventListener(this);
        Event.observe(this.toggleLabelsButton, "click", this.toggleLabelsButtonButtonEvent);
    },

    /** Used to set the Map Tool */
    setMap:function(map) {
        this.map = map;
    },

    openDistanceTool:function() {

        /** Reset parameters */
        this.distanceMarkers = new Array();
        this.distanceLines = new Array();
        this.distanceValues = new Array();
        this.distanceLabels = new Array();
        this.lastMarker = null;
        this.setTotalDistanceValue(0);

        this.distanceEventListener = GEvent.addListener(this.map, "click", function(overlay, latlng) {
            if (latlng) {

                var markerTitle = "Distance Marker " + (this.distanceMarkers.length + 1);

                /** Create custom marker icon */
                var iconOptions = {};
                iconOptions.width = 32;
                iconOptions.height = 32;
                iconOptions.primaryColor = "#00FF00";
                iconOptions.cornerColor = "#FFFFFF";
                iconOptions.strokeColor = "#000000";
                var customIcon = MapIconMaker.createMarkerIcon(iconOptions);
                var markerOptions = {title:markerTitle, icon:customIcon, draggable:true, zIndexProcess:function (marker,b){return 2;}};

                /** Create the GMarker for this well */
                var marker = new GMarker(latlng, markerOptions);

                GEvent.addListener(marker,"drag",function(){
                    this.dragMarker(marker);
                }.bind(this));

                this.map.addOverlay(marker);

                /** Add it to the marker list */
                this.distanceMarkers.push(marker);

                /** Add a line between the two markers is last marker not null */
                if (this.lastMarker != null) {
                     var distancePolyLine = new GPolyline([this.lastMarker.getLatLng(), marker.getLatLng()], "#00FF00", 4, 0.75, {geodesic:true});
                     this.map.addOverlay(distancePolyLine);

                     /** Add the line to the list of lines */
                     this.distanceLines.push(distancePolyLine);

                     var segmentDistance = this.lastMarker.getPoint().distanceFrom(marker.getPoint());

                     this.distanceValues.push(segmentDistance);

                     /** Create the label for this segment */
                     var midPoint = distancePolyLine.getBounds().getCenter();

                     var distanceLabel = new ELabel(midPoint, this.formatDistanceForDisplay(segmentDistance), "distanceLabelsMap", new GSize(-20,-5), 75);

                     if (this.showLabels == false)
                         distanceLabel.hide();

                     this.map.addOverlay(distanceLabel);

                     /** Add the label to the list of labels */
                     this.distanceLabels.push(distanceLabel);

                     /** Get the new total distance */
                     var newTotalDistance = this.totalDistance + segmentDistance;

                     /** Set the new total distance */
                     this.setTotalDistanceValue(newTotalDistance);
                }

                /** Set the new marker to the last marker */
                this.lastMarker = marker;
            }
        }.bind(this));
    },

    closeDistanceTool:function() {

        /** Remove event listener */
        GEvent.removeListener(this.distanceEventListener);

        /** Remove all markers */
        this.removeAllMarkers();
        this.removeAllPolylines();
        this.removeAllLabels();
    },

    resetDistanceTool:function() {

        /** Remove all markers */
        this.removeAllMarkers();
        this.removeAllPolylines();
        this.removeAllLabels();

        /** Reset parameters */
        this.distanceMarkers = new Array();
        this.distanceLines = new Array();
        this.distanceValues = new Array();
        this.distanceLabels = new Array();
        this.lastMarker = null;

        /** Reset the total distance */
        this.setTotalDistanceValue(0);
    },

    removeLastSegment:function() {

        /** Only proceed if there is a segment to remove */
        if (this.distanceMarkers.length > 1) {

            /** Extract the last added values from each list */
            var lMarker = this.distanceMarkers.pop();
            var lPolyline = this.distanceLines.pop();
            var lLabel = this.distanceLabels.pop();
            var lSegmentDistance = this.distanceValues.pop();

            /** Set the last marker to the current end of list item */
            this.lastMarker = this.distanceMarkers[this.distanceMarkers.length - 1];

            /** Remove the marker and polyline */
            this.map.removeOverlay(lMarker);
            this.map.removeOverlay(lPolyline);
            this.map.removeOverlay(lLabel);

            /** Subtract the last distance from the total distance value */
            this.setTotalDistanceValue(this.totalDistance - lSegmentDistance);
        }
    },

    dragMarker:function(marker) {

       /** Find the indexes of the previous and next markers */
       var currentMarkerIndex = this.distanceMarkers.indexOf(marker);
       var previousMarkerIndex = currentMarkerIndex - 1;
       var nextMarkerIndex = currentMarkerIndex + 1;

       /** Perform bounds checking for the previous marker */
       if (previousMarkerIndex != -1) {

           /** Get the old polyline */
           var oldPolyLine = this.distanceLines[previousMarkerIndex];

           /** Get the old label */
           var label = this.distanceLabels[previousMarkerIndex];

           /** Remove the old line */
           this.map.removeOverlay(oldPolyLine);

           /** Create the new poly line */
           var newPolyLine = new GPolyline([this.distanceMarkers[previousMarkerIndex].getLatLng(), this.distanceMarkers[currentMarkerIndex].getLatLng()], "#00FF00", 4, 0.75, {geodesic:true});
           this.map.addOverlay(newPolyLine);

           /** Get the new label location */
           var newMidPoint = newPolyLine.getBounds().getCenter();

           /** Set the new location */
           label.setPoint(newMidPoint);

           /** Set the new polyline into the list of lines */
           this.distanceLines[previousMarkerIndex] = newPolyLine;

           /** Alter the distance value */

           /** Get the old distance value */
           var oldDistanceValue =  this.distanceValues[previousMarkerIndex];

           /** Get the new distance value */
           var newDistanceValue = this.distanceMarkers[previousMarkerIndex].getPoint().distanceFrom(this.distanceMarkers[currentMarkerIndex].getPoint());

           /** Set the label's contents */
           label.setContents(this.formatDistanceForDisplay(newDistanceValue));

           /** Subtract the old distance value and add the new total distance */
           var newTotalDistance = this.totalDistance = this.totalDistance - oldDistanceValue + newDistanceValue;

           /** Set the new distance value into the list */
           this.distanceValues[previousMarkerIndex] = newDistanceValue;

           /** Set the new total distance */
           this.setTotalDistanceValue(newTotalDistance);
       }

       /** Perform bounds checking for the nex marker */
       if (nextMarkerIndex < this.distanceMarkers.length) {

           /** Get the old polyline */
           var oldPolyLine = this.distanceLines[currentMarkerIndex];

           /** Get the old label */
           var label = this.distanceLabels[currentMarkerIndex];

           /** Remove the old line */
           this.map.removeOverlay(oldPolyLine);

           /** Create the new poly line */
           var newPolyLine = new GPolyline([this.distanceMarkers[currentMarkerIndex].getLatLng(), this.distanceMarkers[nextMarkerIndex].getLatLng()], "#00FF00", 4, 0.75, {geodesic:true});
           this.map.addOverlay(newPolyLine);

           /** Get the new label location */
           var newMidPoint = newPolyLine.getBounds().getCenter();

           /** Set the new location */
           label.setPoint(newMidPoint);

           /** Set the new polyline into the list of lines */
           this.distanceLines[currentMarkerIndex] = newPolyLine;

           /** Alter the distance value */

           /** Get the old distance value */
           var oldDistanceValue =  this.distanceValues[currentMarkerIndex];

           /** Get the new distance value */
           var newDistanceValue = this.distanceMarkers[currentMarkerIndex].getPoint().distanceFrom(this.distanceMarkers[nextMarkerIndex].getPoint());

           /** Set the label's contents */
           label.setContents(this.formatDistanceForDisplay(newDistanceValue));

           /** Subtract the old distance value and add the new total distance */
           var newTotalDistance = this.totalDistance = this.totalDistance - oldDistanceValue + newDistanceValue;

           /** Set the new distance value into the list */
           this.distanceValues[currentMarkerIndex] = newDistanceValue;

           /** Set the new total distance */
           this.setTotalDistanceValue(newTotalDistance);
       }
    },

    removeAllMarkers:function() {

        /** Remove all markers */
        while (this.distanceMarkers.length > 0) {
            var marker = this.distanceMarkers.pop();
            this.map.removeOverlay(marker);
        }
    },

    removeAllPolylines:function() {

        /** Remove all markers */
        while (this.distanceLines.length > 0) {
            var polyline = this.distanceLines.pop();
            this.map.removeOverlay(polyline);
        }
    },

    removeAllLabels:function() {

        /** Remove all label */
        while (this.distanceLabels.length > 0) {
            var label = this.distanceLabels.pop();
            this.map.removeOverlay(label);
        }
    },

    hideAllLabels:function() {

        /** Hide all labels */
        this.distanceLabels.each(function(label){
            label.hide();
        });
    },

    showAllLabels:function() {

        /** Show all labels */
        this.distanceLabels.each(function(label){
            label.show();
        });
    },

    convertAllDistances:function() {

        /** Index used with list of segment distances */
        var distIndex = 0;

        this.distanceLabels.each(function(label){

            /** Get the distance value */
            var distance = this.distanceValues[distIndex];

            /** Set the label contents using the distance */
            label.setContents(this.formatDistanceForDisplay(distance));

            /** Increment the distance list index */
            distIndex++;

        }.bind(this));

        /** Set the total distance value to the existing value with new unit */
        this.setTotalDistanceValue(this.totalDistance);
    },

    toggleUnits:function() {

        /** If currently using metric units, switch to English */
        if (this.useMetric) {
            this.useMetric = false;
            this.convertAllDistances();
            this.toggleUnitsTypeButton.innerHTML = "Show: m/km";
        }

        /** If currently using English units, switch to metric */
        else {
            this.useMetric = true;
            this.convertAllDistances();
            this.toggleUnitsTypeButton.innerHTML = "Show: ft/mi";
        }
    },

    toggleLabels:function() {

        if (this.showLabels) {
            this.hideAllLabels();
            this.toggleLabelsButton.innerHTML = "Show Segment Distances";
            this.showLabels = false;
        }

        else {
            this.showAllLabels();
            this.toggleLabelsButton.innerHTML = "Hide Segment Distances";
            this.showLabels = true;
        }

    },

    setTotalDistanceValue:function(newTotalDistance) {

        this.totalDistance = newTotalDistance;

        /** If using Metric units */
        if (this.useMetric) {

            /** Determine if using Kilometers of Meters */
            if (this.totalDistance > 1000) {
                var distanceDisplayValue = (this.totalDistance/1000).toFixed(4);
                this.distanceToolTotalDistanceValue.innerHTML = distanceDisplayValue + "km";
            }

            else {
                var distanceDisplayValue = this.totalDistance.toFixed(2);
                this.distanceToolTotalDistanceValue.innerHTML = distanceDisplayValue + "m";
            }
        }

        /** If using English units */
        else {

            /** Convert from meters to feet */
            var feet = this.totalDistance * 3.2808399;

            if (feet > 5280) {
                var distanceDisplayValue = (feet/5280).toFixed(4);
                this.distanceToolTotalDistanceValue.innerHTML = distanceDisplayValue + "mi";
            }

            else {
                var distanceDisplayValue = feet.toFixed(2);
                this.distanceToolTotalDistanceValue.innerHTML = distanceDisplayValue + "ft";
            }
        }
    },

    formatDistanceForDisplay:function(distance) {

        /** If using metric units */
        if (this.useMetric) {

            /** Determine if using Kilometers or Meters */
            if (distance > 1000) {
                return (distance / 1000).toFixed(4) + "km";
            }

            else {
                return distance.toFixed(2) + "m";
            }
        }

        /** If using English units */
        else {

            /** Convert from meters to feet */
            var feet = distance * 3.2808399;

            /** Determine if using Miles or Feet */
            if (feet > 5280) {
                return (feet / 5280).toFixed(4) + "mi";
            }

            else {
                return feet.toFixed(2) + "ft";
            }
        }
    },

    /** Cleanup timers and event monitors upon destruciton */
    destroy:function () {
        Event.stopObserving(this.resetDistanceToolButton, "click", this.resetDistanceToolButtonEvent);
        Event.stopObserving(this.removeLastSegmentButton, "click", this.submitSearchButtonEvent);
        Event.stopObserving(this.toggleUnitsTypeButton, "click", this.toggleUnitsTypeButtonEvent);
        Event.stopObserving(this.toggleLabelsButton, "click", this.toggleLabelsButtonEvent);
    }
});