// Import the Lasal Runtime base element into your component
// Import SigPolymer to use Polymer methods: html, afterNextRender, beforeNextRender, debouncer, timeOut
// or if your component's getter template() returns a HTML literal
import { LasalRuntimeSigElement, SigPolymer } from '../../sigmatek/sig-element/sig-element.js';
import '../../sigmatek/sig-utils-d3/sig-utils-d3.js';

/**********************************************************************************************************************************************************
 * @class TrendBuffer
***********************************************************************************************************************************************************/
class TrendBuffer {

    /****************************************************************************************************
     * Constructor of class, called when the element has been created
     ****************************************************************************************************/
    constructor() {
        this.buffer = [];
        this.maxEntries = undefined;
    }

    /****************************************************************************************************
     * Method to create the buffer.
     * 
     * @param {Number} Entries number of entries
     ****************************************************************************************************/
    CreateBuffer(Entries) {
        if (this.maxEntries !== Entries) {
            this.DeleteBuffer();
        }
        this.maxEntries = Entries;
    }

    /****************************************************************************************************
     * Method to add a single entry to the buffer
     * 
     * @param {*} entry entry to for adding to the buffer
     ****************************************************************************************************/
    AddEntry(entry) {
        if (this.maxEntries !== undefined) {
            if ( this.buffer.length < this.maxEntries) {
                this.buffer.push(entry);
            } else {
                this.buffer.shift(); // ältesten Eintrag löschen
                this.buffer.push(entry); // neuen Eintrag hinzufügen
            }
            return true;
        } else {
            return false;
        }
    }

    /****************************************************************************************************
     * Method to delete all entries in the buffer
     ****************************************************************************************************/
    DeleteBuffer(){
        this.buffer = [];
    }

    /****************************************************************************************************
     * Method to get the number of entries in the buffer
     * @return {Number} number of entries in the buffer
     ****************************************************************************************************/
    BufferLength() {
        return this.maxEntries;
    } 
}

/****************************************************************************************************
 * @class LasalRuntimeSigTrendElement
 * @extends {LasalRuntimeSigElement}
 ****************************************************************************************************/
class LasalRuntimeSigTrendElement extends LasalRuntimeSigElement {
    
    /****************************************************************************************************
     * When the element is loaded, importMeta() assigns reference to the path from 
     * which an element was imported to its importPath property. 
     * Relative URLs in styles are automatically re-written to be relative to the importPath property. 
     * You can use [[importPath]] in your component to load url resources
     * 
     * @readonly
     * @static
     ****************************************************************************************************/
    static get importMeta() {
        return import.meta;
    }

    /****************************************************************************************************
     * Return the component style and template as a template literal
     *
     * @readonly
     * @static
     ****************************************************************************************************/
    static get template() {
        return SigPolymer.html`
        <style include="sig-element-css">
            :host {
                /* Basic styling of the component */
                color: var(--theme-sig-control-trend-color, inherit);
                background-color: var(--theme-sig-control-trend-background-color, var(--palette-default-color235));
                border-width: var(--theme-sig-control-trend-border-width, 2px);
                border-color: var(--theme-sig-control-trend-border-color, var(--palette-default-color244));
                border-style: var(--theme-sig-control-trend-border-style, solid);
                border-radius: var(--theme-sig-control-trend-border-radius, 3px);
               
                /* Basic box model settings */
                display: block;
                position: absolute;
                overflow: hidden;
            }

            :host * {
                /* Reset margin and paddings for child elements  */
                margin: 0; 
                padding: 0;
                text-decoration: inherit;
            }

            .trend {
                /* The container is rendered as a table so we can set the span to table-cell */
                display: table;
                width: 100%;
                height: 100%;
                text-decoration: inherit;
            }
            .trend div.content {
                /* The container is rendered as a table-cell so we can position the value via vertical-align */
                display: table-cell;
                text-decoration: inherit;
            }

            #trendcontainer
            {
                /* The container handles the measureline track events */
                position: absolute;
                height: 100%;
                width: 100%;
            }

            #svgcontainer
            {
                /* The container handles the drawing */
                position: absolute;
                height: 100%;
                width: 100%;
                pointer-events: none;
            }

        </style>
        <div class="trend clearfix">
            <div id="trendcontainer">
                <div id="svgcontainer"></div>
            </div>
        </div>
        `;
    }

    /****************************************************************************************************
     * define the custom tag name
     *
     * @readonly
     * @static
    ***********************************************************************************************************************************************************/
    static get is() {
        return "sig-control-trend";
    }

    /****************************************************************************************************
     * define the main properties of the component
     * reflected property names should always be lower case so it can be used directly on the element as an attribute
     *
     * @readonly
     * @static
    ***********************************************************************************************************************************************************/
    static get properties() {
        return {
            recordtype: { 
                type: String,
                value: 'Datapoint'
            },
            recordvalueuc: {
                type: Number,
                value: 0, 
                observer: "_recordValueChanged"
            },
            scalingminuc: {
                type: Number,
                value: 0,
                observer: "_updateGraphic"
            },
            scalingmaxuc: {
                type: Number,
                value: 1000,
                observer: "_updateGraphic"
            },
            upperlimituc: {
                type: Number,
                value: 900,
                observer: "_updateGraphic"
            },
            lowerlimituc: {
                type: Number,
                value: 100,
                observer: "_updateGraphic"
            },
            uniqueid: {
                type: Number,
                value: 0
            },
            sampletime: {
                type: Number,
                value: 500, 
                observer: "_sampletimeChanged"
            },
            buffersize: {
                type: Number,
                value: 100,
                observer: "_calculateBounds"
            },
            visiblegrid: {
                type: Boolean,
                value: true
            }, 
            autostart: {
                type: Boolean,
                value: true
            },
            autocreate: {
                type: Boolean,
                value: true
            },
            gridcounthorizontal: {
                type: Number,
                value: 4
            },
            gridcountvertical: {
                type: Number,
                value: 9
            },
            drawdirection: { 
                type: String,
                value: 'right'
            },
            visibleupperlimit: {
                type: Boolean,
                value: true
            },
            visiblelowerlimit: {
                type: Boolean,
                value: true
            },
            visiblemeasureline: {
                type: Boolean,
                value: true
            },
            visibleunittext: {
                type: Boolean,
                value: true
            },
            unittext: {
                type: String,
                value: "unit",
                observer: '_updateUnit'
            },
            measurevaluevertical: { 
                type: String,
                value: 'top'
            }
        }
    }

    /****************************************************************************************************
     * Called when the element has been created,
     * but before property values are set and local DOM is initialized.
     ****************************************************************************************************/
    constructor() {
        super();

        // buffer definitions
        this.DataBuffer = new TrendBuffer();
        this.observerValue = 0;

        // bounds
        this.svgWidth = undefined;
        this.svgHeigth = undefined;
        this.margin = {
            top: 0,
            right: 0, 
            bottom: 0, 
            left: 0};

        // events numbers for recordtype = EVENT
        this.evSingleData = {
            maId: 0x8005,
            miId: 0x0000}
        this.evMultiData = {
            maId: 0x8005,
            miId: 0x0001}; 
        this.evClearBuffer = {
            maId: 0x8005,
            miId: 0x0002};
        
        // merker for violation events
        this.mViolationMaxAlreadyFired = false;
        this.mViolationMinAlreadyFired = false;
        this.mViolationMaxGoneFired = false;
        this.mViolationMinGoneFired = false;
        
        // add predef properties to avoid recalculations during drawing
        this.addPredefProps([{
            src: 'recordvalue',
            dest: 'unittext',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNIT_TEXT
        },
        {
            src: 'recordvalue',
            dest: 'recordvalueuc',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNCONVERTED_VALUE
        },
        {
            src: 'scalingmin',
            dest: 'scalingminuc',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNCONVERTED_VALUE
        },
        {
            src: 'scalingmax',
            dest: 'scalingmaxuc',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNCONVERTED_VALUE
        },
        {
            src: 'upperlimit',
            dest: 'upperlimituc',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNCONVERTED_VALUE
        },
        {
            src: 'lowerlimit',
            dest: 'lowerlimituc',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNCONVERTED_VALUE
        }]);

        this.sigAddRequiredProperty('uniqueid');
        this.sigAddRequiredProperty('recordtype');
        
        // Add sampletime to required Proeprties; otherwise we can get wrong values in _onRequiredPropertiesReady
        this.sigAddRequiredProperty('sampletime');

        // define recordvalue and autostart as required to make sure we have valid data when starting to record automatically
        this.sigAddRequiredProperty('recordvalue');
        this.sigAddRequiredProperty('autostart');  
        this.sigAddRequiredProperty('scalingmin');
        this.sigAddRequiredProperty('scalingmax');     
    }

    /****************************************************************************************************
     *  called after property default values are set and local DOM is initialized
     ****************************************************************************************************/
    ready() {
        super.ready();
        this.isReady = true;
        this._addEventListeners();
    
        // wait for next render in designer, for getting control bounds
        if (this.isdesignmode) {
            SigPolymer.afterNextRender(this, function () {
                this.requiredPropertiesReady = true;        // sigOnRequiredPropertiesReady not called in designer
                this._calculateBounds();        
            });

        // no designer, get control bounds immediately
        } else {
            this._calculateBounds();
        }
    }

    /****************************************************************************************************
     * called when all properties defined with _addRequiredProperty are available 
     ****************************************************************************************************/
    sigOnRequiredPropertiesReady () {
        super.sigOnRequiredPropertiesReady();
        this.requiredPropertiesReady = true;
        this._calculateBounds();

        // if everything is ready and autostart is activated => start recording
        if (this.autostart === true) {
            this._startRecording();
        }
    }
    
    /****************************************************************************************************
     * called when the component is removed from the DOM
     ****************************************************************************************************/
    disconnectedCallback() {
        super.disconnectedCallback();
        if (!this.isdesignmode) {
            if (this.recordtype === "Datapoint") {
                this._stopRecording();
            }
        }
    }

    /****************************************************************************************************
     * this method is called by the Designer after the component was resized in the LVD
     * newBounds and oldBounds are not always set and may be undefined !
     * @param {*} newBounds new size
     * @param {*} oldBounds old size
     ****************************************************************************************************/
    designerOnBoundsChanged() {
        //Handle everything that needs to be handled on resize in a renderCallback
        //at this point the HTML bounds aren't correct yet
        SigPolymer.afterNextRender(this, function () {
                this._calculateBounds();
            });
    }
    
    /****************************************************************************************************
     * Adds the eventlisteners for the data events this needs to wait for the required properties, otherwise
     * the recordtype could be wrong
     ****************************************************************************************************/
    _addEventListeners() {
        if (!this.isdesignmode) {

            // event listener for measure line
            if (this.visiblemeasureline) {
                this.sigAddEventListener('track', (event) => {this._moveMeasureLine(event);}, 'polymerEvent', this.$.trendcontainer);  
            }

            // add event listener for recordtype = EVENT
            if (this.recordtype !== "Datapoint") {
                this.sigAddEventListener(this.evSingleData, (maj, min, dv) => { this._eventDataFromPLC(maj, min, dv);}, 'runtimeEvent');
                this.sigAddEventListener(this.evMultiData, (maj, min, dv) => { this._eventDataFromPLC(maj, min, dv);}, 'runtimeEvent');
                this.sigAddEventListener(this.evClearBuffer, (maj, min, dv) => { this._eventDataFromPLC(maj, min, dv);}, 'runtimeEvent'); 
            }
        } 
    }
    
    /****************************************************************************************************
     * This observer runs if the value of the property 'recordvalue' changes
     * it will not run if the value is equal to the old value!
     * during the initialization of the property the value may be undefined
     * 
     * @param {*} newvalue
     ****************************************************************************************************/
    _recordValueChanged(newvalue) {
        this.observerValue = this.recordvalueuc;
    }
    
    /****************************************************************************************************
     * This observer runs if the value of the property 'sampletime' changes
     * it will not run if the value is equal to the old value!
     * during the initialization of the property the value may be undefined
     * 
     * @param {*} newvalue
     ****************************************************************************************************/
    _sampletimeChanged(newvalue) {
        
        if ((!this.isdesignmode) && (this.requiredPropertiesReady === true)) {           

            // Minimum sampletime is 100ms ; if it is set to lower we set it to 100
            if (newvalue < 100) {
                newvalue = 100;
            }

            // if it's a new sample time
            if (newvalue !== this.sampletime) {
                this.sampletime = newvalue;

                // if recording has been started with a different sampleTime, stop it and activate it again with the new sampleTime
                if (this.timerhandle && this.activeSampleTime !== this.sampletime) {
                    this._stopTrendInterval();
                    this._startTrendInterval();   
                }
            }
        }
    }

    /****************************************************************************************************
     * Called to start intervall
     ****************************************************************************************************/
    _startTrendInterval() {
        // if the timer is not active, start it
        if (this.timerhandle === undefined) {            
            this.timerhandle = this.sigSetInterval(() => {  

                // add the current value to the buffer
                this._addEntry(this.observerValue);

                // update the trendgraphic
                this._updateGraphic(); 

            }, this.sampletime);

            // save new sampletime
            this.activeSampleTime = this.sampletime;
        }
    }

    /****************************************************************************************************
     * Called to clear intervall
     ****************************************************************************************************/
    _stopTrendInterval() {
        // if the timer is running, stop it
        if (this.timerhandle) {            
            this.sigClearInterval(this.timerhandle);
            this.timerhandle = undefined;
            this.activeSampleTime = undefined;
        }
    }

    /****************************************************************************************************
     * Called when a CSS Variable is changed
     * 
     * @param {Object} cssObj - an Object containing the changed CSS variables, and their values
     * @param {boolean} [doBoundsCheck=false] - only relevant in designmode, leave as false
     ****************************************************************************************************/
    sigApplyCSSValue(cssObj, doBoundsCheck = false) {
        super.sigApplyCSSValue(cssObj, doBoundsCheck);
        
        //Only needed in design mode
        if (this.isdesignmode) {
            this._calculateBounds();

        } else {   
            //check if already rendered
            if (this.isReady) {
                if (cssObj.hasOwnProperty('--theme-sig-element-width') || cssObj.hasOwnProperty('--theme-sig-element-height')) {
                    // recalculate bouds
                    this._calculateBounds();
                } else {
                    //simply redraw is enough
                    this._updateGraphic();
                }                
            }           
        }
    }

    /*****************************************************************************************************
     * creates the buffer an calculates the bounds of the control
    ****************************************************************************************************/
    _calculateBounds() {
        if ((this.isReady) && (this.requiredPropertiesReady)) {
            if (this.$ && this.$.svgcontainer) {

                // get border width of each side (could differ if shorthand settings are used)
                this._getBorderWidth(); 

                // get bounds
                const controlBounds = this.getControlBounds();
                this.svgWidth = controlBounds.width - this.borderLeftWidth - this.borderRightWidth;
                this.svgHeigth = controlBounds.height - this.borderTopWidth - this.borderBottomWidth;
                this.transWidth = this.svgWidth - this.margin.left - this.margin.right,
                this.transHeigth = this.svgHeigth - this.margin.top - this.margin.bottom;
                
                // if we already have a valid measurelinex position, we dont need to set it again
                if (this.drawdirection === "right") {
                    if (this.measurelineX < 0 || this.measurelineX === undefined) {
                        this.measurelineX = this.transWidth-1;
                    }
                } else {
                    if(this.measurelineX < 0 || this.measurelineX === undefined) {
                        this.measurelineX = 1;
                    }
                }

                // get css of grid
                this.gridLine = {
                    color: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-grid-color"),
                    width: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-grid-width"),
                    type: this._createLineType(getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-grid-style"))
                };

                // get css of limit line
                this.limitLineLower = {
                    color: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-lowerlimit-color"),
                    width: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-lowerlimit-width"),
                    type: this._createLineType(getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-lowerlimit-style"))
                };

                this.limitLineUpper = {
                    color: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-upperlimit-color"),
                    width: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-upperlimit-width"),
                    type: this._createLineType(getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-upperlimit-style"))
                };

                // get css of measureline
                this.measureLine = {
                    color: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-measureline-color"),
                    width: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-measureline-width"),
                    type: this._createLineType(getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-measureline-style")),
                    font: parseFloat(getComputedStyle(this).getPropertyValue("font-size"))
                };

                // get css of dataline
                this.dataCurve = {
                    color: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-curve-color"),
                    width: getComputedStyle(this).getPropertyValue("--theme-sig-control-trend-curve-width"),
                };

                // buffer size is depending on size of control
                if (this.autocreate === true) {
                    this._bufferResize(this.transWidth);
                } else {
                    this._bufferResize(this.buffersize);
                }
                
                this._updateGraphic();
            }
        }
    }

    /*****************************************************************************************************
     * Deletes the old buffer and creates a buffer with the new size. 
     * In designer mode, the simulation values are created at this point.
     * 
     * @param {number} newSize - new buffer size
    ****************************************************************************************************/
    _bufferResize(newSize) {

        // create data buffer
        this.DataBuffer.CreateBuffer(newSize);
        
        // simulation values for design mode
        if (this.isdesignmode) {
            let tmpIndex = 0;
            let oldIndex = 0;

            for (let index = 0; index < this.DataBuffer.BufferLength(); index++) {
                if ( (index-oldIndex) >= this.transHeigth) {
                    tmpIndex = 0;
                    oldIndex = index;
                }
                this._addEntry(tmpIndex + 200);
                tmpIndex++;
            }
        }
    }

    /*****************************************************************************************************
     * if there is already a svg available, it will be removed and a new
     * one is created.
    ****************************************************************************************************/
    _createSVGContainer() {
        if ((this.svg !== undefined) && (this.svg !== null)) {
            d3.select(this.$.svgcontainer).select("svg").remove();
        } 

        this.svg = d3.select(this.$.svgcontainer)   
            .append("svg")
            .attr("width", this.transWidth)
            .attr("height", this.transHeigth)
            .append("g").attr("transform","translate(" + this.margin.left + "," + this.margin.top + ")");
    }
    
    /*****************************************************************************************************
     * Draws everything new (limit, grid, curve, measureline)
    ****************************************************************************************************/
    _updateGraphic() {
        if ((this.isReady === true) && (this.requiredPropertiesReady)) { 

            // create svg
            this._createSVGContainer();
            
            // draw grid
            if (this.visiblegrid === true) {
                this._drawGrid();
            }

            // draw limits
            if (this.visibleupperlimit === true) {
                this._drawLimitLine("upper");
            }
            if (this.visiblelowerlimit === true) {
                this._drawLimitLine("lower");
            }    
            
            // draw curve
            this._drawCurve();

            // draw mewasurement line (only available for Datapoint because of missing unit in case of Event)
            if (this.visiblemeasureline === true && this.recordtype === "Datapoint") {
                this._measureLine(this.visibleunittext);
            }
        }
    }

    /*****************************************************************************************************
     * Draws a linear curve as svg path (using the recorded buffer)
    ****************************************************************************************************/
    _drawCurve() {
        let linearScaleX = null;
        let linearScaleY = d3.scaleLinear()
                                .domain([this.scalingminuc, this.scalingmaxuc])
                                .range([0, this.transHeigth])

        if (this.drawdirection === "right") {
            linearScaleX = d3.scaleLinear()
                .domain([0, (this.DataBuffer.BufferLength()-1)])
                .range([0, this.svgWidth])
        } 
        else {
            linearScaleX = d3.scaleLinear()
                .domain([0, (this.DataBuffer.BufferLength()-1)])
                .range([this.svgWidth, 0])
        }
        
       let lineFunction = d3.line()
                                .x((d,i) => { return linearScaleX(i); })
                                .y((d) => { return (this.transHeigth - linearScaleY(d)); })
                                .curve(d3.curveLinear);
                                
        if (this.DataBuffer.buffer && !isNaN(this.DataBuffer.buffer[0])) {
            let lineGraph = this.svg.append("path")
                                    .attr("d", lineFunction(this.DataBuffer.buffer))
                                    .attr("stroke", this.dataCurve.color) 
                                    .attr("stroke-width", this.dataCurve.width) 
                                    .attr("fill", "none");
        }
    }

    /*****************************************************************************************************
     * Draws the limit line (lower, upper)
     * 
     * @param {string} limit - which line should be drawn 
     ****************************************************************************************************/
    _drawLimitLine(limit) {

        let tmpValue, lineColor, linewidth , linetype = 0;
        switch (limit) {
            case "upper":
                tmpValue = this.upperlimituc;
                lineColor = this.limitLineUpper.color;
                linewidth = this.limitLineUpper.width;
                linetype = this.limitLineUpper.type;
                break;
            
            case "lower":
                tmpValue = this.lowerlimituc;
                lineColor = this.limitLineLower.color;
                linewidth = this.limitLineLower.width;
                linetype = this.limitLineLower.type;
                break;
        
            default:
                tmpValue = 0;
                lineColor = this.dataCurve.color;
                linewidth = 1;
                linetype = this._createLineType("solid");
                break;
        }
        
        if (tmpValue) {
            let lineData = [    { "x": 0,   "y": tmpValue},  
                                { "x": this.transWidth,  "y": tmpValue}];
        
            let linearScaleY = d3.scaleLinear()
                                .domain([this.scalingminuc, this.scalingmaxuc])
                                .range([0,this.transHeigth])

            let lineFunction = d3.line()
                                    .x(function(d) { return d.x; })
                                    .y((d) => { return (this.transHeigth-  linearScaleY(d.y)); })
                                    .curve(d3.curveLinear);

            this.svg.append("path")
                    .attr("d", lineFunction(lineData))
                    .attr("stroke", lineColor)
                    .attr("stroke-width",linewidth)
                    .attr("stroke-dasharray",linetype)
                    .attr("fill", "none");  
        }  
    }

    /*****************************************************************************************************
     * Draws the grid (vertical, horizontal)
     *
     * @param {number} xCount - the number of lines horizontal
     * @param {number} yCount - the nunber of lines vertical
     ****************************************************************************************************/
    _drawGrid(xCount,yCount) {
               
        // vertical
        if (this.gridcountvertical >= 2)  {
            for (let index = 1; index < (this.gridcountvertical); index++) {
                let lineData = [    { "x": ((this.transWidth / this.gridcountvertical) * index),   "y": 0},  
                                    { "x": ((this.transWidth / this.gridcountvertical) * index),   "y": this.transHeigth}];
                
                let lineFunction = d3.line()
                                        .x(function(d) { return d.x; })
                                        .y(function(d) { return d.y; })
                                        .curve(d3.curveLinear);

                
                this.svg.append("path")
                        .attr("d", lineFunction(lineData))
                        .attr("stroke",  this.gridLine.color)
                        .attr("stroke-width", this.gridLine.width)
                        .attr("stroke-dasharray", this.gridLine.type)
                        .attr("fill", "none");
                        }
        }   

        // horizontal
        if (this.gridcounthorizontal >= 2)  {
            for (let index = 1; index < (this.gridcounthorizontal); index++) {
                let lineData = [    { "x": 0,   "y":  ((this.transHeigth / this.gridcounthorizontal) * index)},  
                                    { "x": this.transWidth,   "y": ((this.transHeigth / this.gridcounthorizontal) * index)}];
               
                let lineFunction = d3.line()
                                        .x(function(d) { return d.x; })
                                        .y(function(d) { return d.y; })
                                        .curve(d3.curveLinear);
                
                this.svg.append("path")
                        .attr("d", lineFunction(lineData))
                        .attr("stroke",  this.gridLine.color)
                        .attr("stroke-width", this.gridLine.width)
                        .attr("stroke-dasharray", this.gridLine.type)
                        .attr("fill", "none");
            }
        }   
    }

    /*****************************************************************************************************
     * Drwas the measureline the value and the unit
     *
     * @param {boolean} visibleUnit - show the unit?
     ****************************************************************************************************/
    _measureLine(visibleUnit) {

        let tmpValue, tempValueTextX, tempValueTextY = 0;
        
        tmpValue = this.measurelineX;

        let textanchor = "middle";
        if (this.measurelineX < (this.transWidth / 2)) {
            textanchor = "start";
            tempValueTextX = tmpValue + parseFloat(this.measureLine.width) / 2 + 3;  // padding
        }
        else {
            textanchor = "end";
            tempValueTextX = tmpValue - parseFloat(this.measureLine.width) / 2 - 3;  // padding
        }

        switch (this.measurevaluevertical) {
            case "top":
                tempValueTextY = this.measureLine.font;
                break;

            case "center":
                tempValueTextY =  (this.transHeigth / 2)+ (this.measureLine.font / 2);
                break;

            case "bottom":
                tempValueTextY =  this.transHeigth - 5;
                break;
        
            default:
                break;
        }

        let lineData = [    { "x": tmpValue,   "y": 0},  
                            { "x": tmpValue,  "y": this.transHeigth}];
        
        let lineFunction = d3.line()
                                .x(function(d) { return d.x; })
                                .y(function(d) { return d.y; })
                                .curve(d3.curveLinear);
       
        this.svg.append("path")
                .attr("d", lineFunction(lineData))
                .attr("stroke", this.measureLine.color)
                .attr("stroke-width", this.measureLine.width)
                .attr("stroke-dasharray", this.measureLine.type)
                .attr("fill", "none");

        let linearScaleX = null;
        if (this.drawdirection === "right") {
            linearScaleX = d3.scaleLinear()
                                .domain([0,this.svgWidth])
                                .range([0,(this.DataBuffer.BufferLength() - 1)])
        } else {
            linearScaleX = d3.scaleLinear()
                                .domain([0,this.svgWidth])
                                .range([(this.DataBuffer.BufferLength() - 1), 0])
        }
        
        let tmpIndex = linearScaleX(tmpValue);
        let tmpText = '';
        if (this.isdesignmode) {
            tmpText = this.DataBuffer.buffer[Math.round(tmpIndex)];
            if (visibleUnit === true) {
                tmpText = tmpText + " " + this.unittext;
            }
            this.svg.append("text")
                        .attr("x", tempValueTextX)
                        .attr("y", tempValueTextY)
                        .text(tmpText)
                        .attr("fill", this.measureLine.color)
                        .attr("text-anchor", textanchor);
        } else {
            tmpText = window.sigApi.properties.convertValueLike(this.id, "recordvalue", this.DataBuffer.buffer[Math.round(tmpIndex)])
            if ((tmpText !== undefined) && (tmpText !=='')) {
                if (visibleUnit === true) {
                    tmpText = tmpText + " " + this.unittext;
                }
            } else {
                tmpText = this.DataBuffer.buffer[Math.round(tmpIndex)];
            }
            this.svg.append("text")
                        .attr("x", tempValueTextX)
                        .attr("y", tempValueTextY)
                        .text(tmpText)
                        .attr("fill", this.measureLine.color)
                        .attr("text-anchor", textanchor);
        }
    }

    /*****************************************************************************************************
     * Converts the linetype to svg format
     *
     * @param {string} linetype - choose the linetype
     * @returns {string} - converted linetype in svg format. 
     ****************************************************************************************************/
    _createLineType(linetype) {
        switch (linetype) {
          
            case "dotted":
                return "1,1,1";
                break;

            case "dashed":
                return "3,3,3";
                break;
        
            default:    // solid
                return "";
                break;
        }
    }

    /*****************************************************************************************************
     * Everything received from the plc is handled inside
     *
     * @param {number} maj - the major id of the event
     * @param {number} min - the minor id of the event
     * @param {DataView} dv - the payload of the event
     ****************************************************************************************************/
    _eventDataFromPLC(maj,min,dv) {
        
        if (dv instanceof DataView) {
            if (dv.byteLength >= 16) { //Sigmatek Header
                const len = dv.getUint32(0, true);
                const majorid = dv.getUint16(4, true);
                const minorid = dv.getUint16(6, true);
                const transactionSource = dv.getUint16(8, true);
                const transactionDest = dv.getUint16(10, true);
                const messageid = dv.getUint32(12, true);
                
                if (dv.byteLength > 16) { //Sigmatek Data
                    
                    // validate unique id for trend object
                    let uniqueID = dv.getInt32(16,true);
                    if (this.uniqueid === uniqueID) {

                        switch (minorid) {
                            case 0: // SingleShot
                                if (this.recording === true) {
                                    this._eventSingleData(dv);
                                }
                                break;
                            case 1: // MultiShot
                                if (this.recording === true) {
                                    this._eventMultiData(dv);
                                }
                                break;
                            case 2: // ClearBuffer
                                    this._deleterecordbuffer();
                                break;
                        
                            default:
                                break;
                        }
                    }
                }
            }
        }
    } 
    
    /*****************************************************************************************************
     * Handles the received data from the plc (one entry)
     *
     * @param {DataView} dv - the payload of the event.
     ****************************************************************************************************/
    _eventSingleData(dv) {
        if (dv instanceof DataView) {
            if (dv.byteLength >= 24)  { //Sigmatek Trend SingleShot Data
                this._addEntry(dv.getInt32(20, true));
                this._updateGraphic();
            }
        }
    } 

    /*****************************************************************************************************
     * Handles the received data from the plc (multiple entries)
     *
     * @param {DataView} dv - the payload of the event.
     ****************************************************************************************************/
    _eventMultiData(dv) {
        if (dv instanceof DataView) {
            if (dv.byteLength >= 24)  { //Sigmatek Trend Multi Data
                const entries = dv.getInt32(20, true);
                let readindex = 24;
                let tmpMultiData = [];
                for (let index = 0; index < entries; index++) {
                    tmpMultiData.push(dv.getInt32(readindex + (index * 4), true));
                }
                tmpMultiData.forEach(element => this._addEntry(element));
                this._updateGraphic();
            }
        }
    }

    /*****************************************************************************************************
     * Handles the received data from the plc (clear buffer) 
     * and
     * Delete event received from the control (property)
     ****************************************************************************************************/
     _deleterecordbuffer() {
        this.DataBuffer.DeleteBuffer();
        this._updateGraphic();    
    }

    /*****************************************************************************************************
     * Measureline move left event received from the control (property)
     *
     * @param {Array} data - number of pixel to move
     ****************************************************************************************************/
    _measurelineleft(data) {

        // distance to shift line
        let changevalue = 1;
        if (data && data.length === 1) {
            changevalue = data[0];
        }

        // limitation
        this.measurelineX = this.measurelineX - changevalue;
        if (this.measurelineX < 1) {
            this.measurelineX = 1;
        }

        // update graphic
        this._updateGraphic();
    }

    /*****************************************************************************************************
     * Measureline move right event received from the control (property)
     *
     * @param {Array} data - number of pixel to move
     ****************************************************************************************************/
    _measurelineright(data) {

        // distance to shift line
        let changevalue = 1;
        if (data && data.length === 1) {
            changevalue = data[0];
        }

        // limitation
        this.measurelineX = this.measurelineX + changevalue;
        if (this.measurelineX > (this.transWidth - 1)) {
            this.measurelineX = (this.transWidth - 1);
        }

        // update graphic
        this._updateGraphic();
    }

    /*****************************************************************************************************
     * Stop recording event received from the control (property)
     ****************************************************************************************************/
    _stopRecording() {
        this.recording = false;

        // stop timer only in case datapoint is recorded
        if (this.recordtype === "Datapoint") {
            this._stopTrendInterval();
        }
    }

    /*****************************************************************************************************
     * Start recording event received from the control (property)
    * ****************************************************************************************************/
    _startRecording() {
        this.recording = true;

        // start interval only in case datapoint is recored
        if (this.recordtype === "Datapoint") {
            this._startTrendInterval();
        }
    }
    
    /*****************************************************************************************************
     * Checks the lower or upper limit and and calls the coresponding functionblock
     *
     * @param {number} entry - the new value which has to be checked
     ****************************************************************************************************/
    _checkLimits(entry) {
        // Check upper limit
        if (this.visibleupperlimit === true) {
            if (entry > this.upperlimituc) {
                if (this.mViolationMaxAlreadyFired === false) {
                    this.mViolationMaxAlreadyFired = true;
                    this.mViolationMaxGoneFired = false;
                    this._dispatchEvent('violation-limit-upper');
                }
            } else {
                if (this.mViolationMaxGoneFired === false) {
                    this.mViolationMaxGoneFired = true;
                    this.mViolationMaxAlreadyFired = false;
                    this._dispatchEvent('violation-limit-upper-gone');
                }
            }
        } else {
            this.mViolationMaxAlreadyFired = false;
            this.mViolationMaxGoneFired = false;
        }

        // Check lower limit
        if (this.visiblelowerlimit === true) {
            if (entry <this.lowerlimituc) {
                if (this.mViolationMinAlreadyFired === false) {
                    this.mViolationMinAlreadyFired = true;
                    this.mViolationMinGoneFired = false;
                    this._dispatchEvent('violation-limit-lower');
                }
            } else {
                if (this.mViolationMinGoneFired === false) {
                    this.mViolationMinGoneFired = true;
                    this.mViolationMinAlreadyFired = false;
                    this._dispatchEvent('violation-limit-lower-gone');
                }
            }

        } else {
            this.mViolationMinAlreadyFired = false;
            this.mViolationMinGoneFired = false;
        }
    }

    /*****************************************************************************************************
     * Add a new value to the buffer and checks the limits 
     *
     * @param {number} entry - the new value
     ****************************************************************************************************/
    _addEntry(entry) {
        this.DataBuffer.AddEntry(entry);
        if (!this.isdesignmode) {
            this._checkLimits(entry);
        }
    }

    /*****************************************************************************************************
     * Handles the movement of the measureline. Is called from a Eventlistener.
     *
     * @param {Event} event - includes the event information.
     ****************************************************************************************************/
    _moveMeasureLine(event) {
        
        switch (event.detail.state) {
            
            case 'track':
                
                switch (this.rotation) {
                    case 0:
                        this.measurelineX = this.measurelineX + (event.detail.ddx / this.getScaleFactor().x);
                        break;
                    
                    case 90:
                        this.measurelineX = this.measurelineX + (event.detail.ddy / this.getScaleFactor().y);
                        break;  
                    
                    case 180:
                        this.measurelineX = this.measurelineX - (event.detail.ddx / this.getScaleFactor().x);
                        break;
                    
                    case 270:
                        this.measurelineX = this.measurelineX - (event.detail.ddy / this.getScaleFactor().y);
                        break;

                    default:
                        break;
                }

                if (this.measurelineX < 0) {
                    this.measurelineX = 0;
                }
                if (this.measurelineX > this.transWidth) {
                    this.measurelineX = this.transWidth;
                }
                
                this._updateGraphic();
                break;
        
            default:
                break;
        }
    }

    /****************************************************************************************************
     * this observer runs if the value of the property 'unittext' changes
     * it will not run if the new value is equal to the old value!
     * during the initalization of the property the value may be undefined
     ****************************************************************************************************/
    _updateUnit() {
        if (!this.isdesignmode) {
            this._updateGraphic();
        }
    }

    /****************************************************************************************************
    * Method to get all the border width elements independent of the used shorthand style
    ****************************************************************************************************/
    _getBorderWidth() {
        const borderStyle = this.style.getPropertyValue('--theme-' + this.tagName.toLowerCase() + '-border-style');

        if (borderStyle === "none") {
            this.borderTopWidth = this.borderRightWidth = this.borderBottomWidth = this.borderLeftWidth = 0;
            return;
        }

        let borderWidth = this.style.getPropertyValue('--theme-' + this.tagName.toLowerCase() + '-border-width');

        if (borderWidth === "") {
            this.borderTopWidth = this.borderRightWidth = this.borderBottomWidth = this.borderLeftWidth = 0;            
        } else {
            let values = borderWidth.split(' ');
        
            switch (values.length) {
                case 1: // "5px"
                    this.borderTopWidth = this.borderRightWidth = this.borderBottomWidth = this.borderLeftWidth = parseInt(values[0]);
                    break;
                case 2: // "1px 5px"
                    this.borderTopWidth = this.borderBottomWidth = parseInt(values[0]);
                    this.borderRightWidth = this.borderLeftWidth = parseInt(values[1]);
                    break;
                case 3: // "1px 5px 10px"
                    this.borderTopWidth = parseInt(values[0]);
                    this.borderRightWidth = this.borderLeftWidth = parseInt(values[1]);
                    this.borderBottomWidth = parseInt(values[2]);
                    break;
                case 4: // "1px 5px 10px 15px"
                    this.borderTopWidth = parseInt(values[0]);
                    this.borderRightWidth = parseInt(values[1]);
                    this.borderBottomWidth = parseInt(values[2]);
                    this.borderLeftWidth = parseInt(values[3]);
                    break;
                default: // invalid format => use no border
                    this.borderTopWidth = this.borderRightWidth = this.borderBottomWidth = this.borderLeftWidth = 0;
                    break;
            }
        }
    }              
}
customElements.define(LasalRuntimeSigTrendElement.is, LasalRuntimeSigTrendElement);
