// 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'

/****************************************************************************************************
 *
 *
 * @export
 * @class LasalRuntimeControlActvalRound
 * @extends {LasalRuntimeSigElement}
 ****************************************************************************************************/
export class LasalRuntimeControlActvalRound extends LasalRuntimeSigElement {

    /****************************************************************************************************
     * Returns the Template HTML & CSS of this Control
     *
     * @readonly
     * @static
     ****************************************************************************************************/
    static get template() {
        return SigPolymer.html`
        <style include="sig-element-css">
            :host {
                /* Basic styling of the component */
                background-color: var(--theme-sig-control-actval-round-background-color, inherit);

                border-width: var(--theme-sig-control-actval-round-border-width, 0px);
                border-color: var(--theme-sig-control-actval-round-border-color, inherit);
                border-style: var(--theme-sig-control-actval-round-border-style, none);
                border-radius: var(--theme-sig-control-actval-round-border-radius, 0px);
                
                /* Basic box model settings */
                display: block;
                position: absolute;
                overflow: hidden;
                box-sizing: border-box;  /* prevents the arc etc. from being pushed outside the bounds */
            }

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

            :host * {
                box-sizing: border-box;
            }

            .clearfix:after {
                @apply --clearfix
            }

            .sig-control-actval-round {
                position: relative;
                width: 100%;
                height: 100%;
            } 

            #actval {
                width: 100%;
                height: 100%;
            }

            #texts-container {
                position: absolute;
                top: var(--theme-sig-control-actvalue-round-txt-padding-top, 10px);
                left: var(--theme-sig-control-actvalue-round-txt-padding-left, 10px);
                z-index: 1;
                width: calc(100% - var(--theme-sig-control-actvalue-round-txt-padding-right, 10px) - var(--theme-sig-control-actvalue-round-txt-padding-left, 10px));
                height: calc(100% - var(--theme-sig-control-actvalue-round-txt-padding-top, 10px) - var(--theme-sig-control-actvalue-round-txt-padding-bottom, 10px));

                display: flex;
                flex-direction: column;
                flex-wrap: nowrap;
                justify-content: var(--theme-sig-control-actval-round-text-justify-content, center);
                align-items: center;
            }

            #image-container {
                flex: 0 1 var(--theme-sig-control-actvalue-round-txt-image-max-height, 50px);
                width: 100%;
                background-size: contain;
                background-repeat: no-repeat;
                background-position: center;
            }

            :host(:not([imgisvisible])) #image-container {
                display: none;
            }

            :host(:not([unitisvisible])) #unit-container {
                display: none;
            }

            :host(:not([descisvisible])) #desc-container {
                display: none;
            }

            :host(:not([valueisvisible])) #value-container {
                display: none;
            }

            #value-container {
                color: var(--theme-sig-control-actvalue-round-value-container-color, inherit);
            }

            #unit-container {
                color: var(--theme-sig-control-actvalue-round-unit-container-color, inherit);
            }

            #desc-container {
                color: var(--theme-sig-control-actvalue-round-desc-container-color, inherit);
            }
        </style>
        <div class="sig-control-actval-round clearfix" controlCustomFont>
            <div id="actval">
            </div>
            <div id="texts-container">
                <div id="unit-container" class="text-container">[[unittext]]</div>
                <div id="value-container" class="text-container"></div>
                <div id="image-container" class="text-container"></div>
                <div id="desc-container" class="text-container">[[titletext]]</div>
            </div>
        </div>
        `
    }

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

    /****************************************************************************************************
     * 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
     ****************************************************************************************************/
    static get importMeta() {
        return import.meta;
    }

    /****************************************************************************************************
     * define the main porperties of the component
     * reflected property names should always be lower case so it can be used directly on the element as an attribute
     ****************************************************************************************************/
    static get properties() {
        let props = {
                // ---------- value ----------
                valueuc: {
                    type: Number,
                    value: 0,
                    observer: "_redraw",
                },
                limitlowuc: {
                    type: Number,
                    value: 0,
                    observer: "_redraw",
                },
                limithighuc: {
                    type: Number,
                    value: 100,
                    observer: "_redraw",
                },
                // ---------- general configs ----------
                unitisvisible: {
                    type: Boolean,
                    value: true,
                    reflectToAttribute: true,
                    observer: "_redraw"
                },
                descisvisible: {
                    type: Boolean,
                    value: true,
                    reflectToAttribute: true,
                    observer: "_redraw"
                },
                valueisvisible: {
                    type: Boolean,
                    value: true,
                    reflectToAttribute: true,
                    observer: "_redraw"
                },
                imgisvisible: {
                    type: Boolean,
                    value: true,
                    reflectToAttribute: true,
                    observer: "_redraw"
                },
                imgsrc: {
                    type: String,
                    value: '',
                    observer: "_redraw"
                },
                // ---------- sizes ----------
                arcwidth: {
                    type: Number,
                    value: 25,
                    observer: "_initObs"
                },
                startangle: {
                    type: Number,
                    value: 45,
                    observer: "_initObs"
                },
                endangle: {
                    type: Number,
                    value: 315,
                    observer: "_initObs"
                },
                progresscounterclockwise: {
                    type: Boolean,
                    value: false,
                    observer: '_redraw'
                },  
                previewvalue: {
                    type: Number,
                    value: 50,
                    observer: '_redraw'
                },  
                previewmin: {
                    type: Number,
                    value: 0,
                    observer: '_redraw'
                },  
                previewmax: {
                    type: Number,
                    value: 100,
                    observer: '_redraw'
                },  
                // ---------- color ----------
                arccolorbg: {
                    type: String,
                    observer: "_redraw",
                    value: 'rgba(165,171,176,1)'
                },
                arccolorfill: {
                    type: String,
                    observer: "_redraw",
                    value: 'rgba(0,197,255,1)'
                },
                // ---------- fonts ----------
                unitfont: {
                    type: Object,
                    value: (() => {return {}})(),
                    observer: '_applyFonts'
                },
                valuefont: {
                    type: Object,
                    value: () => {return {}},
                    observer: '_applyFonts'
                },
                descfont: {
                    type: Object,
                    value: () => {return {}},
                    observer: '_applyFonts'
                }
            };
        return props;
    }

    /****************************************************************************************************
     * Creates an instance of LasalRuntimeControlActvalRound.
     * 
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    constructor() {
        super();

        // add predef properties to avoid recalculations during drawing
        this.addPredefProps([
            {
                src: 'value',
                dest: 'titletext',
                type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_TEXT1
            }, 
            {
                src: 'value',
                dest: 'unittext',
                type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNIT_TEXT
            }, 
            {
                src: 'value',
                dest: 'valueuc',
                type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNCONVERTED_VALUE
            }, 
            {
                src: 'limitlow',
                dest: 'limitlowuc',
                type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNCONVERTED_VALUE
            }, 
            {
                src: 'limithigh',
                dest: 'limithighuc',
                type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNCONVERTED_VALUE
            }
        ]);

        // add required properties
        this.sigAddRequiredProperty('valueuc');
        this.sigAddRequiredProperty('limitlowuc');
        this.sigAddRequiredProperty('limithighuc');        
    }

    /****************************************************************************************************
     * called after property default values are set and local DOM is initialized
     *
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    ready() {
        super.ready();
        this.isReady = true;

        // wait for next render in designer, for getting control bounds
        if (this.isdesignmode) {

            // simulation value for design mode
            this.titletext = "Text";
            this.unittext = "Unit"

            SigPolymer.afterNextRender(this, function () {
                this._getFactor();
                this._draw();
            });

        // no designer, get control bounds immediately
        } else {
            if (this.propsReady) {
                this._getFactor();
                this._draw();
            }  
        }
    }

    /****************************************************************************************************
     * called when values are ready
     *
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    sigOnRequiredPropertiesReady() {
        super.sigOnRequiredPropertiesReady();
        this.propsReady = true; 
        if (this.isReady) {
            this._getFactor();
            this._draw();
        }
    }

    /****************************************************************************************************
     * function is called if component gets resized in designmode
     *
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    designerOnBoundsChanged() {
        if (this.drawed) {
            SigPolymer.afterNextRender(this, () => {
                this._getFactor();
                this._draw();
            })
        }
    }

    /****************************************************************************************************
     * function to calculate factor for drawing and draw bar
     *
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    _initObs () {
        if (this.drawed) {
            this._getFactor();
            this._draw();
        }
    }

    /****************************************************************************************************
     * function to calculate factor for drawing and draw bar
     *
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    _getFactor() {

        // get bound of control
        this.bounds = this.getControlBounds();

        //correct angles for calculation
        let startAngle = (this.startangle - 270);
        let endAngle = (this.endangle - 270);

        //check for crossing the axes
        const cross0 = (startAngle <= 0) && (endAngle >= 0);
        const cross90 = (startAngle <= 90) && (endAngle >= 90);
        const cross180 = (startAngle <= -180) && (endAngle >= -180);
        const cross270 = (startAngle <= -90) && (endAngle >= -90);

        // calculate maximum dimensions
        const xStart = Math.cos(startAngle * Math.PI / 180);
        const xEnd = Math.cos(endAngle * Math.PI / 180);
        const yStart = Math.sin(startAngle * Math.PI / 180);
        const yEnd = Math.sin(endAngle * Math.PI / 180);

        // get maximums and minimums for bound calculation
        const xMinFactor = Math.min(xStart, xEnd);
        const xMaxFactor = Math.max(xStart, xEnd);
        const yMinFactor = Math.min(yStart, yEnd);
        const yMaxFactor = Math.max(yStart, yEnd);

        // calculate outer bounds
        const left = cross180 ? -1 : xMinFactor;
        const right = cross0 ? 1 : xMaxFactor;
        const top = cross90 ? 1 : yMaxFactor;
        const bottom = cross270 ? -1 : yMinFactor;

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

        // we need to manipulate control width as the arcwidth needs to be evaluated too
        // this is only needed if positions are on same side which is the case if the sign is the same
        let controlWidth = (this.bounds.width - this.borderLeftWidth - this.borderRightWidth);
        let controlHeight = (this.bounds.height - this.borderTopWidth - this.borderBottomWidth);

        // check if controlwidth needs to be compensated
        if ((left < 0) === (right < 0)) {
            //we have to compensate the arcwidth for control width
            if (Math.abs(left) < Math.abs(right)) {
                controlWidth -= this.arcwidth * Math.abs(left);
            }
            else {
                controlWidth -= this.arcwidth * Math.abs(right);
            }
        }

        // check if controlHeight needs to be compensated
        if ((top < 0) === (bottom < 0)) {
            //we have to compensate the arcwidth for control width
            if (Math.abs(top) < Math.abs(bottom)) {
                controlHeight -= this.arcwidth * Math.abs(top);
            }
            else {
                controlHeight -= this.arcwidth * Math.abs(bottom);
            }
        }

        // calculate factor
        let widthFactor =  controlWidth / Math.abs(left - right);
        let heightFactor = controlHeight / Math.abs(top - bottom);
        this.radius = (heightFactor < widthFactor ? heightFactor : widthFactor);        
    }

    /****************************************************************************************************
     * draw all the components for displaying the act value
     *
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    _draw() {

        let majorWidth = this.arcwidth;
        if ((majorWidth === "") || (majorWidth === undefined)) {
            majorWidth = 10; // set to default (10px)
        }
        // In case the end angle is smaller, switch it so that they can be compared
        while (this.startangle > this.endangle) {
            this.endangle += 2 * 180;
        }

        // remove old svg
        while(this.$.actval.firstChild) {
            this.$.actval.removeChild(this.$.actval.firstChild);
        }

        // crate new svg
        this.body = d3.select(this.$.actval)
                        .append("svg:svg")
                        .attr("width", "100%")
                        .attr("height", "100%");

        // background arc
        this.body.append("svg:path")
                .attr("d", d3.arc()
                    .innerRadius(this.radius - majorWidth)
                    .outerRadius(this.radius)
                    .startAngle(this.startangle / 180 * Math.PI)
                    .endAngle(this.endangle / 180 * Math.PI)
                )
                .attr("transform", "translate(" + String(this.radius) + "," + String(this.radius) + ") rotate(180)")
                .style("fill", this.arccolorbg);

        // calc the new endpoint of the arc
        let percentage;

        // calculate position of bar
        if (this.isdesignmode === false) {
            if ((this.limitlowuc !== undefined) && (this.limithighuc !== undefined)){
                
                let calcValue = parseFloat(this.valueuc);
                let limithigh = parseFloat(this.limithighuc);
                let limitlow = parseFloat(this.limitlowuc);

                // check limits
                if (calcValue > limithigh) {
                    calcValue = limithigh;
                } else if (calcValue < limitlow) {
                    calcValue = limitlow;
                }

                // calculate position
                percentage = (calcValue - this.limitlowuc) / (this.limithighuc - this.limitlowuc);
                
                //avoid calulating with NaN
                if(isNaN(percentage)) {
                    percentage = 0;
                }
                 // don't go over 100%!
                if (percentage > 1) {
                    percentage = 1;
                }
            }
            else {
                percentage = 0;
            }
        } else {
            percentage = (this.previewvalue - this.previewmin) / (this.previewmax - this.previewmin);
        }

        // inverse direction
        let tempStartAngle = this.startangle;
        let tempEndAngle = this.endangle;
        if (this.progresscounterclockwise === true) {
            let tempAngle = tempStartAngle;
            tempStartAngle = tempEndAngle;
            tempEndAngle = tempAngle;
        }
        let calcEndPoint = percentage * ((tempEndAngle / 180 * Math.PI) - (tempStartAngle / 180 * Math.PI)) + (tempStartAngle / 180 * Math.PI);
        
        // foreground arc
        this.body.append("svg:path")
                .attr("d", d3.arc()
                    .innerRadius(this.radius - majorWidth)
                    .outerRadius(this.radius)
                    .startAngle(tempStartAngle / 180 * Math.PI)
                    .endAngle(calcEndPoint)
                )
                .attr("transform", "translate(" + String(this.radius) + "," + String(this.radius) + ") rotate(180)")
                .style("fill", this.arccolorfill);

        // fit svg to size of control
        let svg = this.$.actval.querySelector('svg');
        let svgBBox = svg.getBBox();
        svg.setAttribute('viewBox', `${svgBBox.x} ${svgBBox.y} ${svgBBox.width} ${svgBBox.height}`);

        // first draw is done
        this.drawed = true;
    }

    /****************************************************************************************************
     * function to redraw the act value and bar
     *
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    _redraw() {

        // convert value
        let valueString; 
        if (!this.isdesignmode) {
            valueString = String(this.value);
        } else {
            valueString = String(this.previewvalue);
        }
        
        // set the Text
        if(this.valueisvisible) {
            this.$["value-container"].innerHTML = valueString;
        }

        // set image
        if ((this.imgisvisible) && (this.imgsrc !== "") && (this.imgsrc !== "none")) {
            this.$["image-container"].style.backgroundImage = "url('" + this.imgsrc.replace(/\\/g, "/") + "')";
            this.$["image-container"].style.removeProperty('display');
        } else {
            this.$["image-container"].style.display = 'none';
        }

        // refresh bar
        if(this.drawed === true) {
            this._draw();
        }
    }

    /****************************************************************************************************
     * Overriding the default sigApplyCSSValue function
     * needed because the component needs to be redrawn on some css changes
     *
     * @param {Object} cssObj
     * @param {Boolean} [doBoundsCheck=false]
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    sigApplyCSSValue(cssObj, doBoundsCheck = false) {
        super.sigApplyCSSValue(cssObj, doBoundsCheck);

        // value only changes in design mode
        if(this.isdesignmode) {
            // if there are more than one change, it is the inital load, and the component gets drawn correctly anyway
            const cssObjLength = Object.keys(cssObj).length;
            if((cssObjLength === 1) && 
              ((cssObj.hasOwnProperty('--theme-sig-control-actval-round-border-width'))||
               (cssObj.hasOwnProperty('--theme-sig-control-actval-round-border-style')))) {
                this._initObs();
            }
        }
    }

    /****************************************************************************************************
     * Overriding the default sigApplyFontObject function
     *
     * @param {String} propName
     * @param {Object} fontObj
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    sigApplyFontObject(propName, fontObj) {
        super.sigApplyFontObject(propName, fontObj);

        if(propName === 'elementMainFont') {
            this.mainFont = fontObj;
            this.mainFont.available = true;
        } else if(propName === 'valuefont') {
            this.valuefont = fontObj;
            this.valuefont.available = true;
        } else if(propName === 'unitfont') {
            this.unitfont = fontObj;
            this.unitfont.available = true;
        } else if(propName === 'descfont') {
            this.descfont = fontObj;
            this.descfont.available = true;
        }

        // redraw if already initialized!
        if (this.drawed === true) {
            this._draw();
        }
    }

    /****************************************************************************************************
     * Function to apply fonts
     *
     * @return {*} 
     * @memberof LasalRuntimeControlActvalRound
    ****************************************************************************************************/
    _applyFonts() {
        
        if (this.valuefont && Object.keys(this.valuefont).length) {
            this._applyFontObjectToNode(this.$["value-container"], this.valuefont);
        } else if (this.mainFont && Object.keys(this.mainFont).length) {
            this._applyFontObjectToNode(this.$["value-container"], this.mainFont);
        }
        
        if (this.unitfont && Object.keys(this.unitfont).length) {
            this._applyFontObjectToNode(this.$["unit-container"], this.unitfont);
        } else if (this.mainFont && Object.keys(this.mainFont).length) {
            this._applyFontObjectToNode(this.$["unit-container"], this.mainFont);
        }
        
        if (this.descfont && Object.keys(this.descfont).length) {
            this._applyFontObjectToNode(this.$["desc-container"], this.descfont);
        } else if (this.mainFont && Object.keys(this.mainFont).length) {
            this._applyFontObjectToNode(this.$["desc-container"], this.mainFont);
        }
    }

    /****************************************************************************************************
    * 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") || (borderStyle === "")) {
            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(LasalRuntimeControlActvalRound.is, LasalRuntimeControlActvalRound);