/*
 * Copyright 2022-2024, Digi International Inc.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// Constants.
const ID_AREA = "area";
const ID_AREA_HEIGHT = "area-height";
const ID_AREA_LEFT_MARGIN = "area-left-margin";
const ID_AREA_TOP_MARGIN = "area-top-margin";
const ID_AREA_WIDTH = "area-width";
const ID_ARROW = "arrow";
const ID_ARROW_MARGIN = "arrow-margin";
const ID_AUDIO = "audio";
const ID_BLUETOOTH_MAC = "bluetooth_mac";
const ID_BOARD_ID = "board_id";
const ID_BOARD_VARIANT = "board_variant";
const ID_BT_READ_DATA = "bluetooth_received_data";
const ID_BT_SENT_DATA = "bluetooth_sent_data";
const ID_BT_STATE = "bluetooth_state";
const ID_COLOR_BLUE = "blue";
const ID_COLOR_GREEN = "green";
const ID_CONSOLE = "console";
const ID_CONFIRM_DIALOG = "confirm_dialog";
const ID_CONFIRM_DIALOG_MESSAGE = "confirm_dialog_message";
const ID_CONFIRM_DIALOG_NO_BUTTON = "confirm_dialog_no_button";
const ID_CONFIRM_DIALOG_TITLE = "confirm_dialog_title";
const ID_CONFIRM_DIALOG_YES_BUTTON = "confirm_dialog_yes_button";
const ID_CPU = "cpu";
const ID_CPU_FREQUENCY = "cpu_frequency";
const ID_CPU_LOAD = "cpu_load";
const ID_CPU_TEMPERATURE = "cpu_temperature";
const ID_CPU_UPTIME = "cpu_uptime";
const ID_CURRENT_DIR = "current_dir";
const ID_DATA = "data";
const ID_DESC = "desc";
const ID_DEVICE_NAME = "device-name";
const ID_DEVICE_TYPE = "device_type";
const ID_DEVICES = "devices";
const ID_DEY_VERSION = "dey_version";
const ID_ERROR = "error";
const ID_ERROR_GUIDE = "error_guide";
const ID_ERROR_MESSAGE = "error_msg";
const ID_ERROR_TITLE = "error_title";
const ID_ETHERNET0 = "ethernet0";
const ID_ETHERNET1 = "ethernet1";
const ID_ETHERNET2 = "ethernet2";
const ID_ETHERNET0_IP = "ethernet0_ip";
const ID_ETHERNET1_IP = "ethernet1_ip";
const ID_ETHERNET2_IP = "ethernet2_ip";
const ID_ETHERNET0_MAC = "ethernet0_mac";
const ID_ETHERNET1_MAC = "ethernet1_mac";
const ID_ETHERNET2_MAC = "ethernet2_mac";
const ID_ETHERNET0_READ_DATA = "ethernet0_received_data";
const ID_ETHERNET1_READ_DATA = "ethernet1_received_data";
const ID_ETHERNET2_READ_DATA = "ethernet2_received_data";
const ID_ETHERNET0_SENT_DATA = "ethernet0_sent_data";
const ID_ETHERNET1_SENT_DATA = "ethernet1_sent_data";
const ID_ETHERNET2_SENT_DATA = "ethernet2_sent_data";
const ID_ETHERNET0_STATE = "ethernet0_state";
const ID_ETHERNET1_STATE = "ethernet1_state";
const ID_ETHERNET2_STATE = "ethernet2_state";
const ID_FILES = "files";
const ID_FLASH_MEMORY = "flash_memory";
const ID_FLASH_SIZE = "flash_size";
const ID_FW_STORE_PATH = "fw_store_path";
const ID_HAS_ARROW = "has-arrow";
const ID_HAS_NPU_DEMOS = "has-npu-demos";
const ID_HAS_PANEL = "has-panel";
const ID_ICON = "icon";
const ID_ID = "id";
const ID_INFO_POPUP = "info_popup";
const ID_INFO_POPUP_MESSAGE = "info_popup_message";
const ID_INFO_POPUP_TITLE = "info_popup_title";
const ID_IS_RUNNING = "is_running";
const ID_KERNEL_VERSION = "kernel_version";
const ID_LAST_MODIFIED = "last_modified";
const ID_LED = "led";
const ID_LOADING_POPUP = "loading_popup";
const ID_LOADING_POPUP_MESSAGE = "loading_popup_message";
const ID_LOADING_WRAPPER = "loading_wrapper";
const ID_MCA_FW_VERSION = "mca_fw_version";
const ID_MCA_HW_VERSION = "mca_hw_version";
const ID_MEMORY = "memory";
const ID_MEMORY_FREE = "memory_free";
const ID_MEMORY_TOTAL = "memory_total";
const ID_MEMORY_USED = "memory_used";
const ID_MESSAGE = "message";
const ID_MODULE_VARIANT = "module_variant";
const ID_NAME = "name";
const ID_NUM_SAMPLES_UPLOAD = "num_samples_upload";
const ID_ONLINE = "online";
const ID_PANEL = "panel";
const ID_PANEL_ALWAYS_VISIBLE = "panel-always-visible";
const ID_PANEL_HORIZONTAL_MARGIN = "panel-horizontal-margin";
const ID_PANEL_ORIENTATION = "panel-orientation";
const ID_PANEL_VERTICAL_MARGIN = "panel-vertical-margin";
const ID_PATH = "path";
const ID_PROGRESS = "progress";
const ID_SAMPLE_RATE = "sample_rate";
const ID_SECTION_DASHBOARD = "section_dashboard";
const ID_SECTION_MANAGEMENT = "section_management";
const ID_SECTION_MULTIMEDIA = "section_multimedia";
const ID_SECTION_NPU = "section_npu";
const ID_SERIAL_NUMBER = "serial_number";
const ID_SESSION_ID = "session_id";
const ID_SIZE = "size";
const ID_STATUS = "status";
const ID_STREAM = "stream";
const ID_TIMESTAMP = "timestamp";
const ID_TYPE = "type";
const ID_UBOOT_VERSION = "uboot_version";
const ID_VALUE = "value";
const ID_VIDEO = "video";
const ID_VIDEO_RESOLUTION = "video_resolution";
const ID_VISIBLE = "visible";
const ID_WIFI_BT = "wifi-bt";
const ID_WIFI_IP = "wifi_ip";
const ID_WIFI_MAC = "wifi_mac";
const ID_WIFI_READ_DATA = "wifi_received_data";
const ID_WIFI_SENT_DATA = "wifi_sent_data";
const ID_WIFI_STATE = "wifi_state";

const VALUE_ACTIVE = "active";
const VALUE_ABORT = "abort";
const VALUE_BOTTOM = "bottom";
const VALUE_CANCELED = "canceled";
const VALUE_FAILED = "failed";
const VALUE_LEFT = "left";
const VALUE_OFF = "Off";
const VALUE_ON = "On";
const VALUE_RIGHT = "right";
const VALUE_SUCCESSFUL = "successful";
const VALUE_TOP = "top";
const VALUE_UNKNOWN = "unknown";

const CLASS_ARROW_DOWN = "fa-caret-down";
const CLASS_ARROW_UP = "fa-caret-up";
const CLASS_D_NONE = "d-none";
const CLASS_DISABLED_DIV = "disabled-div";
const CLASS_ELEMENT_GRAYED = "element-grayed";
const CLASS_INPUT_DISABLED = "input-control-disabled";
const CLASS_INPUT_ERROR = "input-control-error";
const CLASS_CONFIG_BUTTON_DISABLED = "config-button-disabled";
const CLASS_SELECTED = "selected";
const CLASS_VALUE_ANIMATION = "value-animation";
const CLASS_VALUE_UPDATED = "value-updated";

const ERROR_ABORTED = "Operation aborted";
const ERROR_BAD_REQUEST = "Bad request";
const ERROR_FIELD_EMPTY = "Field cannot be empty";
const ERROR_FORBIDDEN = "Could not perform the selected action. Make sure you have the correct access rights.";
const ERROR_URL_NOT_FOUND = "Requested URL not found";
const ERROR_SERVER_ERROR = "Internal server error";
const ERROR_TITLE = "Error";
const ERROR_UNKNOWN_ERROR = "Unknown error. Make sure that the server is running.";

const IFACE_BT = "hci0";
const IFACE_ETH0 = "eth0";
const IFACE_ETH1 = "eth1";
const IFACE_ETH2 = "eth2";
const IFACE_WIFI = "wlan0";

const PREFIX_STREAM = "system_monitor/";
const STREAM_CPU_LOAD = PREFIX_STREAM + "cpu_load";
const STREAM_MEMORY_FREE = PREFIX_STREAM + "free_memory";

const PATH_IMAGES = "../static/images/";

const PORT = "9090";

String.prototype.format = function() {
    var formatted = this;
    for (var arg in arguments)
        formatted = formatted.replace("{" + arg + "}", arguments[arg]);
    return formatted;
};

// Updates the value of the given element ID.
function updateValueWithEffect(elementID, value) {
    // Initialize variables.
    var htmlElement = document.getElementById(elementID);
    // Sanity checks.
    if (htmlElement == null || htmlElement == "undefined" || value == null)
        return;
    // Apply value and classes.
    htmlElement.classList.add(CLASS_VALUE_UPDATED);
    htmlElement.innerText = value;
    htmlElement.classList.add(CLASS_VALUE_ANIMATION);
    setTimeout(function() {
        htmlElement.classList.remove(CLASS_VALUE_UPDATED);
        setTimeout(function() {
            htmlElement.classList.remove(CLASS_VALUE_ANIMATION);
        }, 3000);
    }, 2000);
}

// Returns the server address.
function getServerAddress() {
    var host = window.location.hostname;
    if (!Boolean(host))
        host = "127.0.0.1";

    return host + ":" + PORT;
}

function is_local_access() {
    var host = window.location.hostname;
    return !Boolean(host) || host == "127.0.0.1" || host == "localhost";
}

// Shows/hides a front popup over the given background element.
function showPopup(backElementID, frontElementID, visible) {
    // Initialize variables.
    var backElement = document.getElementById(backElementID);
    var frontElement = document.getElementById(frontElementID);
    // Sanity checks.
    if (backElement == null || frontElement == null)
        return;
    // Show/Hide the popup.
    if (visible) {
        if (!backElement.classList.contains(CLASS_ELEMENT_GRAYED))
            backElement.classList.add(CLASS_ELEMENT_GRAYED);
        if (frontElement.classList.contains(CLASS_D_NONE))
            frontElement.classList.remove(CLASS_D_NONE);
    } else {
        if (backElement.classList.contains(CLASS_ELEMENT_GRAYED))
            backElement.classList.remove(CLASS_ELEMENT_GRAYED);
        if (!frontElement.classList.contains(CLASS_D_NONE))
            frontElement.classList.add(CLASS_D_NONE);
    }
}

// Redirects to the login page.
function redirectToLogin() {
    var url = "/access/login?dest=" + window.location.pathname.replaceAll("/", "");
    var params = new URLSearchParams(window.location.search);
    for (let param of params)
        url += "&" + param[0] + "=" + param[1];
    window.location.href = url;
}

// Processes the error response of the AJAX request.
function processAjaxErrorResponse(response) {
    // Check common error codes.
    if (response.status == 401)
        redirectToLogin();
    var errorMessage = "";
    if (response.statusText == VALUE_ABORT)
        errorMessage = ERROR_ABORTED;
    else if (response.status == 400) {
        errorMessage = response.responseJSON[ID_ERROR];
        // Show the error message (if any).
        if (errorMessage == null)
            errorMessage = ERROR_BAD_REQUEST;
    } else if (response.status == 403)
        errorMessage = ERROR_FORBIDDEN;
    else if (response.status == 404)
        errorMessage = ERROR_URL_NOT_FOUND;
    else if (response.status == 500)
        errorMessage = ERROR_SERVER_ERROR;
    else
        errorMessage = ERROR_UNKNOWN_ERROR;
    // Show toast with the error message.
    toastr.error(errorMessage);
    // Return the error message.
    return errorMessage;
}

// Check if there is any error in the response.
function checkErrorResponse(response, showErrorDialog) {
    if (response[ID_ERROR_MESSAGE] != null || response[ID_ERROR] != null) {
        // Process error.
        var errorTitle = ERROR_TITLE;
        var errorMessage = getErrorFromResponse(response);
        if (response[ID_ERROR_TITLE] != null)
            errorTitle = response[ID_ERROR_TITLE];
        // Show toast error.
        if (errorMessage != null && errorMessage != "")
            toastr.error(errorMessage);
        // Hide the loading panel.
        showLoadingPopup(false);
        // Show error dialog.
        if (showErrorDialog)
            showInfoPopup(true, errorTitle, errorMessage);
        // There was an error, return true.
        return true;
    }
    // No error found.
    return false;
}

// Returns the error message from the response.
function getErrorFromResponse(response) {
    var error = "";
    if (response[ID_ERROR_MESSAGE] != null || response[ID_ERROR] != null) {
        if (response[ID_ERROR_MESSAGE] != null) {
            error = response[ID_ERROR_MESSAGE];
            if (response[ID_ERROR_GUIDE] != null)
                error += response[ID_ERROR_GUIDE];
        } else
            error = response[ID_ERROR];
    }
    return error;
}

// Shows/hides the loading popup panel.
function showLoadingPopup(visible, message=null) {
    // Set loading message only if it is not null and the popup will be visible.
    if (visible && message != null)
        document.getElementById(ID_LOADING_POPUP_MESSAGE).innerHTML = message;
    // Show/Hide the popup.
    showPopup(ID_LOADING_WRAPPER, ID_LOADING_POPUP, visible);
}

// Shows/hides the info popup panel.
function showInfoPopup(visible, tittle=null, message=null) {
    // Set title and message.
    if (visible && tittle != null && message != null) {
        // Initialize vars.
        var infoPopupTittleElement = document.getElementById(ID_INFO_POPUP_TITLE);
        var infoPopupMessageElement = document.getElementById(ID_INFO_POPUP_MESSAGE);
        // Set the info title.
        if (infoPopupTittleElement != null)
            infoPopupTittleElement.innerHTML = tittle;
        // Set the info message.
        if (infoPopupMessageElement != null)
            infoPopupMessageElement.innerHTML = message;
    }
    // Show/Hide the popup.
    showPopup(ID_LOADING_WRAPPER, ID_INFO_POPUP, visible);
}

// Shows the confirm dialog.
function showConfirmDialog(title, message, yesCallback, noCallback) {
    // Initialize variables.
    var confirmDialogTitleElement = document.getElementById(ID_CONFIRM_DIALOG_TITLE);
    var confirmDialogMessageElement = document.getElementById(ID_CONFIRM_DIALOG_MESSAGE);
    var confirmDialogYesButtonElement = document.getElementById(ID_CONFIRM_DIALOG_YES_BUTTON);
    var confirmDialogNoButtonElement = document.getElementById(ID_CONFIRM_DIALOG_NO_BUTTON);
    // Check if the elements are valid.
    if (confirmDialogTitleElement != null && confirmDialogMessageElement != null &&
        confirmDialogYesButtonElement != null && confirmDialogNoButtonElement != null) {
        // Set the dialog title.
        confirmDialogTitleElement.innerHTML = title;
        // Set the dialog message.
        confirmDialogMessageElement.innerHTML = message;
        // Show the dialog.
        showPopup(ID_LOADING_WRAPPER, ID_CONFIRM_DIALOG, true);
        // Set the yes button callback.
        confirmDialogYesButtonElement.onclick = function() {
            // Hide the dialog.
            showPopup(ID_LOADING_WRAPPER, ID_CONFIRM_DIALOG, false);
            // Call the callback.
            if (yesCallback != null)
                yesCallback();
        };
        // Set the no button callback.
        confirmDialogNoButtonElement.onclick = function() {
            // Hide the dialog.
            showPopup(ID_LOADING_WRAPPER, ID_CONFIRM_DIALOG, false);
            // Call the callback.
            if (noCallback != null)
                noCallback();
        };
    }
}

// Transforms the given ISO8601 date to human readable date.
function transformDate(iso8601Date) {
    // Initialize variables.
    var date = new Date(iso8601Date);
    var humanReadableDate = "";
    // Check if the date is valid.
    if (date != null) {
        // Transform the date to human readable date.
        humanReadableDate = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " +
            date.getHours().toString().padStart(2, '0') + ":" + date.getMinutes().toString().padStart(2, '0');
    }
    // Return the human readable date.
    return humanReadableDate;
}

// Transforms the given kilo-bytes to mega-bytes
function kiloBytesToMegaBytes(kiloBytes) {
    return (kiloBytes / 1024).toFixed(2);
}

// Format bytes as human-readable text.
function sizeToHumanRead(bytes, si=true, dp=2) {
    // Initialize variables.
    var threshold = si ? 1000 : 1024;
    var units = si
        ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
        : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
    var unit = -1;
    // Check if minimum threshold is reached.
    if (Math.abs(bytes) < threshold)
        return bytes + ' Bytes';

    const r = 10**dp;
    do {
        bytes /= threshold;
        ++unit;
    } while (Math.round(Math.abs(bytes) * r) / r >= threshold && unit < units.length - 1);

    return bytes.toFixed(dp) + ' ' + units[unit];
}

// Rounds the given number to two decimals.
function roundToTwoDecimals(number) {
    return Math.round(number * 100) / 100;
}

// Returns human readable time from the given seconds.
function secondsToTime(numSeconds) {
    // Initialize variables.
    var days = 0;
    var hours = 0;
    var minutes = 0;
    var seconds = 0;
    // Calculate the time values.
    if (numSeconds < 60)
        seconds = numSeconds;
    else if (numSeconds < (60 * 60)) {
        minutes = Math.floor(numSeconds / 60);
        seconds = numSeconds % 60;
    } else if (numSeconds < (60 * 60 * 24)) {
        hours = Math.floor(numSeconds / (60 * 60));
        minutes = Math.floor((numSeconds % (60 * 60)) / 60);
        seconds = numSeconds % 60;
    } else {
        days = Math.floor(numSeconds / (60 * 60 * 24));
        hours = Math.floor((numSeconds % (60 * 60 * 24)) / (60 * 60));
        minutes = Math.floor((numSeconds % (60 * 60)) / 60);
        seconds = numSeconds % 60;
    }
    // Build time string.
    var time = "";
    var numFields = 0;
    if (days > 0) {
        time += days + " day";
        if (days > 1)
            time += "s";
        time += " ";
        numFields += 1;
    }
    if (hours > 0) {
        time += hours + " hour";
        if (hours > 1)
            time += "s";
        time += " ";
        numFields += 1;
    }
    if (minutes > 0 && numFields < 2) {
        time += minutes + " minute";
        if (minutes > 1)
            time += "s";
        time += " ";
        numFields += 1;
    }
    if ((seconds > 0 && numFields < 2) || time == "") {
        time += seconds + " second";
        if (seconds > 1)
            time += "s";
    }
    // Return the time string.
    return time;
}

// Returns on/off status from the given integer.
function onOffStatus(status) {
    return status == 1 ? VALUE_ON : VALUE_OFF;
}

// Updates the given field ID with the given value.
function updateFieldValue(fieldID, value) {
    var fieldElement = document.getElementById(fieldID);
    if (fieldElement != null && fieldElement != "undefined")
        fieldElement.innerText = value;
}

// Rounds the given amount of kilo-bytes to giga bytes.
function roundToGigaBytes(kiloBytes) {
    return (kiloBytes / (1024 * 1024)).toFixed(2);
}

// Returns whether the dashboard page is showing or not.
function isDashboardShowing() {
    if (window.location.pathname == "/")
        return true;
    return window.location.pathname.indexOf("index") > -1;
}

// Returns whether the network page is showing or not.
function isNetworkShowing() {
    return window.location.pathname.indexOf("network") > -1;
}

// Returns whether the management page is showing or not.
function isManagementShowing() {
    return window.location.pathname.indexOf("management") > -1;
}

// Returns whether the multimedia page is showing or not.
function isMultimediaShowing() {
    return window.location.pathname.indexOf("multimedia") > -1;
}

// Returns whether the NPU page is showing or not.
function isNPUShowing() {
    return window.location.pathname.indexOf("npu") > -1;
}

// Returns the device name.
function getDeviceName() {
    return new URLSearchParams(window.location.search).get(ID_DEVICE_NAME);
}

// Updates the available web sections.
function updateAvailableSections() {
    // Remove device specific sections when rendering the demo from a computer.
    if (!navigator.platform.includes("aarch") && !navigator.platform.includes("arm")) {
        removeSection(ID_SECTION_MULTIMEDIA);
        removeSection(ID_SECTION_NPU);
    }
    // Set visible sections based on device type.
    $.post(
        "http://" + getServerAddress() + "/ajax/get_device_type",
        function(data) {
            // Process answer.
            if (data[ID_DEVICE_TYPE] == null || data[ID_DEVICE_TYPE] == "undefined") {
                // Show error message.
                toastr.error("Could not get device type");
                return;
            }
            // Update multimedia section.
            switch (data[ID_DEVICE_TYPE]) {
                case CCIMX6ULSBC.DEVICE_TYPE:
                case CCMP157.DEVICE_TYPE:
                case CCMP133.DEVICE_TYPE:
                    removeSection(ID_SECTION_MULTIMEDIA);
                    break;
            }
        }
    ).fail(function(response) {
        // Process error.
        processAjaxErrorResponse(response);
    });
    // Set NPU section visibility.
    $.post(
        "http://" + getServerAddress() + "/ajax/get_npu_info",
        function(data) {
            // Process answer.
            if (data[ID_HAS_NPU_DEMOS] != "true") {
                // Remove NPU section.
                removeSection(ID_SECTION_NPU);
            }
        }
    ).fail(function(response) {
        // Process error.
        processAjaxErrorResponse(response);
    });
}

// Removes the given section.
function removeSection(sectionID) {
    var sectionElement = document.getElementById(sectionID);
    if (sectionElement != null && sectionElement != "undefined")
        sectionElement.parentNode.removeChild(sectionElement);
}
