// 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 { mixinManager } from '../../sigmatek/sig-element-mixins/mixin-manager.js';
import { UnitConversionMixin } from '../../sigmatek/sig-element-mixins/unit-conversion-mixin.js';
const mixins = [UnitConversionMixin];

/****************************************************************************************************
 * @class LasalRuntimeSigNumericInputElement
 * @extends {LasalRuntimeSigElement}
 ****************************************************************************************************/
export class LasalRuntimeSigNumericInputElement extends mixinManager.mix(mixins, 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
     ****************************************************************************************************/
    static get importMeta() {
        return import.meta;
    }

    /****************************************************************************************************
     * Return the component style and template as a template literal
     ****************************************************************************************************/
    static get template() {
        return SigPolymer.html`
        <style include="sig-element-css">
            :host {
                /* Basic styling of the component */
                color: var(--theme-sig-control-numeric-input-color, inherit);
                background-color: var(--theme-sig-control-numeric-input-background-color, inherit);
                border-width: var(--theme-sig-control-numeric-input-border-width, 0px);
                border-color: var(--theme-sig-control-numeric-input-border-color, inherit);
                border-style: var(--theme-sig-control-numeric-input-border-style, solid);
                border-radius: var(--theme-sig-control-numeric-input-border-radius, 3px);
                border-top-left-radius: var(--theme-sig-control-numeric-input-border-radius-tl, var(--theme-sig-control-numeric-input-border-radius, 3px));
                border-top-right-radius: var(--theme-sig-control-numeric-input-border-radius-tr, var(--theme-sig-control-numeric-input-border-radius, 3px));
                border-bottom-right-radius: var(--theme-sig-control-numeric-input-border-radius-br, var(--theme-sig-control-numeric-input-border-radius, 3px));
                border-bottom-left-radius: var(--theme-sig-control-numeric-input-border-radius-bl, var(--theme-sig-control-numeric-input-border-radius, 3px));

                /* Basic box model settings */
                position: absolute;
                overflow: hidden;
                padding: 0px 0px;
                display: block;
            }

            :host * {
                /* Reset margins and paddings for child elements and turn of text selection */
                @apply --notextselect;
                margin: 0;
                padding: 0;
            }

            .clearfix:after {
                /* Import the global clearfix css mixin */
                @apply --clearfix
            }

            /* set border color of active state */
            :host([iskeyboardopen]) {
                border-color: var(--theme-sig-control-numeric-input-activ-border-color, inherit);
            }

            /* border has to be deactivated cause bordergradient should be shown */
            :host([bordergradient]:not([iskeyboardopen])),
            :host([bordergradientactiv][iskeyboardopen]) {
                border-width: 0px;            
            }

            /* basic definition for container */
            .sig-input {
                width: 100%;
                height: 100%;
                text-decoration: inherit;
                position: relative;
                box-sizing: border-box;
            }

            /* set bordergradient in inactive style */
            :host([bordergradient]:not([iskeyboardopen])) .sig-input {
                border-width: 0px;
                padding: var(--theme-sig-control-numeric-input-border-width, 0px);
                background-image: linear-gradient(var(--theme-sig-control-numeric-input-border-color-direction, to bottom), var(--theme-sig-control-numeric-input-border-color, inherit), var(--theme-sig-control-numeric-input-border-color-stop, var(--theme-sig-control-numeric-input-border-color)));
            }
            
            /* set bordergradient of active style */
            :host([bordergradientactiv][iskeyboardopen]) .sig-input {
                border-width: 0px;
                padding: var(--theme-sig-control-numeric-input-border-width, 0px);
                background-image: linear-gradient(var(--theme-sig-control-numeric-input-activ-border-color-direction, to bottom), var(--theme-sig-control-numeric-input-activ-border-color, inherit), var(--theme-sig-control-numeric-input-activ-border-color-stop, var(--theme-sig-control-numeric-input-activ-border-color)));
                
            }

            /* basic definition of background style class -> needed for box shadow otherwise border would be overlayed */
            .sig-input-background {
                display: flex;
                flex-direction: column;
                flex-wrap: wrap;
                justify-content: space-evenly;

                cursor: pointer;
                color: inherit;

                /* default set box shadow inactive */
                box-shadow: inset 0 0 0px rgba(255, 0, 0, 0);

                background-color: var(--theme-sig-control-numeric-input-background-color, inherit);
                background-image: var(--theme-sig-control-numeric-input-background-image, none),
                    linear-gradient(var(--theme-sig-control-numeric-input-background-color-direction, to bottom),
                    var(--theme-sig-control-numeric-input-background-color, inherit),
                    var(--theme-sig-control-numeric-input-background-color-stop, var(--theme-sig-control-numeric-input-background-color)));

                border: none;
                outline: none;
                width: 100%;
                height: 100%;
                box-sizing: border-box;
                text-decoration: inherit;
            }

            /* set active colors */
            :host([iskeyboardopen]) .sig-input-background {
                background-color: var(--theme-sig-control-numeric-input-value-active-background-color, inherit);
                background-image: var(--theme-sig-control-numeric-input-background-image, none),
                    linear-gradient(var(--theme-sig-control-numeric-input-active-background-color-direction, to bottom),
                    var(--theme-sig-control-numeric-input-value-active-background-color, inherit),
                    var(--theme-sig-control-numeric-input-value-active-background-color-stop, var(--theme-sig-control-numeric-input-value-active-background-color)));

                color: var(--theme-sig-control-numeric-input-value-active-color, inherit);
            }

            /* avoid cursor in design mode */
            :host([isdesignmode]) .sig-input-field,
            :host([isdesignmode]) .sig-input-field:active,
            :host([isdesignmode]) .sig-input-field:hover,
            :host([isdesignmode]) .sig-input-background,
            :host([isdesignmode]) .sig-input-background:active,
            :host([isdesignmode]) .sig-input-background:hover {
                cursor: unset;
            }

            /* set box shadow of normal state */
            :host([shadowactive]:not([iskeyboardopen])) .sig-input-background {
                box-shadow: inset 0 0 var(--theme-sig-control-numeric-input-shadow-size, 5px) var(--theme-sig-control-numeric-input-shadow-color, inherit);
            }

            /* set box shadow of active state */
            :host([shadowactivactive][iskeyboardopen]) .sig-input-background {
                box-shadow: inset 0 0 var(--theme-sig-control-numeric-input-pressed-shadow-size, 5px) var(--theme-sig-control-numeric-input-pressed-shadow-color, inherit);
            }

            ::placeholder {
                color: var(--theme-sig-control-numeric-input-placeholder-color, #8e8e8e);
                opacity: 1;
                font-family: inherit;
                font-size: inherit;
                text-decoration: inherit;
            }

            /* basic style definition of buttons */
            .sig-input-decrease,
            .sig-input-increase {
                height: 100%;
                width: var(--theme-sig-control-numeric-input-button-width, 30px);
                /* set height of background to display the button image properly */
                background-size: var(--theme-sig-control-numeric-input-button-width, 30px) var(--theme-sig-control-numeric-input-button-height, 20px);
                background-repeat: no-repeat;
                background-position: center;

                position: relative;
            }

            /* style class definition of decrease button */
            .sig-input-decrease {
                top: var(--theme-sig-control-numeric-input-decrease-offset-top, 0px);
                order: 3;
            }

            /* style class definition of increase button */
            .sig-input-increase {
                top: var(--theme-sig-control-numeric-input-increase-offset-top, 0px);
                order: 2;
            }

            /*****************************************************************************************
            ** handle positions in flex -> default is handled in general style class definition
            ******************************************************************************************/

            /* selector for column 3 */
            :host([buttonposition="row-reverse"][buttonalignment="row"]) .sig-input-increase,
            :host([buttonposition="row"][buttonalignment="row"][buttononeachside]) .sig-input-increase,
            :host([buttonposition="row"]:not([buttononeachside])) .sig-input-background-flex {
                order: 3;
            }

            /* selector for column 2*/
            :host([buttonposition="row-reverse"][buttonalignment="row"]:not([buttononeachside])) .sig-input-decrease,
            :host([buttonposition="row"]:not([buttonalignment="row"]):not([buttononeachside])) .sig-input-decrease,
            :host([buttononeachside]) .sig-input-background-flex {
                order: 2;
            }            

            /* selector for column 1*/
            :host(:not([buttonalignment="row"])[buttononeachside]) .sig-input-increase,
            :host([buttonalignment="row"][buttononeachside]) .sig-input-decrease,
            :host([buttonposition="row"]:not([buttonalignment="row"]):not([buttononeachside])) .sig-input-increase,
            :host([buttonposition="row"][buttonalignment="row"]:not([buttononeachside])) .sig-input-decrease {
                order: 1;
            }    

            /* special definition for displaying buttons in one column */
            :host([buttonalignment="column-reverse"]:not([buttononeachside])) .sig-input-decrease,
            :host([buttonalignment="column-reverse"]:not([buttononeachside])) .sig-input-increase {
                height: calc(50% - (var(--theme-sig-control-numeric-input-increase-margin-between, -10px) / 2));
            }

            /* value and unit can be bigger */
            :host([buttonalignment="column-reverse"]:not([buttononeachside])) .sig-input-background-flex {
                width: calc(100% - var(--theme-sig-control-numeric-input-button-width, 30px) - var(--theme-sig-control-numeric-input-increase-margin-right, 0px));
            }            

            /* no spacing between buttons in width */
            :host([buttononeachside]) .sig-input-background-flex {
                width: calc(100% - (2 * var(--theme-sig-control-numeric-input-button-width, 30px)) - (2 * var(--theme-sig-control-numeric-input-increase-margin-right, 0px)));
            }                   

            /* handle margin between buttons -> increase is shown on right side */
            :host([buttonalignment="row"]:not([buttononeachside])) .sig-input-increase {
                margin-left: var(--theme-sig-control-numeric-input-increase-margin-between, -10px);
            }

            /* handle margin between buttons -> decrease is shown on right side */
            :host([buttonalignment="row-reverse"]:not([buttononeachside])) .sig-input-decrease {               
                margin-left: var(--theme-sig-control-numeric-input-increase-margin-between, -10px);
            }

            /* handle margin between buttons -> increase is shown on top */
            :host([buttonalignment="column-reverse"]:not([buttononeachside])) .sig-input-increase {             
                margin-bottom: var(--theme-sig-control-numeric-input-increase-margin-between, -10px);
            }

            /* handle margin to border if buttons are displayed on each side */
            :host([buttonalignment="row"][buttononeachside]) .sig-input-increase,
            :host(:not([buttonalignment="row"])[buttononeachside]) .sig-input-decrease {
                margin-right: var(--theme-sig-control-numeric-input-increase-margin-right, 0px);
            }            

            /* handle margin to border if buttons are displayed on each side */
            :host([buttonalignment="row"][buttononeachside]) .sig-input-decrease,
            :host(:not([buttonalignment="row"])[buttononeachside]) .sig-input-increase {
                margin-left: var(--theme-sig-control-numeric-input-increase-margin-right, 0px);
            }  

            /* handle margin to border if buttons are displayed on one side */
            :host(:not([buttononeachside])[buttonposition="row-reverse"]:not([buttonalignment="row-reverse"])) .sig-input-increase,
            :host(:not([buttononeachside])[buttonposition="row-reverse"]:not([buttonalignment="row"])) .sig-input-decrease {
                margin-right: var(--theme-sig-control-numeric-input-increase-margin-right, 0px);
            }

            /* handle margin to border if buttons are displayed on one side */
            :host(:not([buttononeachside])[buttonposition="row"]:not([buttonalignment="row"])) .sig-input-increase,
            :host(:not([buttononeachside])[buttonposition="row"]:not([buttonalignment="row-reverse"])) .sig-input-decrease {
                margin-left: var(--theme-sig-control-numeric-input-increase-margin-right, 0px);
            } 

            /* basic definition of the background flex container -> used as container for value and unit */
            .sig-input-background-flex {
                width: calc(100% - (2 * var(--theme-sig-control-numeric-input-button-width, 30px)) - var(--theme-sig-control-numeric-input-increase-margin-between, -10px) - var(--theme-sig-control-numeric-input-increase-margin-right, 0px));
                height: 100%;
                display: flex;  
                justify-content: space-between;
                order: 1;
            }

            /* unit should be displayed on top */
            :host([unitactiv][unitposition="uppertop"]) .sig-input-background-flex {
                flex-direction: column-reverse;
            }

             /* unit should be displayed on bottom */
            :host([unitactiv][unitposition="lowerbottom"]) .sig-input-background-flex {
                flex-direction: column;
            }           

             /* unit should be displayed on the left*/
             :host([unitactiv][unitposition="leftside"]) .sig-input-background-flex {
                flex-direction: row-reverse;
            } 

            /* only align items if unit not on top or bottom of value otherwise horizontal alignment won't work */
            :host([unitactiv][unitposition="leftside"]) .sig-input-background-flex,
            :host([unitactiv][unitposition="rightside"]) .sig-input-background-flex {
                align-items: center;
            }
            
            /* basic definition of value container */
            .sig-input-field {
                cursor: pointer;
                color: inherit;
                white-space: nowrap;
                text-overflow: ellipsis;
                background-color: transparent;
                border: none;
                outline: none;

                width: 100%;

                /*avoid text select on longpress */
                pointer-events: none;

                align-self: var(--theme-sig-control-numeric-input-value-vertical-align, center);
                text-align: var(--theme-sig-control-numeric-input-value-text-align, center);
                text-indent: 0px;

                box-sizing: border-box;
                padding-right: var(--theme-sig-control-numeric-input-margin-leftright, 0px);
                padding-left: var(--theme-sig-control-numeric-input-margin-leftright, 0px);
                padding-top: var(--theme-sig-control-numeric-input-margin-top, 0px);
                padding-bottom: var(--theme-sig-control-numeric-input-margin-top, 0px);    
                text-decoration: inherit;
                font-family: inherit;
                font-size: inherit;
                font-weight: inherit;
                font-style: inherit;                        
            }

            input {
                font-size: inherit;
            }

            /* unit displaying style class */
            .content {
                cursor: pointer;
                background-color: transparent;
                text-align: var(--theme-sig-control-numeric-input-unit-text-align, center);
                color: var(--theme-sig-control-numeric-input-unit-color, inherit);
                overflow: hidden;
                /* only min-width works */
                min-width: fit-content;
                box-sizing: border-box;
            }

            :host([unitactiv][unitposition="uppertop"]) .content {
                padding-left: var(--theme-sig-control-numeric-input-unit-margin-leftright, 0px);
                padding-right: var(--theme-sig-control-numeric-input-unit-margin-leftright, 0px);
                padding-top: var(--theme-sig-control-numeric-input-unit-margin-bottom, 5px);
            }

            :host([unitactiv][unitposition="lowerbottom"]) .content {
                padding-left: var(--theme-sig-control-numeric-input-unit-margin-leftright, 0px);
                padding-right: var(--theme-sig-control-numeric-input-unit-margin-leftright, 0px);
                padding-bottom: var(--theme-sig-control-numeric-input-unit-margin-bottom, 5px);
            }

            :host([unitactiv][unitposition="leftside"]) .content {
                padding-left: var(--theme-sig-control-numeric-input-unit-margin-leftright, 0px);
                align-self: var(--theme-sig-control-numeric-input-unit-vertical-align, center);
            }

            :host([unitactiv][unitposition="rightside"]) .content {
                padding-right: var(--theme-sig-control-numeric-input-unit-margin-leftright, 0px);
                align-self: var(--theme-sig-control-numeric-input-unit-vertical-align, center);
            }

            /* handle text color of the unit if the input is opened */
            :host([iskeyboardopen]) .content {
                color: var(--theme-sig-control-numeric-input-unit-active-color, inherit);
            }

            /* symbol displaying if no image selected */
            .symbInc {
                text-align: center;
                top: 50%;
                position: relative;
                transform: translateY(-50%);
            }
            .symbInc:after {
                @apply --symbol;
                content: '\\f106';
            }
            .symbDec {
                text-align: center;
                top: 50%;
                position: relative;
                transform: translateY(-50%);
            }
            .symbDec:after {
                @apply --symbol;
                content: '\\f107';
            }
            /***************************************** */

        </style>
        <div class="sig-input clearfix">
            <div class="sig-input-background" id="container">
                <div id="decBtn" class="sig-input-decrease" on-down="_handleDownDec" on-up="_handleUp">
                </div>
                <div id="incBtn" class="sig-input-increase" on-down="_handleDownInc" on-up="_handleUp">
                </div>                                  
                <div class="sig-input-background-flex" on-tap="_showKeyboard" on-input="_showKeyboard">
                    <input type="text" id="siginput" size="10" class="sig-input-field" placeholder="[[placeholder]]"
                        value="{{valuec}}" on-tap="_showKeyboard" on-input="_showKeyboard" autocomplete="off"
                        spellcheck="false" readonly="readonly" />

                    <template is="dom-if" if="[[unitactiv]]">
                        <div id="label" class="content"><span>[[unittext]]</span></div>
                    </template>
                </div>                       
            </div>
        </div>
        `;
    }

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

    /****************************************************************************************************
     * 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() {
        // property names should always be lower case so we
        // can use them directly on the element as an attribute
        let props = {
            value: {
                type: String,
                value: '',
                observer: "_stateChanged",
                notify: true
            },
            valuec: {
                type: String,
                value: '',
                computed: '_computeText(value,previewtext,isstylepreview,value_converted)'
            },
            ispassword: {
                type: Boolean,
                value: false,
                observer: '_makePassword'
            },
            iskeyboardopen: {
                type: Boolean,
                value: false,
                reflectToAttribute: true,
                observer: '_keyboardopenChanged'
            },
            bordergradient: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            bordergradientactiv: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            shadowactive: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            shadowactivactive: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            unitactiv: {
                type: Boolean,
                value: false,
                reflectToAttribute: true,
            },
            unitposition: {
                type: String,
                value: 'rightside',
                reflectToAttribute: true
            },
            increasebgimage: {
                type: String,
                value: 'none',
                observer: '_increaseBgImageHandler'
            },
            decreasebgimage: {
                type: String,
                value: 'none',
                observer: '_decreaseBgImageHandler'
            },
            buttononeachside: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            buttonalignment: {
                type: String,
                value: 'row-reverse',
                reflectToAttribute: true
            },
            buttonposition: {
                type: String,
                value: 'row-reverse',
                reflectToAttribute: true
            }
        }
        return props;
    }

    /****************************************************************************************************
     * called when the element has been created, but before property values are set and local DOM is initialized
     ****************************************************************************************************/
    constructor() {
        super();
        this.repeatInterval = undefined;
        this.activeLang = ''
        this.vmApi = (window.sigApi && window.sigApi.vm) ? window.sigApi.vm : null;
        this.keyboardApi = (window.sigApi && window.sigApi.keyboard) ? window.sigApi.keyboard : null;
        this.eventMediatorApi = (window.sigApi && window.sigApi.eventMediator) ? window.sigApi.eventMediator : null;
        this.eventsApi = (window.sigApi && window.sigApi.events) ? window.sigApi.events : null;
        this.incamount = 1;
        this.limitLow = Number.MIN_SAFE_INTEGER;
        this.ignoremin = false;
        this.type = 'numeric';
        this.limitHigh = Number.MAX_SAFE_INTEGER;
        this.ignoremax = false;
        this.maxlength = 0;
        this.placeholder = '';
        this.keyboardid = '';
        this.titletext = '';
        this.writeonincrement = false;
        this.increasebgimageactive = 'none';
        this.decreasebgimageactive = 'none';
        this.cinputwidth = '0px';
        this.cunitwidth = '0px';
        this.intervaltype = 'repeat';
        this.intervaltime = 500;
        this.dashboardKeyboard = null;
        this.openkeyboardbeside = false;
        this.keyboardwindow = null;
        this.format = '';
        this.unittext = "[Unit]";
        this.cincreasebgimage = '';
        this.cdecreasebgimage = '';
        this.rendered = false;
        this.updateonsamevalue = false;

        this.errorInfo = 0;
        this.addPredefProps([{
            src: 'value',
            dest: 'value_unconverted',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNCONVERTED_VALUE
        },{
            src: 'value',
            dest: 'limitLow',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_LIMIT_LOW
        }, {
            src: 'value',
            dest: 'limitHigh',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_LIMIT_HIGH
        }, {
            src: 'value',
            dest: 'type',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_KEYBOARD_TYPE
        }, {
            src: 'value',
            dest: 'unittext',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_UNIT_TEXT
        }, {
            src: 'value',
            dest: 'titletext',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_TEXT1
        }, {
            src: 'value',
            dest: 'type',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_KEYBOARD_TYPE
        }, {
            src: 'value',
            dest: 'state',
            type: window.sigApi.SIG_CONST.PROP_PREDEF_PROPS_TYPE_STATE
        }]);
    }

    /****************************************************************************************************
     *  called after property default values are set and local DOM is initialized
     ****************************************************************************************************/
    ready() {
        super.ready();
        this.activeLang = this._getActiveLang();
        if (!this.isdesignmode) { 
            this.sigAddEventListener('LANGUAGE_CHG', () => {
                this._switchLang();
            }, 'runtimeEvent');         
        }       
    }

    /****************************************************************************************************
     * Observer for the increasebgimage property
     * Sets the background-image for the increase button
     * 
     * needed because VS Code shows an error when we use the Polymer Data-Binding in the style attribute
     * @param {*} [increasebgimage=this.increasebgimage]
     ****************************************************************************************************/
     _increaseBgImageHandler(increasebgimage = this.increasebgimage) {
        //calculate the increase background image
        this.cincreasebgimage = this._cgetImage(increasebgimage);
        //set the background to the calculated image
        if(!this.cincreasebgimage) {
            this.$.incBtn.style.removeProperty('background-image');
            this.$.incBtn.innerHTML = `
                <div class="symbInc"></div>
            `
        } else {
            this.$.incBtn.style.backgroundImage = this.cincreasebgimage;
            this.$.incBtn.innerHTML = ''
        }                  
    }

    /****************************************************************************************************
     * Observer for the decreasebgimage property
     * Sets the background-image for the decrease button
     * 
     * Needed because VS Code shows an error when we use the Polymer Data-Binding in the style attribute
     * @param {*} [decreasebgimage=this.decreasebgimage]
     ****************************************************************************************************/
    _decreaseBgImageHandler(decreasebgimage = this.decreasebgimage) {
        //calculate the decrease background image
        this.cdecreasebgimage = this._cgetImage(decreasebgimage);
        //set the background to the calculated image
        if(!this.cdecreasebgimage) {
            this.$.decBtn.style.removeProperty('background-image');
            this.$.decBtn.innerHTML = `
                <div class="symbDec"></div>
            `
        } else {
            this.$.decBtn.style.backgroundImage = this.cdecreasebgimage;
            this.$.decBtn.innerHTML = ''
        }                  
    }

    /****************************************************************************************************
     * Handles language switches
     ****************************************************************************************************/
    _switchLang() {
        this.activeLang = this._getActiveLang();
    }

    /****************************************************************************************************
     * Returns either the specified image, or a default one
     * 
     * @param {String} image - the image that should be used
     * 
     * @return {String} - a, hopefully, valid value for a background-image (url(...))
     ****************************************************************************************************/
    _cgetImage(image) {
        if (!image || image === 'none') {
            //Return here because in this case we use icons
            return false;
        }
        if (this.isdesignmode) {
            //In the designmode for some reason there are \ in the url
            //So they need to be replaced with / because otherwise they just escape characters in the string
            return `url(${image})`.replace(/\\/g, '/');
        }
        return `url(${image})`;
    }

    /****************************************************************************************************
     * Handles touchdown or similar events for increase button
     ****************************************************************************************************/
    _handleDownInc() {
        this._incValue();
        // If autorepeat is on
        // do repeat the action in an interval
        if (this.intervaltype === 'repeat') {
            this.repeatInterval = this.sigSetInterval(() => {
                this._incValue();
            }, parseInt(this.intervaltime));
        }
    }

    /****************************************************************************************************
     * Handles touchdown or similar events for decrease button
     ****************************************************************************************************/
     _handleDownDec() {
        this._decValue();
        // If autorepeat is on
        // do repeat the action in an interval
        if (this.intervaltype === 'repeat') {
            this.repeatInterval = this.sigSetInterval(() => {
                this._decValue();
            }, parseInt(this.intervaltime));
        }
    }

    /****************************************************************************************************
     * Handles touchup or similar events
     ****************************************************************************************************/
    _handleUp() {
        if (this.repeatInterval !== undefined) {
            //The interval should only run when the button is pressed 
            //-> clear the interval when the button is not pressed anymore
            this.sigClearInterval(this.repeatInterval);
            this.repeatInterval = undefined;
        }
    }

    /****************************************************************************************************
     * Decreases the value
     ****************************************************************************************************/
    _decValue() {
        // inc/dec is handled async because of unit conversion 
        // -> dont call if it is not finished
        if (this.incDecInWork === true) return;

        // Try to manually convert the values, otherwise the decimals can flicker
        const valueProp = window.sigApi.properties.getProperty(this.id, 'value');
        if (valueProp) {
            let unit = null;
            let isFloatingPoint = valueProp.isFloatingPoint();

            const unitConversion = this.sigGetUnitConversion('value');
            const unitProp = valueProp.getUnitProp();
            
            // if unitConversion is set, use unit from unitConversion 
            if (unitConversion && unitConversion !== null && unitConversion.unitIndex !== undefined) {
                unit = window.sigApi.unitManager.getUnit(unitConversion.unitIndex);

                // use unit of property
            } else if(unitProp) {
                unit = unitProp.getValue();
            }

            // check if unit has formula
            if ((!unit) || (!unit.hasFormula())) {
                this.value = parseFloat(this.valuec) - parseFloat(this.incamount);
                return;                      
            }                

            // Add the increase ammount to the value
            let decVal = parseFloat(this.valuec) - parseFloat(this.incamount);

            // Make sure the value is within the limits
            // Otherwise there can be a situation where the value changes in the visu,
            // but not in the class
            decVal = this._ensureValueWithinLimits(decVal);

            // Convert to base value (value on datapoint)
            if(unitConversion !== null) {
                // avoid function being called again, because the following code is handled async
                this.incDecInWork = true;

                window.sigApi.properties.convertValueBackWithUnit(unitConversion.unitIndex, decVal, isFloatingPoint).then((convertedValue) => {
                    if(!isNaN(convertedValue) && convertedValue !== null) {
                        if (unitProp !== null) {
                            // Convert back to the actual value that we want to show, and set the value
                            this.value = unitProp.formatValue(convertedValue, isFloatingPoint);
                        } else {
                            this.value = convertedValue;
                        }
                    }
                    this.incDecInWork = false;
                }).catch((e) => this.incDecInWork = false);

                // If unit property exists
            } else if(unitProp) {
                const unconvertedValueObj = unitProp.getUnconvertedValue();

                if (unconvertedValueObj !== null) {
                    // Convert to base value (value on datapoint)
                    const convertedBack = unitProp.convertValueBack ? unitProp.convertValueBack(decVal) : unconvertedValueObj.convertValueBack(incVal);

                    // Convert back to the actual value that we want to show, and set the value
                    this.value = unitProp.formatValue(convertedBack, isFloatingPoint);
                }
                return;
            }
            
        } else {
            // If there isn't a property set to value, just increase or decrease the current value
            // Also just increae the value if there isn't a unit set (that means we don't have to do calculations)
            let decVal = (parseFloat(this.value) - parseFloat(this.incamount));
            decVal = this._ensureValueWithinLimits(decVal);
    
            this.value = decVal;
        }
    }

    /****************************************************************************************************
     * Increases the value
     * @returns
     ****************************************************************************************************/
    _incValue() {
        // inc/dec is handled async because of unit conversion 
        // -> dont call if it is not finished
        if (this.incDecInWork === true) return;

        // Try to manually convert the values, otherwise the decimals can flicker
        const valueProp = window.sigApi.properties.getProperty(this.id, 'value');
        if (valueProp) {
            let unit = null;
            let isFloatingPoint = valueProp.isFloatingPoint();

            const unitConversion = this.sigGetUnitConversion('value');
            const unitProp = valueProp.getUnitProp();
            
            // if unitConversion is set, use unit from unitConversion 
            if (unitConversion && unitConversion !== null && unitConversion.unitIndex !== undefined) {
                unit = window.sigApi.unitManager.getUnit(unitConversion.unitIndex);

                // use unit of property
            } else if(unitProp) {
                unit = unitProp.getValue();
            }

            // check if unit has formula
            if ((!unit) || (!unit.hasFormula())) {
                this.value = parseFloat(this.valuec) + parseFloat(this.incamount);
                return;                      
            }                

            // Add the increase ammount to the value
            let incVal = parseFloat(this.valuec) + parseFloat(this.incamount);

            // Make sure the value is within the limits
            // Otherwise there can be a situation where the value changes in the visu,
            // but not in the class
            incVal = this._ensureValueWithinLimits(incVal);

            // Convert to base value (value on datapoint)
            if(unitConversion !== null) {
                // avoid function being called again, because the following code is handled async
                this.incDecInWork = true;

                window.sigApi.properties.convertValueBackWithUnit(unitConversion.unitIndex, incVal, isFloatingPoint).then((convertedValue) => {
                    if(!isNaN(convertedValue) && convertedValue !== null) {
                        if (unitProp !== null) {
                            // Convert back to the actual value that we want to show, and set the value
                            this.value = unitProp.formatValue(convertedValue, isFloatingPoint);
                        } else {
                            this.value = convertedValue;
                        }
                    }
                    this.incDecInWork = false;
                }).catch((e) => this.incDecInWork = false);

                // If unit property exists
            } else if(unitProp) {
                const unconvertedValueObj = unitProp.getUnconvertedValue();

                if (unconvertedValueObj !== null) {
                    // Convert to base value (value on datapoint)
                    const convertedBack = unitProp.convertValueBack ? unitProp.convertValueBack(incVal) : unconvertedValueObj.convertValueBack(incVal);

                    // Convert back to the actual value that we want to show, and set the value
                    this.value = unitProp.formatValue(convertedBack, isFloatingPoint);
                }
                return;
            }

        } else {
            // If there isn't a property set to value, just increase or decrease the current value
            // Also just increae the value if there isn't a unit set (that means we don't have to do calculations)
            let incVal = (parseFloat(this.value) + parseFloat(this.incamount));
            incVal = this._ensureValueWithinLimits(incVal);
    
            this.value = incVal;
        }
    }

    /****************************************************************************************************
     * Makes sure the value isn't above or below a servers limits
     * @param {*} value
     * @returns
     ****************************************************************************************************/
    _ensureValueWithinLimits(value) {
        const limitHigh = (!isNaN(this.limitHigh_converted) && (this.limitHigh_converted !== null)) ? parseFloat(this.limitHigh_converted) : parseFloat(this.limitHigh);
        const limitLow = (!isNaN(this.limitLow_converted) && (this.limitLow_converted !== null)) ? parseFloat(this.limitLow_converted) : parseFloat(this.limitLow);

        if (value < limitLow) {
            return limitLow;
        }

        if (value > limitHigh) {
            return limitHigh;
        }

        return value;
    }

    /****************************************************************************************************
     * method gets called to check if property should be updated or not
     *
     * @param {*} property - property which should be checked
     * @param {*} originalResult - result of the original property update info
     * @param {*} value - new value
     * @param {*} old - old value
     * @return {*} 
     * @memberof LasalRuntimeSigInputElement
     ****************************************************************************************************/
     sigShouldPropertyUpdate(property, originalResult, value, old) {
        if ((this.updateonsamevalue) && (property === 'value')) {
            return true;
        }
        return originalResult;
    }
        
    /****************************************************************************************************
     * updates the value of the component
     * @param {*} newValue
     ****************************************************************************************************/
    _updateValue(newValue) {
        this.value = newValue;
    }

    /****************************************************************************************************
     * observer for the ispassword property
     * adopted from sig-control-input
     * hides the value of the input
     * @param {*} newval
     * @param {*} oldval
     ****************************************************************************************************/
    _makePassword(newval, oldval) {
        let type;
        if (newval !== undefined) {
            if (newval) {
                type = 'password';
            } else {
                type = 'text';
            }
        }
        this.$.siginput.setAttribute('type', type);
    }


    /****************************************************************************************************
     * Observer for this.value
     * @param {*} newstate
     * @param {*} oldstate
     ****************************************************************************************************/
    _stateChanged(newstate, oldstate) {
        if (oldstate !== undefined & oldstate !== '') {
            this._dispatchEvent('value-changed');
        }
    }

    /**
    * Shows the correct type of keyboard and updates the value once the keyboard value is submitted (Keyboard promise resolved).
    * @private
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigNumericInputElement
    */
    _showKeyboard() {
        if (!this.isdesignmode) {

            //Determine what standalone keyboard is linked to the input by the keyboardid property.
            //The standalone keyboard must be in DOM, visible and with state:active otherwise it is null.
            if(this.keyboardid && this.keyboardid !== '' && this.context && !this.isdesignmode) {
                //Check whether keyboard is in DOM and visible. (Keyboard in CC is not found, because its ID is dynamic)
                const keyboardVM = (this.vmApi) ? this.vmApi.getVmById(this.keyboardid) : null;
                const keyboard = (keyboardVM) ? keyboardVM.getDomRef() : null;

                const stateIntacive = (this.constApi) ? this.constApi.CONTROL_STATE_INACTIVE : 2;
                const stateInvisibile = (this.constApi) ? this.constApi.CONTROL_STATE_INVISIBLE : 3;
                const isKeyboardHidden = (keyboard) ? keyboard.state === stateInvisibile || keyboard.state === stateIntacive : false;

                //Check whether parent is in DOM and visible (dashboard/window)
                const parentVM = (keyboardVM) ? keyboardVM.getParent() : null;
                const parent = (keyboardVM) ? parentVM.getDomRef() : null;
                const isParentHidden = (parent) ? parent.hidden : true;

                //Save the dashboard keyboard only if it is visible.
                this.dashboardKeyboard = (keyboard && !isParentHidden && !isKeyboardHidden) ? keyboard : null;
            }
            //Only send the keyboard ID if the dashboard keyboard is in DOM and visible.
            const keyboardid = (this.dashboardKeyboard instanceof HTMLElement) ? this.keyboardid : undefined;

            if (this.value_converted !== undefined) {
                this.openKeyboardWithUnit('value');
                return;
            }

            if(this.keyboardApi) {
                this.keyboardApi.open(this.type, this.titletext, {
                    keyboardplaceholder: this.placeholder,
                    keyboardmin: this.limitLow,
                    keyboardmax: this.limitHigh,
                    keyboardmaxlength: this.maxlength,
                    keyboardlang: this.activeLang,
                    ispassword: this.ispassword,
                    ignoremin: this.ignoremin,
                    ignoremax: this.ignoremax,
                    keyboardunittext: this.unittext,
                    writeonincrement: this.writeonincrement,
                    datapointtype: this.type
                }, this, 'value', this.keyboardwindow, this.openkeyboardbeside, keyboardid).then((value) => {
                    this._updateValue(value);
                }, () => {
                    //input cancelled -> we don't need this info
                }).catch((error) => {
                    this._log('Error: ' + error.message, 'ERROR');
                });
            }
        }
    }

    /****************************************************************************************************
     * changes the background images for the button when the keyboard is open
     *
     * @param {*} newstate
     ****************************************************************************************************/
    _keyboardopenChanged(newstate){
        if(newstate){
            if((this.increasebgimage && this.increasebgimage !== 'none') &&
                (this.increasebgimageactive && this.increasebgimageactive !== 'none')) {
                this._increaseBgImageHandler(this.increasebgimageactive);
            }

            if((this.decreasebgimage && this.decreasebgimage !== 'none') &&
                (this.decreasebgimageactive && this.decreasebgimageactive !== 'none')) {
                this._decreaseBgImageHandler(this.decreasebgimageactive);
            }
        } else {
            this._increaseBgImageHandler();
            this._decreaseBgImageHandler();
        }
    }

    /**
     * An observer method which returns the computed input text.
     * @private
     * @param {String} _value Text from the text list.
     * @param {String} _previewtext Preview text.
     * @returns {String} The computed text.
     * @memberof LasalRuntimeSigNumericInputElement
     */
    _computeText(_value, _previewtext, _isstylepreview, value_converted) {
        const finalvalue = (value_converted !== undefined) ? value_converted : _value;
        return (finalvalue === '' && _isstylepreview && this.isdesignmode) ? _previewtext : finalvalue;
    }
}
customElements.define(LasalRuntimeSigNumericInputElement.is, LasalRuntimeSigNumericInputElement);
