
function Measure( submitter )
{
    this.startMapPt      = new point(0, 0)
    this.orderedPointIDs = new Array();
    this.lastPointID     = 0;
    this.measurePts      = {};
    this.labelTexts      = {};
    this.addedRows       = {};
    this.scaleBarUnits   = "Kilometers";

    this.parseForPoints   = parseForPoints;
    this.addPoint         = addPoint;
    this.addPointElements = addPointElements;
    this.agiMeasureUrl    = agiMeasureUrl;
    this.pointLabelParams = pointLabelParams;
    this.onMapClick       = onMapClick;
    this.hideMeasure      = hideMeasure;
    this.showMeasure      = showMeasure;
    this.toggleLines      = toggleLines;
    this.queryPolygon     = queryPolygon;
    this.queryPolyParam   = queryPolyParam;
    this.clear            = clear;
    this.clearPoint       = clearPoint;
    this.displayDistance  = displayDistance;
    this.calcSegment      = calcSegment;
    this.calcTotalDist    = calcTotalDist;
    this.calcDistance     = calcDistance;


    function parseForPoints( initialPointsParam ) {
        if (initialPointsParam != "") {
            var pointArgs = initialMapPoints.value.split( "^" );
            for (var i=0; i < pointArgs.length; i++) {
                var args      = pointArgs[i].split( "|" );
                var x         = args[0];
                var y         = args[1];
                var labelText = args[2];
                this.addPoint( new point( x, y), labelText )
            }
        }
    }

    function agiMeasureUrl() {
       return agiMeasure.pointLabelParams();
    }

    function pointLabelParams() {
        if (this.orderedPointIDs.length == 0)
            return "";

        var pointsParam = "";
        for (var i=0; i < this.orderedPointIDs.length; i++) {
            if (pointsParam != "")
                pointsParam += "^";
            var pointID  = this.orderedPointIDs[i];
            pointsParam += this.measurePts[pointID].x + "|" + this.measurePts[pointID].y + "|" + this.labelTexts[pointID];
        }

        return "&mapPoints="+pointsParam + "&showLines="+showLines.value;
    }


    // Point Methods

    function addPoint( pt, labelText ) {
        this.addPointElements( pt );
        this.measurePts[this.lastPointID] = pt;
        this.labelTexts[this.lastPointID] = labelText;
        this.orderedPointIDs.push( this.lastPointID );
        this.lastPointID += 1;
        this.displayDistance();
    }

    function clearPoint( pointID ) {
        var elem  = agiMeasure.addedRows[pointID];
        elem.parentNode.removeChild( elem );
        agiMeasure.orderedPointIDs.remove( pointID );
        delete agiMeasure.addedRows[pointID];
        delete agiMeasure.measurePts[pointID];
        delete agiMeasure.labelTexts[pointID];
    }

    function addPointElements( point ) {
        var measureBody  = document.getElementById( "measureBody" );
        var tr           = document.createElement( "TR" );
        var td1          = document.createElement( "TD" );
        var td2          = document.createElement( "TD" );
        var td3          = document.createElement( "TD" );
        var deleteButton = document.createElement( "BUTTON" );
        var deleteImg    = document.createElement( "IMG" );
        var xInput       = document.createElement( "INPUT" );
        var yInput       = document.createElement( "INPUT" );
        var labelInput   = document.createElement( "INPUT" );

        measureBody.appendChild( tr );
        tr.appendChild( td1 );
        tr.appendChild( td2 );
        tr.appendChild( td3 );

        td1.colSpan = "2";
        td2.colSpan = "2";
        td3.colSpan = "2";

        deleteButton.id = "deleteButton "+this.lastPointID;
        deleteImg.id    = "deleteImg "   +this.lastPointID;
        xInput.id       = "xInput "      +this.lastPointID;
        yInput.id       = "yInput "      +this.lastPointID;
        labelInput.id   = "labelInput "  +this.lastPointID;

        deleteImg.src      = "AGIMapControls/images/eraser.jpg";
        deleteButton.title = "Delete this point";
        deleteButton.appendChild( deleteImg );
        td1.appendChild( deleteButton );

        xInput.type  = "text";
        xInput.value = point.x;
        xInput.size  = "4";
        td1.appendChild( xInput );

        yInput.type  = "text";
        yInput.value = point.y;
        yInput.size  = "4";
        td2.appendChild( yInput );

        labelInput.type  = "text";
        labelInput.value = "";
        labelInput.size  = "4";
        td3.appendChild( labelInput );

        deleteButton.onclick  = onDeletePoint;
        xInput.onkeypress     = onPointKey;
        yInput.onkeypress     = onPointKey;
        labelInput.onkeypress = onLabelKey;

        this.addedRows[this.lastPointID] = tr;
    }

    // Event Handlers

    function onMapClick( evt ) {
        var evt    = agiEventCoordinator.getEvent( evt );
        var coords = agiEventCoordinator.getEventCoords( evt );
        var endPt  = agiMapViewer.toMapPoint( coords.x, coords.y );

        if (!agiMapViewer.isEventOverMap( coords.x, coords.y ))
            return;

        this.addPoint( endPt, "" );
        showMeasure();
        requestMap();
    }

    function onPointKey( evt ) {
        evt = agiEventCoordinator.getEvent( evt );
        if (evt.keyCode == "13")  // Enter
            onPointUpdate( evt );
        else
            agiEventCoordinator.getEventTarget( evt ).onblur = onPointUpdate;
    }

    function onLabelKey( evt ) {
        evt = agiEventCoordinator.getEvent( evt );
        if (evt.keyCode == "13")  // Enter
            onLabelUpdate( evt );
        else
            agiEventCoordinator.getEventTarget( evt ).onblur = onLabelUpdate;
    }

    function onPointUpdate( evt ) {
        var eventTarget                = agiEventCoordinator.getEventTarget( evt );
        var pointID                    = eventTarget.id.split( " " )[1];
        var xInput                     = document.getElementById( "xInput " + pointID );
        var yInput                     = document.getElementById( "yInput " + pointID );
        agiMeasure.measurePts[pointID] = new point( xInput.value, yInput.value );
        eventTarget.onblur             = null;
        requestMap();
    }

    function onLabelUpdate( evt ) {
        var eventTarget                = agiEventCoordinator.getEventTarget( evt );
        var pointID                    = eventTarget.id.split( " " )[1];
        agiMeasure.labelTexts[pointID] = eventTarget.value;
        eventTarget.onblur             = null;
        requestMap();
    }

    function onDeletePoint( evt ) {
        var eventTarget = agiEventCoordinator.getEventTarget( evt );
        var pointID     = eventTarget.id.split( " " )[1];
        clearPoint( pointID );
        displayDistance();
        requestMap();

        if (agiMeasure.orderedPointIDs.length == 0)
            agiMeasure.clear();
    }

    function queryPolygon( evt ) {
        if (this.orderedPointIDs.length >= 3)
            popExternalWindow( "Query.aspx?" + this.queryPolyParam() + "&QueryLayers="+getActiveLayersParam(),
                               "resizable=yes, scrollbars=yes, toolbar=yes, location=yes" );
        else
            alert( 'Query polygons must have at least 3 points!' );
    }

    function queryPolyParam() {
        var poly = "";
        for (var i=0; i < this.orderedPointIDs.length; i++) {
            var pointID = this.orderedPointIDs[i];
            var point   = this.measurePts[pointID];
            poly       += point.x + "," + point.y + ";";
        }
        return "SpatialPolygon=" + poly.substring( 0, poly.length-1 );
    }


    // Display Methods

    function hideMeasure() {
        m_divMeasure.style.visibility       = "hidden";
        m_divMeasureButton.style.visibility = "visible";
    }

    function showMeasure() {
        m_divMeasure.style.visibility       = "visible";
        m_divMeasureButton.style.visibility = "hidden";
    }

    function toggleLines() {
        if (showLines.value == "false")
            showLines.value = "true";
        else
            showLines.value = false;

        if (this.orderedPointIDs.length > 1)
            requestMap();
    }

    function clear() {
        for (var i=0; i < this.orderedPointIDs.length; i++)
            this.clearPoint( this.orderedPointIDs[i] );

        this.lastPointID                    = 0;
        this.orderedPointIDs                = new Array();
        this.addedRows                      = {};
        this.measurePts                     = {};
        this.labelTexts                     = {};
        m_divMeasure.style.visibility       = "hidden";
        m_divMeasureButton.style.visibility = "hidden";
    }

    function displayDistance() {
        var measureDistanceUnits = document.getElementById( "measureDistanceUnits" );
        var lblTotal             = document.getElementById( "lblTotal" );
        var lblSegment           = document.getElementById( "lblSegment" );
        var index                = measureDistanceUnits.selectedIndex;
        this.scaleBarUnits       = measureDistanceUnits.options[index].innerText;
        lblTotal.innerHTML       = roundAsString( calcTotalDist(), 3 );
        lblSegment.innerHTML     = roundAsString( calcSegment(),   3 );
    }

    function roundAsString( num, numDecimal ) {
        var u        = Math.pow( 10, numDecimal );
        var numAsInt = parseInt( (num * u) + .5 );
        return (numAsInt / u).toString();
    }


    // Distance calculation functions

    function calcTotalDist() {
        var n    = agiMeasure.orderedPointIDs.length;
        var dist = 0.0;
        for (var i=2; i <= n; i++) {
            var pointID1 = agiMeasure.orderedPointIDs[i-2];
            var pointID2 = agiMeasure.orderedPointIDs[i-1];
            dist += calcDistance( agiMeasure.measurePts[pointID1], agiMeasure.measurePts[pointID2], agiMapUnits.value, agiMeasure.scaleBarUnits );
        }
        return dist;
    }

    function calcSegment() {
        var n = agiMeasure.orderedPointIDs.length;
        if (n < 2)
            return 0.0;
        var pointID1 = agiMeasure.orderedPointIDs[n-2];
        var pointID2 = agiMeasure.orderedPointIDs[n-1];
        return calcDistance( agiMeasure.measurePts[pointID1], agiMeasure.measurePts[pointID2], agiMapUnits.value, agiMeasure.scaleBarUnits );
    }

    function calcDistance( pt, pt2, mapUnits, scaleBarUnits ) {
        // Note: decimal are not hard coded to allow use with locales using commas instead of points.
        var mX            = pt2.x;
        var mY            = pt2.y;
        var mUnits        = mapUnits;
        var mDistance     = 0;
        var Lon1          = pt.x * Math.PI / 180;
        var Lon2          = mX * Math.PI / 180;
        var Lat1          = pt.y * Math.PI / 180;
        var Lat2          = mY * Math.PI / 180;
        var LonDist       = Lon1-Lon2;
        var LatDist       = Lat1-Lat2;
        var metersPerFoot = .3048;

        if (mapUnits.toUpperCase() == "DEGREES") {
            var A     = Math.pow( Math.sin(LatDist / 2),2) + Math.cos(Lat1) * Math.cos(Lat2) * Math.pow(Math.sin(LonDist /2),2 );
            var C     = 2 * Math.asin(Math.min(1, Math.sqrt(A)));
            var D     = (3963 - 13 * Math.sin((Lat1 + Lat2) / 2)) * C;
            mDistance = D * 5280;
            mDistance = mDistance * metersPerFoot;
            mUnits    = "METERS";
        }
        else {
            var xD    = Math.abs( mX - pt.x );
            var yD    = Math.abs( mY - pt.y );
            mDistance = Math.sqrt( Math.pow(xD,2) + Math.pow(yD,2) );
        }

        return convertUnits( mDistance, mUnits, scaleBarUnits );
    }

    function convertUnits( dist, mUnits, sUnits ) {
        // Note: decimal are not hard coded to allow use with locales using commas instead of points.
        var parsedDist = parseFloat(dist);

        if (mUnits.toUpperCase() == "FEET") {
            switch (sUnits.toUpperCase()) {
                case "MILES":       return parsedDist / 5280;
                case "METERS":      return parsedDist * (3048/10000);
                case "KILOMETERS":  return parsedDist * (3048/10000000);
                default:            return parsedDist;
            }
        }
        else {
            switch (sUnits.toUpperCase()) {
                case "MILES":       return parsedDist * (6213711922/10000000000000);
                case "FEET":        return parsedDist * (3280839895/1000000000);
                case "KILOMETERS":  return parsedDist / 1000;
                default:            return parsedDist;
            }
        }
    }

}
