﻿/** @class Baseclass for line/polygon drawing tools
@param {Object} config configuration settings
@config {MapPanel} mapPanel a provider of mapclick, mapdoubleclick, and mapmousemove events
@config {VEMap} map the virtual earth map on which to draw a shape (used to convert pixel to LatLong)
@config {VEShapeLayer || VEMap} addShapeProvider function returning an object that implements AddShape (either a map or shapeLayer object)
*/
TNRIS.DrawingTool = function(config) {
    Ext.apply(this, config);
    TNRIS.DrawingTool.superclass.constructor.call(this);
    this.activeShape = null;
    this.activePoints = [];
    this.beforemapclickCounter = 0; // to identify click vs doubelclick
    this.geoCalc = new TNRIS.GeoCodeCalc();
};

Ext.extend(TNRIS.DrawingTool, TNRIS.ToolBase,
    /** @scope TNRIS.DrawingTool */
    {
    getKeyListener: function() {
        if (null == this.keyListener) {
            this.keyListener = this.mapPanel.getEl().addKeyListener(Ext.EventObject.ESC, this.cancelShape, this);
            this.keyListener.disable();
        }
        return this.keyListener;
    },

    activate: function() {
        TNRIS.DrawingTool.superclass.activate.call(this);
        this.mapPanel.on({
            'beforemapclick': {fn: this.onClick, scope: this},
            'mapdoubleclick': {fn: this.onDoubleClick, scope: this},
            'mapmousemove': {fn: this.onMouseMove, scope:this}
        });
        this.getKeyListener().enable();
    },
    
    deactivate: function() {
        TNRIS.DrawingTool.superclass.deactivate.call(this);
        this.mapPanel.removeListener('beforemapclick', this.onClick, this);
        this.mapPanel.removeListener('mapdoubleclick', this.onDoubleClick, this);
        this.mapPanel.removeListener('mapmousemove', this.onMouseMove, this);
        this.cancelShape();
        this.getKeyListener().disable();
        this.mapPanel.shapeStatusClear();
    },
    
    endShape: function() {
        // TODO : fire endshape event
        // this.updateShape();    
        this.addShapeProvider().endShape();
        this.activeShape = null;
        this.activePoints.length = 0;
        this.beforemapclickCounter =0;
    },

    cancelShape: function() {
        if (null != this.activeShape) {
            this.addShapeProvider().cancelShape(this.activeShape);
            this.activeShape = null;
            this.activePoints.length = 0;
            this.beforemapclickCounter =0;
        }
    },
    
    drawingInProgress: function() {
        return (this.activeShape != null);
    },
    
    updateStatusBar: function() {
        var unitOfMeasurement = this.geoCalc.MILES;
        var result = this.geoCalc.calcDrawingShapeAttributes(this.activeShape,unitOfMeasurement);
        this.mapPanel.shapeStatusChange(result);
    },
    
    onClick: function(mapEvent) {
        if (null != this.contextClickActivity){ //variable set by base class
            TNRIS.DrawingTool.superclass.doContextClickActivity.call(this,mapEvent);
            return false;
        }
        
        this.beforemapclickCounter += 1;
        var point = this.map.PixelToLatLong(new VEPixel(mapEvent.mapX, mapEvent.mapY));
        this.activePoints.push(point);
        this.updateShape();
        return false;
    },
    
    onDoubleClick: function(mapEvent) {
        
        if (this.beforemapclickCounter == 2) { 
            this.cancelShape(); // shape cancelled when event is a double click
            return true;
        }
        
        if (null != this.activeShape) {
            this.endShape();
            return false; // the double click is finished.
        } else {
            return true;
        }
    },
    
    onMouseMove: function(mapEvent) {
        if (null != this.activeShape) {
            var point = this.map.PixelToLatLong(new VEPixel(mapEvent.mapX, mapEvent.mapY));
            this.updateShape(point);
            this.updateStatusBar();
            return true;
        }
        else
        {
            return false;
        }
    }
});

/** @class Draws shapes on mappanel by responding to mouse drag events
@extends TNRIS.DrawingTool
*/
TNRIS.DragDrawingTool = function(config) {
    Ext.apply(this,config);
    TNRIS.DragDrawingTool.superclass.constructor.call(this);
};

Ext.extend(TNRIS.DragDrawingTool, TNRIS.DrawingTool, 
    /** @scope TNRIS.DragDrawingTool */
    {
    
    activate: function() {
        TNRIS.DragDrawingTool.superclass.activate.call(this);
        this.mapPanel.on({
            'beforemapdrag' : {fn: this.onBeforeMapDrag, scope: this},
            'mapdragging'   : {fn: this.onMapDragging, scope: this}, 
            'mapdragdone'   : {fn: this.onMapDragDone, scope: this}
        });
    },
    
    deactivate: function() {
        TNRIS.DragDrawingTool.superclass.deactivate.call(this);
        this.mapPanel.removeListener('beforemapdrag',this.onBeforeMapDrag,this);
        this.mapPanel.removeListener('mapdragging',this.onMapDragging,this);
        this.mapPanel.removeListener('mapdragdone',this.onMapDragDone,this);
    },
    
    onBeforeMapDrag: function(mapEvent) {
        if ((mapEvent.shiftKey == true )||(this.drawingInProgress())) {
            this.mapPanning = true;
            return false;
        }
        else
        {
            return this.onClick(mapEvent);
        }
    },
    
    //parameter true to notifes the map panel not to change the cursor when there is a mousemove with left mouse down and shift key
    setCursor: function(value) {
        TNRIS.DrawingTool.superclass.setCursor.call(this,value,true);
    },
    
    onMapDragging: function(mapEvent,delta) {
        if (this.mapPanning){
            return true;
        }
       else
        {
            this.onMouseMove(mapEvent);
            return false;
        }
    },
    
    onMapDragDone: function(mapEvent,delta) {
        if (! this.mapPanning) {
            return this.onDoubleClick(mapEvent);
        }
        else
        {
            this.mapPanning = false;
            return false;
        }
    }
});

/** @class Draws a Point on the map
@extends TNRIS.DrawingTool
*/
TNRIS.PointTool = function(config) {
    Ext.apply(this, config);
    TNRIS.PointTool.superclass.constructor.call(this);
};

Ext.extend(TNRIS.PointTool,TNRIS.DrawingTool,
    /** @scope TNRIS.PolygonTool */
    {
    updateShape: function(point) {
        if (null == this.activeShape){
            this.activeShape = new VEShape(VEShapeType.Pushpin, this.activePoints[0]);
            this.addShapeProvider().startShape(this.activeShape);
            return;
        } 
    },
    
    onClick: function(mapEvent) {
        if (null != mapEvent.elementID) {
            return true;
        }
        var value = TNRIS.PointTool.superclass.onClick.call(this, mapEvent);
        //when the click is after context click, there are no shapes drawn
        if (this.drawingInProgress()){
            this.endShape();
        }
        return value;
    },
    
    onDoubleClick: function(mapEvent) {
        return false;
    }

});

/** @class Draws a line on a map
@extends TNRIS.DrawingTool
*/
TNRIS.LineTool = function(config) {
    Ext.apply(this, config);
    TNRIS.LineTool.superclass.constructor.call(this, {});
};

Ext.extend(TNRIS.LineTool,TNRIS.DrawingTool,
    /** @scope TNRIS.LineTool */
    {
    updateShape: function(point) {
        if (null == this.activeShape){
            this.activeShape = new VEShape(VEShapeType.Polyline, [this.activePoints[0], this.activePoints[0]]);
            this.addShapeProvider().startShape(this.activeShape);
        } else {
            var shapePoints = this.activePoints.slice(0);
            
            if (null != point) {
                shapePoints.push(point);
            }
            
            if (shapePoints.length < 2) {
                shapePoints.push(shapePoints[0]);
            }
            try {
                this.activeShape.SetPoints(shapePoints);
            } catch(e) {
                console.log('unable to set points: ' + e);
            }
        }
    }
});

/** @class Draws a Polygon on the map
@extends TNRIS.DrawingTool
*/
TNRIS.PolygonTool = function(config) {
    Ext.apply(this, config);
    TNRIS.PolygonTool.superclass.constructor.call(this);
};

Ext.extend(TNRIS.PolygonTool,TNRIS.DrawingTool,
    /** @scope TNRIS.PolygonTool */
    {
    updateShape: function(point) {
        if (null == this.activeShape){
            this.activeShape = new VEShape(VEShapeType.Polygon, [this.activePoints[0], this.activePoints[0], this.activePoints[0]]);
            this.addShapeProvider().startShape(this.activeShape);
            return;
        } 
        var shapePoints = this.activePoints.slice(0);
        if (point != null) {
            shapePoints.push(point);
        }
        switch (shapePoints.length) {
            case 1:
                this.activeShape.SetPoints(shapePoints.concat([shapePoints[0],shapePoints[0]]));
                break;
            case 2:
                this.activeShape.SetPoints(shapePoints.concat(shapePoints[1]));
                break;
            default:
                this.activeShape.SetPoints(shapePoints);
                break;
        }
    }
});

/** @class Draws a Rectangle on the map
@extends TNRIS.DragDrawingTool
*/
TNRIS.RectangleTool = function(config) {
    Ext.apply(this,config);
    TNRIS.RectangleTool.superclass.constructor.call(this);
};

Ext.extend(TNRIS.RectangleTool, TNRIS.DragDrawingTool,
    /** @scope TNRIS.RectangleTool */
    {
        updateShape: function(point) {
            if (null == this.activeShape) {
                this.activeShape = new VEShape(VEShapeType.Polygon, [this.activePoints[0],this.activePoints[0],this.activePoints[0],this.activePoints[0]]);
                this.addShapeProvider().startShape(this.activeShape);
                return;
            } else {
                var shapePoints = this.activePoints.slice(0);

                if (null != point) {
                    shapePoints.push(point);
                }
                    
                if (shapePoints.length >2 ){
                    var temp = shapePoints.pop();
                    shapePoints[1] = temp;
                }
                this.activeShape.SetPoints(this.rectangleByDiagonals(shapePoints[0],shapePoints[1]));
            }
        },
        
        rectangleByDiagonals: function(startPoint, endPoint) {
            var top_left = startPoint;
            var bottom_right = endPoint;
            var top_right =  new VELatLong(top_left.Latitude, bottom_right.Longitude);
            var bottom_left = new VELatLong(bottom_right.Latitude, top_left.Longitude);
            return ([top_left,top_right,bottom_right,bottom_left]);
        },
        
        onContextClick: function(mapEvent) {
            TNRIS.RectangleTool.superclass.onContextClick.call(this,mapEvent);
            
            if (this.menu && !this.menu.items.containsKey("Menu-drawCircleByFixedRadius")){
                var menuItem = new Ext.menu.Item({
                    id: 'Menu-drawRectangleByArea',
                    text:'Draw Rectangle For Given Area',
                    icon: '../images/buttons/draw_rectangle_by_area.png',
                    scope:this,
                    handler: function(btn, e) {
                        if (null != this.ctxLatLong) {
                            if (null != this.activeShape) {
                                this.cancelShape();
                            }
                            //this.ctxLatLong is cleared by menu hide event, hence it is locally copied
                            this.originPoint = new VELatLong(this.ctxLatLong.Latitude, this.ctxLatLong.Longitude);
                            this.promptArea();
                        }
                        btn.parentMenu.hide();
                    }
                });
                this.menu.add(menuItem);
            }
        },
        
        promptArea: function() {
            Ext.Msg.prompt("Enter Area","Enter the area of rectangle in sq.miles", function(btn,text){
                if ((btn == 'ok') &&(text != ""))  {

                    if (isNaN(parseFloat(text)) || (parseFloat(text) != text))  {
                        Ext.Msg.alert("", "Please enter a valid number");
                        return;
                    } else  if ( parseFloat(text) == 0 || parseFloat(text) > 99999 ) {
                        Ext.Msg.alert("", "The value can be between 1 and 99999. <br> Please try again with valid number.");
                        return;
                    }
                    
                    var area = text;    // given area
                    var iterated_area =0; 
                    var iterator_value = 0;
                    var iteration_count = 40; // max iterations
                    var increment_step = 0.01; // initial step
                    
                    var midPoint = this.originPoint;
                    var topLeft = null;
                    var bottomRight = null;
                    
                    this.activeShape = new VEShape(VEShapeType.Polygon, [midPoint,midPoint,midPoint,midPoint]);
                    this.addShapeProvider().startShape(this.activeShape);
                    
                    if ((area >=100 )&&(area <= 1000))  { 
                        increment_step = 1; 
                    } else if (area >=1000 )  { 
                        increment_step = 2; 
                    }
                    
                    //var count; //to check performance
                    //var masterCount = 0; 
                    for (count = 0 ; count < iteration_count; count++) {
                        while(iterated_area < area )
                        {
                            iterator_value = iterator_value + increment_step;
                            topLeft = new VELatLong(midPoint.Latitude + iterator_value , midPoint.Longitude - iterator_value);
                            bottomRight = new VELatLong(midPoint.Latitude - iterator_value , midPoint.Longitude + iterator_value);
                            this.activeShape.SetPoints(this.rectangleByDiagonals(topLeft,bottomRight));
                            result = this.geoCalc.calcDrawingShapeAttributes(this.activeShape,this.geoCalc.MILES);
                            iterated_area = result.area;
                            //masterCount += 1;
                        }
                        
                        if (iterated_area.toFixed(5) == parseFloat(area).toFixed(5)) {
                                break;
                        }
                        
                        iterator_value =  iterator_value - increment_step;
                        topLeft = new VELatLong(midPoint.Latitude - iterator_value , midPoint.Longitude + iterator_value);
                        bottomRight = new VELatLong(midPoint.Latitude + iterator_value, midPoint.Longitude - iterator_value);
                        this.activeShape.SetPoints(this.rectangleByDiagonals(topLeft,bottomRight));
                        
                        iterated_area = 0;
                        increment_step = increment_step / 2;
                    }
                    
                    this.updateStatusBar();
                    this.endShape();
                    this.originPoint = null;
                    //alert("Total:" + masterCount + " Iteration:" + count);
                }
            },this);
        }
});

/** @class Draws Circle on the map
@extends TNRIS.DragDrawingTool
*/
TNRIS.CircleTool = function(config) {
    Ext.apply(this,config);
    TNRIS.CircleTool.superclass.constructor.call(this);
};

Ext.extend(TNRIS.CircleTool, TNRIS.DragDrawingTool, 
    /** @scope TNRIS.CircleTool*/
    {
        updateShape: function(point) {
            if (null == this.activeShape) {
                this.activeShape = new VEShape(VEShapeType.Polygon, [this.activePoints[0],this.activePoints[0],this.activePoints[0]]);
                this.addShapeProvider().startShape(this.activeShape);
                return;
            } else {
                var shapePoints = this.activePoints.slice(0);

                if (null != point) {
                    shapePoints.push(point);
                }
                    
                if (shapePoints.length >2 ){
                    var temp = shapePoints.pop();
                    shapePoints[1] = temp;
                }

                var centre = shapePoints[0];
                var pointToCalcRadius = shapePoints[1];
                var radius =Math.abs( this.geoCalc.calcDistance (centre.Latitude,centre.Longitude,pointToCalcRadius.Latitude, pointToCalcRadius.Longitude,this.geoCalc.EarthRadiusInMiles));
                this.activeShape.SetPoints(this.getCirclePoints(centre,radius));
            }
        },
        
        onContextClick: function(mapEvent) {
            TNRIS.CircleTool.superclass.onContextClick.call(this,mapEvent);

            if (this.menu && !this.menu.items.containsKey("Menu-drawCircleByFixedRadius")){
                var menuItem =  new Ext.menu.Item({
                    id: 'Menu-drawCircleByFixedRadius',
                    text:'Draw Circle For Given Radius',
                    icon: '../images/buttons/draw_circle_by_radius.png',
                    scope:this,
                    handler: function(btn, e) {
                        if (null != this.ctxLatLong) {
                            if (null != this.activeShape) {
                                this.cancelShape();
                            }
                            //this.ctxLatLong is cleared by menu hide event, hence it is locally copied
                            this.midPoint = new VELatLong(this.ctxLatLong.Latitude, this.ctxLatLong.Longitude);
                            this.promptRadius();
                        }
                        btn.parentMenu.hide();
                    }
                });
                this.menu.add(menuItem);
            }
        },
        
        promptRadius: function() {
            Ext.Msg.prompt("Enter Radius","Enter the radius of circle in miles", function(btn,text){
                if ((btn == 'ok') &&(text != ""))  {
                   if (isNaN(parseFloat(text)) || (parseFloat(text) != text))  {
                        Ext.Msg.alert("", "Please enter a valid number");
                        return;
                    } else  if ( parseFloat(text) == 0 || parseFloat(text) > 999 ) {
                        Ext.Msg.alert("", "The value can be between 1 and 999. <br> Please try again with valid number.");
                        return;
                    }

                    this.activeShape = new VEShape(VEShapeType.Polygon, [this.midPoint,this.midPoint,this.midPoint,this.midPoint]);
                    this.addShapeProvider().startShape(this.activeShape);
                    this.activeShape.SetPoints(this.getCirclePoints(this.midPoint,text));
                    this.updateStatusBar();
                    this.endShape();
                    this.midPoint = null;
                }
            },this);
        },
        
        getCirclePoints : function (location, radius) {
            //Reference : http://pietschsoft.com/post/2008/02/Virtual-Earth-Draw-a-Circle-Radius-Around-a-LatLong-Point.aspx
            var loc = location;
            var units = this.geoCalc.EarthRadiusInMiles;
            var earthRadius = parseFloat(units); 
            var lat = this.geoCalc.toRadian(loc.Latitude);
            var lon = this.geoCalc.toRadian(loc.Longitude);
            var d = parseFloat(radius) / earthRadius;
            var locs = new Array(); 
            for (x = 0; x <= 360; x++) { 
                var p2 = new VELatLong(0,0) ;
                brng = this.geoCalc.toRadian(x);
                var latRadians = Math.asin(Math.sin(lat) * Math.cos(d) + Math.cos(lat) * Math.sin(d) * Math.cos(brng)); 
                var lngRadians = lon + Math.atan2(Math.sin(brng) * Math.sin(d) * Math.cos(lat), Math.cos(d) - Math.sin(lat) * Math.sin(latRadians)); 
                locs.push(new VELatLong(this.geoCalc.toDegrees(latRadians), this.geoCalc.toDegrees(lngRadians))); 
            }
            return locs; 
        }
});

if (typeof(Sys) !== "undefined") { Sys.Application.notifyScriptLoaded(); }
