(function () {
    'use strict';
    let iID = 0
    const CURVE_MAX = 5  // max. number of trend curves
    const jDefault = { x: [], y: [], type: "scatter", mode: "lines+markers", line: { shape: 'line', color: "" } }
    // variables for reference in widget definition
    const className = "CxOsci", // widget name in camel case
        uiType = "cx-osci", // widget keyword (data-ui)
        isContainer = false;

    // example - default configuration
    const defaultConfig = {
        "class-name": "cx-osci",
        "name": null,
        "template": "custom/controls/cx-osci"
    };

    // setup module-logger
    const ENABLE_LOGGING = false, //enable logging with 'log' function
        RECORD_LOG = false, //store logged messages in logger memory
        logger = shmi.requires("visuals.tools.logging").createLogger(uiType, ENABLE_LOGGING, RECORD_LOG),
        fLog = logger.fLog, //force log - logs message even if logger is disabled
        log = logger.log; //log message if logger is enabled

    // declare private functions - START
    function setEnable(self, bEnable) {
        const { elements } = self.vars;
        // if (!bEnable)
        //     self.vars.listeners.forEach((l) => { l.enable(); })
        // else
        //     self.vars.listeners.forEach((l) => { l.disable(); })
        if (elements.btnRecord0)
            elements.btnRecord0.disabled = !bEnable
        for (let i = 0; i < CURVE_MAX; i++) {
            let sName = "selStyle" + i
            if (elements[sName])
                elements[sName].disabled = !bEnable
        }
        elements.selView.disabled = !bEnable
        elements.btnDownload.disabled = !bEnable
    }

    /**
     * set PLC request item, which commands the PLC to start recording
     */
    function setRequest(self, ctrl) {
        const im = shmi.requires("visuals.session.ItemManager");
        let trend = ctrl.getAttribute("data-ui")
        let tmp = {}
        // tmp[self.config.itemOsziMsg] = "cmd rec-1" //trend
        tmp[self.config.itemOsziMsg] = trend
        im.writeDirect(tmp, function (s, i) {
        });
    }
    /**
     * Creates a dom elements and appends it to oParent
     * @param {element} oParent parent element or null
     * @param {string}  sType dom type (e.g. table, td, tr,...)
     * @param {string}  sClass css class to format table or ""
     * @param {string}  sText text to set or ""
     * @returns created dom element
     */
    function addChild(oParent, sType, sClass = "", sText = "") {
        let oChild = document.createElement(sType)
        if (sClass !== "")
            oChild.setAttribute("class", sClass)
        if (sText !== "")
            oChild.innerHTML = sText
        if (oParent)
            oParent.appendChild(oChild)
        return oChild
    }
    function makeTable(self) {
        const elems = self.vars.elements
        const vars = self.vars
        const c = elems.divTable.children
        const iMax = c.length
        // Remove existing table rows
        for (let i = 0; i < iMax; i++)
            c[0].remove()

        let tab = null
        let th = null
        let tb = null
        let tr = null
        let td = null
        let sContent = ""

        tab = addChild(null, 'table', 'plotlyDataTable')
        th = addChild(tab, "thead")

        tr = addChild(th, "tr")
        let arHead = ["#", "x0", "y0", "x1", "y1", "x2", "y2", "x3", "y3", "x4", "y4"]
        arHead[1] = vars.trendlyLayout.xaxis.title ? vars.trendlyLayout.xaxis.title.text : 'x'
        // arHead[2] = vars.trendlyLayout.yaxis.title ? vars.trendlyLayout.yaxis.title.text : 'y0'
        // arHead[3] = vars.trendlyLayout.yaxis.title ? vars.trendlyLayout.yaxis2.title.text : 'y1'
        try {
            arHead[2] = vars.trendlyData[0].name ? vars.trendlyData[0].name : 'y0'
            arHead[4] = vars.trendlyData[1].name ? vars.trendlyData[1].name : 'y1'
            arHead[6] = vars.trendlyData[2].name ? vars.trendlyData[2].name : 'y2'
            arHead[8] = vars.trendlyData[3].name ? vars.trendlyData[3].name : 'y3'
            arHead[10] = vars.trendlyData[4].name ? vars.trendlyData[4].name : 'y4'
        } catch (e) {
            let sLoc = "makeTable: "

        }
        // chart
        for (let i = 0; i < arHead.length; i++) {
            td = addChild(tr, "th", "", arHead[i])
        }

        tb = addChild(tab, "tbody")
        let iPoints = 0
        for (let i = 0; i < CURVE_MAX; i++) {
            if (vars.trendlyData[i]) {
                if (vars.trendlyData[i].x) {
                    iPoints = (vars.trendlyData[i].x.length > iPoints) ? vars.trendlyData[i].x.length : iPoints;
                }
                if (vars.trendlyData[i].y) {
                    iPoints = (vars.trendlyData[i].y.length > iPoints) ? vars.trendlyData[i].y.length : iPoints;
                }
            }
        }
        for (let i = 0; i < iPoints; i++) {
            tr = addChild(tb, "tr")
            td = addChild(tr, "td", "", "" + i)
            for (let j = 0; j < CURVE_MAX; j++) {
                sContent = ""
                if (vars.trendlyData[j])
                    if (vars.trendlyData[j].x)
                        sContent = (i < vars.trendlyData[j].x.length) ? "" + vars.trendlyData[j].x[i] : ""
                td = addChild(tr, "td", "", sContent)

                sContent = ""
                if (vars.trendlyData[j])
                    if (vars.trendlyData[j].y)
                        sContent = (i < vars.trendlyData[j].y.length) ? "" + vars.trendlyData[j].y[i].toFixed(3) : ""
                td = addChild(tr, "td", "", sContent)

            }
        }
        elems.divTable.appendChild(tab)
    }
    /**
     * toggels views graph/table
     */
    function setView(self) {
        const e = self.vars.elements
        const value = e.selView.value
        if (value === 'graph') {
            e.divTrendly.style.display = 'block'
            e.divTable.style.display = 'none'
        } else {
            e.divTable.style.display = 'block'
            e.divTrendly.style.display = 'none'
        }
    }
    /**
     * Download string as file
     * @param {string} sData     string to save as file
     * @param {string} sFileName name of download file
     */
    function downloadTextFile(sData, sFileName, self) {
        log("downloadTextFile " + ":" + sFileName)
        const anchor = document.createElement("a")
        anchor.href = `data:text/json;charset=utf-8,${encodeURIComponent(sData)}`
        anchor.download = sFileName
        anchor.click()
    }
    /**
     * create data + link for download
     */
    function downloadTrend(self, ctrl, iMyID) {
        log("downloadTrend " + self.config.itemOsziMsg + " " + iMyID)
        const vars = self.vars
        const FS = ";"
        const LF = "\n"
        const arHead = ["#", "x", "y0", "y1"]
        arHead[1] = vars.trendlyLayout.xaxis.title ? vars.trendlyLayout.xaxis.title.text : 'x'
        arHead[2] = vars.trendlyLayout.yaxis.title ? vars.trendlyLayout.yaxis.title.text : 'y'
        arHead[3] = vars.trendlyLayout.yaxis2.title ? vars.trendlyLayout.yaxis2.title.text : 'y'

        let sCSV = ""
        // create header
        for (let i = 0; i < arHead.length; i++) {
            sCSV += arHead[i] + FS
        }
        sCSV += LF
        // append data
        let iPoints = 0
        if (vars.trendlyData[0].x.length > vars.trendlyData[0].y.length)
            iPoints = vars.trendlyData[0].x.length
        else
            iPoints = vars.trendlyData[0].y.length
        for (let i = 0; i < iPoints; i++) {
            sCSV += i + FS

            sCSV += (i < vars.trendlyData[0].x.length) ? vars.trendlyData[0].x[i] + FS : FS
            sCSV += (i < vars.trendlyData[0].y.length) ? vars.trendlyData[0].y[i].toFixed(3) + FS : FS
            sCSV += (i < vars.trendlyData[1].y.length) ? vars.trendlyData[1].y[i].toFixed(3) + FS : FS
            sCSV += LF
        }
        try {
            sCSV += ""
            console.warn("typeof sCSV=" +typeof sCSV)
            sCSV = sCSV.replaceAll(".", ",")
        } catch { }
        let sFile = vars.trendlyCsvName + (new Date(Date.now())).toISOString().split(".")[0].replace(/:/g, "-").split("T").join("_") + ".csv"
        downloadTextFile(sCSV, sFile, self)
    }
    /**
     * toggels through trend styles (lines, markers)
     */
    function setTrendStyle(self, ctrl, iMyID) {
        log("setTrendStyle " + self.config.itemOsziMsg + " " + iMyID)
        const { elements } = self.vars;
        const vars = self.vars
        let value = ctrl.value
        vars.trendlyData[iMyID].mode = value
        vars.myGraph = Plotly.newPlot(elements.divTrendly, vars.trendlyData, vars.trendlyLayout, vars.trendlyConfig)
    }
    /**
     * create a button & appends it to jParent
     * @param {dom}    jParent    parent dom element
     * @param {string} sLabel     label of button
     * @param {string} sAttribute data-ui attribute
     * @returns created button {dom}
     */
    function makeBtn(jParent, sLabel, sAttribute) {
        let btn = document.createElement("input")
        btn.type = "button"
        btn.value = shmi.localize(" " + sLabel + " ")
        btn.style.height = "100%"
        btn.setAttribute("data-ui", sAttribute)
        jParent.appendChild(btn)
        return btn
    }
    /** 
     * Create select element & appends it to jParent
     * @param {dom}      jParent    parent dom element
     * @param {string[]} arData     string array with values
     * @param {string}   sAttribute data-ui attribute
     */
    function makeSelect(jParent, arData, sAttribute) {
        let select = document.createElement("select");
        select.style.height = "100%"
        select.style.fontSize = "100%"
        select.style.textAlign = "center"
        // select.style.color = "blue"
        select.setAttribute("data-ui", sAttribute)

        //Create & append options
        for (let i = 0; i < arData.length; i++) {
            let option = document.createElement("option")
            option.value = arData[i]
            option.text = " " + shmi.localize("${" + `cxPlotly.${arData[i]}` + "}") + " "
            option.style.fontSize = "200%"
            // option.style.color = "blue"
            select.appendChild(option)
        }
        jParent.appendChild(select)
        return select
    }
    /**
     * 
     * @param {*} sItem 
     * @returns 
     */
    function getItem(sItem) {
        if (typeof (sItem) != "string") {
            shmi.notify("programming bug: getItem() invalid item name")
            return
        }
        const im = shmi.requires("visuals.session.ItemManager")
        return new Promise((resolve, reject) => {
            im.readDirect([sItem], function (iResult, oData) {
                if (oData[sItem] === null) {
                    shmi.notify(`Invalid item name "${sItem}"`)
                    reject(null)
                } else {
                    resolve(oData[sItem]);
                }
            });
        })
    }
    function getDefault(sType) {
        if (sType === "data") {
            let arData = []
            // for (let i = 0; i < CURVE_MAX; i++)
            arData.push(JSON.parse(JSON.stringify(jDefault)))
            return arData
        } else if (sType === "layout") {
            return {
                title: '???',
                yaxis: { title: 'y1 axis' },
                yaxis2: {
                    title: 'y2 axis',
                    overlaying: 'y',
                    side: 'right'
                }
            }
        } else if (sType === "config") {
            return {
                responsive: true,         // resize when window size change at runtime
                doubleClickDelay: 400,    // increase double click time for touch
                displayModeBar: false,    // always display mode bar
                displaylogo: false,       // hide plotly logo
                modeBarButtonsToRemove: ['toImage', 'lasso2d'] // hide list of mode bar buttons
            }
        } else if (sType === "csvName") {
            return "osci_"
        }
    }

    // declare private functions - END

    // definition of new widget extending BaseControl - START
    const definition = {
        className: className,
        uiType: uiType,
        isContainer: isContainer,
        /* default configuration settings - all available options have to be initialized! */
        config: defaultConfig,
        /* instance variables */
        vars: {
            // (CUSTOM ADDITION)
            elements: { //references for DOM elements accessed with JS code
                btnRecord0: null,
                selView: null,
                btnDownload: null,
                divTable: null,
                divTrendly: null,       //div for trendly output
                divStateBg: null,       //div state output
                divStateProgress: null, //div state output
                divStateText: null,     //div state output
                divCtrl: null           //div ctrl window
            },
            itemOsziMsg: null,
            itemsX: [],
            itemsY: [],

            itemSetup: null,
            // data of trendly chart
            trendlyGraph: null,
            trendlyData: null,
            trendlyLayout: null,
            trendlyConfig: null,

            // listeners: [] //event Listeners
            // (/CUSTOM ADDITION)
        },
        /* imports added at runtime */
        imports: {
            /* example - add import via shmi.requires(...) */
            im: "visuals.session.ItemManager"
            // io: "visuals.io"
        },

        /* array of custom event types fired by this widget */
        events: [],

        /* functions to extend or override the BaseControl prototype */
        prototypeExtensions: {
            /* called when config-file (optional) is loaded and template (optional) is inserted into base element */
            onInit: function () {
                // (CUSTOM ADDITION START)
                const self = this
                const { elements } = self.vars;

                // Store references to the DOM elements for performance reasons
                elements.divTrendly = shmi.getUiElement("trendly", self.element);
                elements.divTable = shmi.getUiElement("table", self.element);
                elements.divCtrl = shmi.getUiElement("ctrl", self.element);

                elements.divStateBg = shmi.getUiElement("state-bg", self.element);
                elements.divStateProgress = shmi.getUiElement("state-progress", self.element);
                elements.divStateText = shmi.getUiElement("state-text", self.element);
                elements.divStateText.textContent = shmi.localize("${cxPlotly.wait4trigger}")

                // create button for all displayed trends
                elements.selView = makeSelect(elements.divCtrl, ["graph", "table"], "selView")
                elements.selView.addEventListener("change", () => { setView(self, elements.selView) });

                elements.btnRecord0 = makeBtn(elements.divCtrl, "${cxPlotly.startRecord}", "cmd rec-0")
                elements.btnRecord0.addEventListener("click", () => { setRequest(self, elements.btnRecord0) });


                self.vars.itemsX.push(self.config.itemX0 ? self.config.itemX0 : "")
                self.vars.itemsX.push(self.config.itemX1 ? self.config.itemX1 : "")
                self.vars.itemsX.push(self.config.itemX2 ? self.config.itemX2 : "")
                self.vars.itemsX.push(self.config.itemX3 ? self.config.itemX3 : "")
                self.vars.itemsX.push(self.config.itemX4 ? self.config.itemX4 : "")

                self.vars.itemsY[0] = self.config.itemY0 ? self.config.itemY0 : ""
                self.vars.itemsY[1] = self.config.itemY1 ? self.config.itemY1 : ""
                self.vars.itemsY[2] = self.config.itemY2 ? self.config.itemY2 : ""
                self.vars.itemsY[3] = self.config.itemY3 ? self.config.itemY3 : ""
                self.vars.itemsY[4] = self.config.itemY4 ? self.config.itemY4 : ""
                // clear x, when y not provided
                for (let i = 0; i < CURVE_MAX; i++) {
                    if (self.vars.itemsY[i] === "") {
                        self.vars.itemsX[i] = ""
                    }
                }
                for (let i = 0; i < CURVE_MAX; i++) {
                    if (self.vars.itemsX[i]) {
                        let sName = `selStyle` + i
                        elements[sName] = makeSelect(elements.divCtrl, ["lines+markers", "lines", "markers"], sName)
                        elements[sName].addEventListener("change", () => { setTrendStyle(self, elements[sName], i) });
                    }
                }

                elements.btnDownload = makeBtn(elements.divCtrl, "${cxPlotly.downloadCsv}", "cmd download")
                elements.btnDownload.addEventListener("click", () => { downloadTrend(self, elements.btnDownload) });
                self.vars.trendlyData = [{ x: [], y: [], type: "scatter", mode: "lines+markers", line: { shape: 'line' } }]
                self.vars.trendlyLayout = {}
                self.vars.trendlyConfig = {}
                self.vars.trendlyCsvName = ""
                setView(self)
            },
            /* called when widget is enabled */
            onEnable: function () {
                iID++
                const self = this
                const { elements } = self.vars
                const { im } = self.imports
                const vars = self.vars
                log("*******")
                log("onEnable " + self.config.itemOsziMsg)
                setEnable(self, true);

                // display empty chart
                vars.myGraph = Plotly.newPlot(elements.divTrendly, vars.trendlyData, vars.trendlyLayout, vars.trendlyConfig)
                makeTable(self)
                // Subscribe to item updates
                if (self.config.itemOsziMsg)
                    vars.itemOsziMsg = im.subscribeItem(self.config.itemOsziMsg, self);
            },
            /* called when widget is disabled */
            onDisable: function () {
                const self = this
                log("onDisable " + self.config.itemOsziMsg)
                setEnable(self, false);
                const { elements } = self.vars;
                if (self.vars.itemOsziMsg) {
                    self.vars.itemOsziMsg.unlisten();
                    self.vars.itemOsziMsg = null;
                }
            },
            /* called when widget is locked - disable mouse- and touch-listeners */
            onLock: function () {
                const self = this
                log("onLock " + self.config.itemOsziMsg)
                setEnable(self, false);
            },
            /* called when widget is unlocked - enable mouse- and touch-listeners */
            onUnlock: function () {
                const self = this
                log("onUnlock " + self.config.itemOsziMsg)
                setEnable(self, true);
            },
            /* called by ItemManager when value of subscribed item changes and once on initial subscription */
            onSetValue: async function (value, type, name) {
                const self = this
                let elems = self.vars.elements
                let vars = self.vars
                log("onSetValue " + self.config.itemOsziMsg)
                if (self.vars.itemOsziMsg.name === name) {
                    let arW = []
                    if (value !== "")
                        arW = value.split(" ")
                    if (value === "") {
                        elems.divStateBg.style.display = 'none'
                        elems.divStateProgress.style.width = "0%";
                    } else if (arW[0] === "cmd") {
                        elems.divStateBg.style.display = 'block'
                        elems.divStateText.textContent = shmi.localize("${cxPlotly.wait4Plc}")
                    } else if (arW[0] === "state") {
                        if (arW[1] === "wait") {
                            setEnable(self, false)
                            elems.divStateBg.style.display = 'block'
                            elems.divStateText.textContent = shmi.localize("${cxPlotly.wait4trigger}")
                        } else if (arW[1] === "done") {
                            setEnable(self, true)
                            elems.divStateText.textContent = "100%"
                            elems.divStateProgress.style.width = "100%"

                            let sVal = ""
                            let sItemName = ""
                            let arX = []
                            let arY = []

                            vars.trendlyLayout = getDefault("layout")
                            vars.trendlyConfig = getDefault("config")
                            vars.trendlyData = getDefault("data")
                            vars.trendlyCsvName = getDefault("csvName")
                            try {
                                if (self.config.itemSetup) {
                                    sItemName = self.config.itemSetup
                                    sVal = await getItem(sItemName, true)
                                    // replace local vars HINT: MUST NOT contain " in text
                                    sVal = shmi.localize(sVal)
                                    let jSetup = JSON.parse(sVal)
                                    if (jSetup.layout) vars.trendlyLayout = jSetup.layout
                                    if (jSetup.config) vars.trendlyConfig = jSetup.config
                                    if (jSetup.data) vars.trendlyData = jSetup.data
                                    if (jSetup.csvFile) vars.trendlyCsvName = jSetup.csvFile
                                }
                                for (let i = vars.trendlyData.length; i < CURVE_MAX; i++) {
                                    vars.trendlyData.push(JSON.parse(JSON.stringify(jDefault)))
                                }
                                for (let i = 0; i < CURVE_MAX; i++) {
                                    sItemName = self.vars.itemsX[i]
                                    if (sItemName !== "") {
                                        sVal = await getItem(sItemName, true)
                                        arX[i] = JSON.parse(sVal)

                                        sItemName = self.vars.itemsY[i]
                                        sVal = await getItem(sItemName, true)
                                        arY[i] = JSON.parse(sVal)

                                        vars.trendlyData[i].x = arX[i]
                                        vars.trendlyData[i].y = arY[i]
                                        vars.trendlyData[i].mode = elems[`selStyle` + i].value
                                    }
                                }
                            } catch (e) {
                                let sLoc = "onSetValue: "
                                shmi.notify(sLoc + sItemName + " " + e.message)
                                fLog(sVal)
                            }

                            vars.trendlyGraph = await Plotly.newPlot(elems.divTrendly, vars.trendlyData, vars.trendlyLayout, vars.trendlyConfig)
                            // set color of select box to color of curve
                            // for (let i = 0; i < 5; i++) {
                            //                         // [2].line.color
                            //     if (vars.trendlyData[i].line) {
                            //         if (vars.trendlyData[i].line.color)
                            //         console.log(`sColor${i} =` + vars.trendlyData[i].line.color)
                            //     }
                            // }
                            makeTable(self)
                        } else { // update progress bar in %
                            let rPercent = parseInt(arW[1]) % 100
                            elems.divStateText.textContent = rPercent + "%"
                            elems.divStateProgress.style.width = `${rPercent}%`;
                        }
                    } else if (value === "err plc_string_too_small") { // PLC string full=>size must be increased
                        elems.divStateText.textContent = shmi.localize("${cxPlotly.plcStringTooSmall}")
                    }
                }

            },
            /* called by ItemManager to provide properties (min & max values etc.) of subscribed item */
            onSetProperties: function (min, max, step, name, type, warnMin, warnMax, prewarnMin, prewarnMax, digits) {
                const self = this
                log("onSetProperties " + self.config.itemOsziMsg)
            },
            /* called when widget is deleted - used for instance clean-up */
            onDelete: function () {
                const self = this
                log("onDelete " + self.config.itemOsziMsg)
                Plotly.purge(self.vars.elements.divTrendly)
            }
        }
    };

    // definition of new widget extending BaseControl - END

    // generate widget constructor & prototype using the control-generator tool
    shmi.requires("visuals.tools.control-generator").generate(definition);
})();
