// Load spots and populate the table.
function loadSpots() {
    $.getJSON('/api/v1/spots' + buildQueryString(), function(jsonData) {
        // Store last updated time
        lastUpdateTime = moment.utc();
        updateRefreshDisplay();
        // Store data
        spots = jsonData;
        // Update table
        updateTable();
    });
}

// Build a query string for the API, based on the filters that the user has selected.
function buildQueryString() {
    var str =  "?";
    ["dx_continent", "de_continent", "mode_type", "source", "band"].forEach(fn => {
        if (!allFilterOptionsSelected(fn)) {
            str = str + getQueryStringFor(fn) + "&";
        }
    });
    str = str + "limit=" + $("#spots-to-fetch option:selected").val();
    if ($("#filter-dx-call").val() != "") {
        str = str + "&dx_call_includes=" + encodeURIComponent($("#filter-dx-call").val());
    }
    return str;
}

// Update the spots table
function updateTable() {
    // Use local time instead of UTC?
    var useLocalTime = $("#timeZone")[0].value == "local";

    // Get user grid if valid, this will be null if it's not.
    var userPos = latLonForGridCentre($("#userGrid").val());

    // Table data toggles
    var showTime = $("#tableShowTime")[0].checked;
    var showDX = $("#tableShowDX")[0].checked;
    var showFreq = $("#tableShowFreq")[0].checked;
    var showMode = $("#tableShowMode")[0].checked;
    var showComment = $("#tableShowComment")[0].checked;
    var showBearing = $("#tableShowBearing")[0].checked && userPos != null;
    var showType = $("#tableShowType")[0].checked;
    var showRef = $("#tableShowRef")[0].checked;
    var showDE = $("#tableShowDE")[0].checked;

    // Populate table with headers
    let table = $('<table class="table table-hover">').append('<thead><tr class="table-primary"></tr></thead><tbody></tbody>');
    if (showTime) {
        table.find('thead tr').append(`<th>${useLocalTime ? "Local" : "UTC"}</th>`);
    }
    if (showDX) {
        table.find('thead tr').append(`<th>DX</th>`);
    }
    if (showFreq) {
        table.find('thead tr').append(`<th>Freq<span class='hideonmobile'>uency</span></th>`);
    }
    if (showMode) {
        table.find('thead tr').append(`<th>Mode</th>`);
    }
    if (showComment) {
        table.find('thead tr').append(`<th class='hideonmobile'>Comment</th>`);
    }
    if (showBearing) {
        table.find('thead tr').append(`<th class='hideonmobile'>Bearing</th>`);
    }
    if (showType) {
        table.find('thead tr').append(`<th class='hideonmobile'>Type</th>`);
    }
    if (showRef) {
        table.find('thead tr').append(`<th class='hideonmobile'>Ref.</th>`);
    }
    if (showDE) {
        table.find('thead tr').append(`<th class='hideonmobile'>DE</th>`);
    }

    if (spots.length == 0) {
        table.find('tbody').append('<tr class="table-danger"><td colspan="100" style="text-align:center;">No spots match your filters.</td></tr>');
    }

    var count = 0;
    spots.forEach(s => {
        // Create row
        let $tr = $('<tr>');

        // Apply striping to the table. We can't just use Bootstrap's table-striped class because we have all sorts of
        // extra faff to deal with, like the mobile view having extra rows, and the On Now / Next 24h / Later banners
        // which cause the table-striped colouring to go awry.
        if (count % 2 == 1) {
            $tr.addClass("table-active");
        }

        // Show faded out if QRT
        if (s["qrt"] == true) {
          $tr.addClass("table-faded");
        }

        // Format a UTC or local time for display
        var time = moment.unix(s["time"]).utc();
        if (useLocalTime) {
            time.local();
        }
        var time_formatted = time.format("HH:mm");

        // Format DX call
        var dx_call = s["dx_call"];
        if (dx_call == null) {
            dx_call = "";
            dx_flag = "";
        }
        if (s["dx_ssid"] != null) {
            dx_call = dx_call + "-" + s["dx_ssid"];
        }

        // Format DX flag
        var dx_flag = "<i class='fa-solid fa-globe-africa'></i>";
        if (s["dx_flag"] && s["dx_flag"] != null && s["dx_flag"] != "") {
            dx_flag = s["dx_flag"];
        }

        // Format dx country
        var dx_country = s["dx_country"];
        if (dx_country == null) {
            dx_country = "Unknown or not a country";
        }

        // Format the frequency
        var freq_string = "Unknown"
        if (s["freq"] != null) {
            var mhz = Math.floor(s["freq"] / 1000000.0);
            var khz = Math.floor((s["freq"] - (mhz * 1000000.0)) / 1000.0);
            var hz = Math.floor(s["freq"] - (mhz * 1000000.0) - (khz * 1000.0));
            var hz_string = (hz > 0) ? hz.toFixed(0)[0] : "";
            freq_string = `<span class='freq-mhz freq-mhz-pad'>${mhz.toFixed(0)}</span><span class='freq-khz'>${khz.toFixed(0).padStart(3, '0')}</span><span class='freq-hz hideonmobile'>${hz_string}</span>`
        }

        // Format the mode
        mode_string = s["mode"];
        if (s["mode"] == null) {
            mode_string = "???";
        }
        if (s["mode_source"] == "BANDPLAN") {
            mode_string = mode_string + "<span class='mode-q hideonmobile'><i class='fa-solid fa-circle-question' title='The mode was not reported via the spotting service. This is a guess based on the frequency.'></i></span>";
        }

        // Format comment
        var commentText = "";
        if (s["comment"] != null) {
            commentText = escapeHtml(s["comment"]);
        }

        // Format bearing text
        var bearingText = "---<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service, and we could not determine one. A bearing to this DX is not available.'></i></span>";
        if (userPos != null && s["dx_latitude"] != null && s["dx_longitude"] != null) {
            var bearing = calcBearing(userPos[0], userPos[1], s["dx_latitude"], s["dx_longitude"]);
            bearingText = bearing.toFixed(0).padStart(3, '0') + "°";
            if (s["dx_location_good"] == null || s["dx_location_good"] == false) {
                if (s["dx_location_source"] == "HOME QTH") {
                    bearingText = bearingText + "<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service. We had to fall back to a QRZ \"home\" location for a portable/mobile/alternative spot, so this bearing may not be accurate if the DX is close to you..'></i></span>";
                } else {
                    bearingText = bearingText + "<span class='bearing-q hideonmobile'><i class='fa-solid fa-circle-question' title='The position was not reported via the spotting service. We had to fall back to just using the centre of a DXCC entity, so this bearing may not be accurate if the DX is close to you.'></i></span>";
                }
            }
        }

        // Format "type" (Sig or fallback to source)
        var typeText = s["source"];
        if (s["sig"]) {
            typeText = s["sig"];
        }

        // Format sig_refs
        var sig_refs = "";
        if (s["sig_refs"] != null) {
            var items = []
            for (var i = 0; i < s["sig_refs"].length; i++) {
                if (s["sig_refs"][i]["url"] != null) {
                    items[i] = `<a href='${s["sig_refs"][i]["url"]}' title='${s["sig_refs"][i]["name"]}' target='_new' class='sig-ref-link'>${s["sig_refs"][i]["id"]}</a>`
                } else {
                    items[i] = `${s["sig_refs"][i]["id"]}`
                }
            }
            sig_refs = items.join(", ");
        }

        // Format DE flag
        var de_flag = "<i class='fa-solid fa-circle-question'></i>";
        if (s["de_flag"] && s["de_flag"] != null && s["de_flag"] != "") {
            de_flag = s["de_flag"];
        }

        // Format de country
        var de_country = s["de_country"];
        if (de_country == null) {
            de_country = "Unknown or not a country";
        }

        // Format de call
        var de_call = s["de_call"];
        if (de_call == null) {
            de_call = "";
            de_flag = "";
        }
        if (s["de_ssid"] != null) {
            de_call = de_call + "-" + s["de_ssid"];
        }

        // Format band name
        var bandFullName = s['band'] ? s['band'] + " band": "Unknown band";

        // Populate the row
        if (showTime) {
            $tr.append(`<td class='nowrap'>${time_formatted}</td>`);
        }
        if (showDX) {
            $tr.append(`<td class='nowrap'><span class='flag-wrapper hideonmobile' title='${dx_country}'>${dx_flag}</span><a class='dx-link' href='https://qrz.com/db/${s["dx_call"]}' target='_new' title='${s["dx_name"] != null ? s["dx_name"] : ""}'>${dx_call}</a></td>`);
        }
        if (showFreq) {
            $tr.append(`<td class='nowrap'><span class='band-bullet' title='${bandFullName}' style='${(s["freq"] != null) ? "color: " + s["band_color"] : "display: none;"}'>&#9632;</span>${freq_string}</td>`);
        }
        if (showMode) {
            $tr.append(`<td class='nowrap'>${mode_string}</td>`);
        }
        if (showComment) {
            $tr.append(`<td class='hideonmobile'>${commentText}</td>`);
        }
        if (showBearing) {
            $tr.append(`<td class='nowrap hideonmobile'>${bearingText}</td>`);
        }
        if (showType) {
            $tr.append(`<td class='nowrap hideonmobile'><span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${typeText}</td>`);
        }
        if (showRef) {
            $tr.append(`<td class='hideonmobile'>${sig_refs}</td>`);
        }
        if (showDE) {
            $tr.append(`<td class='nowrap hideonmobile'><span class='flag-wrapper' title='${de_country}'>${de_flag}</span>${de_call}</td>`);
        }
        table.find('tbody').append($tr);

        // Second row for mobile view only, containing type, ref & comment
        $tr2 = $("<tr class='hidenotonmobile'>");
        if (count % 2 == 1) {
            $tr2.addClass("table-active");
        }
        if (s["qrt"] == true) {
          $tr2.addClass("table-faded");
        }
        $td2 = $("<td colspan='100'>");
        if (showType) {
            $td2.append(`<span class='icon-wrapper'><i class='fa-solid fa-${s["icon"]}'></i></span> ${typeText} `);
        }
        if (showRef) {
            $td2.append(`${sig_refs} `);
        }
        if (showBearing) {
            $td2.append(` &nbsp; Bearing: ${bearingText} `);
        }
        if (showComment) {
            $td2.append(`<br/>${commentText}`);
        }
        $tr2.append($td2);
        table.find('tbody').append($tr2);

        count++;
    });

    // Update DOM
    $('#table-container').html(table);
}

// Load server options. Once a successful callback is made from this, we then query spots and set up the timer to query
// spots repeatedly.
function loadOptions() {
    $.getJSON('/api/v1/options', function(jsonData) {
        // Store options
        options = jsonData;

        // Add CSS for band toggle buttons
        addBandToggleColourCSS(options["bands"]);

        // Populate the filters panel
        generateBandsMultiToggleFilterCard(options["bands"]);
        generateMultiToggleFilterCard("#dx-continent-options", "dx_continent", options["continents"]);
        generateMultiToggleFilterCard("#de-continent-options", "de_continent", options["continents"]);
        generateMultiToggleFilterCard("#mode-options", "mode_type", options["mode_types"]);
        generateMultiToggleFilterCard("#source-options", "source", options["spot_sources"]);

        // Load settings from settings storage now all the controls are available
        loadSettings();

        // Extra setting - the toggle for the "bearing" column is disabled if the user has not entered a valid grid, and
        // normally this logic is handled on user input to the grid field, but we might have just loaded a value direct
        // into the field, so apply the same logic here.
        $("#tableShowBearing").prop('disabled', !isUserGridValid());
        if (!isUserGridValid()) {
            $("#tableShowBearing").prop('checked', false);
        }

        // Show the Add Spot button if spotting is allowed
        if (options["spot_allowed"]) {
            $("#add-spot-button").show();
        }

        // Load spots and set up the timer
        loadSpots();
        setInterval(loadSpots, REFRESH_INTERVAL_SEC * 1000);
    });
}

// Work out if the user's entered grid is a valid Maidenhead grid
function isUserGridValid() {
    userGrid = $("#userGrid").val().toUpperCase();
    return latLonForGridCentre(userGrid) != null;
}

// Method called when the user's grid input is changed.
function userGridUpdated() {
    var userGridValid = isUserGridValid();
    if (userGridValid) {
        updateTable();
    }
    // Enable/disable bearing column depending on grid validity
    $("#tableShowBearing").prop('disabled', !userGridValid);
    if (!userGridValid) {
        $("#tableShowBearing").prop('checked', false);
    }
    // Save settings even if not a valid grid, this allows the user to clear their grid and have it save.
    saveSettings();
}

// React to toggling/closing panels
function toggleFiltersPanel() {
    // If we are going to show the filters panel, hide the display and add spot panels
    if (!$("#filters-area").is(":visible") && $("#display-area").is(":visible")) {
        $("#display-area").hide();
        $("#display-button").button("toggle");
    }
    $("#filters-area").toggle();
}
function closeFiltersPanel() {
    $("#filters-button").button("toggle");
    $("#filters-area").hide();
}

function toggleDisplayPanel() {
    // If we are going to show the display panel, hide the filters and add spot panels
    if (!$("#display-area").is(":visible") && $("#filters-area").is(":visible")) {
        $("#filters-area").hide();
        $("#filters-button").button("toggle");
    }
    $("#display-area").toggle();
}
function closeDisplayPanel() {
    $("#display-button").button("toggle");
    $("#display-area").hide();
}

// Display the intro box, unless the user has already dismissed it once.
function displayIntroBox() {
    if (localStorage.getItem("intro-box-dismissed") == null) {
        $("#intro-box").show();
    }
    $("#intro-box-dismiss").click(function() {
        localStorage.setItem("intro-box-dismissed", true);
    });
}

// Startup
$(document).ready(function() {
    // Call loadOptions(), this will then trigger loading spots and setting up timers.
    loadOptions();
    // Update the refresh timing display every second
    setInterval(updateRefreshDisplay, 1000);
    // Display intro box
    displayIntroBox();
});