Source: utils/template/functions/custom/staOverview.js

import Plotly from 'plotly.js-dist';

import { displayMessage } from '../../../utils.js';
import { Sidebar } from "../../../../ui/sidebar/Sidebar.js";
import { PlotlyVefPlot } from '../../../../plot/templates/PlotlyVefPlot.js'
import { Window } from '../../../../ui/Window.js';

import "./staOverview.css";
import { displayImage } from '../media.js';

let vefPlotly = null;
let plotbar = null;
let resizeObserver = null;
let infoWindow = null;

function filterDatastreams(datastreams, sensor, observedProperty) {
    const result = {
        sensors: {},
        observedProperties: {},
        datastreams: {},
        selectedSensor: sensor,
        selectedObservedProperty: observedProperty
    }

    datastreams.forEach(ds => {
        if ("Sensor" in ds) result.sensors[ds.Sensor["@iot.id"]] = ds.Sensor;

        if ((sensor.length == 0) && (observedProperty.length == 0)) {
            if ("ObservedProperty" in ds) result.observedProperties[ds.ObservedProperty["@iot.id"]] = ds.ObservedProperty;
            result.datastreams[ds["@iot.id"]] = ds;
        } else if ((sensor.length > 0) && (observedProperty.length == 0)) {
            if (ds.Sensor["@iot.id"] == sensor) {
                if ("ObservedProperty" in ds) result.observedProperties[ds.ObservedProperty["@iot.id"]] = ds.ObservedProperty;
                result.datastreams[ds["@iot.id"]] = ds;
            }
        } else if ((sensor.length > 0) && (observedProperty.length > 0)) {
            if (ds.Sensor["@iot.id"] == sensor) {
                result.observedProperties[ds.ObservedProperty["@iot.id"]] = ds.ObservedProperty;
                if (ds.ObservedProperty["@iot.id"] == observedProperty) result.datastreams[ds["@iot.id"]] = ds;
            }
        } else if ((sensor.length == 0) && (observedProperty.length > 0)) {
            result.observedProperties[ds.ObservedProperty["@iot.id"]] = ds.ObservedProperty;
            if (ds.ObservedProperty["@iot.id"] == observedProperty) result.datastreams[ds["@iot.id"]] = ds;
            if ("Sensor" in ds) result.selectedSensor = ds.Sensor["@iot.id"];
        }
    });

    if (Object.keys(result.observedProperties).length == 1) {
        result.selectedObservedProperty = result.observedProperties[Object.keys(result.observedProperties)[0]]["@iot.id"];
    }

    return result;
}

function showPlotbar(title) {
    if (!plotbar) {
        plotbar = new Sidebar(null, {
            position: "bottom",
            menu: false,
            closeable: true,
            responsive: true
        });

        plotbar.addGroup("plot-content");
        plotbar.on("close", () => {
            if (vefPlotly) Plotly.purge(vefPlotly.graphDiv);
        });

        resizeObserver = new ResizeObserver(() => {
            if (vefPlotly) Plotly.Plots.resize(vefPlotly.graphDiv);
        });
        resizeObserver.observe(plotbar.getGroupContainer("plot-content"));
    }

    const container = plotbar.getGroupContainer("plot-content");
    container.style.overflow = "hidden";
    container.innerHTML = `
            <div style="text-align: center; margin: 20px 0">
                <i style="color:var(--primary-color); font-size: 18px;" class="fas fa-spinner fa-spin fa-fw"></i>'
            </div>
        `;

    let target = document.querySelector(".vef-ui.viewer");
    if (!target) target = document.body;

    plotbar.appendTo(target);
    plotbar.setTitle("plot-content", title);
    plotbar.show("plot-content");
}

function displayDatastreamInfo(infoButton, datastream) {
    if (!infoWindow) {
        infoWindow = new Window(null, {
            width: "400px",
            height: "unset",
            open: false,
            mode: "slim",
            title: null
        });

        let windowElement = infoWindow.getElement();
        windowElement.classList.add("pointer-left");
        windowElement.style.pointerEvents = "none";
        windowElement.querySelector(".fas.fa-times").style.display = "none";
    }

    let rect = infoButton.getBoundingClientRect();
    infoWindow.setOptions({
        top: (rect.top - 3) + "px",
        left: (rect.left + rect.width + 15) + "px",
        open: true,
        content: `
            <div><b>The latest 1000 measurements are displayed</b></div><br>
            <div>${datastream?.description || ""}<div>
        `
    });
}

function displayPlot(observations, datastream) {
    const container = plotbar.getGroupContainer("plot-content");
    container.style.overflow = "hidden";

    if (observations.length == 0) {
        container.innerHTML = "<b style='font-size: 16px;'>This datastream does not have any data.</b>";
        return;
    } else {
        container.innerHTML = ``;
    }

    const data = { x: [], y: [] };

    observations.forEach(observation => {
        data.x.push(observation.resultTime || observation.phenomenonTime);
        data.y.push(observation.result);
    });

    const layout = {
        xaxis: { title: { text: "Time" } },
        yaxis: { title: { text: `${datastream?.unitOfMeasurement?.name} [${datastream?.unitOfMeasurement?.symbol}]` } },
        autosize: true,
        height: 400
    };

    if (vefPlotly) Plotly.purge(vefPlotly.graphDiv);
    vefPlotly = new PlotlyVefPlot(container, [data], layout);
    Plotly.newPlot(vefPlotly.graphDiv, vefPlotly.data, vefPlotly.layout, vefPlotly.config);
    vefPlotly.extensions.apply({ 'redirectNotification': { 'callback': displayMessage } });

    const infoButton = plotbar.getElement().querySelector(".sta-ds-info-button");
    infoButton.addEventListener("mouseover", () => displayDatastreamInfo(infoButton, datastream));
    infoButton.addEventListener("mouseleave", () => infoWindow.close());
}

/**
 * 
 * @param {object} datastreams
 * @param {Layer} layer
 * @returns {string} div placeholder
 */
export function staOverview(datastreams, layer) {
    if (!datastreams) return undefined;

    const div = document.createElement("div");
    div.classList.add("sta-overview");

    let filtered = filterDatastreams(datastreams, "", "");

    const create = () => {
        const sensors = filtered.sensors;
        const observedProperties = filtered.observedProperties;

        div.innerHTML = "";

        const sensorSelect = document.createElement("select");
        let option = document.createElement("option");
        option.value = "";
        option.innerText = "-- select a sensor --";
        sensorSelect.appendChild(option);
        for (let id in sensors) {
            option = document.createElement("option");
            option.value = id;
            option.innerText = sensors[id].name;
            sensorSelect.appendChild(option);
        }
        sensorSelect.value = filtered.selectedSensor;
        sensorSelect.addEventListener("change", () => {
            filtered = filterDatastreams(datastreams, sensorSelect.value, "");
            create();
        });

        div.appendChild(sensorSelect);

        const observedPropertySelect = document.createElement("select");
        option = document.createElement("option");
        option.value = "";
        option.innerText = "-- select an observed property --";
        observedPropertySelect.appendChild(option);
        for (let id in observedProperties) {
            option = document.createElement("option");
            option.value = id;
            option.innerText = observedProperties[id].name;
            observedPropertySelect.appendChild(option);
        }
        observedPropertySelect.value = filtered.selectedObservedProperty;
        observedPropertySelect.addEventListener("change", () => {
            filtered = filterDatastreams(datastreams, sensorSelect.value, observedPropertySelect.value);
            create();
        });
        div.appendChild(observedPropertySelect);

        const dsKeys = Object.keys(filtered.datastreams);
        if (dsKeys.length == 1) {
            const button = document.createElement("button");
            button.innerText = "Visualize Time Series";
            button.addEventListener("click", () => {
                const id = dsKeys[0];
                showPlotbar("<i title='' class='sta-ds-info-button fas fa-info'></i>" + filtered.datastreams[id].name);
                layer.getObservations(filtered.datastreams[id]["Observations@iot.navigationLink"]).then(observations => {
                    displayPlot(observations, filtered.datastreams[id]);
                });
            });
            div.appendChild(button);
        } else {
            const datastreamPropertySelect = document.createElement("select");
            option = document.createElement("option");
            option.value = "";
            option.innerText = "-- select a datastream --";
            datastreamPropertySelect.appendChild(option);

            for (let id in filtered.datastreams) {
                option = document.createElement("option");
                option.value = id;
                option.innerText = filtered.datastreams[id].name;
                datastreamPropertySelect.appendChild(option);
            }

            datastreamPropertySelect.value = "";

            const button = document.createElement("button");
            button.innerText = "Visualize Time Series";
            button.addEventListener("click", () => {
                const id = datastreamPropertySelect.value;
                showPlotbar("<i title='' class='sta-ds-info-button fas fa-info'></i>" + filtered.datastreams[id].name);
                layer.getObservations(filtered.datastreams[id]["Observations@iot.navigationLink"]).then(observations => {
                    displayPlot(observations, filtered.datastreams[id]);
                });
            });

            datastreamPropertySelect.addEventListener("change", () => {
                const value = datastreamPropertySelect.value;
                if (value.length == 0) {
                    button.remove()
                } else {
                    div.appendChild(button);
                }
            });
            div.appendChild(datastreamPropertySelect);
        }
    };

    create();

    return div;
}

/**
 * Generates a div element containing images and their captions.
 * 
 * @param {Array} images - An array of image objects.
 * @param {string} images[].url - The URL of the image.
 * @param {string} images[].caption - The caption for the image.
 * @returns {HTMLDivElement|undefined} A div element containing the images and captions, or undefined if no images are provided.
 */
export function staThingImages(images) {
    if (!images) return undefined;

    const div = document.createElement("div");

    for (let i = 0; i < images.length; ++i) {
        const caption = document.createElement("div");
        caption.innerText = images[i].caption;
        div.appendChild(caption);
        const img = displayImage(images[i].url);
        img.style.marginBottom = "5px";
        div.appendChild(img);
    }

    return div;
}