var AtisContextMenu = function (map, options) {
    var publicItem = {};

    var nearestLocationsArray;

    publicItem.populateMenu = function(menu, clickedX, clickedY, clickedLatLng, displayMenu) {
        var useZoom = false;
        //When we are using zoom, it is to set a Maximum Functional class (the more we are zoomed-out, the bigger the road we want)
        var zoom = 16;//default to very zoomed-in to get all functional classes
        if (useZoom) {
            zoom = map.zoom;
        }
        $.ajax('/api/route/getlocations?latitude=' + clickedLatLng.lat() + '&longitude=' + clickedLatLng.lng() + '&zoom=' + zoom)
            .done(function (data) {
                var contextString = '';
                nearestLocationsArray = [];

                if (data == null) {
                    //there was an ajax error.
                } else if (!data.length) {
                    contextString = '<li><p class="error">' + resources.CouldNotRetrieveMapLocation + '</p></li>';
                } else {
                    for (var i = 0, count = data.length; i < count; i++) {
                        if (data[i].name) {
                            //add the roadway name options.
                            contextString += '<li><a  href="#' + i + '">' + data[i].nameDirection + '</a></li>';
                            nearestLocationsArray.push(data[i]);
                        }
                    }
                }

                //display the menu.
                displayMenu(contextString, clickedX, clickedY, clickedLatLng);
            })
            .fail(function () {
                displayMenu(null, clickedX, clickedY, clickedLatLng);
            });
    }

    publicItem.doAction = function (menu, event, actionInt) {
        var location = nearestLocationsArray[actionInt];

        //special flow for location items.
        locationClicked(event, location, menu);
    }

    publicItem.setupEventHandlers = function(menu) {
        // Add hover effects on items
        menu.find('a').hover(function () {
            $(this).parent().addClass('hover');
        }, function () {
            $(this).parent().removeClass('hover');
        });
    }

    publicItem.resetMarkers = function(menu) {
        //event trigger kept in for backwards compatibility
        $(document).trigger('resetRoutePlanner-contextMenu');
    }

    
    var getNearestLocationsCallback = function (data) {
        var contextString = '';
        nearestLocationsArray = [];

        if (data == null) {
            //there was an ajax error.
        } else if (!data.length) {
            contextString = '<li><p class="error">' + resources.CouldNotRetrieveMapLocation + '</p></li>';
        } else {
            for (var i = 0, count = data.length; i < count; i++) {
                if (data[i].name) {
                    //add the roadway name options.
                    contextString += '<li><a  href="#' + i + '">' + data[i].nameDirection + '</a></li>';
                    nearestLocationsArray.push(data[i]);
                }
            }
        }

        //display the menu.
        displayMenu(contextString);
    };

    // location menu item clicked.
    var locationClicked = function (event, location, contextMenu) {

        //get click event target.
        var jqueryObj = $(event.target);

        //get parent of event target.
        var parent = jqueryObj.parent();

        //detach click event target from parent.
        var original = jqueryObj.detach();

        //reference to this for closure.
        var $this = this;
        $this.mostRecentLocation = location;

        parent.append($this.toHereFromHereMenuItem || '<div class="btn-group" id="contextMenuToFrom"><button class="btn btn-info" data-type="fromHere">' + resources.FromHere + '</button><button class="btn btn-info" data-type="toHere">' + resources.ToHere + '</button></div>');

        //if to here / from here menu created for first time, attach event handlers.
        if (!$this.toHereFromHereMenuItem) {
            var firstChild = parent.children().first();

            var clickAction = function (toFrom) {
                //trigger selection event and fade out context menu.
                $(document).trigger('locationSelected-contextMenu', [toFrom, $this.mostRecentLocation]);
                contextMenu.fadeOut(75);
            }; //attach event handler to click event.
            $('[data-type="fromHere"],[data-type="toHere"]', firstChild).on('click', function (e) {
                clickAction($(this).attr('data-type'));
            }).css('cursor', 'pointer');
        }

        //on mouse leave of parent
        parent.on('mouseleave.atisContextMenu', function (e) {
            //remove current content and append original content.
            $this.toHereFromHereMenuItem = parent.children().first().detach();
            parent.append(original);

            //remove event handler and hover class.
            parent.off('mouseleave.atisContextMenu');
            parent.removeClass('hover');
        });
    };
    return publicItem;
};
var NewAtisContextMenu = function (appPublic, options) {
    var publicItem = {};
    var latLng, contextMenu, clickedLatLng, clickedX, clickedY, nearestLocationsArray, addWaypointToContextMenu;

    publicItem.populateMenu = function (menu, clickedX, clickedY, clickedLatLng, displayMenu) {

        latLng = clickedLatLng;
        displayMenu('<a class="list-group-item contextPointButton" data-type="fromHere" href="#1">' + resources.FromHere + '</a><a class="list-group-item contextPointButton" data-type="toHere" href="#2">' + resources.ToHere + '</a>', clickedX, clickedY, clickedLatLng);

    };

    var onRightClick = function (e) {
        // start by hiding the context menu if its open
        contextMenu.hide();

        // See if My Route tab exist first, switch tab to My Routes if it is on other tabs
        if ($("#RoutesTab").length > 0) {
            if (!$("#RoutesTab").hasClass("active")) {
                $("#RoutesTab > a").trigger("click");
            }
        }

        // pixel location of click
        clickedX = e.pixel.x;
        clickedY = e.pixel.y;

        // latitude, longitude of click
        clickedLatLng = e.latLng;

        if (resources.DisplayLocationsInContextMenu == "True") {
            // Call getNearestLocations, gMarker is null since it is yet to be created.

            publicItem.getNearestLocations(clickedLatLng, getNearestLocationsCallback);
            //displayMenu('<div class="btn-group" id="contextMenuToFrom"><button class="btn btn-info contextPointButton" data-type="fromHere">' + resources.FromHere + '</button><button class="btn btn-info contextPointButton" data-type="toHere">' + resources.ToHere + '</button></div>');

        } else {
            var displayString = '<a class="list-group-item contextPointButton" data-type="fromHere">' + resources.FromHere + '</a><a class="list-group-item contextPointButton" data-type="toHere">' + resources.ToHere + '</a>';
            if (addWaypointToContextMenu) {
                displayString += '<a class="list-group-item contextPointButton" data-type="waypoint">' + resources.AddWaypoint + '</a>';
            }
            //TODO: modify for when we do permissions properly
            if (globalVars.canCreateEvents == "True") {
                displayString += '<a class="list-group-item addEventButton">Add Event</a>';
            }
            displayMenu(displayString);

            $('.contextPointButton').click(function () {
                $(document).trigger('locationSelected-contextMenu', [$(this).attr('data-type'), { point: { latitude: clickedLatLng.lat(), longitude: clickedLatLng.lng() } }]);
                contextMenu.fadeOut(75);
            });
            $('.addEventButton').click(function () {
                //redirct to admin page
                window.location.href = "/Admin/EditEvent?lat=" + clickedLatLng.lat() + "&lng=" + clickedLatLng.lng();
            });
        }
    };

    publicItem.AddWaypointToContextMenu = function (value) {
        addWaypointToContextMenu = value;
    };

    // gets the roadway names closest to a lat, lng.
    publicItem.getNearestLocations = function (latLng, callback, useZoom) {
        //When we are using zoom, it is to set a Maximum Functional class (the more we are zoomed-out, the bigger the road we want)
        var zoom = 16;//default to very zoomed-in to get all functional classes
        if (useZoom) {
            zoom = appPublic.map.zoom;
        }

        $.ajax('/api/route/getlocations?latitude=' + latLng.lat() + '&longitude=' + latLng.lng() + '&zoom=' + zoom)
            .done(function (data) {
                callback(data || []);
            })
            .fail(function () {
                callback(null);
            });
    };

    var getNearestLocationsCallback = function (data) {
        var contextString = '';
        nearestLocationsArray = [];

        if (data == null) {
            //there was an ajax error.
        } else if (!data.length) {
            contextString = '<li><p class="error">' + resources.CouldNotRetrieveMapLocation + '</p></li>';
        } else {
            for (var i = 0, count = data.length; i < count; i++) {
                if (data[i].name) {
                    //add the roadway name options.
                    contextString += '<li><a  href="#' + i + '">' + data[i].nameDirection + '</a></li>';
                    nearestLocationsArray.push(data[i]);
                }
            }
        }

        //display the menu.
        displayMenu(contextString);
    };

    var displayMenu = function (contextString) {
        // remove all menu items and add fresh ones.
        contextMenu.empty();
        appendItems(contextString);

        // get div which contains map.
        var mapDiv = $(map.getDiv());

        // adjust contextMenu if clicked near the edges so it will always be fully visible.
        if (clickedX > mapDiv.width() - contextMenu.width())
            clickedX -= contextMenu.width();

        if (clickedY > mapDiv.height() - contextMenu.height())
            clickedY -= contextMenu.height();

        // fade in the menu at the correct location.
        contextMenu.css({ top: clickedY, left: clickedX }).fadeIn(300);
    };

    var appendItems = function (contextString) {
        if (resources.DisplayLocationsInContextMenu == "True") {
            contextMenu.append(contextString + '<hr>');
            contextMenu.append(
                '<li><a href="#resetMarkers">' + resources.StartOver + '</a></li>' +
                '<li><a href="#zoomIn">' + resources.ZoomIn + '</a></li>' +
                '<li><a href="#zoomOut">' + resources.ZoomOut + '</a></li>' +
                '<li><a href="#centerMap">' + resources.CenterHere + '</a></li>');
        }
        else {

            contextMenu.append(contextString);

            // append static menu content
            contextMenu.append(
                '<a href="#resetMarkers" class="list-group-item defaultMenu">' + resources.StartOver + '</a>' +
                '<a href="#zoomIn" class="list-group-item defaultMenu">' + resources.ZoomIn + '</a>' +
                '<a href="#zoomOut" class="list-group-item defaultMenu">' + resources.ZoomOut + '</a>' +
                '<a href="#centerMap" class="list-group-item defaultMenu">' + resources.CenterHere + '</a>'
            );
        }
        setUpMenuEventHandlers();
    };

    publicItem.setupEventHandlers = function (menu) {

    };

    publicItem.setupEventHandlersold = function () {
        if (resources.DisplayLocationsInContextMenu == "True") {
            contextMenu.find('a').click(function (e) {
                e.preventDefault();

                // get action value.
                var action = $(e.target).attr('href').substr(1);

                // is this a roadway name?
                var actionInt = possiblyParseInt(action);

                if (actionInt != null) {
                    var location = nearestLocationsArray[actionInt];

                    //special flow for location items.
                    locationClicked(e, location);
                } else {

                    // fade out the menu
                    contextMenu.fadeOut(75);

                    //one of the static options.
                    switch (action) {
                        case 'resetMarkers':
                            $(document).trigger('resetRoutePlanner-contextMenu');
                            break;
                        case 'zoomIn':
                            map.setZoom(map.getZoom() + 1);
                            map.panTo(clickedLatLng);
                            break;
                        case 'zoomOut':
                            map.setZoom(map.getZoom() - 1);
                            map.panTo(clickedLatLng);
                            break;
                        case 'centerMap':
                            map.panTo(clickedLatLng);
                            break;
                    }
                }
            });
        } else {
            //on menu item click.
            $('.defaultMenu').click(function (e) {
                e.preventDefault();

                // get action value.
                var action = $(e.target).attr('href').substr(1);

                // is this a roadway name?
                var actionInt = possiblyParseInt(action);

                if (actionInt != null) {
                    var location = nearestLocationsArray[actionInt];

                    //special flow for location items.
                    locationClicked(e, location);
                } else {

                    // fade out the menu
                    contextMenu.fadeOut(75);

                    //one of the static options.
                    switch (action) {
                        case 'resetMarkers':
                            $(document).trigger('resetRoutePlanner-contextMenu');
                            break;
                        case 'zoomIn':
                            map.setZoom(map.getZoom() + 1);
                            map.panTo(clickedLatLng);
                            break;
                        case 'zoomOut':
                            map.setZoom(map.getZoom() - 1);
                            map.panTo(clickedLatLng);
                            break;
                        case 'centerMap':
                            map.panTo(clickedLatLng);
                            break;
                    }
                }
            });
        }


        // Add hover effects on items
        contextMenu.find('a').hover(function () {
            $(this).parent().addClass('hover');
        }, function () {
            $(this).parent().removeClass('hover');
        });
    };

    // location menu item clicked.
    var locationClicked = function (event, location) {

        //get click event target.
        var jqueryObj = $(event.target);

        //get parent of event target.
        var parent = jqueryObj.parent();

        //detach click event target from parent.
        var original = jqueryObj.detach();

        //reference to this for closure.
        var $this = this;
        $this.mostRecentLocation = location;

        //append existing to/from menu item if available, otherwise create on.
        parent.append($this.toHereFromHereMenuItem || '<div class="btn-group" id="contextMenuToFrom"><button class="btn btn-info" data-type="fromHere">' + resources.FromHere + '</button><button class="btn btn-info" data-type="toHere">' + resources.ToHere + '</button></div>');

        //if to here / from here menu created for first time, attach event handlers.
        if (!$this.toHereFromHereMenuItem) {
            var firstChild = parent.children().first();

            var clickAction = function (toFrom) {
                //trigger selection event and fade out context menu.
                $(document).trigger('locationSelected-contextMenu', [toFrom, $this.mostRecentLocation]);
                contextMenu.fadeOut(75);
            }; //attach event handler to click event.
            $('[data-type="fromHere"],[data-type="toHere"]', firstChild).on('click', function (e) {
                clickAction($(this).attr('data-type'));
            }).css('cursor', 'pointer');
        }

        //on mouse leave of parent
        parent.on('mouseleave.atisContextMenu', function (e) {
            //remove current content and append original content.
            $this.toHereFromHereMenuItem = parent.children().first().detach();
            parent.append(original);

            //remove event handler and hover class.
            parent.off('mouseleave.atisContextMenu');
            parent.removeClass('hover');
        });
    };

    //returns null if val is not a number.
    var possiblyParseInt = function (val) {
        try {
            var intVal = parseInt(val);
            return isNaN(intVal) ? null : intVal;
        } catch (e) {
            return null;
        }
    };

    //init the atis context menu.
    //init();
    publicItem.resetMarkers = function (menu) {
        //event trigger kept in for backwards compatibility
        $(document).trigger('resetRoutePlanner-contextMenu');
    };

    publicItem.doAction = function (menu, event, actionInt) {
        $(document).trigger('locationSelected-contextMenu', [actionInt == 1 ? 'fromHere' : 'toHere', { point: { latitude: latLng.lat(), longitude: latLng.lng() } }]);
        if ($("#mapLocation").is(":visible")) {
            $(".myRouteBtn").trigger("click");
        }
        menu.fadeOut(75);
    };

    return publicItem;
};

var NewAtisContextMenuFromErs = function (map, options) {
    var publicItem = {};

    var latLng;

    publicItem.populateMenu = function (menu, clickedX, clickedY, clickedLatLng, displayMenu) {
        latLng = clickedLatLng;
        displayMenu('<a class="list-group-item contextPointButton" data-type="fromHere" href="1">' + resources.FromHere + '</a><a class="list-group-item contextPointButton" data-type="toHere" href="2">' + resources.ToHere + '</a>', clickedX, clickedY, clickedLatLng);
    };

    publicItem.doAction = function (menu, event, actionInt) {
        $(document).trigger('locationSelected-contextMenu', [actionInt == 1 ? 'fromHere' : 'toHere', { point: { latitude: latLng.lat(), longitude: latLng.lng() } }]);
        contextMenu.fadeOut(75);
    };

    publicItem.setupEventHandlers = function (menu) {

    };

    // gets the roadway names closest to a lat, lng.
    publicItem.getNearestLocations = function (latLng, callback, useZoom) {
        //When we are using zoom, it is to set a Maximum Functional class (the more we are zoomed-out, the bigger the road we want)
        var zoom = 16;//default to very zoomed-in to get all functional classes
        if (useZoom) {
            zoom = map.zoom;
        }

        $.ajax('/api/route/getlocations?latitude=' + latLng.lat() + '&longitude=' + latLng.lng() + '&zoom=' + zoom)
            .done(function (data) {
                callback(data || []);
            })
            .fail(function () {
                callback(null);
            });
    };
    
    var getNearestLocationsCallback = function (data) {
        var contextString = '';
        nearestLocationsArray = [];

        if (data == null) {
            //there was an ajax error.
        } else if (!data.length) {
            contextString = '<li><p class="error">' + resources.CouldNotRetrieveMapLocation + '</p></li>';
        } else {
            for (var i = 0, count = data.length; i < count; i++) {
                if (data[i].name) {
                    //add the roadway name options.
                    contextString += '<li><a  href="#' + i + '">' + data[i].nameDirection + '</a></li>';
                    nearestLocationsArray.push(data[i]);
                }
            }
        }

        //display the menu.
        displayMenu(contextString);
    };

    publicItem.resetMarkers = function (menu) {
        //event trigger kept in for backwards compatibility
        $(document).trigger('resetRoutePlanner-contextMenu');
    };

    return publicItem;
};
//Tab Key Helper.  Focus on the correct button when tab is pressed or shift+tab is pressed.
$(() => {
    $('#setStartGeolocation')[0].addEventListener('focus', () => {
        if ($('#startLocationText').val() !== '' ) {
            $('#swapLocationsBtn').focus();
        }
    });
    $('#setEndGeolocation')[0].addEventListener('focus', () => {
        if ($('#endLocationText').val() !== '') {
            $('#generateRouteBtn').focus();
        }
    });

    $('#generateRouteBtn').keydown(function (e) {
        if (e.key === 'Tab' && e.shiftKey) {
            setTimeout(() => {$('#endLocationText').focus();});
        }
    });

    $('#swapLocationsBtn').keydown(function (e) {
        if (e.key === 'Tab' && e.shiftKey) {
            setTimeout(() => { $('#startLocationText').focus(); });
        }
    });

});

var RoutePlanner = function (appPublicApi, contextMenuApi, options) {
    var publicItem = {};

    var autoComplete = new RoutePlannerAutocomplete(appPublicApi.map, options, window.extraPoi || []);
    // init waypoint tools.
    var waypointManager = new WaypointManager(appPublicApi.map, autoComplete, contextMenuApi, publicItem);

    // init route polyline
    var routePolyline = new RoutingPolyline(appPublicApi, contextMenuApi, publicItem);

    // reference to map.
    var map = appPublicApi.map;

    //user geolocation tools.
    var geolocation = new UserGeolocation(autoComplete, waypointManager, contextMenuApi, appPublicApi.map, appPublicApi);

    //transit route planner.
    var transitPlanner = new TransitDirections(appPublicApi, routePolyline, document.getElementById('transitRouteResults'));
    //some common map related functions.
    var mapFctns = new MapFctns();

    //By remembering our routes we can speed up creating a waypoint for it
    //because we already know which parameters are most likely needed
    var currentDriveRoutes = [];
    //used to store which of the 3 possible routes is currently selected
    var curSelected = 0;

    //Used to store the id of the current routing task so that
    //If there are old routes coming in we can throw them away
    var currentRoutingTask = "";

    var currentRouteData = null;

    var lastEventTargetId = "";

    autoComplete.SetupAutoComplete('#startLocationText', waypointManager.getLocations()[0]);

    autoComplete.SetupAutoComplete('#endLocationText', waypointManager.getLocations()[waypointManager.getLocations().length-1]);

    var routeRequested = function (event) {     
        event.preventDefault();
        currentDriveRoutes = [];
        curSelected = 0;

        var fromLocation = $('#startLocationText').val();
        var toLocation = $('#endLocationText').val();
        //getLocationsTextFromUI();
        
        if (fromLocation.match(/^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$/g) && toLocation.match(/^(\-?\d+(\.\d+)?),\s*(\-?\d+(\.\d+)?)$/g)) {

            calculateRoute([{
                point: { latitude: fromLocation.split(',')[0], longitude: fromLocation.split(',')[1] }
            }, {
                point: { latitude: toLocation.split(',')[0], longitude: toLocation.split(',')[1] }
            }], event);
        }
        else {
            waypointManager.getLocationArray()
                .then(locations => {
                    if (locationsInvalidHandling(locations)) {
                        clearRouteView(true);
                    } else if(isSameLocation(locations)) {
                        updateStatus(resources.StartAndEndLocationCannotBeSame);
                        clearRouteView(true);
                    } else {
                        updateStatus();
                        calculateRoute(locations, event); 
                    }
                })
                .catch(()=>updateStatus(resources.PleaseCheckStartEndLocation));
        }
        //Close streetView
        map.getStreetView().setVisible(false);
        //Remove the enter key event handler that was added when the route was defined.
        $(document).off('keypress', null, WaypointManager.enterkeyHandler);
    };

    var locationsInvalidHandling = function(locations){
        for (let i = 0; i < locations.length; i++) {
            if (!locations[i]) {
                if (i === 0) {
                    updateStatus(resources.PleaseCheckStartLocation);
                } else if (i === locations.length - 1) {
                    updateStatus(resources.PleaseCheckEndLocation);
                } else {
                    updateStatus(resources.PleaseCheckWaypoints);
                }
                
                return true;
            }
        }
        return false;
    }

    var generateGuid = function () {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    };

    /* Fully resets the route planner, removing all data related to the previous route. */
    var resetRoutePlanner = function () {
        $(".waypointContainer").hide();
        //remove route summary, markers, status messages, polylines.
        clearRouteView();

        currentDriveRoutes = [];
        curSelected = 0;

        if (typeof TransitOptions !== 'undefined') {
            TransitOptions.reset();
        }
        currentRoutingTask = "";
        //clear text boxes.
        waypointManager.clearAll();
        $('#btnSaveRoute').text(resources.Save);

        //trigger route cleared event.
        $(document).trigger('routeCleared-routePlanner');

        if ($("#Map_Index_RoutePlannerDescription > div").contents().length > 0) {
            $("#routePlannerDesc").show();
        }
        $(document).off('keypress', null, waypointManager.enterkeyHandler);
    };

    /* Resets the display of the route planner, while keeping previous route data in memory. */
    var clearRouteView = function (keepMsg) {
        //remove polylines.
        routePolyline.DeleteAll();

        //hide results.
        $('#routeResults').hide();

        //remove result content.
        $('#routeTabContent').empty();
        $('#routeTabs').empty();

        //clear messages.
        if (!keepMsg) updateStatus();

        //clear transit directions.
        transitPlanner.Reset();
    };

    $(document).on('clearRouteView-routePlanner', function (evnt) {
        clearRouteView();
    });

    $(document).on('saveRoutePoints', function () {
        var locations = $.map(waypointManager.getLocations(), function (x) {
            return x.point;
        });

        var obj = {
            locations: locations,
            avoidToll: $("#avoidTollsCheckBox").is(":checked")
        }
        localStorage.setItem('routeLocations', JSON.stringify(obj));
    });

    var triggerRouteFailEvent = function () {
        //trigger event that signals routing has failed.
        $(document).trigger('routingFail-routePlanner');
    };

    var triggerRouteSuccessEvent = function () {
        //trigger event that signals routing has succeeded.     
        $(document).trigger('routingSuccess-routePlanner');       
    };

    var generateTransitRoute = function (locations, options, includeDriving) {
        $(".waypointContainer").hide();
        // show transit results
        $('#transitRouteResults').show();
        transitPlanner.Directions(locations, options, includeDriving);
        // trigger event for listener adjust transit results height (Routingsidebar.cshtml)
        $('#routeResults').trigger('adjustTransitResultsHeight');
        // Trigger Transit route requested event
        //$(document).trigger('transitRouteRequested-routePlanner');
        //Display transit message
        updateStatus(resources.TransitForImmediateDepartures);
    };

    var generateDriveRoute = function (locations, transitHubLocation, options, transitOptions) {

        var driveLocations = locations;
        var transitLocations;

        //We are doing a drive+transit route.
        //We now need to drive to the nearest hub, and then transit from the hub to our destination.
        if (transitHubLocation) {
            driveLocations = [locations[0], transitHubLocation];
            transitLocations = [transitHubLocation, locations[1]];
        }

        // Get Route using Marker WayPoints 
        $.ajax('/api/route/getroutes', {
            type: 'POST',
            data: { Waypoints: driveLocations, Options: options },
            transitHubLocation: transitHubLocation
        }).done(function (data) {
            if (data.length > 0) {
                for (let i = 0; i < data[0].waypoints.length; i++) {
                    if (i === 1 && transitHubLocation) {
                        break;
                    }
                    waypointManager.adjustMarker(i, data[0].waypoints[i].point.latitude, data[0].waypoints[i].point.longitude);
                }

                $(document).trigger("routeGenerated-loginModal");
            }

            //display the route.
            displayRoute(data, transitHubLocation !== false, transitHubLocation.name);

            if (transitHubLocation) {
                generateTransitRoute(transitLocations, transitOptions, true);

                //fit map to full drive+transit result.
                mapFctns.fitMapToRoute(map, locations);

                loadBlockerApi.hideSpinner('calculateDriveTransitRoute');
            } 

            triggerRouteSuccessEvent()
        })
            .fail(function () {
                triggerRouteFailEvent();
                updateStatus(resources.ErrorRetrievingYourRoute);
                loadBlockerApi.hideSpinner('calculateDriveTransitRoute');
            })
            .always(function () {
                loadBlockerApi.hideSpinner('calculateRoute');
            });
    }

    var clearMarkerListeners = function (markers, listener) {
        markers.forEach(function (marker) {
            marker.listenerAdded = false;
            google.maps.event.clearListeners(marker, listener);
        });
    };

    var calculateRoute = function (locations, event) {

        event.currentTarget.id = event.currentTarget.id ? event.currentTarget.id : event.target.id;

        //show spinner and clear out details of previous route.
        loadBlockerApi.showSpinner('calculateRoute');
        clearRouteView();
        currentRoutingTask = "";

        $("#routePlannerDesc").hide();

        //make sure text fields and markers are shown correctly.
        waypointManager.setDetails();
        //setupWaymarkers();

        //get transit options if they exists
        var options = {};
        var transitOptions = {};
        if (typeof TransitOptions !== 'undefined') {
            if (event && event.currentTarget.id === 'generateWalkOnlyRouteBtn') {
                TransitOptions.UpdateTransitOptions("WALK");
            }
            if (event && event.currentTarget.id === 'generateBicycleOnlyRouteBtn') {
                TransitOptions.UpdateTransitOptions("BICYCLE");
            }
            if (event && event.currentTarget.id === 'generateTransitRouteBtn') {
                TransitOptions.UpdateTransitOptionsFromDropDown();
            }

            transitOptions = TransitOptions.GetTransitOptions();
            if (transitOptions.error) {
                loadBlockerApi.hideSpinner('calculateRoute');
                RoutePlannerStatus.updateStatus(transitOptions.errMsg, RoutePlannerStatus.StatusTypes.danger);
                return;
            }
            else {
                try {
                    transitOptions = JSON.stringify(transitOptions);
                } catch (e) {
                }
            }
        }

        if ($('#avoidTollsCheckBox').length > 0) {
            options.AvoidTolls = $('#avoidTollsCheckBox').is(':checked');
        }

        if ($('#avoidFerriesCheckBox').length > 0) {
            options.AvoidFerries = $('#avoidFerriesCheckBox').is(':checked');
        }

        if (event.currentTarget.id != "") lastEventTargetId = event.currentTarget.id;
        else event.currentTarget.id = lastEventTargetId;

        // Drive + Transit Results
        if (event && event.currentTarget.id === 'generateDriveTransitRouteBtn') {
            transitOptions.travelMode = 'transit';
            $(".waypointContainer").hide(); 
            loadBlockerApi.hideSpinner('calculateRoute');
            //always hide route name input and save button.
            $('.routeName').toggle(false);
            $('#btnSaveRoute').toggle(false);
            //here we want to first zoom to their start location
            map.setZoom(12);
            map.panTo(new google.maps.LatLng(locations[0].point.latitude, locations[0].point.longitude));
            //turn off all other layers + turn on transit Park&Ride layer
            //then alert them to select a Park&Ride lot
            updateStatus(resources.DriveTransitHelpText);
            var parkAndRideLayers = resources.ParkAndRideLayers.split(",");
            //appPublicApi.appHelper.iconManager.RefreshLayer("Transit", true)
            var allParkAndRideMarkers = [];
            parkAndRideLayers.forEach(function (l) {
                $(document).on('layerRefreshed-iconsAdded.' + l, function (event, layer, addedMarkers) {
                    if (parkAndRideLayers.indexOf(layer) > -1) {
                        $(document).off('layerRefreshed-iconsAdded.' + layer);
                        var layer = appPublicApi.appHelper.iconManager.GetIconLayer(layer);
                        var markers = layer.GetIcons();
                        allParkAndRideMarkers = allParkAndRideMarkers.concat(markers);
                        markers.forEach(function (marker) {
                            if (!marker.listenerAdded) {
                                marker.listenerAdded = true;
                                var listener = marker.addListener('mouseup', function (e) {
                                    waypointManager.getLocationArray().then((locations) => {
                                        if (locationsInvalidHandling(locations)) {
                                            clearRouteView();
                                            clearMarkerListeners(allParkAndRideMarkers, 'mouseup');
                                            loadBlockerApi.hideSpinner('calculateDriveTransitRoute');
                                        } else {
                                            //clearRouteView();
                                            loadBlockerApi.showSpinner('calculateDriveTransitRoute');
                                            var hubLocation = autoComplete.GetCustomSimplePlace(marker.title, null, e.latLng.lat(), e.latLng.lng(), false);
                                            //We shouldn't be using waypoints so the last 3 parameters (waypoint related) should be zeroed
                                            generateDriveRoute(locations, hubLocation, options, transitOptions);
                                            //add class to fix padding issue when both route and transit are generated
                                            $('#transitRouteResults').addClass('padFix');

                                            clearMarkerListeners(allParkAndRideMarkers, 'mouseup');
                                        }
                                        
                                    });
                                });
                            }
                        });
                    }
                });
            });
            enableLayers(parkAndRideLayers);
        } else if (event && event.currentTarget.id === 'generateTransitRouteBtn') {
            transitOptions.travelMode = 'transit';

            generateTransitRoute(locations, transitOptions, false);
        } else if (event && event.currentTarget.id === 'generateWalkOnlyRouteBtn') {
            transitOptions.travelMode = 'walking';

            generateTransitRoute(locations, transitOptions, false);
        } else if (event && event.currentTarget.id === 'generateBicycleOnlyRouteBtn') {
            transitOptions.travelMode = 'bicycling';
            generateTransitRoute(locations, transitOptions, false);
        } else {
            //Driving Route
            $('#transitRouteResults').hide();
            if (currentDriveRoutes.length > 0) {
                var current = currentDriveRoutes[curSelected];
                generateDriveRoute(locations, false, options);
            } else {
                generateDriveRoute(locations, false, options);
            }
        }
    
    };

    var displayRoute = function (routeResults, isTransit, hubName) {
              
        $("#routePlannerDesc").hide();
        if (routeResults.length == 0) {
            triggerRouteFailEvent();
            let locations = waypointManager.getLocations();
            updateStatus(locations.length <= 2 ? resources.RouteNotAvailable : resources.RouteNotAvailableWithWaypoints);
        } else {
            $(".waypointContainer").show();

            // show route tabs
            $('#routeTabs').show();

            //trigger route generated event.
            $(document).trigger('routeGenerated-routePlanner', [routeResults]);

            // clear route view first in case the route is generated twice in some situations
            clearRouteView(true);

            //display each returned route.
            for (var i = 0; i < routeResults.length; i++) {
                addRouteTab(routeResults[i], i + 1, isTransit, hubName);
            }

            //make each route tab clickable.
            $('#routeTabs a').click(function (e) {
                e.preventDefault();

                //show the tab.
                $(this).tab('show');

                //make route polyline active.
                routePolyline.MakeActive($(this).attr('data-guids') + getGuids('#transitRouteTabs li.active a'));
            });

            //activate the first route and display results.
            routePolyline.MakeActive($('#routeTabs a:first').attr('data-guids') + getGuids('#transitRouteTabs li.active a'));
            $('#routeTabs a:first').tab('show');
            $('#routeResults').show();

            //set the scroll bar to top position
            $('#routeResults').scrollTop(0);

            if (routeResults[0].waypoints.length > 0) {
                //fit map to first result.
                mapFctns.fitMapToRoute(map, routeResults[0].waypoints);
            }

            // trigger event for listener to adjust route planner height (Routingshidebar.cshtml)
            $('#routeResults').trigger('adjustRoutePlannerHeight');

            //show layers if they are not already selected
            enableLayers(routeResults[0].enableMapLayers);

            //Close streetView
            map.getStreetView().setVisible(false);

            checkEventOnRoutePlanner();

            $('#routeTabs a').on('shown.bs.tab', function () {
                checkEventOnRoutePlanner();
            });
        }
    };

    //gets guids from a selector, it assumes that if the selector is found it will have an attibute 'data-guids'
    //mainly used for drive + transit
    //Returns an empty string if the selector does not find an object
    function getGuids(selector) {
        var activeTab = $(selector);
        var otherGuids = '';
        if (activeTab != null && activeTab.length && activeTab != this) {
            otherGuids = ';' + activeTab.attr('data-guids');
        }
        return otherGuids;
    }

    var beforeDisplayRoute = function () {
        // show route tabs
        $('#routeTabs').show();
        $('#routeResults').show();
        $('#routeResults').scrollTop(0);

        //Close streetView
        map.getStreetView().setVisible(false);

        // calculate route results height in mobile mode
        if (Modernizr.mq('(max-width: 992px)')) {
            $(document).trigger("BeforeDisplayRoute");
        }
    }

    var displaySingleRoute = function (route, id, active) {
        addRouteTab(route, id, false);
        //make each route tab clickable.
        $('#routeTabs a').click(function (e) {
            e.preventDefault();
            //show the tab.
            $(this).tab('show');
            curSelected = id - 1;
            //make route polyline active.
            routePolyline.MakeActive($(this).attr('data-guids'));
        });

        if (active) {
            $('#routeTabs a:first').tab('show');
            routePolyline.MakeActive($('#routeTabs a:first').attr('data-guids'));
            if (route.waypoints.length > 0) {
                //fit map to first result.
                mapFctns.fitMapToRoute(map, route.waypoints);
            }
            //show layers if they are not already selected
            enableLayers(route.enableMapLayers);
        } else {
            routePolyline.MakeActive(routePolyline.getActivePolylines());
        }
        $(document).trigger('routeGenerated-routePlanner', [currentDriveRoutes]);
    }

    var enableLayers = function (mapLayers) {
        //iterate through layers to be enabled
        for (var i in mapLayers) {
            var checkBox = $('input[type=\'checkbox\'][data-layerid =' + mapLayers[i] + ']', $('#layerSelection'));
            if (checkBox && checkBox[0] && !checkBox[0].checked) {
                checkBox[0].click();
            } else {
                appPublicApi.appHelper.iconManager.RefreshLayer(mapLayers[i], true);
            }
        }
    };

    var addRouteTab = function (route, id, isTransit, hubName) {
        //reference to route statistics.
        var routeStats = route.statistics;
        var useRouteClosureDisclaimer = resources.EnableRouteClosureDisclaimer == "True" && route.statistics.includesClosures;
        var useRouteConditionDisclaimer = resources.EnableRouteConditionDisclaimer == "True" && route.statistics.includesRouteConditions;
        var options = useRouteClosureDisclaimer ? { strokeColor: '#FF0000' } : null;
        //add route polyline.
        var guid = routePolyline.AddRoute(route.encodedPolyline, options, false, isTransit);

        var fromLocation = '';
        var endLocation = '';
        if (route.waypoints.length > 1) {
            fromLocation = route.waypoints[0].nameDirection;
            endLocation = route.waypoints[route.waypoints.length - 1].nameDirection;
        }

        var withDisclaimerClass = useRouteClosureDisclaimer || useRouteConditionDisclaimer ? 'withDisclaimer' : '';
        //Add the route tab. // We add transit to the beginning of the id so that we don't confuse the tabs when we do drive+transit
        $('#routeTabs').append('<li class=\'routeTabHeader ' + withDisclaimerClass + '\' role=\'presentation\'><a href=\'#' + (isTransit ? 'transit' : '') + 'routeTab-' + id + '\' data-id=\'' + id + '\' data-guids=\'' + guid + '\' data-travelTimeInSeconds=\'' + routeStats.postedTravelTimeSeconds + '\'>' + resources.Route + ' ' + id + '  <span class=\'badge\'>' + routeStats.travelTimeDisplay + '</span></a></li>');
        var html = '';

        if (useRouteClosureDisclaimer || useRouteConditionDisclaimer) {
            //first, we put a notification at the top of the tab.
            html += '<div id="routeIncludesDisclaimerDiv' + id + '" class="alert alert-danger" role="alert">';
            if (useRouteClosureDisclaimer) {
                if (useRouteConditionDisclaimer) {
                    html += '<p>' + resources.RouteIncludesClosuresAndConditionsDisclaimer + '</p>';
                } else {
                    html += '<p>' + resources.RouteIncludesClosuresDisclaimer + '</p>';
                }               
            } else {               
                html += '<p>' + resources.RouteIncludesConditionsDisclaimer + '</p>';
            }

            html += '<button id="acceptRouteDisclaimerBtn' + id + '" type="button" class="btn btn-info">' + resources.OK + '</button>'
                + '</div >';

            //then we add a div with opacity over the whole thing
            html += '<div class="hideRouteTab' + id + '" style="opacity: .10;">';
        }

        //Add the route details.
        html += '<div class=\'panel panel-default routeContent ' + withDisclaimerClass + '\'><h3 class=\'printHeader\'>'
            + resources.SiteHeader + ' ' + resources.RouteDetails
            + '</h3><div class=\'panel-body\'><i class=\'far fa-car fa-2x\'></i><span class=\'printToFrom\'>'
            + resources.From
            + ' '
            + fromLocation
            + ' '
            + resources.To
            + ' '
            + endLocation
            + '</span><p><b>'
            + resources.TotalTime
            + ' <span class=\'pull-right\'> '
            + routeStats.travelTimeDisplay + '</span></b><br />';

        html += '<b>' + resources.TotalTravelDistance + ' <span class=\'pull-right\'> ' + routeStats.lengthDisplay + '</span></b></p>';

        // route info msg for Tolls, Ferries and Closures combination
        var text = [];
        if (route.includesTollLink) {
            text.push(resources.Tolls.toLowerCase());
        }
        if (route.includesFerryLink) {
            text.push(resources.Ferries.toLowerCase());
        }

        var infoText = "";
        if (text.length == 2) {
            infoText = text[0] + " " + resources.And + " " + text[1];
        } else if (text.length == 1) {
            infoText = text[0];
        }

        if (text.length > 0) {
            html += '<div class="route-alert"><i class="fas fa-exclamation-triangle" aria-hidden="true"></i> '
                    + resources.ThisRouteIncludes + " " + infoText
                    + '.</div>';           
        }

        html += '</div>';
        html += '<ul class=\'list-group\'>';

        if (hubName) {
            hubName = ' (' + hubName + ')';
        } else {
            hubName = '';
        }

        if (routeStats.instructions) {
            for (var j = 0; j < routeStats.instructions.length; j++) {
                let step = routeStats.instructions[j];
                let arrowImg = arrowDirection.getDriveImg(step.instruction);                
                html += '<li class="list-group-item"><div class="directionArrow">' + arrowImg +
                    '</div><div ' + applyWTAStyling(step.linkConditions) +
                    '>' + step.instruction +
                    linkEventsHtml(step.linkEvents, id, j) +
                    linkCamerasHtml(step.linkCameras, id, j) +
                    linkWtaHtml(step.linkConditions) +
                    '</div></li>';
            }
        } else {
            for (var j = 0; j < routeStats.links.length; j++) {
                if (j == routeStats.links.length - 1) {
                    //Last link.
                    html += '<li class=\'list-group-item\'><b>' + routeStats.links[j].nameDirection + hubName + '</b><br/>' + routeStats.links[j].travelTimeDisplay + ' (' + routeStats.links[j].lengthDisplay + ')' + linkEventsHtml(routeStats.links[j].linkEvents, id, j) + linkCamerasHtml(routeStats.links[j].linkCameras, id, j) + '</li>';
                } else {
                    html += '<li class=\'list-group-item\'><b>' + resources.From + ' ' + routeStats.links[j].nameDirection + ' ' + resources.To + ' ' + routeStats.links[j + 1].nameDirection + '</b><br/>' + routeStats.links[j].travelTimeDisplay + ' (' + routeStats.links[j].lengthDisplay + ')' + linkEventsHtml(routeStats.links[j].linkEvents, id, j) + linkCamerasHtml(routeStats.links[j].linkCameras, id, j) + '</li>';
                }
            }
        }
        html += '</ul></div>';

        if (useRouteClosureDisclaimer || useRouteConditionDisclaimer) {
            html += '</div>'
        }

        let notes;

        if (route.includesFerryLink || resources.OutsideONTTDistanceTravelPortions !== '') {
            notes = `<div class="routeNotes">
                        <p>${resources.Notes}</p>
                        <ul>
                            <li>${resources.AllTravelTimesBasedOnEstimates}</li>
                            ${resources.OutsideONTTDistanceTravelPortions !== '' ? '<li>' + resources.OutsideONTTDistanceTravelPortions + '</li>' : ''}
                            ${route.includesFerryLink ? '<li>' + resources.FerryNote + '</li> ' : ''}   
                        </ul>
                     </div> `;
        } else {
            notes = `<p class="routeNote"><b>${resources.NoteColon}</b> ${resources.AllTravelTimesBasedOnEstimates}</p>`;
        }
       
        // We add transit to the beginning of the id so that we don't confuse the tabs when we do drive+transit
        $('#routeTabContent').append('<div id="' + (isTransit ? 'transit' : '') + 'routeTab-' + id + '" data-id="' + id + '" class="routeTab tab-pane fade">' + html + notes + '</div>');


        $("#cameraRouteToggle" + id + " , #eventRouteToggle" + id).on("click", function () {         
            var id = $(this).data("id");
            $("i[id='" + id + "circle'").toggleClass('fa-plus-circle fa-minus-circle');
            $("div[id='" + id + "div'").toggle(400);
            if (this.className == "eventRouteToggle") {
                var hasShowMore = $(this).parent().find(".showMore").length > 0;
                if (!hasShowMore) {
                    checkEventOnRoutePlanner(this);
                }
            }                  
        });
        $(".Cctv-link").on("click", function () {
            UserCameras.zoomToCamera(this.getAttribute('data-id'), 'Camera', 'Cameras');
        });
        $('#acceptRouteDisclaimerBtn' + id).on('click', function () {
            $(".hideRouteTab" + id).css({ "opacity": "1" });
            $("#routeIncludesDisclaimerDiv" + id).hide();
        });
    };

    String.prototype.replaceAll = function (find, replace) {
        return this.split(find).join(replace);
    };

    var linkCamerasFormat = null;
    var linkCamerasHtml = function (cameras, tabId, rowId) {
        if (!cameras || !cameras.length) {
            return '';
        }

        var sectionId = cameras[0].id + tabId + rowId;

        if (!linkCamerasFormat) {
            linkCamerasFormat = '<div class="route-tooltip routeLinkCamera"><p><img src="{IconURL}" alt="{Name}">&nbsp;<a class="Cctv-link" href="#" data-id="{Id}">{Name}</a></p></div>';
        }

        var text = '';       
        for (var i in cameras) {
            text += linkCamerasFormat.replaceAll('{Name}', cameras[i].displayName).replaceAll('{Id}', cameras[i].id).replaceAll('{IconURL}', cameras[i].icon.url);
        }

        if (cameras.length > 1) {
            var prepend = '<div class="cameraRouteSection"><button id="cameraRouteToggle' + tabId + '" class="cameraRouteToggle" data-id="' + sectionId + '" data-toggle="collapse" title="Toggle" aria-expanded="true" class="">' + resources.Cameras + ' <span class="badge">' + cameras.length + '</span><i id="' + sectionId + 'circle" class="far fa-plus-circle"></i></button><div id="' + sectionId + 'div" class="routeCameras" style="display:none;">';
            var append = '</div>';
            text = prepend + text + append;
        }

        return text;
    };

    var linkEventsFormat = null;
    var linkEventsHtml = function (events, tabId, rowId) {
        //no events on this link.
        if (!events || !events.length) {
            return '';
        }

        //if format not yet built.
        if (!linkEventsFormat) {
            var format = '<div class="route-tooltip routeLinkEvent">';
            format += '<button onclick="routeViewOnMap(\'{viewLink}\',\'{layerId}\')" class="routeViewOnMap">' + resources.ViewOnMap + '</button>';
            format += '<h4><img src="{iconUrl}" alt="{heading}">&nbsp;{heading}</h4>';
            format += '<div class="text">';
            format += '{text}';
            format += '</div></div>';

            //set class variable so we do not generate the format string each time.
            linkEventsFormat = format;
        }

        //the text we will return.
        var text = '';     

       
        for (var idx in events) {
                var attrsText = '';

                //build the attributes html.
                for (var infoIdx in events[idx].orderedPiecesOfInfo) {
                    if (events[idx].orderedPiecesOfInfo[infoIdx].title == resources.Description) {
                        // remove extra spaces
                        var content = events[idx].orderedPiecesOfInfo[infoIdx].text.trim().replace(/\s\s+/g, ' ');
                        content = content.replace(" .", ".");
                        var modHtml = '<div class="shorten">';
                        modHtml += content;
                        modHtml += '</div>';
                        attrsText += linkEventsFormat.replaceAll('{text}', modHtml);
                    }
                }

                //build the overall html.
            text += attrsText.replaceAll('{iconUrl}',
                    events[idx].icon.url).replaceAll('{heading}',
                    events[idx].heading).replaceAll('{viewLink}',
                    events[idx].urlFriendlyName).replaceAll('{layerId}',
                    events[idx].urlFriendlyName + "-" + events[idx].id);

            

          
        }

        if (events.length > 1) {
            var sectionId = events[0].id + tabId + rowId;
            var prepend = '<div class="eventRouteSection"><button id="eventRouteToggle' + tabId + '" class="eventRouteToggle" data-id="' + sectionId + '" data-toggle="collapse" title="Toggle" aria-expanded="true" class=""> ' + resources.Events + '<span class="badge">' + events.length +'</span><i id="' + sectionId + 'circle" class="far fa-plus-circle"></i></button><div id="' + sectionId + 'div" class="routeEvents" style="display:none;">';
            var append = '</div></div>';
            text = prepend + text + append;
        }
  
        return text;
    };

    var linkWtaHtml = function (conditions) {
        if (!conditions || !conditions.length) {
            return '';
        }
        var format = '<div class="route-tooltip routeLinkEvent">';
        format += '<h4><img class="notMarker" src="/Content/Images/ic_wta.svg" alt="' + resources.RoadConditions + '">' + resources.RoadConditions + '</h4>';
        format += '<ul class="wtaInfo">';

        //var uniqueList = [...new Map(conditions.map(x => [x.id, x])).values()]; // IE 11 not supported   
        var uniqueList = conditions.reduce(function (result, e1) {
            var matches = result.filter(function (e2) {
                return e1.id == e2.id;
            })
            if (matches.length == 0)
                result.push(e1)
            return result;
        }, []);
        for (var i = 0; i < uniqueList.length; i++) {
            format += '<li><span class="wtaDot" style="background-color: ' + uniqueList[i].colour + '" aria-hidden="true"></span>' + uniqueList[i].statusName + '</li>';
        }

        format += '</ul>';
        format += '</div>';

        return format;
    };

    var applyWTAStyling = function (conditions) {
        var gradient = '';
        if (conditions && conditions.length > 0) {
            var step = Math.round(100 / conditions.length);
            for (var i = 0; i < conditions.length; i++) {
                var current = (step * i);
                gradient += conditions[i].colour + ' ' + current + '%, ' + conditions[i].colour + ' ' + (current + step) + '%';
                if ((i + 1) != conditions.length) {
                    gradient += ',';
                }
                else {
                    gradient += ', ' + conditions[i].colour + ' 100%'; //double up the last colour, this create a solid line if we only have 1 condition.
                }
            }
            return 'class="instructionText" style ="border-style: solid; border-width: 5px; border-right: 0; margin-left: -15px; margin-top: -10px; margin-bottom: -10px; padding-left: 30px; padding-top: 5px; padding-bottom: 5px; border-image: linear-gradient(to bottom, ' + gradient + ') 1 100%;"';
        } else return 'class="instructionText"';
    }

    //reference to global updateStatus function.
    var updateStatus = RoutePlannerStatus.updateStatus;

    var isSameLocation = function (locations) {
        //give nulls/undefined random values so they do not equal another null/undefined.
        var sameLatLng = (locations[0].point.latitude || 'rand') == locations[locations.length - 1].point.latitude && (locations[0].point.longitude || 'rand') == locations[locations.length - 1].point.longitude;
        var sameLinkId = (locations[0].linkId || 'rand') == locations[locations.length - 1].linkId;

        return sameLatLng || sameLinkId;
    };

    //Sets the destination from a POI Id.
    var setDestination = function (e, placeId) {
        var simplePlace = autoComplete.GetSimplePlace(placeId);
        if (simplePlace) {
            waypointManager.setLocation(false, simplePlace, true);
        }
    };

    publicItem.SetWaypoint = function (simplePlace, index, existingWaypoint) {
        //we do +1 because index is the index of the polyline and we want the index of the waypoint
        waypointManager.setWaypoint(simplePlace, Number(index) + 1, true, existingWaypoint);
    };

    //returns the active route tab index.
    publicItem.getActiveRouteIndex = function () {
        var dataId = $('#routeTabs li.active a').attr('data-id');
        return isNaN(dataId) ? null : dataId - 1;
    };

    publicItem.getNameForSimplePlace = function (simplePlace, callback) {
        waypointManager.getNameForSimplePlace(simplePlace, callback);
    }

    publicItem.displayRoutes = function (routes) {
        //reset route planner.
        resetRoutePlanner();

        //if the destination is a POI set destination as POI instead of last link
        if (routes[0].waypoints.length > 0) {
            if (routes[0].waypoints[routes[0].waypoints.length - 1].poiId) {
                setDestination(null, routes[0].waypoints[routes[0].waypoints.length].poiId);
                waypointManager.setLocation(true, routes[0].waypoints[0], true);
            } else {
                waypointManager.setAllLocations(routes[0].waypoints);
            }
        }

        //reset autocomplete for start and end location since all locations are reset by resetRoutePlanner
        autoComplete.SetupAutoComplete('#startLocationText', waypointManager.getLocations()[0]);
        autoComplete.SetupAutoComplete('#endLocationText', waypointManager.getLocations()[waypointManager.getLocations().length - 1]);
        //display the route.
        displayRoute(routes, false);
    };

    //display a calculated route.
    publicItem.displayRoute = function (routeData) {
        publicItem.displayRoutes([routeData], false);
    };

    publicItem.displayTransitRoute = function (routeData) {
        resetRoutePlanner();
        waypointManager.setStartEndPoint(new autoComplete.GetCustomSimplePlace(routeData.fromAddress, null, routeData.fromLat, routeData.fromLong, false), new autoComplete.GetCustomSimplePlace(routeData.toAddress, null, routeData.toLat, routeData.toLong, false));
        waypointManager.getLocationArray().then((locations) => {
            if (locationsInvalidHandling(locations)) {
                clearRouteView();
            } else {
                generateTransitRoute(locations, routeData.options, false);
            }
        });

        //Set route options
        if (typeof TransitOptions !== 'undefined') TransitOptions.SetTransitOptions(routeData.options);
    }

    //ability to reset route planner.
    publicItem.resetRoutePlanner = resetRoutePlanner;

    publicItem.calculateRoute = function (mode) {
        var event = {};
        event.currentTarget = { id: mode };
        event.target = { id: mode };
        event.preventDefault = function () { };
        routeRequested(event);
    }

    publicItem.clearLoationsWithoutPoints = function () {
        waypointManager.clearLoationsWithoutPoints();
    };

    var checkIsEmptyWaypointTextbox = function () {
        var inputWayPointTextbox = $(".waypointText:last");
        var isEmpty = inputWayPointTextbox.html() != undefined && inputWayPointTextbox.val().length === 0;
        return isEmpty;
    }

    var buildWaypoint = function (index, name, existingWaypoint) {
        var html = '<div class="form-group hidden-print" data-waypointparent="' + index + '">' +
            '<div class="input-group">' +
            '<label class="sr-only" for="waypointText-' + index + '">' + resources.Waypoint + ' ' + index + '</label>' +
            '<div class="input-group-addon"><div class="waypointCircle" aria-hidden="true"></div></div>' +
            '<input type="text" class="form-control waypointText" id="waypointText-' + index + '" placeholder="' + resources.Waypoint + '">' +
            '<div class="input-group-btn">' +
            '<button class="btn btn-default" id="setWaypointGeolocation-' + index + '" type="button" data-wpId="' + index + '" title="' + resources.MyLocation + '">' +
            '<i class="far fa-crosshairs" aria-hidden="true"></i>' +
            '<span class="sr-only">' + resources.MyLocation + '</span>' +
            '</button>' + '<button class="btn btn-default" aria-label= "' + resources.Remove + '" id="deleteWaypoint-' + index + '" type="button" data-wpId="' + index + '" title="' + resources.Remove + '"><i class="far fa-times"</button>' +
            '</div>' +
            '</div>' +
            '</div>';
        $("#waypoints").append(html);
        $("#waypointText-" + index).val(name);
        $("#deleteWaypoint-" + index).click(function () {
            var index = $(this).data('wpid');
            waypointManager.ClearWaypoint(index, null, true);

            //publicItem.redrawWaypoints(false);
        });
        $('#setWaypointGeolocation-' + index).click(function () {
            var index = $(this).data('wpid');
            geolocation.AttemptToSetWaypointWithUsersGeolocation(index);
        });
        autoComplete.SetupAutoComplete('#waypointText-' + index, waypointManager.getLocations()[index], existingWaypoint);
    }

    publicItem.redrawWaypoints = function (name, isEmptyWaypoint) {
        let locations = waypointManager.getLocations();
        if (isEmptyWaypoint) {
            var index = locations.length - 1; //we add to the end
            waypointManager.addWaypoint(index);
            buildWaypoint(index, name, false);
        } else {
            $("#waypoints").html('');
            for (var i = 1; i < locations.length - 1; i++) {
                buildWaypoint(i, locations[i].text, true);
            }
        }
    };

    publicItem.AddWaypointToPlanner = function (name, isEmptyWaypoint) {
        //var index = locations == null ? 1 : locations.length - 1;        
        publicItem.redrawWaypoints(name, isEmptyWaypoint);
    };
    var printRouteDetails = function () {
        var toPrint = $('#routeTabs').html();
        var printDrive = toPrint.length > 0;
        var transitPrint = $('#transitRouteResults').html();
        var printTransit = transitPrint.length > 0;

        if (!(printDrive || printTransit)) {
            // display msg no route / transit detail generated if route is empty
            updateStatus(resources.NoRouteDetailGenerated, RoutePlannerStatus.StatusTypes.danger);
        } else {
            //Finding which divs need to be printed
            var itemsToPrint = (printDrive && printTransit ? '#routeTabContent div.active, #transitRouteResults' : printDrive ? '#routeTabContent div.active' : '#transitRouteResults');
            $(itemsToPrint).printThis();
        }
    };

    var checkQuerystringsForDestination = function () {
        //Check for a placeId specified as the destination in the URL.
        var placeId = $.QueryString['placeId'];

        //Check for a destination text specified in the URL.
        var destinationText = $.QueryString['destination'];

        //placeId takes precedence over destination text.
        if (placeId) {
            setDestination(null, parseInt(placeId));
        } else if (destinationText) {
            //perform text search.
            autoComplete.GetSimplePlaceByTextSearch(destinationText, function (simplePlace) {
                if (simplePlace) {
                    //search yielded a result.
                    waypointManager.setLocation(false, simplePlace, true);
                }
            });
        }
    };

    // on detecting User route dropdown, show link route icon
    $(document).on('UserRouteDropDownVisible', function () {
        $('.linkRoute').toggle(true);
    });

    //set all the locations and display them.
    setAllLocations = function (newWaypoints) {
        //specific things for the start and end locations
        waypointManager.setStartEndPoint(newWaypoints[0], newWaypoints[newWaypoints.length - 1]);
        //insert the waypoints that are not the start and end
        for (var i = 1; i < newWaypoints.length - 1; ++i) {
            buildWaypoint(i, '', false);
            waypointManager.setWaypoint(newWaypoints[i], i, true, false);
        }
    };

    var loadCachedRoute = function () {
        var localRouteObj = localStorage.getItem('routeLocations');
        if (localRouteObj) {
            var routeObj = JSON.parse(localRouteObj);
            setAllLocations(routeObj.locations);
            if (routeObj.avoidToll) {
                $("#avoidTollsCheckBox").prop("checked", true);
                $("#routeOptions").click();
            }

            $('.myRouteBtn').trigger("click", {skipPtBSetup: true});

            var cachedGenerateButton = localStorage.getItem('routeGenerateButton')
            if (cachedGenerateButton === 'TRANSIT') {
                $('#generateTransitRouteBtn').click()
            } else if (cachedGenerateButton === 'DRIVETRANSIT') {
                $('#generateDriveTransitRouteBtn').click()
            } else if (cachedGenerateButton === 'WALK') {
                $('#generateWalkOnlyRouteBtn').click()
            } else if (cachedGenerateButton === 'CYCLE') {
                $('#generateBicycleOnlyRouteBtn').click()
            } else {
                $('#generateRouteBtn').click()
            }

            // Only want this to load once
            localStorage.removeItem('routeLocations');
        }
    }

    var init = function () {
        //generate route buttons and event handlers.
        $('#generateRouteBtn').click(routeRequested);
        $('#generateTransitRouteBtn').click(routeRequested);
        $('#generateDriveTransitRouteBtn').click(routeRequested);
        $('#generateWalkOnlyRouteBtn').click(routeRequested);
        $('#generateBicycleOnlyRouteBtn').click(routeRequested);
        $('#generateRouteBtn').submit(function (event) { event.preventDefault(); });
        $('#generateTransitRouteBtn').submit(function (event) { event.preventDefault(); });
        $('#generateWalkOnlyRouteBtn').submit(function (event) { event.preventDefault(); });
        $('#generateBicycleOnlyRouteBtn').submit(function (event) { event.preventDefault(); });
        $('#generateDriveTransitRouteBtn').submit(function (event) { event.preventDefault(); });

        // if route is in url, don't load cache route
        if (window.location.hash.indexOf("#route-") < 0) {
            loadCachedRoute();
        } else {
            // remove local cache route
            localStorage.removeItem('routeLocations');
        }
       

        //context menu clear route option.
        $(document).on('resetRoutePlanner-contextMenu', resetRoutePlanner);

        //Handle the event for setting route destination from venue tooltip.
        //$(document).on('setVenueDestination.venueTooltip', function (e, poiId) {
        //    // Check to see if the route planner is closed, open if it is closed
        //    var isSideBarClosed = $('.showSideBar').css('display') == 'block';
        //    if (isSideBarClosed) {
        //        $('.showSideBar').trigger('click');
        //    }
        //    setDestination(e, poiId);

        //    //close modal tooltip in case it is open.
        //    bootbox.closeMapPageDialog();
        //});

        $(document).on('setCarpoolDestination.carpoolTooltip', function (e, longitude, latitude, label, poiId) {
            // Check to see if the route planner is closed, open if it is closed
            var isSideBarClosed = $('.showSideBar').css('display') == 'block';
            if (isSideBarClosed) {
                $('.showSideBar').trigger('click');
            }
            var lat = latitude / 1000000.0;
            var lng = longitude / 1000000.0;

            //Sometimes Oracle stores these with an extra decimal place that we need to take into account.
            if (lat < -90 || lat > 90) {
                lat = lat / 10.0;
            }

            if (lng < -180 || lng > 180) {
                lng = lng / 10.0;
            }

            waypointManager.setLocation(false, autoComplete.GetCustomSimplePlace(label, null, lat, lng, false), true);

            //close modal tooltip in case it is open.
            bootbox.closeMapPageDialog();
        });

        //clear url hash on clear route button clicked or transit route requested.
        $(document).on('clearRouteButtonClicked.routeSave', urlHash.clearHash);

        //clear route button.
        $('#clearRouteBtn').click(function () {
            resetRoutePlanner();
            $(document).trigger('clearRouteButtonClicked');
            $('.clearLocateBtn').trigger("click");
        });

        $(document).on("clearUserRouteTrigger", function () {
            $('#clearRouteBtn').trigger("click");
        });

        //Swap Locations (Start and End) button
        $('#swapLocationsBtn').click(function (event) {
            //switch texts
            var tempStart = $('#startLocationText').val();
            $('#startLocationText').val($('#endLocationText').val());
            $('#endLocationText').val(tempStart);
            //switch start and end locations
            waypointManager.swapStartEnd();
            //switch markers
            waypointManager.setDetails();

            if (resources.AutoGenerateDriveRoute === "True") {
                //if both locatiosn exists regenerate the route with reversed start and end points
                waypointManager.getLocationArray().then((locations) => {
                    if (locations != null) {
                        routeRequested(event);
                    }
                });
            }

        });

        //check query strings for route destination.
        checkQuerystringsForDestination();  

        // print route details button click event handler
        $('.printRoute').click(printRouteDetails);

        $('.printRoute').bind('keydown', function (e) {
            if (e.keyCode == 13) {
                printRouteDetails();
            }
        });

        if ($('#routeNotificationAlert').length > 0) {
            $(".createRouteAlert .close").on("click", function () {
                $(".createRouteAlert").hide();
            });
        }
        //Set start to geolocation button and do not pan to
        $('#setStartGeolocation').click(function () {
            geolocation.AttemptToSetLocationWithUsersGeolocation(true, true);
        });

        //Set start to geolocation button and do not pan to
        $('#setEndGeolocation').click(function () {
            geolocation.AttemptToSetLocationWithUsersGeolocation(false, true);
        });

        $('#addWaypointBtn').click(function () {
            var isEmptyWaypointTextbox = checkIsEmptyWaypointTextbox();
            if (!isEmptyWaypointTextbox) {
                publicItem.AddWaypointToPlanner(null, true);
            }
        });

        $(document).on("checkEventOnRoutePlanner", checkEventOnRoutePlanner);
    };

    // called on load and resize
    function checkEventOnRoutePlanner(evt, windowResize) {
        var routeLinkEvents = evt ? $(evt).parent().find(".routeLinkEvent") :
                                         $(".routeTab:visible .routeLinkEvent");
        routeLinkEvents.each(function () {
            var box = $(this).find(".shorten")[0];
            var showMoreLink = $(this).find(".showMore");
            if (windowResize == "windowResize") {
                if ($(box).hasClass("expanded")) {
                    $(box).removeClass("expanded");
                }
                if (showMoreLink) {
                    showMoreLink.remove();
                }
            }

            //add a show more button only if needed based on height and it hasn't been added already
            if (box && box.clientHeight < box.scrollHeight && !box.nextElementSibling) {
                $(this).find(".text").append("<button class='showMore'>" + resources.ShowMore + "</button>");
            }
        });        
    }

    $(document).ready(init);
    return publicItem;
};

$(document).on('appInitComplete', function (event, appPublicApi, options) {
    // init atis context menu.
    var contextMenuHelper;

    if (resources.DisplayLocationsInContextMenu == "true") {
        contextMenuHelper = new AtisContextMenu(appPublicApi.map, options);
    } else {
        contextMenuHelper = new NewAtisContextMenu(appPublicApi, options);
    }
    var contextMenu = new ContextMenu(appPublicApi.map, contextMenuHelper);

    // init route planner
    var routePlanner = new RoutePlanner(appPublicApi, contextMenu, options);

    // init user route planner (save, edit).
    var userRoutePlanner = new UserRoutePlanner(routePlanner);

    $(document).trigger('hashChanged-urlHash');
});
/*
    extraPoi is an empty array or an array of objects with the 
    following design.

    {
        poiId: idValue,
        latLng: [ latValue, lngValue ],
        label: nameValue
    }
*/
var RoutePlannerAutocomplete = function (map, options, extraPois) {
    var publicItem = {};

    //if extra pois passed in.
    if (extraPois) {
        $(extraPois).each(function (index, obj) { obj.isGoogle = false; });
    }

    //south-west and north-east lat, lng pair which defines the boundaries of the area we are interested in. 
    var swBounds = new google.maps.LatLng(options.RoutingModel.MapBottomLeftBounds.Latitude, options.RoutingModel.MapBottomLeftBounds.Longitude);
    var neBounds = new google.maps.LatLng(options.RoutingModel.MapTopRightBounds.Latitude, options.RoutingModel.MapTopRightBounds.Longitude);

    //google auto-complete boundaries.
    var autoCompleteBounds = new google.maps.LatLngBounds(swBounds, neBounds);

    //init google auto-complete service.
    var googleService = new google.maps.places.AutocompleteService();

    //google places object.
    var googlePlacesService = new google.maps.places.PlacesService(map);

    var autocompleteFilter = function (googleResults, term) {

        //first only match poi whose labels start with term.
        var matcher = new RegExp('^' + $.ui.autocomplete.escapeRegex(term), 'i');
        var firstPois = $.grep(extraPois, function (value) {
            return matcher.test(value.label);
        });

        /* 
        concat extra pois with google auto complete results
        */
        return firstPois.concat(googleResults);
    };

    publicItem.GetLocationDetails = function (location) {
        if (location.point && location.point.name == location.text) {
            return new Promise((resolve, reject) => {
                resolve(location.point);
            });
        }
        return publicItem.autoCompleteSource(location,location.text)
            .then((autocomplete) => {
                if (autocomplete.length) {
                    var selcetedLocation = autocomplete[0];
                    location.poiId = selcetedLocation.poiId;
                    location.text = selcetedLocation.label;
                    $(location.selector).val(location.text);
                        
                    return publicItem.GetGoogleLocaition(location)
                        .then(details => {
                            location.point = details;
                            return details;
                        });
                }
            });
        
    };

    publicItem.GetGoogleLocaition = function (location) {
        var googleSerivcePromise = new Promise((resolve, reject) => {
            if (!location.poiId) {
                reject("invalid poiId");
            }
            googlePlacesService.getDetails({
                placeId: location.poiId,
                fields: ['address_component,adr_address,alt_id,formatted_address,geometry,icon,id,name,place_id,plus_code,scope,type,url,utc_offset,vicinity'],
                sessionToken: location.token
            }, (place, status) => {
                location.token = new google.maps.places.AutocompleteSessionToken();
                if (status !== google.maps.places.PlacesServiceStatus.OK) {
                    reject(status);
                    return;
                }
                place.formatted_address = location.text;
                resolve(convertGooglePlaceResultToSimplePlaceResult(place));
            });
        });

        return googleSerivcePromise;
    }

    publicItem.selectHandler = function (e, ui) {
        var location = this;
        location.poiId = ui.item.poiId;
        location.isGoogle = true;
        location.text = ui.item.label;

        publicItem.GetGoogleLocaition(location)
            .then(
                details => {
                    location.point = details;
                    $(document).trigger('locationSelected-autocomplete', location);
                }
            );
    };

    publicItem.autoCompleteSource = function (location,input) {

        //var location = 
        var input = (input || location.text || '').trim();

        if (!input || input.length < resources.AutoCompleteNumCharacters) {
            //why not return empty here???;
            return Promise.resolve($.ui.autocomplete.filter(extraPois, input));
        }

        //if session token is not created or expired
        if (location.token == null || location.tokenCreatedTime == null || location.tokenCreatedTime < new Date(Date.now() - 180000)) {
            location.token = new google.maps.places.AutocompleteSessionToken();
            location.tokenCreatedTime = Date.now();
        }

        var settings = {
            input: input, bounds: autoCompleteBounds, componentRestrictions:
                { country: options.RoutingModel.AutoCompleteCountryCode.split(',') }, sessionToken: location.token
        };

        // get google prediction
        return googleService.getPlacePredictions(settings)
            .then((response) => {
                googleResults = convertGooglePlaceResultsToJqueryUiAutoCompleteFilterArray(response.predictions);

                var autocomplete = autocompleteFilter(googleResults, input);

                return autocomplete;
            });
        
    };

    publicItem.appendGoogleLogoToAutocompleteResults = function (event, ui) {
        $('ul.ui-autocomplete.ui-menu').append($('<div>').css('text-align', 'right').html($('<img>').attr('src', '/Content/images/poweredByGoogle.png')));
    };

    var convertGooglePlaceResultsToJqueryUiAutoCompleteFilterArray = function (placeResults) {

        //for each prediction, add to googleResults projected as new object.
        for (var i = 0, prediction, jqueryUiFilter = []; prediction = placeResults[i]; i++) {
            jqueryUiFilter.push({
                label: prediction.description || prediction.name,
                poiId: prediction.place_id,
                isGoogle: true,
                googleObj: prediction
            });
        }

        return jqueryUiFilter;
    };

    var simplePlace = function (name, placeId, latitude, longitude, isGoogle, streetNumber, streetName, postalCode) {
        this.name = name;
        this.placeId = placeId;
        this.point = { latitude: latitude, longitude: longitude };
        this.isGoogle = isGoogle;
        this.linkId = null;
        this.streetNumber = streetNumber;
        this.streetName = streetName;
        this.postalCode = postalCode;
    };
    publicItem.GetCustomSimplePlace = function (name, placeId, latitude, longitude, isGoogle) {
        return new simplePlace(name, placeId, latitude, longitude, isGoogle);
    };

    //convert extraPoi item to a SimplePlace.
    var convertExtraPoiToSimplePlaceResult = function (extraPoi) {
        return new simplePlace(extraPoi.label, extraPoi.poiId, extraPoi.latLng[0], extraPoi.latLng[1], false);
    };

    var convertGooglePlaceResultToSimplePlaceResult = function (placeResult) {
        var placeOverrides = resources.PlaceOverrides.split(';');
        for (var i = 0; i < placeOverrides.length; i++) {
            var items = placeOverrides[i].split('|');
            if (placeResult.formatted_address == items[0]) {
                var latLng = items[1].split(',');
                placeResult.geometry.location = new google.maps.LatLng(latLng[0], latLng[1]);
            }
        }
        var name = placeResult.formatted_address || placeResult.name;
        var lat = placeResult.geometry.location.lat();
        var lng = placeResult.geometry.location.lng();
        var addressDetails = publicItem.GetAddressDetails(placeResult.address_components);

        return new simplePlace(name, placeResult.place_id, lat, lng, true, addressDetails.streetNumber,
            addressDetails.streetName, addressDetails.postalCode);
    };

    publicItem.GetAddressDetails = function (addressComponents) {
        //can't index Google's place result because it's size can vary and may not contain a street number
        //or postal code. Need to iterate instead.
        var name, number, code, state;
        for (var i = 0; i < addressComponents.length; i++) {
            if (addressComponents[i].types.indexOf("route") > -1) {
                name = addressComponents[i].short_name;
            }
            if (addressComponents[i].types.indexOf("street_number") > -1) {
                number = addressComponents[i].long_name;
            }
            if (addressComponents[i].types.indexOf("postal_code") > -1) {
                code = addressComponents[i].long_name;
            }
            if (addressComponents[i].types.indexOf("administrative_area_level_1") > -1) {
                state = addressComponents[i].short_name;
            }
        }
        return { streetName: name, streetNumber: number, postalCode: code, state: state };
    }

    var getAutoCompeleteSource = function (request, resolve) {
        var location = this;
        publicItem.autoCompleteSource(location, request.term)
            //.then(autoComplete.handleAutoCompleteData)
            .then((suggestions) => {
                resolve(suggestions);
            }).catch(failHandler);
    };

    publicItem.SetupAutoComplete = function (selector, location, existingWaypoint) {
        $(selector).autocomplete({
            minLength: resources.AutoCompleteNumCharacters,
            source: getAutoCompeleteSource.bind(location),
            select: publicItem.selectHandler.bind(location),
            open: publicItem.appendGoogleLogoToAutocompleteResults,
            delay: 150 //150 ms delay between keystroke and search.
        }).click(function () {
            //select all text on click.
            $(this).select();
        }).attr({
            'data-exists': existingWaypoint != 'undefined' ? existingWaypoint : false
        });

        $(selector).off('focusout');
        $(selector).on('focusout', (e) => {
            //location.text is the old value.  Compare it with the new value to see if location has changed.
            //If not, don't regenerate route.
            let oldText = location.text;
            location.text = $(location.selector).val();
            if (oldText !== location.text && location.text && location.text.length >= resources.AutoCompleteNumCharacters) {
                publicItem.GetLocationDetails(location)
                    .then(() => {
                            $(document).trigger('locationSelected-autocomplete', location);                        
                    });
            }
        });
    };

    var failHandler = function (err) {
        console.log(err);
    }

    return publicItem;
}

//a global object to update the status message of the route planner.
var RoutePlannerStatus = new function () {
    var publicItem = {};

    //the types of statuses.
    publicItem.StatusTypes = {
        info: 'alert-info',
        success: 'alert-success',
        danger: 'alert-danger'
    };

    //statusType is optional.
    publicItem.updateStatus = function (message, statusType, subscriberRouteId, isNewRoute, subscriberRouteName) {
        var statusBar = $('#statusBar');
        var routePlannerDesc = $("#routePlannerDesc");
        var validStatusType = false;

        //remove previous style and check if passed statusType is valid.
        for (var i in publicItem.StatusTypes) {
            statusBar.removeClass(publicItem.StatusTypes[i]);
            if (statusType == publicItem.StatusTypes[i]) {
                validStatusType = true;
            }
        }

        //if statusType is valid add the corresponding css class otherwise default to info.
        statusBar.addClass(validStatusType ? statusType : publicItem.StatusTypes.info);

        if ($('#routeNotificationAlert').length > 0) {
            var createRouteAlert = $(".createRouteAlert");
            var createRouteAlertMsg = $(".createRouteAlert .msg");
            //set the message.
            if (message && message.length > 1) {
                routePlannerDesc.hide();
                statusBar.attr('style', 'display:block;');
                statusBar.html(message).show();
                if (subscriberRouteId && isNewRoute) {
                    createRouteAlertMsg.html('<i class="fas fa-bell" aria-hidden="true"></i><a href= "/my511/routealert?routeID=' + subscriberRouteId + '">' + window.resources.CreateAlertLink);

                    if (Modernizr.mq('(min-width: 993px)')) {
                        var moveLeftPx = $(".sideBarColContainer").width() + 40;
                        createRouteAlert.css("left", moveLeftPx + "px");
                    } else {
                        createRouteAlert.insertAfter("#routingInput #statusBar");
                    }
                    createRouteAlert.show();
                }
            } else {
                statusBar.text('').hide();
                createRouteAlertMsg.html('');
                createRouteAlert.hide();
            }
        } else {
            if (message && message.length > 1) {
                routePlannerDesc.hide();
                statusBar.attr('style', 'display:block;');
                statusBar.html(message).show();
                if (subscriberRouteId && isNewRoute) {
                    if (resources.ShowSimplifyRouteNotificationModal === "true") {
                        var hideRouteNotification = Cookies.get("_hideSimplifyRouteNotificationModal");
                        if (!hideRouteNotification) {
                            $("#subscriberRouteName").attr("data-subscriberRouteId", subscriberRouteId).text(subscriberRouteName);
                            $(".routeNotificationMsg").show();
                            $(".doNotShowThisMessageAgain").show();
                            var href = '/my511/routealert?routeID=' + subscriberRouteId;
                            $('.editRouteAlert').attr("href", href);
                            $('#quickRouteNotificationModal').modal('show');
                        }
                    } else {
                        var hideRouteNotification = Cookies.get("_hideRouteNotificationModal");
                        if (!hideRouteNotification) {
                            $('#createAlterForRoute').attr("href", '/my511/routealert?routeID=' + subscriberRouteId);
                            $('#routeNotificationModal').modal('show');
                        }
                    }
                }
            } else {
                statusBar.text('').hide();
            }
        }
    };

    return publicItem;
};
$(document).ready(function () {
    var switchToAlerts = true;
    var isTabSelected = $("ul.nav-tabs > li").hasClass("active");
    if (!isTabSelected) {
        var currentTab = (window.location.hash === "" || window.location.hash.indexOf("-") >= 0) ? "#MyRoutes" : window.location.hash.replace(":", "");
        $("#MainTabs a[href='" + currentTab + "']").tab("show");
    }

    $("#MainTabs a").click(function (e) {
        e.preventDefault();
        $(this).tab('show');
        if ($(this).attr("href") == '#MyRoutes') {
            //get cameras
            window.DisplayMyCameras();      
        } else if ($(this).attr("href") == '#Alerts') {
            $(document).trigger("checkAlertsTab");
        }
    });

    // store the currently selected tab in hash value
    $("ul.nav-tabs > li > a").on("shown.bs.tab", function (e) {
        var id = $(e.target).attr("href").substr(1);
        // added : so the screen doesn't jump when you click on a tab.
        window.location.hash = ":" + id;
    });

    // on load of page: switch to the currenlty selected tab, using replace so the hash works
    var hash = window.location.hash.replace(':', '');

    // setting width of the Twitter widget
    setTimeout(function () {
        $('.twitter-timeline').each(function () {
            var head = $(this).contents().find('head');
            if (head.length) {
                head.append('<style type="text/css">.timeline { max-width: 100% !important; } </style>');
            }
        });
    }, 1000);

    // returns -1 if "route" doesn't exist
    var isRoute = hash.toLowerCase().indexOf("route") >= 0;
    var isCamera = hash.toLowerCase().indexOf("camera") >= 0;
    var isTransitRegion = hash.toLowerCase().indexOf("transitregion") >= 0;
    var isNews = hash.toLocaleLowerCase().indexOf("news") >= 0;
    if (!(isRoute || isCamera || isTransitRegion || isNews || hash.indexOf('-') >= 0)) {
        switchToAlerts = true;
    } else {
        // show My Route tab when clicking on View on the Manage My Routes/Alerts page
        switchToAlerts = false;
        if (!$("#RoutesTab").hasClass("active") && !isNews) {
            $("#RoutesTab > a").trigger("click");
            var hashFiltered = window.location.hash.replace(':', '');
            var firstPos = hash.lastIndexOf('#');
            var lastPos = hash.length;
            hash = firstPos === 0 ? hash : hash.substring(firstPos, lastPos);

            if (hashFiltered.toLowerCase() !== hash) {
                window.location.hash += hash;
            }
        }
    }
    $(document).on('contentFiltered', function () {
        GetAlertContent(switchToAlerts);
        switchToAlerts = false;
    });
    adjustHeight();
    OrganizeRouting();
});
var RoutingPolyline = function (appPublicApi, contextMenuApi, routePlanner) {
    var publicItem = {};

    var polylines = [];

    var movingPolyline = null;
    var marker = null;
    var map = appPublicApi.map;

    //default polyline options.
    var lineOptions = {
        strokeWeight: 4,
        strokeOpacity: 1,
        strokeColor: resources.RoutePolylineColour,
        zIndex: -99
    };



    publicItem.AddRoute = function (googleEncodedPolylines, options, show, isTransit, polylineInfoArray, routeNum) {
        //Generating multiple polylines for a single route, use the same id for each of them
        var id = generateGuid();

        //backwards compatible with transit routing and such, if its not an array of polylines
        //make it one
        if (typeof googleEncodedPolylines === "string" || googleEncodedPolylines instanceof String) {
            googleEncodedPolylines = [googleEncodedPolylines];
        }

        for (var i in googleEncodedPolylines) {
            var googleEncodedPolyline = googleEncodedPolylines[i];
            //get valid options.
            options = getOptions(options);

            //display on map?
            if (show) {
                options.map = map;
            }

            if (googleEncodedPolyline) {
                //decode polyline.
                options.path = google.maps.geometry.encoding.decodePath(googleEncodedPolyline);
            }

            //add polyline with unique id to collection.
            var polylineObj = { id: id, polyline: new google.maps.Polyline(options), strokeColor: options.strokeColor, isActive: false, index: i, isTransit: isTransit };
            google.maps.event.addListener(polylineObj.polyline, 'click', function (h) {
                if (!polylineObj.isActive) {
                    $('#routeTabs a[data-guids="' + polylineObj.id + '"]').click();
                }
            });

            if (polylineInfoArray && polylineInfoArray.length > 0 && polylineInfoArray[i].length > 0) {
                google.maps.event.addListener(polylineObj.polyline, 'mouseover', function (e) {
                    if (polylineObj.isActive) {
                        appPublicApi.appHelper.showInfoWindow(polylineInfoArray[i], null, true, e.latLng);
                    } else {
                        //TODO: use resource strings
                       
                        appPublicApi.appHelper.showInfoWindow('<span class="simpleToolTip"> Select route ' + routeNum + ' to see details</span>', null, true, e.latLng);
                        
                    }
                });
            }

            //if it is a transit route we don't want to enable waypoint routing
            //Same if we are on mobile
            if (!isTransit && resources.DraggableRoutesEnabled === "True" && Modernizr.mq('(min-width: 992px)')) {
                polylineObj.polyline.addListener('dragstart', function (h) {
                    var p = getPolylineObjFromPolyline(this);
                        var path = google.maps.geometry.encoding.decodePath(google.maps.geometry.encoding.encodePath(p.polyline.getPath()));
                        var copy = new google.maps.Polyline({ path: path, map: map, strokeColor: p.strokeColor, draggable: false });
                        p.copy = copy;
                        p.polyline.setOptions({ strokeColor: 'transparent' });
                        if (p.isActive) {
                            p.isMoving = true;

                            marker = new google.maps.Marker({
                                position: h.latLng,
                                icon: {
                                    path: google.maps.SymbolPath.CIRCLE,
                                    scale: 3
                                },
                                draggable: false,
                                map: map
                            });

                            movingPolyline = p;
                    }
                });
                polylineObj.polyline.addListener('drag', function (h) {
                    if (movingPolyline && marker) {
                        marker.setPosition(h.latLng);
                    }
                });

                polylineObj.polyline.addListener('dragend', function (h) {
                    if (movingPolyline) {
                        var p = getPolylineObjFromPolyline(this);
                        p.copy.setMap(null);
                        //If the point is already on an active polyline then we don't need to set a waypoint
                        if (!publicItem.isOnActivePolylines(h.latLng)) {
                            var si = {
                                point: {
                                    latitude: h.latLng.lat(),
                                    longitude: h.latLng.lng()
                                }
                            };
                            routePlanner.getNameForSimplePlace(si, function (simplePlace) {
                                routePlanner.clearLoationsWithoutPoints();
                                routePlanner.SetWaypoint(simplePlace, movingPolyline.index, false);
                                routePlanner.AddWaypointToPlanner(simplePlace.name, false);

                                if (resources.AutoGenerateDriveRoute !== "True") {
                                    routePlanner.calculateRoute();
                                }

                                marker.setMap(null);
                                marker = null;
                                movingPolyline = null;
                            });
                        }
                    }
                });

            }

            polylines.push(polylineObj);
        }
        //return polyline id.
        return id;
    };

    var getPolylineObjFromPolyline = function(polyline) {
        for (var i in polylines) {
            if (polylines[i].polyline === polyline) return polylines[i];
        }
    }

    var generateGuid = function () {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    };

    var getOptions = function (userOptions) {
        //create a copy of the options.
        var options = JSON.parse(JSON.stringify(lineOptions));

        //no options.
        if (!userOptions) {
            return options;
        }

        //user user options if available, otherwise stick to default.
        options.strokeWeight = userOptions.strokeWeight || options.strokeWeight;
        options.strokeOpacity = userOptions.strokeOpacity || options.strokeOpacity;
        options.strokeColor = userOptions.strokeColor || options.strokeColor;
        options.zIndex = userOptions.zIndex || options.zIndex;

        return options;
    };

    publicItem.SetOptions = function (id, options) {
        //find correct polyline.
        var result = $.grep(polylines, function (e) { return e.id == id; });

        if (result.length > 0) {
            var polyline = result[0].polyline;

            //get valid options.
            options = getOptions(options);

            //if already visible.
            if (polyline.getMap()) {
                options.Map = map;
            }

            //set polyline options.
            polyline.setOptions(options);
        }
    };

    publicItem.ToggleVisibility = function (id) {
        //find correct polyline.
        var result = $.grep(polylines, function (e) { return e.id == id; });

        if (result.length > 0) {
            var polyline = result[0].polyline;

            //hide or show polyline.
            polyline.setMap(polyline.getMap() ? null : map);
        }
    };

    publicItem.HideAll = function () {
        appPublicApi.appHelper.closeInfoWindow();
        //for each polyline.
        for (var i in polylines) {
            var polyline = polylines[i].polyline;

            //remove from map.
            polyline.setMap(null);
        }
    };

    publicItem.ShowAll = function () {
        //for each polyline.
        for (var i in polylines) {
            var polyline = polylines[i].polyline;

            //add to map.
            polyline.setMap(map);
        }
    };

    publicItem.DeleteAll = function () {
        //hide all polylines.
        publicItem.HideAll();
      

        //empty array.
        polylines = [];
        movingPolyline = null;
    };

    publicItem.Delete = function (id) {
        //find correct polyline.
        var result = $.grep(polylines, function (e) { return e.id == id; });

        if (result.length > 0) {
            //hide polyline.
            for (var i in result) {
                result[i].polyline.setMap(null);
            }

            //remove desired polyline from collection.
            polylines = $.grep(polylines, function (e) { return e.id == id; }, true);
        }
    };

    //Sets the polylines in the parameter active, and marks the others as non-active.
    publicItem.MakeActive = function (ids) {
        publicItem.HideAll();

        var splitIds = ids.split(';');

        for (var i in polylines) {
            var polyline = polylines[i].polyline;

            if (splitIds.indexOf(polylines[i].id) > -1) {
                polylines[i].isActive = true;
                if (!polylines[i].isTransit && resources.DraggableRoutesEnabled === "True" && Modernizr.mq('(min-width: 992px)')) {
                    polyline.setDraggable(true);
                }
                polyline.setOptions({ strokeWeight: 3.5, strokeColor: polylines[i].strokeColor, zIndex: 2, });
            } else {
                polylines[i].isActive = false;
                polyline.setDraggable(false);
                polyline.setOptions({ strokeWeight: 3, strokeColor: '#6A6A6A', zIndex: 1, });
            }
        }

        publicItem.ShowAll();
    };

    //Gets all active polylines
    publicItem.getActivePolylines = function () {
        var ids = "";
        for (var i in polylines) {
            if (polylines[i].isActive) {
                ids += polylines[i].id + ";"
            }
        }
        return ids;
    };

    //Check if the latLng is on any of the active polylines
    publicItem.isOnActivePolylines = function(latLng) {
        for (var i in polylines) {
            if (polylines[i].isActive && google.maps.geometry.poly.isLocationOnEdge(latLng, polylines[i].polyline)) {
                return true;
            }
        }
        return false;
    };

    return publicItem;
};
var TransitDirections = function (appPublicApi, routePolylineApi, directionsDiv) {

    var publicItem = {};
    var polylineId = null;

    //some common map related functions.
    var mapFctns = new MapFctns();

    var secondsToTimeDisplay = function (totalSeconds) {
        var hours = Math.floor(totalSeconds / 3600);
        var minutes = Math.floor((totalSeconds - (hours * 3600)) / 60);
        var seconds = totalSeconds - (hours * 3600) - (minutes * 60);

        //Don't want to show that it takes 0 minutes to travel somewhere.
        if ((minutes < 1) && (seconds > 0)) {
            minutes = 1;
        }

        if (hours > 0) {
            return hours + ' ' + window.resources.Hr + ' ' + minutes + ' ' + window.resources.Min;
        } else {
            return minutes + ' ' + window.resources.Min;
        }
    };

    var renderDirections = function (locations, result, includeDriving) {
        //set summary contents and 
        $(directionsDiv).html(result.summaryHtml);

        //set the polylines
        //we can have mutliple route results with multiple polylines for each route
        if (result.polyline && result.polyline.length > 0) {
            for (j=0; j < result.polyline.length; j++){
                for (i = 0; i < result.polyline[j].length; i++) {

                    var currentPolyline = result.polyline[j][i].polyline;

                    var lineOptions = {
                        strokeWeight: 4,
                        strokeOpacity: 1,
                        strokeColor: '#252525',
                        zIndex: 2
                    };                   
                    if (result.polyline[j][i].transit_details) {
                        if (result.polyline[j][i].line_color && result.polyline[j][i].line_color.indexOf('#') == -1) {
                            result.polyline[j][i].line_color = '#' + result.polyline[j][i].line_color;
                        }
                        lineOptions.strokeColor = result.polyline[j][i].line_color || '#00BFFF';
                    }
                    //only show the first route polyline
                    polylineId = routePolylineApi.AddRoute(currentPolyline, lineOptions, j == 0 ? true : false, true, [result.polyline[j][i].infoWindowInformation], j+1);

                    //For Drive + transit where we don't show transit in seperate tabs from the driving
                    if (includeDriving && !($('#transitRouteTabs').length))
                    {
                        $('#routeTabs a').each(function () {
                            $(this).attr('data-guids', $(this).attr('data-guids') + ';' + polylineId);
                        }, [polylineId]);
                    }
                    
                    //if we have route tabs add the polyline guids to them
                    if ($('#transitRouteTabs').length) {
                        var routeTab = $('#transitRouteTabs :nth-child(' + (j + 1) + ') a');
                        routeTab.attr('data-guids', polylineId + ';' + routeTab.attr('data-guids'));
                    }

                    //if bus, walk or bike steps shown, match line on map with line in steps
                    if (document.getElementsByClassName("directionStep")) {
                        document.getElementsByClassName("directionStep")[i].style.borderColor = lineOptions.strokeColor;
                    }                                       
                }
            }

            //If we are doing drive+transit, another function will zoom the map.
            if (!includeDriving) {
                //fit map to route bounds.
                mapFctns.fitMapToRoute(appPublicApi.map, locations);
            }
        }

        //set checkboxes to true
        var i;
        var checkBox;
        for (i in result.enableMapLayers) {
            checkBox = $('input[type=\'checkbox\'][data-layerid = ' + result.enableMapLayers[i] + ']', $('#layerSelection'));
            if (checkBox)
                checkBox[0].checked = true;
        }

        //set checkboxes to false
        for (i in result.disableMapLayers) {
            checkBox = $('input[type=\'checkbox\'][data-layerid = ' + result.disableMapLayers[i] + ']', $('#layerSelection'));
            if (checkBox)
                checkBox[0].checked = false;
        }

        //show summary contents
        $('#routeResults').show();

        //if we have route tabs then we need to setup the route tabs
        if ($('#transitRouteTabs').length) {
            $('#transitRouteTabs a').click(function (e) {
                e.preventDefault();

                //show the tab.
                $(this).tab('show');

                //make route polyline active.
                routePolylineApi.MakeActive($(this).attr('data-guids') + getGuids('#routeTabs li.active a'));
            });
            //activate the first route
            $('#transitRouteTabs a:first').tab('show');

            routePolylineApi.MakeActive($('#transitRouteTabs a:first').attr('data-guids') + getGuids('#routeTabs li.active a'));
        }

        //set the scroll bar to top position
        $('#routeResults').scrollTop(0);

        // trigger event for listener to adjust route planner height (Routingshidebar.cshtml)
        $('#routeResults').trigger('adjustRoutePlannerHeight');

        //Set the end and start point for printing. We do this here so when doing drive+transit the start will be the transit hub
        $('.startPoint').text(result.waypoints[0].nameDirection);
        $('.endPoint').text(result.waypoints[result.waypoints.length - 1].nameDirection);

        //If we are doing drive+transit, we need to update each route tab to add up the driving and transit time for a total route time.
        if (includeDriving) {
            $('#routeTabs a').each(function () {
                var drivingTime = $(this).attr('data-travelTimeInSeconds');
                var transitTime = $('#transitTimeInSeconds').val();
                var totalTime = +drivingTime + +transitTime;
                $(this).find('.badge').text(secondsToTimeDisplay(totalTime));
            });
        }
    };

    //gets guids from a selector, it assumes that if the selector is found it will have an attibute 'data-guids'
    //mainly used for drive + transit
    //Returns an empty string if the selector does not find an object
    function getGuids(selector) {
        var activeTab = $(selector);
        var otherGuids = '';
        if (activeTab != null && activeTab.length && activeTab != this) {
            otherGuids = ';' + activeTab.attr('data-guids');
        }
        return otherGuids;
    }

    //remove transit route from UI.
    publicItem.Reset = function () {
        routePolylineApi.Delete(polylineId);
        $(directionsDiv).html(null);
    };

    publicItem.Directions = function (locations, options, includeDriving) {

        //make call to server to get directions.
        $.ajax('/map/GetTransitRoute', {
                type: 'POST',
            data: { Waypoints: locations, travelMode: options.travelMode ? options.travelMode : options }
        }).done(function (data) {
                //results are good.
                if (data.status == 'OK') {
                    if (includeDriving) { //if drive + transit
                        $(document).trigger('driveTransitRouteGenerated-routePlanner', [data]);
                    } else {
                        $(document).trigger('transitRouteGenerated-routePlanner', [data]);
                }
                    renderDirections(locations, data, includeDriving);

                    let iconTypes = {
                        walking: 'walking',
                        bicycling: 'bicycle',
                        transit: 'bus'
                    }
                    let selectedTravelMode = document.getElementById('transitRouteResults').getElementsByTagName('i')[0];
                    if (selectedTravelMode) {
                        selectedTravelMode.className = "far fa-" + iconTypes[options.travelMode] + " fa-2x";
                        selectedTravelMode.setAttribute("title", iconTypes[options.travelMode]);
                    }


                    //handle other kinds of results.
                } else if (includeDriving) {
                    //trigger event that signals routing has failed.
                    $(document).trigger('clearRouteView-routePlanner');
                    RoutePlannerStatus.updateStatus(window.resources.DriveAndTransitRouteNotAvailable);
                } else if (data.status == 'ZERO_RESULTS') {
                    //trigger event that signals routing has failed.
                    $(document).trigger('clearRouteView-routePlanner');
                    RoutePlannerStatus.updateStatus(window.resources.RouteNotAvailable);
                } else if (data.status == 'NOT_FOUND') {
                    //trigger event that signals routing has failed.
                    $(document).trigger('clearRouteView-routePlanner');
                    //one of the start or end could not be geocoded by google.
                    RoutePlannerStatus.updateStatus(window.resources.RouteNotAvailable);
                } else if (data.status == 'PATH_NOT_FOUND') {
                    //trigger event that signals routing has failed.
                    $(document).trigger('clearRouteView-routePlanner');
                    //error from otp when it can't find a route
                    RoutePlannerStatus.updateStatus(window.resources.RouteNotAvailable);
                } else if (data.status == 'OUTSIDE_BOUNDS') {
                    //trigger event that signals routing has failed.
                    $(document).trigger('clearRouteView-routePlanner');
                    //error from otp when the area we are trying to route in has no data
                    RoutePlannerStatus.updateStatus(window.resources.AreaNotCovered);
                } else {
                    //trigger event that signals routing has failed.
                    $(document).trigger('clearRouteView-routePlanner');
                    RoutePlannerStatus.updateStatus(window.resources.ErrorRetrievingYourRoute, RoutePlannerStatus.StatusTypes.danger);
            }

            //apply arrow direction image.
            let allSubsteps = Array.from(document.getElementsByClassName("substeps"));
            allSubsteps.forEach((s) => {
                arrowDirection.setTransitSubstepImg(s);              
            });

            if (allSubsteps.length == 0) {
                let steps = Array.from(document.getElementsByClassName("directionStep"));
                steps.forEach((s) => {
                    arrowDirection.setStepImg(s);
                });
            }
           
            }).fail(function () {
                //trigger event that signals routing has failed.
                $(document).trigger('clearRouteView-routePlanner');
                RoutePlannerStatus.updateStatus(window.resources.ErrorRetrievingYourRoute, RoutePlannerStatus.StatusTypes.danger);
                loadBlockerApi.hideSpinner('calculateDriveTransitRoute');
            })
            //always hide the loading spinner no matter what.
            .always(function () {
                loadBlockerApi.hideSpinner('calculateRoute');
            });
    };

    publicItem.SaveRoute = function(locations, includeDriving)
    {
        $.ajax('/map/SaveTransitRoute', {
            type: 'POST',
            data: { Waypoints: locations }
        }).done(function (data) {

        })
        .fail(function () {
            //trigger event that signals routing has failed.
            $(document).trigger('clearRouteView-routePlanner');
            RoutePlannerStatus.updateStatus(window.resources.ErrorRetrievingYourRoute, RoutePlannerStatus.StatusTypes.danger);
            loadBlockerApi.hideSpinner('calculateDriveTransitRoute');
        })
        .always(function () {
            loadBlockerApi.hideSpinner('calculateRoute');
        });
    }

    return publicItem;
};
var UserRoutePlanner = function (routePlannerApi) {
    var publicItem = {};
    var routeData = null;

    var saveBtn = $('#btnSaveRoute');
    var nameInputDiv = $('.routeName');
    var nameInput = $('#routeName', nameInputDiv);
    var saveType = null;
    var currentRoute = null;

    var init = function () {
        //set event listeners and handlers.
        setUpEventHandlers();
        //If there are transit options on the page initialize them
        if (typeof TransitOptions !== 'undefined') {
            TransitOptions.init();
        }
        //if button exists then user is logged in.
        if (saveBtn.length) {

            //hide save button and route name input by default.
            saveBtn.toggle(false);
            nameInputDiv.toggle(false);

            //set name input placeholder.
            nameInput.attr('placeholder', resources.EnterRouteNameToSave);

            //set-up dropdown click.
            setUpDropdown();

            //refresh user route dropdown.
            populateDropdown();
        }
    };

    var originalDdText = null;
    var setUpDropdown = function () {
        var dd = $('.atisUserDd');
        var ddText = $('.btn .atisUserDdBtnText', dd);
        //save original text.
        originalDdText = ddText.text();

        //on click of option.
        dd.on('click', 'li a', function (event) {
            var thisJquery = $(this);
            var atisUserRoutesIndex = thisJquery.attr('data-id');
            currentRoute = window.atisUserRoutes[atisUserRoutesIndex];

            if (currentRoute.isTransit) {
                var routeName = currentRoute.routeName;
                var r = currentRoute;
                urlHashForRouteId(currentRoute.shareId);
                routePlannerApi.displayTransitRoute(currentRoute);
                currentRoute = r;
                nameInput.val(routeName);
                ddText.text(thisJquery.text()).attr('data-id', atisUserRoutesIndex);
                saveBtn.text(resources.Update);
            } else {
                //show loading spinner.
                loadBlockerApi.showSpinner('loadSavedRoute');
                //convert save object to route data.
                var route = convertSaveObjectToRouteData(currentRoute);

                //get real-time route statistics.
                getRouteStatistics(route, function (data) {
                    if (!data) {
                        //error flow.
                        routePlannerApi.resetRoutePlanner();
                        RoutePlannerStatus.updateStatus(resources.SorryUnableToFetchRoute);
                    } else {
                        currentRoute.statistics = data;
                        route.statistics = data;

                        routePlannerApi.displayRoute(route);
                        currentRoute = window.atisUserRoutes[atisUserRoutesIndex];
                        urlHashForRouteId(route.shareId);

                        //wait for all current event handlers in event queue to finish.
                        setTimeout(function () {
                            //set dropdown button text.
                            ddText.text(thisJquery.text()).attr('data-id', atisUserRoutesIndex);
                        
                            //set current route data.
                            routeData = [route];

                            //after calling routePlannerApi.displayRoute, name input box will be shown.
                            nameInput.val(route.routeName);
                        }, 0);
                    }
                    routePlannerApi.redrawWaypoints(false);
                    //hide loading spinner.
                    loadBlockerApi.hideSpinner('loadSavedRoute');
                    saveBtn.text(resources.Update);
                });
            }

            //prevent default action.
            event.preventDefault();
        });
    };

    var populateDropdown = function () {
        //set-up dd if routes available.
        if (window.atisUserRoutes) {

            //get refrence to dd options wrapper and empty the tag.
            var dd = $('.atisUserDdVals');
            dd.empty();

            for (var i in window.atisUserRoutes) {
                //add the options.
                dd.append($('<li role="presentation">').append($('<a role="menuitem" tabindex="-1" href="">').attr('data-id', i).text(window.atisUserRoutes[i].routeName)));
            }
        }

        //trigger that we populated the dropdown.
        $(document).trigger('dropdownPopulated-userRoutePlanner');
    };

    var resetDropdownButton = function () {
        var dd = $('.atisUserDd');
        var ddText = $('.btn .atisUserDdBtnText', dd);

        //restore text and remove id attribute.
        ddText.text(originalDdText).removeAttr('data-id');
    };

    var isSaveBtnDisabled = false;
    function disableSaveBtn(disable) {
        if ($('#btnSaveRoute').length > 0) { 
            isSaveBtnDisabled = disable;
            let saveBtn = $('#btnSaveRoute')[0];
            saveBtn.disabled = disable;
            saveBtn.classList.toggle("disabled", disable);
        }
    }

    var setUpEventHandlers = function () {
        //on route cleared, routing failed or transit route requested.
        $(document).on('routeCleared-routePlanner.routeSave routingFail-routePlanner.routeSave driveTransitRouteGenerated-routePlanner.routeSave', function (evnt) {
            routeData = null;

            currentRoute = null;

            //hide route name input and save button.
            nameInputDiv.toggle(false);
            saveBtn.toggle(false);

            //set name input to nothing. 
            nameInput.val(null);

            //reset dropdown to original content.
            resetDropdownButton();
        });
        $(document).on('clearRouteButtonClicked', function (evnt) {
            saveType = null;
            disableSaveBtn(false);
        });
        //on transit route generated
        $(document).on('transitRouteGenerated-routePlanner.routeSave', function (evnt, result) {
            //If we are supposed to be saving transit routes for this client
            if (resources.SaveTransit === 'true') {
                routeData = result;
                //if we have a currently selected route use its share id
                if (currentRoute) {
                    routeData.shareId = currentRoute.shareId;
                }
                //we are going to be saving a transit route if the user hits save
                saveType = 'transit';
                nameInputDiv.toggle(true);
                nameInput.focus();
                saveBtn.toggle(true);
            } else {
                nameInputDiv.toggle(false);
                saveBtn.toggle(false);
            }
        });
        //on route generated.
        $(document).on('routeGenerated-routePlanner.routeSave', function (evnt, result) {
            //if their is existing segmentId in routeData, we are editing a previously saved route.
            var segmentId = routeData != null && routeData.length && routeData[0].segmentId ? routeData[0].segmentId : null;
            //we are going to be saving a drive route if the user hits save
            saveType = 'drive';

            //save reference to result and set statistics date for each route.
            routeData = result;

            if (currentRoute) {
                routeData.shareId = currentRoute.shareId;
            }

            $(routeData).each(function (idx, route) {
                route.statistics.date = new Date();
            });

            //set segmentId if applicable. see comments above.
            if (segmentId) {
                $(routeData).each(function (i, obj) {
                    obj.segmentId = segmentId;
                });
            }

            //show route name input and save button.
            nameInputDiv.toggle(true);
            nameInput.focus();
            if (routeData[0].saveable === false) {
                saveBtn.toggle(false);
                nameInputDiv.toggle(false);
            } else {
                saveBtn.toggle(true);
                nameInputDiv.toggle(true);
                nameInput.focus();
            }
        });

        $(document).on('hashChanged-urlHash', function (event, hashVal) {
            var shareId = urlHashForRouteId();
            var routeIndex = null;

            if (shareId && (routeIndex = getIndexInAtisUserRoutes(shareId))) {
                //select item that was just specified via url hash.
                $('.atisUserDd li a[data-id="' + routeIndex + '"]').click();
                saveBtn.text(resources.Update);
            } else if(shareId) {
                getRoute(shareId);
                saveBtn.text(resources.Save);
            }
        });

        //toggle visibility of dropdown.
        $(document).on('dropdownPopulated-userRoutePlanner', function () {
            var showDd = window.atisUserRoutes != null && window.atisUserRoutes.length > 0;
            $('.atisUserDd').toggle(showDd);
            // trigger user route dropdown visible event on true
            if (showDd) {
                $(document).trigger('UserRouteDropDownVisible');
            }
        });

        saveBtn.on('click.routeSave', function (event) {
            var routeName = nameInput.val() || '';
            routeName = routeName.trim();

            //clear status.
            RoutePlannerStatus.updateStatus();

            //if no value for route name.
            if (!routeName) {
                RoutePlannerStatus.updateStatus(resources.PleaseEnterRouteNameToSave);
                return false;
            }

            //show loading spinner.
            loadBlockerApi.showSpinner('saveRoute');

            if (saveType == 'transit') {
                //get transit options if they exists
                var options = "";
                if (typeof TransitOptions !== 'undefined') {
                    var obj = TransitOptions.GetTransitOptions();
                    //just in case if not json
                    try {
                        options = JSON.stringify(obj);
                    } catch (e) {
                        options = obj;
                    }
                }
                //convert current route data to a save object
                var saveObj = convertTransitRouteDataToSaveObject(routeName, options);

                $.ajax('/My511/SaveTransitRoute', { data: saveObj, type: 'POST' })
                    .done(function (data) {
                        //Get the share id back from saving
                        saveObj.shareId = data.shareId;
                        saveObj.isTransit = true;
                        $(document).one('transitRouteGenerated-routePlanner.routeSave-msg', function () {
                            RoutePlannerStatus.updateStatus(resources.YourRouteHasBeenSaved, RoutePlannerStatus.StatusTypes.success);
                        });
                        //this will close spinner.
                        //Update the route data array
                        routeSaved(saveObj);
                        saveBtn.text(resources.Update);
                    })
                    .fail(function() {
                        RoutePlannerStatus.updateStatus(resources.SorryErrorSavingRoute);
                    })
                    .always(function() {
                        loadBlockerApi.hideSpinner('saveRoute');
                    });
            } else {
                var isNewRoute = saveBtn.text() === resources.Save;
                //get active route index and save.
                var activeRouteIndex = routePlannerApi.getActiveRouteIndex();
                var saveObj = convertRouteDataToSaveObject(routeName, activeRouteIndex);

                //make ajax call to save or update.
                $.ajax('/Api/Route/SaveUserRoute', { data: saveObj, type: 'POST', })
                    .done(function (data) {
                        saveObj.segmentId = data.segmentId;
                        saveObj.allLinks = data.allLinks;
                        saveObj.statistics = routeData[activeRouteIndex].statistics;
                        saveObj.subscriberRouteId = data.subscriberRouteId;
                        saveObj.shareId = data.shareId;
                        //wait for route planner to do its thing before displaying message.
                        $(document).one('routeGenerated-routePlanner.routeSave-msg', function () {
                            RoutePlannerStatus.updateStatus(resources.YourRouteHasBeenSaved, RoutePlannerStatus.StatusTypes.success, saveObj.subscriberRouteId, isNewRoute, saveObj.routeName);
                        });

                        //this will close spinner.
                        routeSaved(saveObj);

                        saveBtn.text(resources.Update);
                    })
                    .fail(function (data) {
                        if (data.status == 400) {
                            //Show validation errors
                            var errorMessage = "";
                            for (var i = 0; i < data.responseJSON.length; i++) {
                                errorMessage += data.responseJSON[i].errorMessage + '<br>';
                            }
                            RoutePlannerStatus.updateStatus(errorMessage);
                        } else {
                            RoutePlannerStatus.updateStatus(resources.SorryErrorSavingRoute);
                        }
                    }).always(function () {
                        loadBlockerApi.hideSpinner('saveRoute');
                    });
            }

            return false;
        });

        $('.linkRoute').click(linkRouteId);

        $('.linkRoute').bind('keydown', function (e) {
            if (e.keyCode == 13) {
                linkRouteId();
            }
        });

        if (document.querySelector('#routeName')) {
            let statusBar = document.querySelector('#statusBar');
            document.querySelector('#routeName').addEventListener('input', (e) => {
                if (e.target.value.length > parseInt(resources.RouteNameLengthLimit) && !isSaveBtnDisabled) {
                    $('#routeNameTooLong').show();
                    disableSaveBtn(true);
                } else if (e.target.value.length <= parseInt(resources.RouteNameLengthLimit) && isSaveBtnDisabled) {
                    $('#routeNameTooLong').hide();
                    disableSaveBtn(false);
                }
                if (statusBar.style.display !== 'none') {
                    statusBar.style.display = 'none'; //clear out status bar in case it is visible since route name has been changed.
                }
            })

            document.querySelector('.atisUserDdVals').addEventListener('click', (e) => {
                $('#routeNameTooLong').hide();
            });
        }      
    };

    var getRoute = function (shareId) {
        if (shareId != null && shareId.length > 0) {
            if (shareId.indexOf('-') >= 0) {
                loadBlockerApi.showSpinner('loadSavedRoute');
                $.ajax('/Api/Route/GetRouteByShareID?shareId=' + shareId, { type: 'POST' })
                    .done(function (data) {
                        if (!data) {
                            //error flow.
                            routePlannerApi.resetRoutePlanner();
                            RoutePlannerStatus.updateStatus(resources.SorryUnableToFetchRoute);
                        } else {
                            //If we returned a transit route
                            if (data.isTransit == true) {
                                routePlannerApi.displayTransitRoute(data);
                            } else { //This is a non-transit route
                                routePlannerApi.displayRoute(convertSaveObjectToRouteData(data));
                                routePlannerApi.redrawWaypoints(false);
                            }
                            
                        }
                    })
                .fail(function () { RoutePlannerStatus.updateStatus(resources.SorryUnableToFetchRoute); });
                loadBlockerApi.hideSpinner('loadSavedRoute');
            } else {
                window.location.href = 'my511/login?ReturnUrl=/map%23route-' + shareId;
            }
        }
    }

    var routeSaved = function (saveObj) {
        var index = -1;
        window.atisUserRoutes = window.atisUserRoutes || [];

        //check if item aleady exists in window.atisUserRoutes.
        for (var i in window.atisUserRoutes) {
            if (window.atisUserRoutes[i].shareId == saveObj.shareId) {
                window.atisUserRoutes[i] = saveObj;
                index = i;
            }
                //if a user is editing an existing saved route the shareIds will be different
                //because technically it's a new route. 
            else if(window.atisUserRoutes[i].routeName == saveObj.routeName){
                window.atisUserRoutes[i] = saveObj;
                index = i;
            }
        }

        //item was not found, add it to collection.
        if (index == -1) {
            window.atisUserRoutes.push(saveObj);
            index = window.atisUserRoutes.length - 1;
        }

        //reset and re-populate dropdown.
        resetDropdownButton();
        populateDropdown();

        //select item that was just saved from dropdown.
        $('.atisUserDd li a[data-id="' + index + '"]').click();
    };

    var urlHashForRouteId = function (shareId) {
        var hash = urlHash.hash();
        if (!shareId && !(hash && hash.toLowerCase().lastIndexOf('route-', 0) == 0)) {
            return null;
        }

        var format = 'route-{0}';
        var hash = format.replace('{0}', shareId || '');

        if (shareId) {
            //set value.
            urlHash.hash(hash);
        } else {
            //get value.
            //var currHash = urlHash.hash();
            //segmentId = (currHash || '').replace(hash, '');
            shareId = urlHash.hash();
            shareId = shareId.substring(shareId.indexOf('-') + 1);
        }

        return shareId;
    };

    var linkRouteId = function () {
        // display dialog for link route
        if (currentRoute && currentRoute.shareId) {
            bootbox.dialog({
                title: resources.LinkToYourRoute,
                message: '<input class="form-control" value="' + window.location.protocol + "//" + window.location.host + '/map#route-' + currentRoute.shareId + '" ><br/>' + resources.CopyFromBrowserText
            });
        } else {
            RoutePlannerStatus.updateStatus(resources.SelectRouteFromDropdownText, RoutePlannerStatus.StatusTypes.danger);
        }
    };

    //gets the real-time route details of user route.
    var getRouteStatistics = function (route, callback) {
        //if route statistics are less than 60 seconds old, don't fetch new ones.
        if (route.statistics && route.statistics.date && new Date() - route.statistics.date < 60000) {
            callback(route.statistics);
            return;
        }

        //get most recent route statistics.
        $.ajax('/Api/Route/GetUserRouteStatistics?segmentId=' + route.segmentId, { type: 'POST' })
            .done(function (data) {
                data.date = new Date();
                callback(data);
            })
            .fail(function () { callback(null); });
    };

    var getIndexInAtisUserRoutes = function (shareId) {
        var routeIndex = null;

        //for each entry is atisUserRoutes.
        for (var idx in window.atisUserRoutes) {

            //if segmentId matches, we are done.
            if (window.atisUserRoutes[idx].shareId == shareId) {
                routeIndex = idx;
                break;
            }

            if (window.atisUserRoutes[idx].segmentId == shareId) {
                routeIndex = idx;
                break;
            }
        }

        return routeIndex;
    };

    var convertSaveObjectToRouteData = function (saveObj) {
        var obj = {};

        //set properties.
        obj.segmentId = saveObj.segmentId;
        obj.routeName = saveObj.routeName;
        obj.encodedPolyline = saveObj.encodedPolyline;
        obj.includesTollLink = saveObj.includesTollLink;
        obj.includesFerryLink = saveObj.includesFerryLink;
        obj.allLinks = saveObj.allLinks;
        obj.statistics = saveObj.statistics;
        obj.shareId = saveObj.shareId;
        obj.cacheId = saveObj.cacheId;
        obj.instructions = saveObj.instructions;
        obj.saveable = saveObj.saveable;

        //set waypoints.
        obj.waypoints = convertSaveObjectJsonStringToWaypoints(saveObj.encodedMarkers);

        return obj;
    };

    var convertSaveObjectJsonStringToWaypoints = function (encodedMarkers) {
        var list = [];

        if (encodedMarkers) {
            //convert from string to json object.
            encodedMarkers = JSON.parse(encodedMarkers);

            //for the two encoded markers.
            for (var i = 0; i < encodedMarkers.length; i++) {
                var marker = encodedMarkers[i];
                var obj = {};

                //build object representing waypoint.
                obj.linkId = marker.location.LinkId;
                obj.name = obj.nameDirection = marker.location.Name;
                obj.isForward = marker.location.IsForward;
                obj.poiId = marker.location.PoiId;
                obj.point = {
                    latitude: marker.lat,
                    longitude: marker.lng
                };

                //add to array.
                list.push(obj);
            };
        }

        return list;
    };

    var convertTransitRouteDataToSaveObject = function (routeName, options) {
        var obj = {};
        var route = routeData;

        obj.shareId = route.shareId;
        obj.routeName = routeName;
        obj.fromAddress = route.waypoints[0].name;
        obj.fromLat = route.waypoints[0].point.latitude;
        obj.fromLong = route.waypoints[0].point.longitude;
        obj.toAddress = route.waypoints[route.waypoints.length -1].name;
        obj.toLat = route.waypoints[route.waypoints.length - 1].point.latitude;
        obj.toLong = route.waypoints[route.waypoints.length - 1].point.longitude;
        obj.options = options;
        
        return obj;
    }

    var convertRouteDataToSaveObject = function (routeName, routeIndex) {
        var obj = {};
        var route = routeData[routeIndex];

        //set object properties.
        obj.cacheId = route.cacheId;
        obj.segmentId = route.segmentId;
        obj.routeName = routeName;
        obj.encodedPolyline = route.encodedPolyline;
        obj.encodedMarkers = convertWaypointsToSaveObjectJsonString(route.waypoints);
        obj.includesTollLink = route.includesTollLink;
        obj.includesFerryLink = route.includesFerryLink;
        obj.lengthMeters = route.statistics.lengthMeters;
        obj.postedTravelTimeSeconds = route.statistics.postedTravelTimeSeconds;
        obj.subscriberRouteId = route.subscriberRouteId;
        obj.shareId = route.shareId;
        if (route.statistics.instructions) obj.instructions = route.statistics.instructions;
        obj.previousTransitShareId = routeData.shareId;

        return obj;
    };

    var convertWaypointsToSaveObjectJsonString = function (waypoints) {
        var list = [];

        //for each waypoint.
        for (var i = 0; i < waypoints.length; i++) {
            var waypoint = waypoints[i];
            var obj = {};

            //determine waypoint letter.
            var letter = i == 0 ? 'A' : i == waypoints.length - 1 ? 'B' : 'W';

            //build object representing waypoint.
            obj.lat = waypoint.point.latitude;
            obj.lng = waypoint.point.longitude;
            obj.letter = letter;
            obj.location = {
                Name: waypoint.nameDirection == null ? waypoint.name : waypoint.nameDirection,
                X: waypoint.point.longitude,
                Y: waypoint.point.latitude,
                LinkId: waypoint.linkId,
                IsForward: waypoint.isForward,
                Letter: letter,
                PoiId: waypoint.poiId
            };

            //add to array.
            list.push(obj);
        }

        //convert array to json string.
        return JSON.stringify(list);
    };

    init();

    return publicItem;
};
