bqc.DRAG_NONE = 0;
bqc.DRAG_EXISTING = 1;
bqc.DRAG_NEW = 2;
bqc.DRAGTIME = 250;
bqc.WAYPOINT_LIMIT = 12;

bqc.Route = Class.create({

    initialize: function(map, router) {
        this.map = map;
        this.router = router;
        this.gdir = new GDirections();
        this.secondGDir = new GDirections();
        this.routeNodes = [];
        this.waypoints = [];
        this.iconNode = this.createIconNode();
        this.hoverNode = this.createHoverNode();
        this.normalProj = G_NORMAL_MAP.getProjection();
        this.overlays = [];
        this.originalPosition = null;
        this.onEnable = null;

        var that = this;
        GEvent.addListener(map, 'mousemove', function(mouseLatLng) { that.getProximity(mouseLatLng); });
        GEvent.addListener(map, 'zoomend', function() { that.routeNodes = []; });
        GEvent.addListener(this.hoverNode, 'drag', function() { that.dragged() });
        GEvent.addListener(this.hoverNode, 'dragend', function() { that.dragEnd() });
        GEvent.addListener(this.gdir, 'load', function() { window.setTimeout(function() { that.loaded() }, 1) });
        GEvent.addListener(this.gdir, 'error', function() { router.routingFailed(); });
        GEvent.addListener(this.secondGDir, 'load', function() { window.setTimeout(function() { that.secondLoaded() }, 1) });
        GEvent.addListener(this.secondGDir, 'error', function() { that.secondError(); });
    },

    placemarkToString: function(placemark) {
        return placemark.Point.coordinates[1] + ',' + placemark.Point.coordinates[0];
    },

    load: function(fromPlacemark, toPlacemark, language) {
        this.clear();
        this.fromPlacemark = fromPlacemark;
        this.toPlacemark = toPlacemark;
        this.language = language;

        if (this.fromPlacemark != null && this.toPlacemark != null) {
            bqc.showLoading();
            this.gdir.load("from: " + this.placemarkToString(this.fromPlacemark) +
                " to: " + this.placemarkToString(this.toPlacemark),
                { "locale": this.language, getPolyline: true, getSteps: true});
        } else {
            alert('Missing point');
        }
    },

    reverse: function() {
        this.marker = null;
        this.map.removeOverlay(this.route);
        this.clearProximityMarkers();

        var reversedWaypoints = [];
        for (var i = this.waypoints.length - 1; i >= 0; --i) {
            var waypoint = this.waypoints[i];
            waypoint.MyIndex = i;
            reversedWaypoints.push(waypoint);
        }
        this.waypoints = reversedWaypoints;
        var temp = this.fromPlacemark;
        this.fromPlacemark = this.toPlacemark;
        this.toPlacemark = temp;

        this.isReversing = true;
        this.updateDetails = true;
        this.loadWaypoints(this.gdir, this.waypoints);
    },
    
    clearOverlays: function() {
        for (var i = 0; i < this.overlays.length; ++i) {
            this.map.removeOverlay(this.overlays[i]);
        }
        this.overlays = [];
    },

    clearProximityMarkers: function() {
        if (this.startMarker != null) {
            this.map.removeOverlay(this.startMarker);
            this.map.removeOverlay(this.endMarker);

            this.startMarker = null;
            this.endMarker = null;
        }
    },

    isActive: function(){
        if(this.route){
            return true;
        }
        return false;
    },

    clear: function() {
        this.clearOverlays();
        this.clearProximityMarkers();
        this.routeNodes = [];
        this.waypoints = [];
        this.routeNodes = [];
        this.route = null;
        this.marker = null;
    },

    addExtremityMarkers: function() {
        this.startMarker = this.router.addStartPoint(this.route.getVertex(0));
        this.endMarker = this.router.addEndPoint(
            this.route.getVertex(this.route.getVertexCount() - 1));
        if (!this.isReversing) {
            this.waypoints.push(this.startMarker);
            this.waypoints.push(this.endMarker);
        }
        this.isReversing = false;
    },

    loaded: function() {
        this.route = this.gdir.getPolyline();

        this.addExtremityMarkers();

        this.map.addOverlay(this.route);

        this.routeNodes = [];

        this.overlays.push(this.route);
        
        this.router.routeLoaded(this.gdir, true && this.waypoints.length <= 2, true);
    },

    updateWaypointsIndex: function() {
        for (var i = this.hoverNode.MyIndex; i >= 0 && i < this.waypoints.length; i++) {
            this.waypoints[i].MyIndex = i;
        }
    },

    secondError: function() {
        this.markerDragged = false;
        this.enableRouting();
        if (this.updateDetails) {
            if (this.originalPosition == null) {
                if (this.marker != null) {
                    this.waypoints.splice(this.marker.MyIndex, 1);
                    this.map.removeOverlay(this.marker);
                    this.marker = null;

                    this.updateWaypointsIndex();

                    this.updateDetails = false;
                    this.loadWaypoints(this.secondGDir, this.waypoints);
                }
            } else {
                this.marker.setLatLng(this.originalPosition);
                this.originalPosition = null;
                this.dragMarkerEnd();
            }
        }
    },

    enableRouting: function() {
        var that = this;
        setTimeout(function() { 
            that.isDragged = bqc.DRAG_NONE;
            if (that.markerDragged && that.onEnable != null) {
                var exec = that.onEnable;
                that.onEnable = null;
                exec.apply(that);
            }
        }, bqc.DRAGTIME);
    },

    secondLoaded: function() {
        this.map.removeOverlay(this.route);
        this.route = this.secondGDir.getPolyline();

        this.map.addOverlay(this.route);
        this.overlays.push(this.route);

        this.routeNodes = [];
        this.getProximity();

        if (this.updateDetails) {
            this.originalPosition = null;
            this.markerDragged = false;
            this.snapOnRoute();
        }
        this.enableRouting();
        this.router.routeLoaded(this.secondGDir, false, this.updateDetails);
    },

    getDistance: function(pointX, pointY, lineX0, lineY0, lineX1, lineY1) {
        var dx = lineX1 - lineX0;
        var dy = lineY1 - lineY0;
        var d = dx * dx + dy * dy;

        var u = ((pointX - lineX1) * dx + (pointY - lineY1) * dy) / d;
        var x2 = lineX1 + (u * dx);
        var y2 = lineY1 + (u * dy);

        return (pointX - x2) * (pointX - x2) + (pointY - y2) * (pointY - y2);
    },

    getWaypointDistance: function(newWaypointX, newWaypointY, oldWaypointIndex, zoom) {
        var oldWaypoint = this.waypoints[oldWaypointIndex];
        if (oldWaypoint == null) {
            return Number.MAX_VALUE;
        }
        oldWaypoint = this.normalProj.fromLatLngToPixel(oldWaypoint.getPoint(), zoom);

        return Math.sqrt(Math.pow(newWaypointX - oldWaypoint.x, 2) +
            Math.pow(newWaypointY - oldWaypoint.y, 2))
    },

    isBeforeWaypoint: function(waypoint, x, y, lineX0, lineY0, zoom) {
        waypoint = this.waypoints[waypoint];
        if (waypoint == null) {
            return false;
        }
        waypoint = this.normalProj.fromLatLngToPixel(waypoint.getPoint(), zoom);

        var lengthNewWaypoint = Math.sqrt(Math.pow(x - lineX0, 2) + Math.pow(y - lineY0, 2));
        var lengthWaypoint = Math.sqrt(Math.pow(waypoint.x - lineX0, 2) + Math.pow(waypoint.y - lineY0, 2));

        return lengthNewWaypoint < lengthWaypoint;
    },

    findWaypointOnLine: function(lineX0, lineY0, lineX1, lineY1, mouseX, mouseY, zoom, baseIndex) {
        var index = -1;
        var minDist = Number.MAX_VALUE;
        var buffer = 5;

        for (var i = baseIndex; i < this.waypoints.length - 1; ++i) {
            var waypoint = this.normalProj.fromLatLngToPixel(this.waypoints[i].getPoint(), zoom);

            if (((lineX0 < lineX1 && waypoint.x >= lineX0 - buffer && waypoint.x <= lineX1 + buffer) ||
                 (lineX0 >= lineX1 && waypoint.x <= lineX0 + buffer && waypoint.x >= lineX1 - buffer)) &&
                ((lineY0 < lineY1 && waypoint.y >= lineY0 - buffer && waypoint.y <= lineY1 + buffer) ||
                 (lineY0 >= lineY1 && waypoint.y <= lineY0 + buffer && waypoint.y >= lineY1 - buffer))) {

                var dist = this.getDistance(waypoint.x, waypoint.y,
                    lineX0, lineY0, lineX1, lineY1);

                if (5 > dist) {
                    var newWaypointDist = this.getWaypointDistance(mouseX, mouseY, i, zoom);
                    if (minDist > newWaypointDist) {
                        minDist = newWaypointDist;
                        index = i;
                    }
                }
            }
        }
        return index;
    },

    snapOnRoute: function() {
        if (this.marker == null) {
            return;
        }
        var point = this.marker.getLatLng();
        var index = this.marker.MyIndex;
        var zoom = this.map.getZoom();
        var pointPx = this.normalProj.fromLatLngToPixel(point, zoom);
        var pointX = pointPx.x;
        var pointY = pointPx.y;

        var x = this.routeNodes[0];
        var y = this.routeNodes[1];
        var minX = x;
        var minY = y;
        var minDist = Number.MAX_VALUE;
        var waypoint = 0;

        var l = this.routeNodes.length;

        for (var n = 2; n < l; n += 2) {
            var x0 = x;
            var y0 = y;
            x = this.routeNodes[n];
            y = this.routeNodes[n+1];
            var newWaypoint = this.findWaypointOnLine(x0, y0, x, y, pointX, pointY, zoom, waypoint);

            if (newWaypoint != -1) {
                if (newWaypoint < index - 1) {
                    continue
                }
                if (newWaypoint > index + 1) {
                    break;
                }
            }
            var dx = x - x0;
            var dy = y - y0;
            var d = dx * dx + dy * dy;

            var u = ((pointX - x) * dx + (pointY - y) * dy) / d;
            var x2 = x + (u * dx);
            var y2 = y + (u * dy);

            var dist = (pointX - x2) * (pointX - x2) + (pointY - y2) * (pointY - y2);

            if (minDist > dist) {
                var d1 = (pointX - x0) * (pointX - x0) + (pointY - y0) * (pointY - y0);
                var d2 = (pointX - x) * (pointX - x) + (pointY - y) * (pointY - y)

                if ((d1 - dist) + (d2 - dist) > d) {
                    if (d1 < d2) {
                        dist = d1;
                        x2 = x0;
                        y2 = y0;
                    } else {
                        dist = d2;
                        x2 = x;
                        y2 = y;
                    }
                }
            }

            if (minDist > dist) {
                minDist = dist;
                minX = x2;
                minY = y2;
            }
        }
        this.marker.setLatLng(this.normalProj.fromPixelToLatLng(new GPoint(minX, minY), zoom));
        this.marker = null;
    },

    getProximity: function(mouseLatLng) {
        var zoom, point;

        if (!this.isDragging  && this.routeNodes.length == 0 && this.route != null) {
            var dist = this.route.getLength();
            zoom = this.map.getZoom();

            var last_point;

            for (var j = 0; j < this.route.getVertexCount(); ++j) {
                point = this.normalProj.fromLatLngToPixel(this.route.getVertex(j), zoom);
                point.x = parseInt(0.5 + point.x);
                point.y = parseInt(0.5 + point.y);

                if (j==0 || last_point.x != point.x || last_point.y != point.y) {
                    this.routeNodes.push(point.x);
                    this.routeNodes.push(point.y);
                    last_point = point;
                }
            }
        }
        if (this.waypoints.length < bqc.WAYPOINT_LIMIT) {
            var l = this.routeNodes.length;

            if (!mouseLatLng || l <= 1 || this.isDragged > bqc.DRAG_NONE) {
                return;
            }
            zoom = this.map.getZoom();
            var mousePx = this.normalProj.fromLatLngToPixel(mouseLatLng, zoom);
            var mouseX = mousePx.x;
            var mouseY = mousePx.y;

            var x = this.routeNodes[0];
            var y = this.routeNodes[1];
            var minX = x;
            var minY = y;
            var minDist = Number.MAX_VALUE;
            var waypoint = 0;
            if (!this.isDragging) {
                this.hoverNode.MyIndex = waypoint;
            }
            var waypointX0 = -1;
            var waypointY0 = -1;

            for (var n = 2; n < l; n += 2) {
                var x0 = x;
                var y0 = y;
                x = this.routeNodes[n];
                y = this.routeNodes[n+1];
                var newWaypoint = this.findWaypointOnLine(x0, y0, x, y, mouseX, mouseY, zoom, waypoint);
                if (newWaypoint != -1 && newWaypoint > waypoint) {
                    waypoint = newWaypoint;
                }
                if ((x < mouseX-50 && x0 < mouseX-50) || (x > mouseX+50 && x0 > mouseX+50)) {
                    continue;
                }
                if ((y < mouseY-50 && y0 < mouseY-50) || (y > mouseY+50 && y0 > mouseY+50)) {
                    continue;
                }

                var dx = x - x0;
                var dy = y - y0;
                var d = dx * dx + dy * dy;

                var u = ((mouseX - x) * dx + (mouseY - y) * dy) / d;
                var x2 = x + (u * dx);
                var y2 = y + (u * dy);

                dist = (mouseX - x2) * (mouseX - x2) + (mouseY - y2) * (mouseY - y2);

                if (minDist > dist) {
                    var d1 = (mouseX - x0) * (mouseX - x0) + (mouseY - y0) * (mouseY - y0);
                    var d2 = (mouseX - x) * (mouseX - x) + (mouseY - y) * (mouseY - y)

                    if ((d1 - dist) + (d2 - dist) > d) {
                        if (d1 < d2) {
                            dist = d1;
                            x2 = x0;
                            y2 = y0;
                        } else {
                            dist = d2;
                            x2 = x;
                            y2 = y;
                        }
                    }
                }

                if (minDist > dist) {
                    minDist = dist;
                    minX = x2;
                    minY = y2;

                    if (!this.isDragging) {
                        this.hoverNode.MyIndex = waypoint;
                        if (newWaypoint != -1) {
                            waypointX0 = x0;
                            waypointY0 = y0;
                        } else {
                            waypointX0 = -1;
                            waypointY0 = -1;
                        }
                    }
                }
            }

            if (minDist > 500) {
                this.hoverNode.hide();
            }
            else {
                for (n = this.waypoints.length; --n >= 0;) {
                    var markerPx = this.normalProj.fromLatLngToPixel(this.waypoints[n].getPoint(), zoom);

                    dx = markerPx.x - minX;
                    dy = markerPx.y - minY;

                    if (dx*dx + dy*dy < 25) {
                        this.hoverNode.hide();
                        return;
                    }
                }
                if (waypointX0 != -1 && this.isBeforeWaypoint(this.hoverNode.MyIndex, mouseX, mouseY, waypointX0, waypointY0, zoom)) {
                    --this.hoverNode.MyIndex;
                }
                this.hoverNode.setPoint(this.normalProj.fromPixelToLatLng(new GPoint(minX, minY), zoom));
                this.hoverNode.show();
            }
        }
    },

    createIconNode: function() {
        var iconNode = new GIcon();
        iconNode.image = 'images/itinaryDrag.png';
        iconNode.shadow = '';
        iconNode.iconSize = new GSize(10, 10);
        iconNode.shadowSize = new GSize(0,0);
        iconNode.iconAnchor = new GPoint(5,5);
        iconNode.infoWindowAnchor = new GPoint(5,5);
        iconNode.dragCrossSize = GSize(1, 1);
        iconNode.maxHeight = 1;
        return iconNode;
    },

    createHoverNode: function() {
        var node = new GMarker(this.map.getCenter(), {icon: this.iconNode, draggable: true, bouncy: false, zIndexProcess: function() { return 1; } } );
        this.map.addOverlay(node);
        node.show();
        node.hide();
        return node;
    },

    dragged: function() {
        this.hoverNode.show();
        
        this.isDragging = true;

        if (this.isDragged == bqc.DRAG_NEW) {
            this.markerDragged = this.hoverNode;
            this.onEnable = this.dragged;
            return;
        }

        if (this.hoverNode.MyIndex < this.waypoints.length) {
            this.isDragged = bqc.DRAG_NEW;
            this.markerDragged = false;

            this.updateDetails = false;

            var wayPoints = this.createWaypointArray(this.waypoints);
            wayPoints.splice(this.hoverNode.MyIndex + 1, 0, this.hoverNode.getPoint());
            this.secondGDir.loadFromWaypoints(wayPoints, { getPolyline: true });
        }
    },

    dragMarkerStart: function(position) {
        this.originalPosition = position;
        this.isDragged = bqc.DRAG_EXISTING;
        this.hoverNode.hide();
    },

    dragEnd: function() {
        this.hoverNode.hide();
        
        var point = this.hoverNode.getPoint();
        var marker = new GMarker(point, {icon: this.iconNode, draggable: true, dragCrossMove: false, bouncy: false, zIndexProcess: function() { return 1; }});
        this.waypoints.splice(this.hoverNode.MyIndex + 1, 0, marker);

        this.updateWaypointsIndex();
        
        var dummy = new GPolyline([point]);
        this.map.addOverlay(dummy);
        this.overlays.push(dummy);

        var that = this;
        GEvent.addListener(marker, 'dragstart', function(position) { that.dragMarkerStart(position); });
        GEvent.addListener(marker, 'dragend', function() { that.dragMarkerEnd(this); } );
        GEvent.addListener(marker, 'drag', function() { that.dragMarker(this); });

        this.map.addOverlay(marker);
        this.overlays.push(marker);
        this.marker = marker;

        this.isDragging = false;
        this.updateDetails = true;
        this.markerDragged = false;

        this.loadWaypoints(this.secondGDir, this.waypoints);
    },

    loadWaypoints: function(gdir, waypoints) {
        bqc.showLoading();
        gdir.loadFromWaypoints(this.createWaypointArray(waypoints), { "locale": this.language, getPolyline: true, getSteps: true});
    },

    createWaypointArray: function(waypoints) {
        var wayPointsArray = [];
        for (var i = 0; i < waypoints.length; ++i) {
            wayPointsArray.push(waypoints[i].getPoint());
        }
        return wayPointsArray;
    },

    dragMarkerEnd: function() {
        this.isDragged = bqc.DRAG_NONE;
        this.isDragging = false;
        this.updateDetails = true;
        this.markerDragged = false;

        bqc.showLoading();
        this.secondGDir.loadFromWaypoints(this.createWaypointArray(this.waypoints), { "locale": this.language, getPolyline: true, getSteps: true});
    },

    reDragMarker: function() {
        this.dragMarker(this.markerDragged);
    },

    dragMarker: function(marker) {
        this.isDragging = true;
        if (this.isDragged == bqc.DRAG_NEW) {
            this.markerDragged = marker;
            this.onEnable = this.reDragMarker
            return;
        }
        this.isDragged = bqc.DRAG_NEW;

        if (this.markerDragged) {
            this.marker = this.markerDragged;
            this.markerDragged = false;
        }
        else {
            this.marker = marker;
        }

        this.updateDetails = false;
        
        this.secondGDir.loadFromWaypoints(this.createWaypointArray(this.waypoints), { getPolyline: true });
    },

    reset: function() {
        this.load(this.fromPlacemark, this.toPlacemark, this.language);
    }
});
