// from http://demo.geostart.nl/cSnapToRoute.js
//4umi edited for use with editable poly's


function cSnapToRoute(){

    this.routePoints    = Array();
    this.routePixels    = Array();
    this.routeOverlay   = null;
    this.routeListeners   = [];
    this.normalProj     = G_NORMAL_MAP.getProjection();

    /**
    *   @desc Initialize the objects.
    *   @param Map object
    *   @param GMarker object to move along the route
    *   @param GPolyline object - the 'route'
    **/
    this.init = function(oMap, oMarker, oPolyline){
        this._oMap      = oMap;
        this._oMarker   = oMarker;
        this._oPolyline = oPolyline;
        this.loadRouteData();
        this.loadMapListener();
    }


    /**
    *   @desc Update targets
    *   @param GMarker object to move along the route
    *   @param GPolyline object - the 'route'
    **/
    this.updateTargets = function(oMarker, oPolyline) {
        this._oMarker   = oMarker   || this._oMarker;
        this._oPolyline = oPolyline || this._oPolyline;
        this.loadRouteData();
    }

    /**
    *   @desc internal use only, Load map listeners to calculate and update this.oMarker position.
    **/
    this.loadMapListener = function(){
        var self = this;
        this.routeListeners[0] = GEvent.addListener(self._oMap, 'mousemove', GEvent.callback(self, self.updateMarkerLocation));
        this.routeListeners[1] = GEvent.addListener(self._oMap, 'zoomend', GEvent.callback(self, self.loadRouteData));
    }

    /**
    *   @desc external use only sofar, Unload map listeners.
    **/
    this.stopListening = function(){
        var self = this;
        GEvent.removeListener( self.routeListeners[0] );
        GEvent.removeListener( self.routeListeners[1] );
    }

    /**
    *   @desc internal use mostly, Load route points into RoutePixel array for calculations, do this whenever zoom changes
    **/
    this.loadRouteData = function(){
        this.routePixels = new Array();
        var zoom = this._oMap.getZoom();
        var n = this._oPolyline.getVertexCount();
        for ( var i = 0; i < n; i++ ) {
            var Px = this.normalProj.fromLatLngToPixel(this._oPolyline.getVertex(i), zoom);
            this.routePixels.push(Px);
        }
    }

    /**
    *   @desc internal use only, Handle the move listeners output and move the given marker.
    *   @param GLatLng()
    **/
    this.updateMarkerLocation = function(mouseLatLng) {
        var oMarkerLatLng = this.getClosestLatLng(mouseLatLng);
        this._oMarker.setPoint(oMarkerLatLng);
    }

    /**
    *   @desc Get closest point on route to test point
    *   @param GLatLng() the test point
    *   @return new GLatLng();
    **/
    this.getClosestLatLng = function(latlng){
        var r = this.distanceToLines(latlng);
        return this.normalProj.fromPixelToLatLng(new GPoint(r.x,r.y), this._oMap.getZoom());
    }

    /**
    *   @desc Get distance along route in meters of closest point on route to test point
    *   @param GLatLng() the test point
    *   @return distance in meters;
    **/
    this.getDistAlongRoute = function(latlng){
        var r = this.distanceToLines(latlng);
        return this.getDistToLine(r.i, r.fTo);
    }

    /**
    *   @desc internal use only, gets test point xy and then calls fundamental algorithm
    **/
    this.distanceToLines = function(mouseLatLng) {
        var zoom        = this._oMap.getZoom();
        var mousePx     = this.normalProj.fromLatLngToPixel(mouseLatLng, zoom);
        var routePixels = this.routePixels;
        return this.getClosestPointOnLines(mousePx,routePixels);
    }

    /**
    *   @desc internal use only, moves xy result back to GLatLng
    *   @ decpricated? Now let API handle calculation of px->latlng
    **/
    this.getClosestPoint = function(r){
        var routeOverlay = this._oPolyline;
        var iPercentage;

        if ( r.fTo > 1 )
            iPercentage = 1;
        else if ( r.fFrom > 1 )
            iPercentage = 0;
        else
            iPercentage = r.fTo;

        /* Query this legitimate for big distances */
        var latSpan = routeOverlay.getVertex((r.i)-1).lat() - routeOverlay.getVertex(r.i).lat();
        var lngSpan = routeOverlay.getVertex((r.i)-1).lng() - routeOverlay.getVertex(r.i).lng();
        var newLat = routeOverlay.getVertex((r.i)-1).lat() - ( latSpan * (iPercentage) );
        var newLng = routeOverlay.getVertex((r.i)-1).lng() - ( lngSpan * (iPercentage) );
        return new GLatLng(parseFloat(newLat), parseFloat(newLng));
    }

    /**
    *   @desc internal use only, find distance along route to point nearest test point
    **/
    this.getDistToLine = function(iLine, fTo){
      var routeOverlay = this._oPolyline;
      var d = 0;
        for (var n = 1 ; n < iLine ; n++ )
            d += routeOverlay.getVertex(n-1).distanceFrom(routeOverlay.getVertex(n));
        d += routeOverlay.getVertex(iLine-1).distanceFrom(routeOverlay.getVertex(iLine)) * fTo;
        return d;
    }

    /* desc Static function. Find point on lines nearest test point
       test point pXy with properties .x and .y
       lines defined by array aXys with nodes having properties .x and .y
       return is object with .x and .y properties and property i indicating nearest segment in aXys
       and property fFrom the fractional distance of the returned point from aXy[i-1]
       and property fTo the fractional distance of the returned point from aXy[i]    */

    this.getClosestPointOnLines = function (pXy,aXys) {
        var minDist;
        var fTo;
        var fFrom;
        var x;
        var y;
        var i;
        var dist;

        if (aXys.length > 1) { //aXys.unshift( {x:aXys[aXys.length-2].x,y:aXys[aXys.length-2].y} );
                var l = aXys.length;
                for (var n = 1; n < l; n++ ) {

                    if (aXys[n].x != aXys[n-1].x) {
                        var a = (aXys[n].y - aXys[n-1].y) / (aXys[n].x - aXys[n-1].x);
                        var b = aXys[n].y - a * aXys[n].x;
                        dist = Math.abs(a*pXy.x + b - pXy.y) / Math.sqrt(a*a+1);
                    }
                    else
                        dist = Math.abs(pXy.x - aXys[n].x)

                    // length^2 of line segment
                    var rl2 = Math.pow(aXys[n].y - aXys[n-1].y,2) + Math.pow(aXys[n].x - aXys[n-1].x,2);

                    // distance^2 of pt to end line segment
                    var ln2 = Math.pow(aXys[n].y - pXy.y,2) + Math.pow(aXys[n].x - pXy.x,2);

                    // distance^2 of pt to begin line segment
                    var lnm12 = Math.pow(aXys[n-1].y - pXy.y,2) + Math.pow(aXys[n-1].x - pXy.x,2);

                    // minimum distance^2 of pt to infinite line
                    var dist2 = Math.pow(dist,2);

                    // calculated length^2 of line segment
                    var calcrl2 = ln2 - dist2 + lnm12 - dist2;

                    // redefine minimum distance to line segment (not infinite line) if necessary
                    if (calcrl2 > rl2)
                        dist = Math.sqrt( Math.min(ln2,lnm12) );

                    if ( (minDist > dist) || (minDist == null) ) {
                       fTo  = ((Math.sqrt(lnm12 - dist2)) / Math.sqrt(rl2));
                       minDist = dist;
                       i = n;
                    }
            }
            if (fTo > 1 ) {status=i+' '+fTo; fTo = 1; }
            fFrom = 1-fTo;
            var dx = aXys[i-1].x - aXys[i].x;
            var dy = aXys[i-1].y - aXys[i].y;
            x = aXys[i-1].x - (dx * fTo);
            y = aXys[i-1].y - (dy * fTo);
        }
        return {'x':x, 'y':y, 'i':i, 'fTo':fTo, 'fFrom':fFrom};
    }
}