WME Closure Helper

A script to help out with WME closure efforts! :D

// ==UserScript==
// @name         WME Closure Helper
// @namespace    https://greasyfork.org/en/users/673666-fourloop
// @version      2025.02.14.01
// @description  A script to help out with WME closure efforts! :D
// @author       fourLoop & maintained by jm6087 and fuji2086
// @match        https://beta.waze.com/*editor*
// @match        https://www.waze.com/*editor*
// @exclude      https://www.waze.com/*user/*editor/*
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @connect      api.timezonedb.com
// @grant        GM.xmlHttpRequest

// ==/UserScript==

/* global W */
/* global toastr */
/* global $ */
/* global settings */
/* global WazeWrap */
/* global OpenLayers */
/* global GM_xmlhttpRequest */
/* global xmlHttpRequest */

var G_AMOUNTOFPRESETS = 100;

(function() {
    'use strict';

    var customCSmin;
    var settings = {};
    var DateFormat = "";
    let Lang;
    var dateSeparator;
    let radio = "";

    //Bootstrap
    function bootstrap(tries = 1) {
        if (typeof W === 'object' && W.userscripts?.state?.isReady && W.map &&
            W.model && W.loginManager.user && WazeWrap.Ready) {
            log("Here we go!!! Starting program!");
            init();
        } else if (tries < 1000) {
            setTimeout(function() { bootstrap(tries++); }, 200);
        }
    }

    function init() {
        Lang = I18n.currentLocale()
        let lang_mmddyyyy = ["en-US", "en", "no", "zh"]; // mm/dd/yyyy
        let lang_ddmmyyyy = ["es-419", "es", "en-GB", "en-AU", "af", "ca", "el", "fr", "gl", "id", "it", "ms", "pt-BR", "pt-PT", "th"]; // dd/mm/yyyy
        let lang_ddmmyyyy2 = ["bg", "cs", "da", "de", "et", "fi", "hr", "lv", "pl", "ro", "ru", "sk", "sl", "tr", "uk"]; // dd.mm.yyyy
        let lang_yyyymmdd = ["eu", "lt", "sv"]; // yyyy-mm-dd
        let lang_ddmmyyyy3 = ["nl"]; // dd-mm-yyyy
        let lang_yyyymmdd2 = ["zh-TW"]; // yyyy/mm/dd
        let lang_yyyymmdd3 = ["hu"]; // yyyy.mm.dd
        if (lang_mmddyyyy.indexOf(Lang) != -1) {
            DateFormat = "mmddyyyy";
            dateSeparator = "/";
        }
        if (lang_ddmmyyyy.indexOf(Lang) != -1) {
            DateFormat = "ddmmyyyy";
            dateSeparator = "/";
        }
        if (lang_ddmmyyyy2.indexOf(Lang) != -1){
            DateFormat = "ddmmyyyy";
            dateSeparator = ".";
        }
        if (lang_yyyymmdd.indexOf(Lang) != -1){
            DateFormat = "yyyymmdd";
            dateSeparator = "-";
        }
        if (lang_ddmmyyyy3.indexOf(Lang) != -1){
            DateFormat = "ddmmyyyy"
            dateSeparator = "-";
        }
        if (lang_yyyymmdd2.indexOf(Lang) != -1){
            DateFormat = "yyyymmdd"
            dateSeparator = "/";
        }
        if (lang_yyyymmdd3.indexOf(Lang) != -1){
            DateFormat = "yyyymmdd"
            dateSeparator = ".";
            //        dateseparator2 = ".";
        }
        if (DateFormat == ""){
            DateFormat = "mmddyyyy";
            dateSeparator = "/";
        }

        var $section = $("<div>");
        var formString = '';
        var preset = 0;
        for (preset = 1; preset < (G_AMOUNTOFPRESETS + 1); preset++) {
            formString += '<div class="wmech_presetdiv" id="wmech_presetrow' + preset + '"><label class="wmech_presetlabel" style="text-align: center; width: 100%; margin-left: 0; margin-bottom: 0;" for="wmech_preset' + preset + 'name">Preset ' + preset + '</label>' +
                '<input id="wmech_preset' + preset + 'name" type="text" placeholder="Name" class="wmech_input wmech_inputpreset wmech_namepreset">' +
                '<input id="wmech_preset' + preset + 'reason" type="text" placeholder="Description" class="wmech_input wmech_inputpreset">' +
                '<input id="wmech_preset' + preset + 'timeString" type="text" placeholder="Time String (default U: 23:59)" class="wmech_input wmech_inputpreset">' +
                '<label class="wmech_presetlabel" for="wmech_preset' + preset + 'permanent">Make permanent:</label>' +
                '<i class="waze-tooltip wmech_presetpermatooltip" data-original-title="This feature will check the HOV / Service Road adjacent checkbox, meaning the closure will not listen to traffic."></i>' +
                '<input class="wmech_checkbox wmech_presetcheckbox wmech_presetsetting wmech_presetpermanent" title="Enable permanent closures by default" id="wmech_preset' + preset + 'permanent" type="checkbox">' +
                '<br><label class="wmech_presetlabel" for="wmech_preset' + preset + 'nodes">Node closures:</label>' +
                '<select class="wmech_presetsetting wmech_presetdropdown wmech_presetnodes" id="wmech_preset' + preset + 'nodes">' +
                '<option>Middle</option>' +
                '<option>All</option>' +
                '<option>Ends</option>' +
                '<option>None</option>' +
                '</select><br><label class="wmech_presetlabel" for="wmech_preset' + preset + 'direction">Direction:</label>' +
                '<select class="wmech_presetsetting wmech_presetdropdown wmech_presetdirection" id="wmech_preset' + preset + 'direction">' +
                '<option>Two Way</option>' +
                '<option>A --> B</option>' +
                '<option>B --> A</option>' +
                '</select><div><label class="wmech_presetlabel" for="wmech_preset' + preset + 'mteString">MTE:</label>' +
                '<input style="width: 50%;" id="wmech_preset' + preset + 'mteString" type="text" placeholder="MTE Search" class="wmech_input wmech_inputpreset wmech_presetsetting wmech_presetmteString"></div>' +
                '<div><label class="wmech_presetlabel" for="wmech_preset' + preset + 'color">Color:</label>' +
                '<input class="wmech_colorinput wmech_presetsetting wmech_presetcolor" type="color" id="wmech_preset' + preset + 'color"></div>' +
                '<button class="wmech_closurebutton wmech_presetdeletebutton" style="background-color: red; color: white;">Delete Preset</button>' +
                '</div>';
        }
        var tabString = '<ul class="nav nav-tabs"><li class="active"><a data-toggle="tab" href="#wmech-tab-presets">Presets</a></li>' +
            '<li><a data-toggle="tab" href="#wmech-tab-settings">Settings</a></li>' +
            '<li><a data-toggle="tab" href="#wmech-tab-format">Formatting</a></li>' +
            '<li><a data-toggle="tab" href="#wmech-tab-about">About</a></li>' +
            '</ul>';
        var settingsString = '<div class="wmech-tab-pane" id="wmech-tab-settings"><h2><center>Settings</center></h2><div id="wmech-main-settings"><div id="wmech-settings-boxes"></div></div><div id="wmech-quicksearch-settings"></div></div>';
        var formatString = '<div class="wmech-tab-pane" id="wmech-tab-format">' +
            '<h2><center>Formatting</center></h2>' +
            '<h3>Formatting Time Strings</h3>' +
            '<ul>' +
            '<li><b>No Flag:</b> Sets the closure for a specified duration. Duration string only <ul><li>"1m" = 1 Minute</li><li>"1d4h" = 1 Day, 4 Hours</li><li>"1o4d32m" = 1 Month, 4 Days, 32 Minutes</li><li>"1y" = 1 Year</ul></li>' +
            '<li><b>U Flag:</b> Sets the closure until a specified time, with an optional additional duration string.<ul>' +
            '<li>"U: 06:00" = Sets closure until the next instance of 6AM</li><li>"U: 23:59" = Sets closure until the next instance of 11:59PM</li><li>"U: 08:46, 1d" = Sets closure until 1 day after the next instance of 8:46AM' +
            '<li>"U: 12:45, 1y3m" = Sets closure until 1 year, 3 months after the next instance of 12:45PM</li>' +
            '<li>"U: 12:45, Mon" = Sets closure until 12:45PM on the next Monday (which, if today was Monday before 12:45PM, would be today)</li>' +
            '<li>"U: 12:45, Mon, 1y2o" = Sets closure until 12:45PM on the next Monday (which, if today was Monday before 12:45PM, would be today) and adds 1 year, 2 months</li></ul>' +
            '</li><li><b>D Flag:</b> Sets the closure until a specified date and time, in calendar format.' +
            '<ul><li>"D: 2024-09-30 06:00" = Sets the closure until 2024-09-30 at 6:00AM.</li><li>"D: 2020-07-08 14:15" = Sets closure until 2020-07-08 at 14:15.</li></ul></li>' +
            '</ul>' +
            '<h3>Duration Strings</h3>' +
            '<ul><li>"m" = Minute</li><li>"h" = Hour</li><li>"d" = Day</li><li>"o" = Month</li><li>"y" = Year</li><li><b>Note: </b> Weeks and other date/times are not supported</li></ul>' +
            '<h3>Name String</h3>' +
            '<ul>' +
            '<li><b>{{type}}</b> = The type of the segment</li>' +
            '<li><b>{{firstSegName}}</b> = The name of the first selected segment, in order of click</li>' +
            '<li><b>{{lastSegName}}</b> = The name of the last selected segment, in order of click</li>' +
            '</ul>' +
            '</div>';
        var aboutString = '<div class="wmech-tab-pane" id="wmech-tab-about"><h2><center>About</center></h2>' +
            '<ul>' +
            '<li>' + GM_info.script.version + '</li>' +
            '<li>Made by ' + GM_info.script.author + '</li>' +
            '<li>Documentation: <a href="https://docs.google.com/document/d/1mPE8qKezU720VCgrVCKpury7fkW5y5FbDrXzYbBpQK4/edit?usp=sharing" target="_blank">Here</a>' +
            '<li>Thanks to all of you amazing editors who make the map better every day <3' +
            '</ul>' +
            '</div>';
        formString = '<div class="wmech-tab-pane active" id="wmech-tab-presets"><label for="wmech_presetchooser">Choose a preset:</label><br><select id="wmech_presetchooser"></select>' + formString +
            '<button class="wmech_closurebutton wmech_presetsavebutton" style="background-color: blue; color: white;">Save Presets</button></div>' +
            '<div class="wmech-alert" id="wmech-save-notice">Save Successful</div>';
        $section.html(tabString + "<div class='tab-content'>" + formString + settingsString + formatString + aboutString + "</div>");

        setTimeout(function() {
            WazeWrap.Interface.Tab('CH', $section.html(), initializeSettings, 'CH');
            $(".wmech_presetdiv").hide();
            $("#wmech_presetrow1").show();
            $("#wmech_presetchooser").change(function() {
                var sel = parseInt($(this).children("option:selected").val()) + 1;
                if (sel != -1) {
                    $(".wmech_presetdiv").hide();
                    $("#wmech_presetrow" + sel).show();
                }
            });
        }, 1000);
    }

    function prepareSettings() {
        addSettingsCheckbox("Set segment list on closures to default collapsed", "wmech_settingseglistcollapse");
        addSettingsCheckbox("Direction click-saver buttons do not use directional cursors", "wmech_settingdircsdircur");
        addSettingsHeader("Time Zone Settings");
        addSettingsCheckbox("Enable time zone warning", "wmech_settingtimezonewarn");
        addSettingsInput("timezonedb.com/api Personal Key", "wmech_settingtimezoneapi");
        addSettingsHeader("Custom Minutes - Enter number of minutes (numbers only)");
        addSettingsInput("Custom time clicksaver - Enter number of minutes", "wmech_settingcustomcs");
        //         addSettingsCheckbox("Minutes", "wmech_settingcustomcsMin");
        $("#wmech_settingtimezonewarn").change(function() {
            if (!this.checked) {
                $("#wmech_settingtimezoneapi").prop('disabled', true);
            } else {
                $("#wmech_settingtimezoneapi").prop('disabled', false);
            }
        });
    }

    function addSettingsCheckbox(text, id) {
        $("#wmech-settings-boxes").append("<div class='controls-container'><input class='wmech_checkbox wmech_settingscheckbox' id='" + id + "' type='checkbox'><label class='wmechSettingsLabel' for='" + id + "'>" + text + "</label></div><br>");
    }

    function addSettingsHeader(text) {
        $("#wmech-settings-boxes").append("<p class='wmech_settingsheader'>" + text + "</p>");
    }

    function addSettingsInput(placeholder, id) {
        $("#wmech-settings-boxes").append("<input type='text' id=\"" + id + "\" placeholder='" + placeholder + "' class='wmech_input wmech_inputpreset wmech_settingsinput'>");
    }

    function initializeSettings() {
        prepareSettings();
        setUpSavePresetButton();
        setUpDeletePresetButton();
        attachObserver();

        $(".wmech_inputpreset").change(function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_preset([0-9]*)(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var presetIndex = harvestIdInfo[1];
            var prop = harvestIdInfo[2];
            if (!settings.presets[parseInt(presetIndex - 1)]) {
                settings.presets[parseInt(presetIndex - 1)] = {};
            }
            settings.presets[parseInt(presetIndex - 1)][prop] = this.value;
        });
        $(".wmech_namepreset").on('input', function() {
            var curVal = $("#wmech_presetchooser").val();
            $("#wmech_presetchooser").children().eq(curVal).text("Preset " + (parseInt(curVal) + 1) + " - " + $(this).val());
            loadDropdown();
        });
        $(".wmech_presetcheckbox").change(function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_preset([0-9]*)(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var presetIndex = harvestIdInfo[1];
            var prop = harvestIdInfo[2];
            if (!settings.presets[parseInt(presetIndex - 1)]) {
                settings.presets[parseInt(presetIndex - 1)] = {};
            }
            settings.presets[parseInt(presetIndex - 1)][prop] = this.checked;
        });
        $(".wmech_presetcolor").on("change", function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_preset([0-9]*)(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var presetIndex = harvestIdInfo[1];
            var prop = harvestIdInfo[2];
            if (!settings.presets[parseInt(presetIndex - 1)]) {
                settings.presets[parseInt(presetIndex - 1)] = {};
            }
            settings.presets[parseInt(presetIndex - 1)][prop] = this.value;
        });
        $(".wmech_presetdropdown").change(function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_preset([0-9]*)(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var presetIndex = harvestIdInfo[1];
            var prop = harvestIdInfo[2];
            if (!settings.presets[parseInt(presetIndex - 1)]) {
                settings.presets[parseInt(presetIndex - 1)] = {};
            }
            settings.presets[parseInt(presetIndex - 1)][prop] = $(this).val();
        });
        $(".wmech_settingscheckbox").change(function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_setting(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var settingName = harvestIdInfo[1];
            if (!settings.settingsCheckboxes) {
                settings.settingsCheckboxes = {};
            }
            settings.settingsCheckboxes[settingName] = $(this).is(":checked");
            saveSettings();
        });
        $(".wmech_settingsinput").on('change paste keyup input', function() {
            var id = $(this)[0].id;
            var harvestIdInfoRE = new RegExp(/wmech_setting(.*)/);
            var harvestIdInfo = id.match(harvestIdInfoRE);
            var settingName = harvestIdInfo[1];
            if (!settings.settingsInputs) {
                settings.settingsInputs = {};
            }
            settings.settingsInputs[settingName] = $(this).val();
            saveSettings();
        });
        //Added save button to eliminate the need to save settings every 6 seconds.
        /*var settingsSaver = setInterval(function() {
            saveSettings();
            // log("Save settings ran.");
        }, 60000);*/

        // Enable tooltips
        $(".wmech_presetpermatooltip").tooltip();

        setTimeout(loadSettings, 2500);
        log("Settings initialized.");
    }

    function setUpSavePresetButton() {
        $(".wmech_presetsavebutton").click(async function() {
            saveSettings();
            $('#wmech-save-notice').css("display", "block");
            setTimeout(function() { $('#wmech-save-notice').css("display", "none"); }, 5000);
        });
    }

    function setUpDeletePresetButton() {
        $(".wmech_presetdeletebutton").click(async function() {
            // Find max preset value
            var maxValue = 0;
            $("#wmech_presetchooser").find("option").each(function() {
                var curVal = $(this).val();
                if (curVal > maxValue) {
                    maxValue = curVal;
                }
            });

            // Clear information about the current preset and the last preset
            clearPreset($(this).parent());
            var curId = $("#wmech_presetchooser").val();
            $("#wmech_presetchooser").val(curId - 1).change();
            settings.presets.splice(curId, 1);

            await saveSettings();
            clearPreset($("#wmech_presetrow" + maxValue));
            if (curId == 0) {
                $("#wmech_presetrow1").show();
            }
        });
    }

    function clearPreset(el) {
        var preset = el;
        preset.find(".wmech_inputpreset").val("").change();
        preset.find(".wmech_presetpermanent").prop("checked", false).change();
        preset.find(".wmech_presetnodes").val("Middle").change();
        preset.find(".wmech_presetdirection").val("Two Way").change();
        preset.find(".wmech_presetcolor").val("#000000").change();
    }

    async function saveSettings() {
        log("Saved the settings. :D");
        if (localStorage) {
            localStorage.setItem("wmech_Settings", JSON.stringify(settings));
        }
// COMMENTED OUT BECAUASE OF WW ISSUES
//        await saveToServer();
// COMMENTED OUT BECAUASE OF WW ISSUES
        setTimeout(loadSettings, 100);
    }

    async function saveToServer() {
        // log("Attempting to save to the WazeDev server.");
        var res = await WazeWrap.Remote.SaveSettings(GM_info.script.name, settings);
        if (res == false) {
            error("Error saving settings to the WazeDev server.");
        } else if (res == null) {
            // log("Tried to save settings to WazeDev server, but you don't have a PIN set.")
        } else {
            // log("Saved settings to WazeDev server.");
        }
    }

    async function loadSettings() {
        var loadedSettings = $.parseJSON(localStorage.getItem("wmech_Settings"));
        var serverSettings = await WazeWrap.Remote.RetrieveSettings(GM_info.script.name);
        var defaultSettings = {
            enabled: true,
            presets: [{
                name: "Your first preset...",
                reason: "This is where your closure reason goes...",
                timeString: "And this is where your time string goes!",
                permanent: false,
                nodes: "Middle",
                direction: "Two Way",
                mteString: "And the MTE name",
                mteMatchIndex: 0,
                color: "#ffffff"
            }]
        };
// COMMENTED OUT BECAUASE OF WW ISSUES
//        if (serverSettings != null && serverSettings.hasOwnProperty("enabled")) {
            // log("Using settings from WazeDev server.");
//            settings = serverSettings;
//        } else
// COMMENTED OUT BECAUASE OF WW ISSUES
        if (loadedSettings != null && loadedSettings.hasOwnProperty("enabled")) {
            // log("Using settings from local settings.");
            settings = loadedSettings;
        } else {
            // log("Looks like you don't have settings yet. Using the default settings.");
            settings = defaultSettings;
        }

        // Set up presets
        var presets = settings.presets;
        for (var i = 0; i < presets.length; i++) {
            var preset = presets[i];
            if (preset == null || preset.name == "") {
                settings.presets.splice(i, 1);
                saveSettings();
            }
            for (var key in preset) {
                if (preset.hasOwnProperty(key)) {
                    // If preset has value
                    if (key == "color") {
                        $("#wmech_preset" + (i + 1) + key).val(preset[key]);
                    } else if (key == "permanent") {
                        if (preset[key]) {
                            $("#wmech_preset" + (i + 1) + key).attr("checked", "checked");
                        }
                    } else if (key == "nodes" || key == "direction") {
                        $("#wmech_preset" + (i + 1) + key).val(preset[key]);
                    } else {
                        $("#wmech_preset" + (i + 1) + key).val(preset[key]);
                    }
                }
            }
        }
        if (settings.settingsCheckboxes) {
            var settingsCBs = settings.settingsCheckboxes;
            for (var cbKey in settingsCBs) {
                if (settingsCBs[cbKey]) {
                    $("#wmech_setting" + cbKey).attr("checked", "checked");
                }
            }
        }
        if (settings.settingsInputs) {
            var settingsInputs = settings.settingsInputs;
            for (var key in settingsInputs) {
                $("#wmech_setting" + key).val(settingsInputs[key]);
            }
            if (settings.settingsInputs.hasOwnProperty('customcs')) {
                customCSmin = settings.settingsInputs.customcs;
            }
        }
        initCSS();
        loadDropdown();
    }

    function loadDropdown() {
        $("#wmech_presetchooser").find('option').remove();
        var newPresetIndex = 0,
            visibleIndex = 0;
        $(".wmech_namepreset").each(function(i, e) {
            var val = $(e).val();
            if (val.length > 0) {
                $("#wmech_presetchooser").append($('<option>', {
                    value: i,
                    text: "Preset " + (i + 1) + " - " + val
                }));
                newPresetIndex = i;
            }
            if ($("#wmech_presetchooser").children().length < 1) {
                $("#wmech_presetchooser").append($('<option>', {
                    value: 0,
                    text: "Preset 1 - "
                }));
            }
            if ($(this).is(":visible")) {
                visibleIndex = i;
            }
        });
        $("#wmech_presetchooser").append($('<option>', {
            value: newPresetIndex + 1,
            text: "Preset " + (newPresetIndex + 1 + 1) + " - Add an option..."
        }));
        $("#wmech_presetchooser").val(visibleIndex);
    }

    var observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            // Mutation is a NodeList and doesn't support forEach like an array
            for (var i = 0; i < mutation.addedNodes.length; i++) {
                var addedNode = mutation.addedNodes[i];

                // Only fire up if it's a node
                if (addedNode.nodeType === Node.ELEMENT_NODE && addedNode.className=='segment-feature-editor') {
                    var closuresPanel = addedNode.querySelector('.closures');
                    if (closuresPanel) {
                        setup();
                    }
                }
            }
        });
    });

    function attachObserver() {
        log("Observing...");
        WazeWrap.Events.unregister("selectionchanged", null, attachObserver);
        if (document.querySelector("#edit-panel")) {
            observer.observe(document.querySelector("#edit-panel"), { childList: true, subtree: true });
            // setup();
        } else {
            WazeWrap.Events.register("selectionchanged", null, attachObserver);
        }
    }

    function setup() {
        addClosureButtons();
        addPanelWatcher();
        addClosureCounter();
        formatClosureList();
//         $(".toggleHistory").click(function() {
//             setTimeout(addEnhancedClosureHistory, 1000);
//         });
    }

    function addClosureCounter() {
        // TODO In future, make this sorted by active/scheduled
        var num = $(".closure-item").length;
        var msg = "Road Closures (" + num.toString() + ")";
        if ($("#wmech-counter").length == 0)
            $(".closures-tab").append("<div id='wmech-counter'></div>");
        $("#wmech-counter").text(msg);
        setTimeout(function() {
            if ($(".closure-list").hasClass("active")) {
                addClosureCounter();
            }
        }, 1000);
    }

    function addClosureButtons() {
        var tmpButtonClicks = 0;
        var first = null;
        //alert("Appending node.");
        $(".closures-list").append("<div id='wmech-container' style='margin-top: 10px;'></div>");
        var presetCount = 1;
        for (presetCount = 1; presetCount < G_AMOUNTOFPRESETS; presetCount++) {
            var nameInput = $("#wmech_preset" + presetCount + "name").val();
            var timeInput = $("#wmech_preset" + presetCount + "timeString").val();
            var color = $(".wmech_colorinput").eq(presetCount - 1).val();
            var textColor = getTextContrastColor(color);
            if (nameInput) {
                $("#wmech-container").append(
                    $('<button>', {
                        id: ('wmechButton' + presetCount),
                        class: 'wmech_closurebutton',
                        style: ('background-color: ' + color + '; color:' + textColor)
                    }).text(nameInput).attr("data-preset-val", presetCount - 1).on("click", function() {
                        clickClosure($(this), false);
                        radio = "no";
                    }));
            }
        }
    }

    function getTextContrastColor(hex) {
        var match = hex.match(/#(.{2})(.{2})(.{2})/);
        var red = parseInt(match[1], 16);
        var green = parseInt(match[2], 16);
        var blue = parseInt(match[3], 16);
        var calc = (red * 0.299 + green * 0.587 + blue * 0.114);
        return (calc > 150) ? "black" : "white";
    }

    function formatClosureList() {
        $(".details").css("padding", "0 25px 0 15px");
        $(".direction .dir-label").css("margin", "0").css("padding", "0");
        $(".closures-list .direction").css("line-height", "15px").css("height", "20px").css("margin-left", "6px");
        $(".closure-item").css("margin-bottom", "5px").css("padding", "0");
        $(".section").css("padding", "0");
        $(".dates").css("margin-left", "10px");
        $(".closure-title").css("padding", "0").css("min-height", "19px");
        $(".buttons").css("top", "0px");
        //        $("#sidebar .tab-content").css("overflow", "visible").css("overflow-x", "visibile");
        //        $(".closures-list-items").css({"overflow-y": "visible", "padding": 0});
    }

    function addClosureCheckboxes(reason = "addPanelWatcher()") {
        makeBulkButtons();
        $("li.closure-item").each(function() {
            var $checkboxDiv = $("<div />");
            var $checkbox = $("<input />", { type: "checkbox", "class": "wmech_bulkCheckbox" }).css("height", "100%").css("margin-top", "0");
            $checkboxDiv.css("vertical-align", "middle").css("position", "relative").css("margin-left", "4px");
            $checkboxDiv.append($checkbox);
            if ($( this ).find(".wmech_bulkCheckbox").length == 0) {
                $( this ).css("display", "flex").css("margin-bottom", "5px");
                $( this ).wrapInner("<div style='margin-left: 4px; width: 90%;'></div>");
                $( this ).prepend($checkboxDiv);
            }
        });
        $(":checkbox.wmech_bulkCheckbox").click(function(e) {
            toggleBulkButtons();
            e.stopPropagation();
        });

        // Add select all closures checkbox
        //         if ($("#wmech_selectAllDiv").length > 0) {
        //             let cccc = $("#wmech_selectAllDiv").length;

        if ($(".closure-item").length > 1) {
            var holderDiv = $("<div />", { id: "wmech_selectAllDiv" }).css("margin-bottom", "4px");
            holderDiv.append(
                $("<input />", { type: "checkbox", id: "wmech_selectAllCheckbox" }).click(function() {
                    $(".wmech_bulkCheckbox").prop("checked", this.checked);
                    toggleBulkButtons();
                }));
            holderDiv.append($("<p />", { id: "wmech_selectAllText" }).text("Select all closures"));
            $(".full-closures").prepend(holderDiv);
        }
        //        }
    }

    function toggleBulkButtons() {
        if ($(":checkbox.wmech_bulkCheckbox:checked").length == 0)
            hideBulkButtons();
        else
            showBulkButtons();
        if ($(":checkbox.wmech_bulkCheckbox:checked").length == 0) {
            $("#wmech_selectAllCheckbox").prop("checked", false);
        } else if ($(":checkbox.wmech_bulkCheckbox:checked").length == $(":checkbox.wmech_bulkCheckbox").length) {
            $("#wmech_selectAllCheckbox").prop("checked", true);
        }
    }

    function makeBulkButtons() {
        var $buttonDiv = $("<div />", { id: "wmech_bulkButtonDiv" }).css("margin-bottom", "10px");
        var $deleteAllButton = $("<button />", { id: "wmech_bulkDeleteAll", "class": "wmech_closurebutton" }).css("background-color", "red").css("color", "white").text("Delete Selected Closures");
        var $xButton = $("<button />", { id: "wmech_bulkX", "class": "wmech_closurebutton" }).css("background-color", "black").css("color", "red").css("float", "right").css("width", "10%").text("X");
        var $cloneButton = $("<button />", { id: "wmech_bulkClone", "class": "wmech_closurebutton" }).css("background-color", "green").css("color", "white").text("Simple Clone");
        // var $propertiesButton = $("<button />", { id: "wmech_bulkProperties", "class": "wmech_closurebutton" }).css("background-color", "orange").css("color", "white").text("Edit Properties");
        $buttonDiv.append($xButton);
        $buttonDiv.append($deleteAllButton);
        $buttonDiv.append($cloneButton);
        // $buttonDiv.append($propertiesButton);
        $(".closures-list").prepend($buttonDiv);
        $buttonDiv.hide();
        $("#wmech_bulkX").click(function() {
            hideBulkButtons();
            $(".wmech_bulkCheckbox").prop("checked", false);
            $('#wmech_selectAllCheckbox').prop("checked", false);
        });
        $("li.closure-item, .add-closure-button").click(function() {
            $("#wmech_bulkButtonDiv").remove();
        });
        $("#wmech_bulkDeleteAll").click(deleteAllClosures);
        $("#wmech_bulkClone").click(simpleCloneClosure);
    }

    function showBulkButtons() {
        $("#wmech_bulkButtonDiv").show();
    }

    function hideBulkButtons() {
        $("#wmech_bulkButtonDiv").hide();
    }

    function harvestCloneInfo(si) {
        $(".closure-item").eq(si).click();
        var title = $("#closure_reason").val();
        var dir = $("#closure_direction").val();
        var startDate = $("#closure_startDate").val();
//        var startTime = $("#closure_startTime").val();
        var startTime = $("#edit-panel div.closures div.form-group.start-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val()
        var endDate = $("#closure_endDate").val();
//        var endTime = $("#closure_endTime").val();
        var endTime = $("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val()
        var waitForMTE = setInterval(function() {
            // Every 100 seconds check for late info!
            if ($(".wmech_mtelabel").length > 0) {
                clearInterval(waitForMTE);
                var mte = $(".wmech_mtelabelselected").prev().data("mte-val");
                var permanentChecked = $("#closure_permanent").attr("checked");
                var nodes = []
                $(".fromNodeClosed").each(function() {
                    if ($(this).attr("checked") == "checked") {
                        nodes.push(true);
                    } else {
                        nodes.push(false);
                    }
                });

                // Now, time to add a new closure!
                $(".cancel-button").click();
                $(".add-closure-button").click();
                $("#closure_direction wz-option[value=" + dir +"]").click();
                $("#closure_reason").val(title).change();
                changeDateField("#closure_startDate", startDate);
//                $("#closure_startDate").val(startDate).change();
                changeTimeField($("#edit-panel div.closures div.form-group.start-date-form-group > div.date-time-picker > wz-text-input.time-picker-input"),startTime);
//                $("#closure_startTime").val(startTime).change();
                $(".fromNodeClosed").each(function(i, e) {
                    if (nodes[i]) {
                        $(e).attr("checked", "checked");
                    }
                });
                if (permanentChecked) {
                    $("#closure_permanent").attr("checked", "checked").change();
                }
                addToEndStartDate(0, 1, 0, "start");
                if (mte == "") {
                    $("#closure_eventId").val("").change();
                    setTimeout(function() {
                        $("#closure_eventId").removeAttr("value");
                    }, 10);
                } else {
                    $("#closure_eventId").val(mte).change();
                }
                setTimeout(function() {
                    // Wait for default end date/time adjustment
                    changeDateField("#closure_endDate", endDate);
//                    $("#closure_endDate").val(endDate).change();
                    changeTimeField($("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input"),endTime);
//                    $("#closure_endTime").val(endTime).change();
                    addToEndStartDate(0, 1, 0);
                    addPanelWatcher();
                }, 100);
            }
        }, 100);
    }

    function chooseMTE(name) {
        if ($(".wmech_mtelabel").length > 0) {
            $("label:contains('" + name + "')").click();
        } else {
            setTimeout(function() {
                chooseMTE(name);
            }, 100);
        }
    }

    function simpleCloneClosure() {
        log("Starting simple clone.");
        var checked = getIndexOfSelectedCheckboxes();
        if (checked.length != 1) {
            return WazeWrap.Alerts.error(GM_info.script.name, "Currently, simple clone only allows you to clone one segment at a time.");
        }
        var si = checked[0];
        harvestCloneInfo(si);
        return;
    }

    function waitForPermaAndNodes() {
        if ($("#closure_permanent").length > 1 && $(".fromNodeClosed").length > 1) {
            var permanentChecked = ($("#closure_permanent").attr("checked") == "checked");
            var nodes = [];
            $(".fromNodeClosed").each(function() {
                if ($(this).attr("checked") == "checked") {
                    nodes.push(true);
                } else {
                    nodes.push(false);
                }
            });
            return [permanentChecked, nodes];
        } else {
            setTimeout(waitForPermaAndNodes, 100);
        }
    }

    function parseClosureListingDate(dateString) {
        var pattern = new RegExp(/(.{3}) (.{3}) ([0-9]{2}) ([0-9]{4})/);
        var matches = dateString.match(pattern);
        var dayOfWeek = matches[1];
        var shortMonth = matches[2];
        var day = matches[3];
        var year = matches[4];
        var monthNum = 0;
        switch (shortMonth) {
            case "Jan":
                monthNum = 1;
                break;
            case "Feb":
                monthNum = 2;
                break;
            case "Mar":
                monthNum = 3;
                break;
            case "Apr":
                monthNum = 4;
                break;
            case "May":
                monthNum = 5;
                break;
            case "Jun":
                monthNum = 6;
                break;
            case "Jul":
                monthNum = 7;
                break;
            case "Aug":
                monthNum = 8;
                break;
            case "Sep":
                monthNum = 9;
                break;
            case "Oct":
                monthNum = 10;
                break;
            case "Nov":
                monthNum = 11;
                break;
            case "Dec":
                monthNum = 12;
                break;
        }
        return {
            'month': formatTimeProp(monthNum.toString()),
            'day': formatTimeProp(day.toString()),
            'year': year,
        };
    }

    function getIndexOfSelectedCheckboxes() {
        var checked = [];
        $(".wmech_bulkCheckbox").each(function(i) {
            if ($(this).is(":checked")) { checked.push(i); }
        });
        return checked;
    }

    function deleteAllClosures() {
        var checked = getIndexOfSelectedCheckboxes();
        $("wz-menu-item.delete").on('click.wmech_bulk', function(e) {
            e.stopImmediatePropagation();
        });

        //            var _confirm = window.confirm;
        //    window.confirm = function(msg)
        //    {
        //       var cm_delete_confirm = I18n.lookup("closures.delete_confirm").split('"')[0].trimRight(1);

        //       if(msg.indexOf(cm_delete_confirm) != -1)
        //       {
        //          uroAddLog('intercepted closure delete confirmation...');
        //          if(uroConfirmClosureDelete)
        //          {
        //             return _confirm(msg);
        //          }
        //          else
        //          {
        //             return true;
        //          }
        //       }
        //       else if(typeof(msg) == 'undefined')
        //       {
        //          uroAddLog('Intercepted blank confirmation...');
        //          return true;
        //       }
        //       else
        //       {
        //          return _confirm(msg);
        //       }
        //    };
        // Override window.confirm
        var _confirm = window.confirm;
        var oldConfirm = window.confirm;
        var msg = "Delete Closure?";
        window.confirm = function(msg) {
            log(msg);
            if (msg.indexOf("Delete closure") != -1) {
                return true;
            } else {
                return oldConfirm(msg);
            }
        };
        $("wz-menu-item.delete").each(function(i) {
            if (checked.includes(i)) {
                $(this).click();
            }
        });
        $("wz-menu-item.delete").off('click.wmech_bulk');
        setTimeout(addPanelWatcher, 3000);
    }

    function timeZoneCompare() {
        if ($("#wmech_settingtimezonewarn").is(":checked")) {
            var apiVal = $("#wmech_settingtimezoneapi").val();
            var center = W.map.getCenter();
            var actualCenter = WazeWrap.Geometry.ConvertTo4326(center.lon, center.lat);
            var d = new Date();
            GM.xmlHttpRequest({
                method: "GET",
                url: "https://api.timezonedb.com/v2.1/get-time-zone?key=" + apiVal + "&format=json&by=position&lat=" + actualCenter.lat + "&lng=" + actualCenter.lon,
                responseType: "json",
                onload: resp => {
                    var newD = new Date(resp.response.formatted);
                    var diff = Math.round((newD - d) / (1000 * 60 * 60));
                    var timeZone = resp.response.abbreviation;
                    if (diff < 0) {
                        var msg = (-1 * diff) + " hour" + (diff != -1 ? "s" : "") + " behind.  Make sure to adjust your start time if you want the closure to go live now."
                        } else {
                            var msg = diff + " hour" + (diff != 1 ? "s" : "") + " ahead."
                            }
                    if (diff != 0) {
                        $(".edit-closure > form > div:nth-child(4)").after("<div class='wmech_timezonewarnmessage'><span>Warning, the times for the closure you are adding is " + msg + "</span></div>");
                    }
                }
            });
        }
    }

    function addPanelWatcher() {
        $("li.closure-item, .add-closure-button").off();
        $("li.closure-item, .add-closure-button").click(function() {
            setTimeout(addNodeClosureButtons, 5);
            setTimeout(addDirectionCS, 5);
            setTimeout(addClosureSegInfo, 5);
            setTimeout(addClosureLengthValue, 5);
            setTimeout(addMTERadios, 5);
            setTimeout(addLengthExtenders, 5);
            setTimeout(checkIfNeedToAddPanelWatcher, 5);
            setTimeout(removeClosureLines, 5);
            setTimeout(timeZoneCompare, 5);
            setTimeout(function() {
                $('.edit-closure > form > div.action-buttons > wz-button.cancel-button').click(function() {
                    $('.edit-closure > form > div.action-buttons > wz-button.cancel-button').off();
                    $('.edit-closure [class^="wmech"]').remove();
                    $('.edit-closure [id^="wmech"]').remove();
                    setTimeout(function() { setup(); }, 50);
                })
            }, 20);
        });
        formatClosureList();
        addClosureCheckboxes();
    }

    function numOfSegsSelected() {
        return W.selectionManager.getSegmentSelection().segments.length;
    }

    function getAllStreets() {
        var res1 = W.selectionManager.getSegmentSelection();
        var finalRes = [];
        for (var i = 0; i < res1.segments.length; i++) {
            var seg = res1.segments[i];
            var pID = seg.attributes.primaryStreetID;
            var pS = W.model.streets.getObjectById(pID);
            var name = pS.name;
            finalRes.push((name == null ? "No Name" : name));
        }
        return combineStreets(finalRes);
    }

    function combineStreets(arr) {
        var a = [],
            b = [],
            prev;

        arr.sort();
        for (var i = 0; i < arr.length; i++) {
            if (arr[i] !== prev) {
                a.push(arr[i]);
                b.push(1);
            } else {
                b[b.length - 1]++;
            }
            prev = arr[i];
        }

        var res = [];
        for (i = 0; i < a.length; i++) {
            res.push(a[i] + " (" + b[i] + ")");
        }
        return res;
    }

    function removeClosureLines() {
        $(".form-group").css("border-top", "0");
    }

    function addClosureSegInfo() {
        var segsLength = $(".length-attribute .value").text();
        segsLength = segsLength.replace(/m/, "m / ");
        var numOfSegs = numOfSegsSelected();
        var segLabel = numOfSegs + " segs (" + segsLength + ")";
        $(".edit-closure form").prepend('<div class="form-group">' +
                                        '<span><i class="fa fa-fw fa-chevron-down wmech_seglistchevron"></i></span>' +
                                        '<label id="wmech_seginfolabel" class="control-label" for="closure_reason" style="margin-bottom: 0;">Segments</label>' +
                                        '<label id="wmech_seginfolabel" class="control-label" style="font-weight: normal;">' + segLabel + '</label>' +
                                        '<div class="controls"><ul id="wmech_seginfonames">' + '</ul></div></div>');
        $(".edit-closure form .form-group").first().click(collapseSegList);
        if ($("#wmech_settingseglistcollapse").prop("checked")) {
//            radio = "checked"
            collapseSegList();
        }
        var streets = getAllStreets();
        for (var i = 0; i < streets.length; i++) {
            $("#wmech_seginfonames").append("<li>" + streets[i] + "</li>");
        }
    }

    function collapseSegList() {
        $(".edit-closure form .form-group").first().find("ul").toggle();
        $(".wmech_seglistchevron").toggleClass("fa-chevron-down fa-chevron-up");
    }

    function updateClosureLength() {
        $("#wmech_closurelengthval").text(closureLength());
    }

    function addClosureLengthValue() {
        $(".form-group.end-date-form-group").after('<div class="form-group">' +
                                                   '<label class="control-label" for="closure_reason">Closure Length</label>' +
                                                   '<div class="controls" style="text-align: center;">' +
                                                   '<span id="wmech_closurelengthval"></span>' +
                                                   '</div></div>');
        $("#wmech_closurelengthval").text(closureLength());
        $("#closure_startDate, " +
          "#closure_endDate, " +
          ".time-picker-input").on('change paste keyup input', function() {
            setTimeout(updateClosureLength,50);
        });
    }

    function closureLength() {
        var startDate = $("#closure_startDate").val();
//        var startTime = $("#closure_startTime").val();
        var startTime = $("#edit-panel div.closures div.form-group.start-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val()
        var endDate = $("#closure_endDate").val();
//        var endTime = $("#closure_endTime").val();
        var endTime = $("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val()
        //        var regex = /(.*)(\-|\.|\/)(.*)(\-|\.|\/)(.*)/;
        var regex = /(\d*)(\-|\.|\/)(\d*)(\-|\.|\/)(\d*)(.*)/;
        var startDateResult = regex.exec(startDate);
        var endDateResult = regex.exec(endDate);
        if (DateFormat == "ddmmyyyy"){
            var startYear = startDateResult[5];
            var startMonth = startDateResult[3];
            var startDay = startDateResult[1];
            var endYear = endDateResult[5];
            var endMonth = endDateResult[3];
            var endDay = endDateResult[1];
        }else{
            if (DateFormat == "yyyymmdd"){
                var startYear = startDateResult[1];
                var startMonth = startDateResult[3];
                var startDay = startDateResult[5];
                var endYear = endDateResult[1];
                var endMonth = endDateResult[3];
                var endDay = endDateResult[5];
            }else{
                var startYear = startDateResult[5];
                var startMonth = startDateResult[1];
                var startDay = startDateResult[3];
                var endYear = endDateResult[5];
                var endMonth = endDateResult[1];
                var endDay = endDateResult[3];
            }}
        var regex2 = /(.*):(.*)/;
        var startTimeResult = regex2.exec(startTime);
        var startHour = startTimeResult[1];
        var startMin = startTimeResult[2];
        var endTimeResult = regex2.exec(endTime);
        var endHour = endTimeResult[1];
        var endMin = endTimeResult[2];
        var d1 = new Date(startYear, parseInt(startMonth) - 1, startDay, startHour, startMin, 0, 0);
        var d2 = new Date(endYear, parseInt(endMonth) - 1, endDay, endHour, endMin, 0, 0);
        if (d2 - d1 < 0) {
            endDateBeforeStartDate();
            return "End date is before start date!";
        } else {
            endDateAfterStartDate();
        }
        var dif = dateDiff(d1, d2);
        var finalString = [];
        if (dif['year'] > 0) {
            finalString.push(dif['year'] + " year" + (dif['year'] != 1 ? "s" : ""));
        }
        if (dif['month'] > 0) {
            finalString.push(dif['month'] + " month" + (dif['month'] != 1 ? "s" : ""));
        }
        if (dif['week'] > 0) {
            finalString.push(dif['week'] + " week" + (dif['week'] != 1 ? "s" : ""));
        }
        if (dif['day'] > 0) {
            finalString.push(dif['day'] + " day" + (dif['day'] != 1 ? "s" : ""));
        }
        if (dif['hour'] > 0) {
            finalString.push(dif['hour'] + " hour" + (dif['hour'] != 1 ? "s" : ""));
        }
        if (dif['minute'] > 0) {
            finalString.push(dif['minute'] + " minute" + (dif['minute'] != 1 ? "s" : ""));
        }
        return finalString.join(", ");
    }

    function endDateBeforeStartDate() {
        $("#wmech_closurelengthval").css("color", "red");
        $(".edit-closure").css("background-color", "#f7b0b0");
        $(".edit-closure").find("input, select").css("background-color", "#ffd1d1");
        $(".closure-node-item").css("background-color", "#ffd1d1");
    }

    function endDateAfterStartDate() {
        $("#wmech_closurelengthval").css("color", "black");
        $(".edit-closure").css("background-color", "#eeeeee");
        $(".edit-closure").find("input, select").css("background-color", "#fff");
        $(".closure-node-item").css("background-color", "#f2f4f7");
    }

    function dateDiff(d1, d2) {
        // Thank you for this code RienNaVaPlus (https://stackoverflow.com/a/32514236)!
        var d = Math.abs(d2 - d1) / 1000; // delta
        var r = {}; // result
        var s = { // structure
            year: 31536000,
            month: 2592000,
            week: 604800, // uncomment row to ignore
            day: 86400, // feel free to add your own row
            hour: 3600,
            minute: 60,
            second: 1
        };

        Object.keys(s).forEach(function(key) {
            r[key] = Math.floor(d / s[key]);
            d -= r[key] * s[key];
        });

        return r;
    };

    function addDirectionCS() {
        var DirLen = W.selectionManager.getSelectedWMEFeatures().length
        var segDir;
        if (DirLen > 1) {
            segDir = 3
            $("#closure_direction wz-option[value='3']").click();
        }else{
            segDir = $("#closure_direction").val();
        }

        var directionalCursors = $("#wmech_settingdircsdircur").is(":checked");
        $("#closure_direction").after("<div id='wmech_dBAB' class='wmech_closureButton wmech_dirbutton'>A → B</div>" +
                                      "<div id='wmech_dBBA' class='wmech_closureButton wmech_dirbutton'>B → A</div>" +
                                      "<div id='wmech_dBTW' class='wmech_closureButton wmech_dirbutton'>Two way (⇆)</div>");
        var permDir = "";
        if ($(".heading").length > 0 && numOfSegsSelected() <= 1) {
            if ($(".letter-circle:eq(0)").text() == "A") {
                var dir = $(".heading:eq(0)").text().match(/(?<=Drive ).*(?= on)/)[0];
                $(".wmech_dirbutton:eq(0)").append("(" + dir + ")").css("cursor", (directionalCursors ? "pointer" : determineCursor(dir)));
                if (dir.length > 1) permDir = dir;
            } else {
                var dir = $(".heading:eq(0)").text().match(/(?<=Drive ).*(?= on)/)[0];
                $(".wmech_dirbutton:eq(1)").append("(" + dir + ")").css("cursor", (directionalCursors ? "pointer" : determineCursor(dir)));
                if (dir.length > 1) permDir = dir;
            }
            if ($(".letter-circle:eq(2)").text() == "A") {
                var dir = $(".heading:eq(1)").text().match(/(?<=Drive ).*(?= on)/)[0];
                $(".wmech_dirbutton:eq(0)").append("(" + dir + ")").css("cursor", (directionalCursors ? "pointer" : determineCursor(dir)));
                if (dir.length > 1) permDir = dir;
            } else if ($(".letter-circle:eq(2)").text() == "B") {
                var dir = $(".heading:eq(1)").text().match(/(?<=Drive ).*(?= on)/)[0];
                $(".wmech_dirbutton:eq(1)").append("(" + dir + ")").css("cursor", (directionalCursors ? "pointer" : determineCursor(dir)));
                if (dir.length > 1) permDir = dir;
            }
        }
        $("#wmech_dBAB").click(function() {
            $("#closure_direction wz-option[value='1']").click();
            $("#wmech_dBAB").css('background-color', '#26bae8');
            $("#wmech_dBBA").css('background-color', '#ddd');
            $("#wmech_dBTW").css('background-color', '#ddd');
        });
        $("#wmech_dBBA").click(function() {
            $("#closure_direction wz-option[value='2']").click();
            $("#wmech_dBAB").css('background-color', '#ddd');
            $("#wmech_dBBA").css('background-color', '#26bae8');
            $("#wmech_dBTW").css('background-color', '#ddd');
        });
        $("#wmech_dBTW").click(function() {
            $("#closure_direction wz-option[value='3']").click();
            $("#wmech_dBAB").css('background-color', '#ddd');
            $("#wmech_dBBA").css('background-color', '#ddd');
            $("#wmech_dBTW").css('background-color', '#26bae8');
        });
        if (segDir == 1) {
            // Segment direction is A --> B
            $("#wmech_dBBA, #wmech_dBTW").remove();
            $("#wmech_dBAB").css('background-color', '#26bae8');
        } else if (segDir == 2) {
            // Segment direction is B --> A
            $("#wmech_dBAB, #wmech_dBTW").remove();
            $("#wmech_dBBA").css('background-color', '#26bae8');
        }
        $("#wmech_dBTW").css("cursor", (directionalCursors ? "pointer" : determineCursorDouble(permDir)));
        $("#wmech_dBTW").css('background-color', '#26bae8');
    }

    function determineCursor(dir) {
        if (dir == "south") return "s-resize";
        if (dir == "north") return "n-resize";
        if (dir == "east") return "e-resize";
        if (dir == "west") return "w-resize";
        if (dir == "southeast") return "se-resize";
        if (dir == "northwest") return "nw-resize";
        if (dir == "northeast") return "ne-resize";
        if (dir == "southwest") return "sw-resize";
        if (dir.length < 1) return "help";
    }

    function determineCursorDouble(dir) {
        if (dir == "east" || dir == "west") return "ew-resize";
        if (dir == "north" || dir == "south") return "ns-resize";
        if (dir == "northeast" || dir == "southwest") return "nesw-resize";
        if (dir == "northwest" || dir == "southeast") return "nwse-resize";
    }

    function addLengthExtenders() {
        var $html = [
            '<span id="wmech_lEB1m" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #f5ffba;">+1m</span>',
            '<span id="wmech_lEB15m" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #f5ffba;">+15m</span>',
            '<span id="wmech_lEB1h" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #c9ffba;">+1h</span>',
            '<span id="wmech_lEB2h" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #c9ffba;">+2h</span>',
            '<span id="wmech_lEB1d" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #bafff7;">+1d</span>',
            '<span id="wmech_lEB1w" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #bdbaff;">+1w</span>',
            '<span id="wmech_lEB1mo" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #ffbaf9;">+1mo</span>',
            '<span id="wmech_lEBcustomMin" class="wmech_closureButton wmech_lengthExtenderButton" style="background-color: #ffffff;">custom</span>',
        ].join("\n");
        $("#wmech_closurelengthval").after("<div id='wmech_timeExtenderDiv'></div>");
        $("#wmech_timeExtenderDiv").append($html);
        if (customCSmin == "") {
            $("#wmech_lEBcustomMin").css('visibility', 'hidden');
        } else {
            $("#wmech_lEBcustomMin").text(customCSmin + "m");
        }
        $("#wmech_lEB1m").click(function() { addToEndStartDate(0, 0, 1); });
        $("#wmech_lEB15m").click(function() { addToEndStartDate(0, 0, 15); });
        $("#wmech_lEB1h").click(function() { addToEndStartDate(0, 0, 60); });
        $("#wmech_lEB2h").click(function() { addToEndStartDate(0, 0, 120); });
        $("#wmech_lEB1d").click(function() { addToEndStartDate(0, 1, 0); });
        $("#wmech_lEB1w").click(function() { addToEndStartDate(0, 7, 0); });
        $("#wmech_lEB1mo").click(function() { addToEndStartDate(1, 0, 0); });
        $("#wmech_lEBcustomMin").click(function() { addToEndStartDate(0, 0, customCSmin); });
    }

    function addToEndStartDate(o, d, m, type = "end") {
        var LY;
        var finalDate;
        var finalTime;
        var endDate = $("#closure_" + type + "Date").val();
        var endTime = $("#edit-panel div.closures div.form-group." + type + "-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").val();
//        var endTime = $("#closure_" + type + "Time").val();
        //            var regex = /(.*)\/(.*)\/(.*)/;
        //        var regex = /(.*)(\-|\.|\/)(.*)(\-|\.|\/)(.*)/;
        var regex = /(\d*)(\-|\.|\/)(\d*)(\-|\.|\/)(\d*)(.*)/;
        var endDateResult = regex.exec(endDate);
        if (DateFormat == "ddmmyyyy"){
            var endYear = endDateResult[5];
            var endMonth = endDateResult[3];
            var endDay = endDateResult[1];
        }else{
            if (DateFormat == "yyyymmdd"){
                var endYear = endDateResult[1];
                var endMonth = endDateResult[3];
                var endDay = endDateResult[5];
            }else{
                var endYear = endDateResult[5];
                var endMonth = endDateResult[1];
                var endDay = endDateResult[3];
            }}
        // fix for last day of month and adding 1 month with clicksaver to ensure it is actually last day of following month.
        if (endYear == "2024" || endYear == "2028" || endYear == "2032" || endYear == "2036" || endYear == "2040" || endYear == "2044" || endYear == "2048") LY = "yes";
        if (o == 1){
            if (endMonth == "04" || endMonth == "06" || endMonth == "07" || endMonth == "09" || endMonth == "11" || endMonth == "12"){
                if (endDay == "30"){
                    o = 0;
                    d = 31;
                }
            }else{
                if (endMonth == "03" || endMonth == "05" || endMonth == "08" || endMonth == "10"){
                    if (endDay == "31"){
                        o = 0;
                        d = 30;
                    }
                }else{
                    if (endMonth == "02" && endDay > "27"){
                        o = 0;
                        if (LY == "yes"){
                            d = 31;
                        }else{
                            d = 30;
                        }
                    }else{
                        if (endMonth == "01" && endDay > "27"){
                            o = 0;
                            if (LY == "yes"){
                                d = 31 - (endDay - 29);
                            }else{
                                d = 31 - (endDay - 28);
                            }
                        }
                    }
                }
            }
        }

        var regex2 = /(.*):(.*)/;
        var endTimeResult = regex2.exec(endTime);
        var endHour = endTimeResult[1];
        var endMin = endTimeResult[2];
        var res = new Date(endYear, parseInt(endMonth) - 1, endDay, endHour, endMin, 0, 0);
        res.setTime(res.getTime() + (m * 60 * 1000));
        res.setDate(res.getDate() + d);
        res.setMonth(res.getMonth() + o);
        if (DateFormat == "ddmmyyyy"){
            finalDate = formatTimeProp(formatTimeProp(res.getDate())) + dateSeparator + (parseInt(res.getMonth()) + 1) + dateSeparator + res.getFullYear();
        }else{
            if (DateFormat == "yyyymmdd"){
                finalDate = res.getFullYear() + dateSeparator + (parseInt(res.getMonth()) + 1) + dateSeparator + formatTimeProp(res.getDate());
            }else{
                finalDate = formatTimeProp(parseInt(res.getMonth()) + 1) + dateSeparator + formatTimeProp(res.getDate()) + dateSeparator + res.getFullYear();
            }}
        finalTime = formatTimeProp(res.getHours()) + ":" + formatTimeProp(res.getMinutes());
//        $("#closure_" + type + "Date").val(finalDate).change();
        changeDateField("#closure_" + type + "Date", finalDate);
//        $("#closure_" + type + "Time").val(finalTime).change();
        changeTimeField($("#edit-panel div.closures div.form-group." + type + "-date-form-group > div.date-time-picker > wz-text-input.time-picker-input"),finalTime);
    }

    function changeDateField(element, newDate) {
        const newDateObj = $(element).data('daterangepicker')
        newDateObj.setStartDate(newDate)
        $(element).trigger(
            'apply.daterangepicker',
            [newDateObj]
        )
    }

    function changeTimeField($element, newtime) {
         $element.timepicker('setTime',newtime);
    }

    function formatTimeProp(num) {
        return ("0" + num).slice(-2);
    }

    function addNodeClosureButtons() {
        $(".closure-nodes.form-group > wz-label").after("<span id='wmech_nCBNone' class='wmech_closureButton  wmech_nodeClosureButton'>None</span>" +
                                                        "<span id='wmech_nCBAll' class='wmech_closureButton wmech_nodeClosureButton'>All</span>" +
                                                        "<span id='wmech_nCBMiddle'class='wmech_closureButton wmech_nodeClosureButton'>Middle</span>" +
                                                        "<span id='wmech_nCBEnds'class='wmech_closureButton wmech_nodeClosureButton'>Ends</span>");
        $(".wmech_nodeClosureButton").unbind();
        $("#wmech_nCBNone").click(toggleNoNodes);
        $("#wmech_nCBAll").click(toggleAllNodes);
        $("#wmech_nCBMiddle").click(toggleMiddleNodes);
        $("#wmech_nCBEnds").click(toggleEndsNodes);
    }

    function toggleNoNodes(colorize = false) {
        panelToggleNodes(".fromNodeClosed", false, colorize);
        $("#wmech_nCBNone").css('background-color', '#26bae8');
        $("#wmech_nCBAll").css('background-color', '#ddd');
        $("#wmech_nCBMiddle").css('background-color', '#ddd');
        $("#wmech_nCBEnds").css('background-color', '#ddd');
    }

    function toggleAllNodes(colorize = false) {
        panelToggleNodes(".fromNodeClosed", true, colorize);
        $("#wmech_nCBNone").css('background-color', '#ddd');
        $("#wmech_nCBAll").css('background-color', '#26bae8');
        $("#wmech_nCBMiddle").css('background-color', '#ddd');
        $("#wmech_nCBEnds").css('background-color', '#ddd');
    }

    function toggleMiddleNodes(colorize = false) {
        panelToggleNodes(".fromNodeClosed", true, colorize);
        panelToggleNodes(".fromNodeClosed:first", false, colorize);
        panelToggleNodes(".fromNodeClosed:last", false, colorize);
        $("#wmech_nCBNone").css('background-color', '#ddd');
        $("#wmech_nCBAll").css('background-color', '#ddd');
        $("#wmech_nCBMiddle").css('background-color', '#26bae8');
        $("#wmech_nCBEnds").css('background-color', '#ddd');
    }

    function toggleEndsNodes(colorize = false) {
        panelToggleNodes(".fromNodeClosed", false, colorize);
        panelToggleNodes(".fromNodeClosed:first", true, colorize);
        panelToggleNodes(".fromNodeClosed:last", true, colorize);
        $("#wmech_nCBNone").css('background-color', '#ddd');
        $("#wmech_nCBAll").css('background-color', '#ddd');
        $("#wmech_nCBMiddle").css('background-color', '#ddd');
        $("#wmech_nCBEnds").css('background-color', '#26bae8');
    }

    function panelToggleNodes(selector, setting, colorize = false) {
        $(selector).each(function() {
            this.checked = setting;
            $(this).change();
            if (colorize) {
                setTimeout(function() {
                    colorizeRow(this);
                }, 20);
            }
        });
    }

    function colorizeRow(elem) {
        var root = elem; //.shadow-root;
        $(root).find(".wz-slider").css("background-color", "rgb(63, 188, 113)");
        $(elem).parent().parent().css("background-color", "rgba(63, 188, 113, 0.4)");
        $(elem).one("click", function() {
            uncolorizeRow(elem);
        });
    }

    function uncolorizeRow(button) {
        var root = button; // .shadow-root;
        $(root).find(".wz-slider").css("background-color", "");
        $(button).parent().parent().css("background-color", "rgb(242, 244, 247);");
    }

    function addMTERadios() {
        if (radio != "no"){
         $("#closure_eventId").parent().css("height", 0).css("overflow", "hidden");
        $("#closure_eventId").removeAttr("required");
        $(".label-with-tooltip").after("<div id='wmech_mteradiosdiv'><form id='wmech_mteradiosform' name='wmech_mte'></form></div>");
        var to = $("#closure_eventId").children().length - 1;
        for (var i = 0; i < to; i++) {
            var labelText = $("#closure_eventId wz-option:nth-child(" + (i + 1) + ")").text();
            var labelVal = $("#closure_eventId wz-option:nth-child(" + (i + 1) + ")").val();
            $("#wmech_mteradiosform").append('<div><input id="testButton' + i + '" type="radio" name="wmech_mte" data-mte-val="' + labelVal + '"><label for="testButton' + i + '" class="wmech_mtelabel">' + labelText + '</label></div>');
        }
        $('input[type=radio][name="wmech_mte"]').change(function() {
            if (this.id == "testButton0") {
                $("#closure_eventId").removeAttr("value");
            } else {
                $("#closure_eventId").val($(this).data("mte-val")).change();
            }
            $(".wmech_mtelabel").removeClass("wmech_mtelabelselected");
            $("label[for='" + this.id + "']").addClass("wmech_mtelabelselected");
        });
        var firstSelected = $("#closure_eventId").val();
        if (firstSelected == "") {
            $("#closure_eventId").val("").change();
            setTimeout(function() {
                $("#closure_eventId").removeAttr("value");
            }, 100);
            $("input[data-mte-val='']").click();
        } else {
            $("input[data-mte-val='" + firstSelected + "']").click();
        }
        }
        radio = ""
    }

    function checkIfNeedToAddPanelWatcher() {
        setTimeout(function() {
            if ($("#closure_permanent").length == 0) {
                addPanelWatcher();
            } else {
                checkIfNeedToAddPanelWatcher();
            }
        }, 1000);
    }

    function initCSS() {
        log("Initializing CSS.");
        $("<style id='wmechStyle'></style>").appendTo("head");
        $("#wmechStyle").html([
            ".wmechClosureDetailsDiv { background-color: rgba(63, 188, 113, 0.5); margin: 5px; padding: 5px; border-radius: 5px; }",
            ".wmechMainText { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; margin-bottom: 0px !important; line-height: 1; margin-left: 10px; }",
            ".wmechSettingsLabel { white-space: pre-line !important; display: inline-block; font-weight: none !important; padding-left: 5px; }",
            ".wmechSettingsDiv { position: relative; }",
            ".wmech_input { border-radius: 5px; border: 1px solid lightgray; margin: 1px; height: 25px !important;  }",
            ".wmech_input.wmech_inputregex:nth-of-type(2) { width: 40%}",
            ".wmech_input.wmech_inputregex:nth-of-type(3) { width: 20%}",
            ".wmech_input.wmech_inputregex:nth-of-type(4) { width: 30%}",
            ".wmech_inputpreset { width: 100%; text-align: center; }",
            ".wmech_presetcheckbox { margin-right: 10px !important; margin-top: 5px !important; }",
            ".wmech_closurebutton { font-weight: bold; width: 100%; height: 25px; margin: 2px 0; border-radius: 5px; background-color: white; }",
            "#wmechStringAppendSpan { font-weight: bold; }",
            "#wmechStringAppendSpan::before { content: 'String: '; }",
            ".wmech_colorinputlabel { background-color: black; width: 100%; height: 25px; border-radius: 5px; text-align: center; }",
            ".wmechClosureTrackingContainer { margin-bottom: 2px; font-family: font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; border-radius: 5px; background-color: red; clear: both; padding: 5px; } ",
            ".wmechClosureTrackingContainer h2 { line-height: 20px; font-size: 16px; font-weight: bold; padding-bottom: 0px !important; color: white; }",
            ".wmechClosureTrackingContainer ul { list-style-position: inside; background-color: white; padding: 5px; margin-bottom: 0px; border-radius: 5px;} ",
            ".wmechCTCreated { color: purple; } ",
            ".wmechCTUpdated { color: blue; } ",
            ".wmechCTOpened { color: #33b300; } ",
            ".wmechCTButton { margin: 0 2px 2px 0; padding: 2px; font-weight: bold; display: inline-block; background-color: white; border-radius: 2px; font-size: 12px; line-height: 1; } ",
            ".wmechCTPL { color: blue; } ",
            ".wmechCTVisit { color: blue; } ",
            ".wmechCTOpen { background-color: #82b57f; color: white; } ",
            ".wmechCTExtend { background-color: #ffdc00; } ",
            ".wmechCTSubmitPL { background-color: #82b57f; color: white; border-radius: 5px;} ",
            ".wmech_closureButton { text-align: center; font-family: 'Rubik', 'Boing-light', sans-serif; font-weight: 700; border: 1px solid gray; background-color: #ddd; color: black; border-radius: 5px; font-size: 11px; cursor: pointer;} ",
            ".wmech_nodeClosureButton { display: inline-block; width: 23%; margin: 1%;  }",
            ".wmech_dirbutton { width: 100%; margin: 0.3em 0; }",
            ".wmech_buttonNotAllowed { background: lightgray; color: gray; cursor: not-allowed; }",
            ".wmech_colorinput { width: 20%; } ",
            "#wmech_mteradiosdiv { background-color: #f2f4f7; overflow-y: scroll; height: 100px; border: 1px solid gray; border-radius: 5px; padding: 5px; margin-bottom: 2px; } ",
            "input[name='wmech_mte'] { margin-right: 3px; } ",
            ".wmech_lengthExtenderButton { margin: 0 1px; padding: 0 4px; color: black; } ",
            "#wmech_presetchooser { width: 100%; height: 25px; }",
            ".wmech_presetlabel { margin-left: 10px; height: 25px; }",
            ".wmech_presetsetting { margin-right: 10px; float: right; }",
            ".wmech_presetdropdown { height: 25px; } ",
            ".wmech_mtelabel { font-weight: normal; font-size: 14px; }",
            ".wmech_mtelabelselected { font-weight: bold; }",
            ".wmech_seglistchevron { position: absolute; cursor: pointer; font-size: 14px; float: right; width: 100%; text-align: right; margin: 5px 5px 0 0; }",
            ".wmech_presetpermatooltip { margin-left: 10px; }",
            "#wmech_selectAllCheckbox { margin-left: 4px; } ",
            "#wmech_selectAllDiv { margin-bottom: 4px } ",
            "#wmech_selectAllText { font-weight: bold; margin-left: 4px; display: inline }",
            ".wmech_settingsheader { font-weight: bold; margin-bottom: 0 !important; }",
            ".wmech_timezonewarnmessage { text-align: center }",
            ".wmech_timezonewarnmessage span { font-weight: bold; color: black; background-color: red }",
            ".wmech_settingsinput { text-align: center; width: 100%; }",
            ".wmech-tab-pane { width: 100%; display: none; padding: 15px 0; }",
            ".wmech-tab-pane .active { width: 100%; display: block; padding: 15px 0; }",
            ".wmech-alert { background-color: #00FFFF; border-radius: 5px; display: none; }"
        ].join('\n\n'));
    }

    async function clickClosure(elem, dbl = false) {
        if (W.model.actionManager._undoStack.length > 0) {
            return WazeWrap.Alerts.error(GM_info.script.name, "Can't add closure because you have unsaved edits.");
        }
        $("wz-button.add-closure-button").click();
        await new Promise(r => setTimeout(r, 100));
        var ruleIndex = parseInt($(elem).data("preset-val"));
        var nameString = $("#wmech_preset" + (ruleIndex + 1) + "reason").val();
        $("#closure_reason").val(closureName(nameString)).change();
        if ($("#wmech_preset" + (ruleIndex + 1) + "timeString").val().length > 0) {
            var ruleParsed = parseRule($("#wmech_preset" + (ruleIndex + 1) + "timeString").val());
            changeDateField("#closure_endDate", ruleParsed[0]);
//            $("#closure_endDate").val(ruleParsed[0]).change();
            changeTimeField($("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input"), ruleParsed[1]);
//            $("#closure_endTime").val(ruleParsed[1]).change();
        }
        var permClosures = $(".wmech_presetcheckbox").eq(ruleIndex).prop("checked");
        if (permClosures) {
            setTimeout(function() {
                $("#closure_permanent").prop("checked", "checked").change();
            }, 50);
        }
        var nodeClosuresOption = $("#wmech_preset" + (ruleIndex + 1) + "nodes").val();
        if (nodeClosuresOption == "Middle") {
            setTimeout(function() {
                toggleMiddleNodes(true);
            }, 50);
        }
        if (nodeClosuresOption == "All") {
            setTimeout(function() {
                toggleAllNodes(true);
            }, 50);
        }
        if (nodeClosuresOption == "Ends") {
            setTimeout(function() {
                toggleEndsNodes(true);
            }, 50);
        }
        if (nodeClosuresOption == "None") {
            setTimeout(function() {
                toggleNoNodes(true);
            }, 50);
        }
        setTimeout(function() {
            $("#closure_reason").css("background-color", "rgba(63, 188, 113, 0.5)");
            $("#closure_endDate").css("background-color", "rgba(63, 188, 113, 0.5)");
            $("#edit-panel div.closures div.form-group.end-date-form-group > div.date-time-picker > wz-text-input.time-picker-input").css("background-color", "rgba(63, 188, 113, 0.5)");
//            $("#closure_endTime").css("background-color", "rgba(63, 188, 113, 0.5)");
            if (permClosures) {
                $(".edit-closure > form > div > #closure_permanent").css("color", "rgba(63, 188, 113, 1)");
            }
        }, 20);
        var direction = $("#wmech_preset" + (ruleIndex + 1) + "direction").val();
        var dirNumber = 3;
        if (direction == "Two Way") {
            dirNumber = 3;
            $("#wmech_dBAB").css('background-color', '#ddd');
            $("#wmech_dBBA").css('background-color', '#ddd');
            $("#wmech_dBTW").css('background-color', '#26bae8');
        } else if (direction == "A --> B") {
            dirNumber = 1;
            $("#wmech_dBAB").css('background-color', '#26bae8');
            $("#wmech_dBBA").css('background-color', '#ddd');
            $("#wmech_dBTW").css('background-color', '#ddd');
        } else if (direction == "B --> A") {
            dirNumber = 2;
            $("#wmech_dBAB").css('background-color', '#ddd');
            $("#wmech_dBBA").css('background-color', '#26bae8');
            $("#wmech_dBTW").css('background-color', '#ddd');
        }
        $("#closure_direction wz-option[value='" + dirNumber + "']").click();

        var mteRegEx = $("#wmech_preset" + (ruleIndex + 1) + "mteString").val();
        if (mteRegEx.length > 0) {
            var mteFuncResult = matchMTE(mteRegEx);
            if (mteFuncResult != false) {
                $("#closure_eventId").val(mteFuncResult.val.toString());
            }
        }else{
            document.querySelector("#closure_eventId > wz-option:nth-child(1)").shadowRoot.querySelector("div").click();
        }
    }

    function matchMTE(match) {
        var mtes = [];
        $("#closure_eventId").children().each(function() {
            var text = $(this).text();
            var val = $(this).val();
            mtes.push({ 'name': text, 'val': val });
        });
        for (var i = 0; i < mtes.length; i++) {
            if (mtes[i].name.toLowerCase() == match.toLowerCase()) {
                console.log(mtes[i]);
                return mtes[i];
            }
        }
        return false;
    }

    function closureName(reason) {
        var finalString = reason;
        var selectedType = getSelectedType(selectedType);
        // Replace with name and type
        finalString = finalString.replace("{{type}}", selectedType);

        // Replace with segs
        var selectedSegs = W.selectionManager.getSegmentSelection().segments;
        var firstSelectedSegName = W.model.streets.getObjectById(selectedSegs[0].attributes.primaryStreetID).attributes.name;
        var lastSelectedSegName = W.model.streets.getObjectById(selectedSegs[selectedSegs.length - 1].attributes.primaryStreetID).attributes.name;
        if (firstSelectedSegName == null) {
            firstSelectedSegName = "";
        }
        if (lastSelectedSegName == null) {
            lastSelectedSegName = "";
        }
        finalString = finalString.replace("{{firstSegName}}", firstSelectedSegName).replace("{{lastSegName}}", lastSelectedSegName);

        // RegEx
        // var replaceRE = new RegExp($("input.wmech_inputregex").val(), $("input.wmech_inputregex").eq(1).val());
        // finalString = finalString.replace(replaceRE, $("input.wmech_inputregex").eq(2).val());

        // Return
        return finalString;
    }

    function getSelectedType(option) {
        var SelObj = W.selectionManager.getSelectedDataModelObjects();
        var rawType = SelObj[0].attributes.roadType;
        var newType;
        switch (rawType) {
            case 8: // Off-road / Not maintained"
                newType = "Road";
                break;
            case 1: // Local Street"
                newType = "Street";
                break;
            case 2: // Primary Street"
                newType = "Primary Street";
                break;
            case 3: // Freeway (Interstate / Other)
                newType = "Freeway";
                break;
            case 6: // Major Highway
                newType = "Highway";
                break;
            case 7: //Minor Highway
                newType = "Highway";
                break;
            case 4: // Ramp
                newType = "Ramp";
                break;
            case 20: // PLR
                newType = "Parking Lot";
                break;
            case 17: // PR
                newType = "Private";
                break;
            case 15: // Ferry
                newType = "Ferry";
                break;
            default: // Other road types
                newType = "Roads";
                break;
        }
               if (SelObj.length > 1) { // If multiple segments selected, check for different road types
                   const multipleTypesSelected = SelObj.some(seg => seg.attributes.roadType !== SelObj[0].attributes.roadType);
                   if (multipleTypesSelected == true) {newType = "Multiple Road Types"};
               }
        return newType;
    }

    function parseRule(rule) {
        //alert(rule);
        var LY = "no";
        var newMon = 0;
        var newDay = 0;
        var d = new Date();
        var yr = d.getFullYear();
        var mon = d.getMonth() + 1;
        var day = d.getDate();
        var hr = d.getHours();
        var min = d.getMinutes();
        if (rule.substring(0, 1) == "U") {
            var count = (rule.match(/,/g) || []).length;
            var timeString = rule.substring(3);
            var ruleHr = parseInt(timeString.substring(0, 2));
            var ruleMin = parseInt(timeString.substring(3, 5));
            // fix for last day of month and adding 1 month with clicksaver to ensure it is actually last day of following month.
        if (yr == "2024" || yr == "2028" || yr == "2032" || yr == "2036" || yr == "2040" || yr == "2044" || yr == "2048") LY = "yes";
            if (mon == "04" || mon == "06" || mon == "07" || mon == "09" || mon == "11" || mon == "12"){
                if (day == "30"){
                    newMon = mon + 1;
                    newDay = 1;
                }else{
                    newMon = mon;
                    newDay = day + 1;
                }
            }else{
                if (mon == "03" || mon == "05" || mon == "08" || mon == "10"){
                    if (day == "31"){
                        newMon = mon + 1;
                        newDay = 1;
                    }else{
                    newMon = mon;
                    newDay = day + 1;
                }
                }else{
                    if (mon == "02" && day > "27" && LY == "no" || mon == "02" && day > "28" && LY == "yes"){
//                        if (LY == "yes" && day > "28"){
                            newMon = mon + 1;
                            newDay = 1;
                        }else{
                            newMon = mon;
                            newDay = day;
                        }
                    }
                }
//       }
            if (count == 0) {
                // (ex. "U: 05:00", "U: 23:15")
                if (ruleHr > hr || (ruleHr == hr && ruleMin > min)) { return [assembleYear([yr, mon, day]), assembleTime([ruleHr, ruleMin])]; }
                if ((ruleHr == hr && ruleMin == min) || (ruleHr == hr && ruleMin < min) || (ruleHr < hr)) { return [assembleYear([yr, newMon, newDay]), assembleTime([ruleHr, ruleMin])]; }
            } else if (count == 1) {
                timeString = rule.substring(3, 8);
                var durationString = rule.substring(rule.lastIndexOf(" ") + 1).trim();
                var newDate = new Date();
                if (durationString.match(/^[M|T|W|F|S]/) == null) {
                    var loopLength = durationString.match(/[a-z]/g).length;
                    //alert(newDate);
                    for (var i = 0; i < loopLength; i++) {
                        var nextNum = durationString.match(/[^(y|o|d|h|m)]*/)[0];
                        var nextLetter = durationString.match(/[a-z]/g)[0];
                        switch (nextLetter) {
                            case "y":
                                newDate.setFullYear(newDate.getFullYear() + parseInt(nextNum));
                                break; //newYr += parseInt(nextNum); break;
                            case "o":
                                newDate.setMonth(newDate.getMonth() + parseInt(nextNum));
                                break; //newMon += parseInt(nextNum); break;
                            case "d":
                                newDate.setDate(newDate.getDate() + parseInt(nextNum));
                                break; //newDay += parseInt(nextNum); break;
                            case "h":
                                newDate.setHours(newDate.getHours() + parseInt(nextNum));
                                break; //newHr += parseInt(nextNum); break;
                            case "m":
                                newDate.setMinutes(newDate.getMinutes() + parseInt(nextNum));
                                break; //newMin += parseInt(nextNum); break;
                        }
                        durationString = durationString.replace(durationString.substring(0, (nextNum + nextLetter).length), "");
                    }
                    if ((ruleHr == hr && ruleMin == min) || (ruleHr == hr && ruleMin < min) || (ruleHr < hr)) { newDate.setDate(newDate.getDate() + 1); }
                } else {
                    var dayOfWeek = durationString;
                    var dOWNum;
                    var dOWNDate = d;
                    if ((ruleHr == hr && ruleMin == min) || (ruleHr == hr && ruleMin < min) || (ruleHr < hr)) { dOWNDate.setDate(dOWNDate.getDate() + 1); }
                    switch (dayOfWeek) {
                        case "Sun":
                            dOWNum = 0;
                            break;
                        case "Mon":
                            dOWNum = 1;
                            break;
                        case "Tue":
                            dOWNum = 2;
                            break;
                        case "Wed":
                            dOWNum = 3;
                            break;
                        case "Thu":
                            dOWNum = 4;
                            break;
                        case "Fri":
                            dOWNum = 5;
                            break;
                        case "Sat":
                            dOWNum = 6;
                            break;
                    }
                    newDate.setDate(dOWNDate.getDate() + (dOWNum + 7 - dOWNDate.getDay()) % 7);
                    //alert(newDate);
                }
                //alert(ruleHr + ruleMin);
                return [assembleYear([newDate.getFullYear(), newDate.getMonth() + 1, newDate.getDate()]), assembleTime([ruleHr, ruleMin])];
            } else if (count == 2) {
                timeString = rule.substring(3, 8);
                var dayOfWeek = rule.substring(10, 13);
                var durationString = rule.substring(rule.lastIndexOf(" ") + 1).trim();
                var newDate = d;
                var dOWNum;
                //alert(timeString + dayOfWeek + durationString);
                if ((ruleHr == hr && ruleMin == min) || (ruleHr == hr && ruleMin < min) || (ruleHr < hr)) { newDate.setDate(newDate.getDate() + 1); }
                switch (dayOfWeek) {
                    case "Sun":
                        dOWNum = 0;
                        break;
                    case "Mon":
                        dOWNum = 1;
                        break;
                    case "Tue":
                        dOWNum = 2;
                        break;
                    case "Wed":
                        dOWNum = 3;
                        break;
                    case "Thu":
                        dOWNum = 4;
                        break;
                    case "Fri":
                        dOWNum = 5;
                        break;
                    case "Sat":
                        dOWNum = 6;
                        break;
                }
                newDate.setDate(newDate.getDate() + (dOWNum + 7 - newDate.getDay()) % 7);
                var loopLength = durationString.match(/[a-z]/g).length;
                //alert(newDate);
                //alert(loopLength);
                for (var i = 0; i < loopLength; i++) {
                    var nextNum = durationString.match(/[^(y|o|d|h|m)]*/)[0];
                    var nextLetter = durationString.match(/[a-z]/g)[0];
                    switch (nextLetter) {
                        case "y":
                            newDate.setFullYear(newDate.getFullYear() + parseInt(nextNum));
                            break; //newYr += parseInt(nextNum); break;
                        case "o":
                            newDate.setMonth(newDate.getMonth() + parseInt(nextNum));
                            break; //newMon += parseInt(nextNum); break;
                        case "d":
                            newDate.setDate(newDate.getDate() + parseInt(nextNum));
                            break; //newDay += parseInt(nextNum); break;
                        case "h":
                            newDate.setHours(newDate.getHours() + parseInt(nextNum));
                            break; //newHr += parseInt(nextNum); break;
                        case "m":
                            newDate.setMinutes(newDate.getMinutes() + parseInt(nextNum));
                            break; //newMin += parseInt(nextNum); break;
                    }
                    durationString = durationString.replace(durationString.substring(0, (nextNum + nextLetter).length), "");
                }
                //alert(newDate);
                return [assembleYear([newDate.getFullYear(), newDate.getMonth() + 1, newDate.getDate()]), assembleTime([ruleHr, ruleMin])];
            }
        } else if (rule.substring(0, 1) == "D") {
            // Date closure (ex. "D: 2020-03-14 03:14")
            var date = rule.substring(3, 13);
            var time = rule.substring(14, 19);
            return [assembleYear([date.substring(0, 4), date.substring(5, 7), date.substring(8, 10)]), assembleTime([time.substring(0, 2), time.substring(3, 5)])];
        } else {
            var newDate = new Date();
            var loopLength = rule.match(/[a-z]/g).length;
            for (var i = 0; i < loopLength; i++) {
                var nextNum = rule.match(/[^(y|o|d|h|m)]*/)[0];
                var nextLetter = rule.match(/[a-z]/g)[0];
                switch (nextLetter) {
                    case "y":
                        newDate.setFullYear(newDate.getFullYear() + parseInt(nextNum));
                        break; //newYr += parseInt(nextNum); break;
                    case "o":
                        newDate.setMonth(newDate.getMonth() + parseInt(nextNum));
                        break; //newMon += parseInt(nextNum); break;
                    case "d":
                        newDate.setDate(newDate.getDate() + parseInt(nextNum));
                        break; //newDay += parseInt(nextNum); break;
                    case "h":
                        newDate.setHours(newDate.getHours() + parseInt(nextNum));
                        break; //newHr += parseInt(nextNum); break;
                    case "m":
                        newDate.setMinutes(newDate.getMinutes() + parseInt(nextNum));
                        break; //newMin += parseInt(nextNum); break;
                }
                rule = rule.replace(rule.substring(0, (nextNum + nextLetter).length), "");
            }
            return [assembleYear([newDate.getFullYear(), newDate.getMonth() + 1, newDate.getDate()]), assembleTime([newDate.getHours(), newDate.getMinutes()])];
        }
    }

    function assembleYear(parts) {
        // parts[0] is yr, parts[1] is mon, parts[2] is day
        //        Lang = I18n.currentLocale()
        if (DateFormat == "mmddyyyy"){
            return addZero(parts[1]) + dateSeparator + addZero(parts[2]) + dateSeparator + parts[0];
        }else{
            if (DateFormat == "yyyymmdd"){
                return parts[0] + dateSeparator + addZero(parts[1]) + dateSeparator + addZero(parts[2]);
            }else{
                if (DateFormat == "ddmmyyyy"){
                    return addZero(parts[2]) + dateSeparator + addZero(parts[1]) + dateSeparator + parts[0];
                }}}}

    function assembleTime(parts) {
        // parts[0] is hr, parts[1] is min
        return addZero(parts[0]) + ":" + addZero(parts[1]);
    }

    function getDate(prop) {
        var d = new Date();
        switch (prop) {
            case "yr":
                return d.getFullYear();
            case "mm":
                return addZero(d.getMonth());
            case "dd":
                return addZero(d.getDate());
            case "hr":
                return addZero(d.getHours());
            case "min":
                return addZero(d.getMinutes());
            case "sec":
                return d.getSeconds();
        }
    }

    function addZero(string, length = 2) {
        return ("0" + string).substr(-length);
    }

    function closureDateToString() {
        return getDate("yr") + "/" + getDate("mm") + "/" + getDate("dd") + " " + getDate("hr") + ":" + getDate("min");
    }

    function log(message) {
        console.log("WMECH: " + message);
    }

    function error(message) {
        console.log("WMECH ERROR: " + message);
    }

    bootstrap();
})();