import { LasalRuntimeSigElement, SigPolymer } from '../../sigmatek/sig-element/sig-element.js';
import '../sig-control-input/sig-control-input.js';
import '../../sigmatek/sig-utils-jquery/sig-utils-jquery.js';
import '../../sigmatek/sig-utils-jquery-mask/sig-utils-jquery-mask.js';

export class LasalRuntimeSigKeyboard extends LasalRuntimeSigElement {

    static get is() {
        return "sig-keyboard";
    }

    static get importMeta() { return import.meta; }

    static get template() {
        return SigPolymer.html`
        <style include="sig-element-css">
            :host {
                
                background-color: var(--theme-sig-keyboard-background-color, rgba(37, 137, 202, .7));
                border-width: var(--theme-sig-keyboard-border-width, 0px);
                border-color: var(--theme-sig-keyboard-border-color, transparent);
                border-style: var(--theme-sig-keyboard-border-style, none);
                border-radius: var(--theme-sig-keyboard-border-radius, 0px);

                
                display: block;
                margin: 0px;
                padding: 0px;
                overflow: hidden;
                width: var(--theme-sig-element-width, 100%);
                height: var(--theme-sig-element-height, 100%);
                position: absolute;
            }
        
             :host * {
                
                margin: 0;
                padding: 0;
            }
            
            .clearfix:after {
                
                @apply --clearfix
            }
            
            .notextselect {
                @apply --notextselect
            }
            
            .sig-keyboard {
                text-align: center;
                position: relative;
                height: 100%;
                
                background-image: var(--theme-sig-keyboard-background-image, none);
                background-repeat: var(--theme-sig-keyboard-background-repeat, repeat);

                
            }
            
            #sigkeyboard .keyshell {
                padding:0px 7px;
            }
            
            :host .inputholder {
                display: flex;
                cursor: text;
                align-items: center;
                min-height: var(--theme-sig-keyboard-input-min-height, 25px);
                border-bottom: 2px solid var(--theme-sig-keyboard-input-border-bottom-color, rgba(119, 119, 119, 1));
                background-color: var(--theme-sig-keyboard-input-background-color, transparent);
                color: var(--theme-sig-keyboard-input-color, rgba(51,51,51,1));
                font-size: initial;
                margin-top: 12px;
                margin-bottom: 8px;
            }

            :host .inputfield {
                flex: 0 1 100%;
                padding-top: var(--theme-sig-keyboard-input-padding-top, var(--theme-sig-keyboard-input-padding-default, 0px));
                padding-bottom: var(--theme-sig-keyboard-input-padding-bottom, var(--theme-sig-keyboard-input-padding-default, 0px));
                padding-left: var(--theme-sig-keyboard-input-padding-left, var(--theme-sig-keyboard-input-padding-default, 0px));
                padding-right: var(--theme-sig-keyboard-input-padding-right, var(--theme-sig-keyboard-input-padding-default, 0px));
            }

            :host .unittext {
                flex: 0 1 auto;
                display: none;
                font-size: var(--theme-sig-keyboard-input-unit-font-size, 25px);
                padding-top: var(--theme-sig-keyboard-input-unit-padding-top, var(--theme-sig-keyboard-input-unit-padding-default, 0px));
                padding-bottom: var(--theme-sig-keyboard-input-unit-padding-bottom, var(--theme-sig-keyboard-input-unit-padding-default, 0px));
                padding-left: var(--theme-sig-keyboard-input-unit-padding-left, var(--theme-sig-keyboard-input-unit-padding-default, 0px));
                padding-right: var(--theme-sig-keyboard-input-unit-padding-right, var(--theme-sig-keyboard-input-unit-padding-default, 0px));
            }

            .keyboardinput {
                width: 100%;
                font-size: var(--theme-sig-keyboard-input-font-size, 25px);
                border: none;
                background:none;
                outline: none;
                font-family: inherit;
                font-style:inherit;
                font-weight:inherit;
                color: inherit;
            }
               
            .keyboardinput::-ms-clear {
                display: none;
            }
            
            #info {
                color: var(--theme-sig-keyboard-info-color, rgba(102,102,102,1));
                border-top: 12px solid transparent;
                font-size: var(--theme-sig-keyboard-info-font-size, 10px);
                text-align: left;
                display: none;
            }
            
            #min {
                float: left;
            }
            
            #max {
                float: right;
            }
            
            .layout {
                text-align: left;
            }
            
            .layer {
                text-align: center;
                display: none;
            }
            
            .layer[selected] {
                display: block;
            }

            .row {
                font-size: initial;
            }
             
            .key {
                color: var(--theme-sig-keyboard-key-color, rgba(0,0,0,1));
                background-color: var(--theme-sig-keyboard-key-background-color,rgba(255,255,255,1));
                display: inline-block;
                font-size: var(--theme-sig-keyboard-key-font-size, 18px);
                border-top-width: var(--theme-sig-keyboard-key-border-top-width, 1px);
                border-top-style: var(--theme-sig-keyboard-key-border-top-style, solid);
                border-top-color: var(--theme-sig-keyboard-key-border-top-color, rgba(255,255,255,1));
                border-left-width: var(--theme-sig-keyboard-key-border-left-width, 1px);
                border-left-style: var(--theme-sig-keyboard-key-border-left-style, solid);
                border-left-color: var(--theme-sig-keyboard-key-border-left-color, rgba(255,255,255,1));
                border-right-width: var(--theme-sig-keyboard-key-border-right-width, 1px);
                border-right-style: var(--theme-sig-keyboard-key-border-right-style, solid);
                border-right-color: var(--theme-sig-keyboard-key-border-right-color, rgba(255,255,255,1));
                border-bottom-width: var(--theme-sig-keyboard-key-border-bottom-width, 1px);
                border-bottom-style: var(--theme-sig-keyboard-key-border-bottom-style, solid);
                border-bottom-color: var(--theme-sig-keyboard-key-border-bottom-color, rgba(153,153,153,1));
                margin: 0 var(--theme-sig-keyboard-key-margin-right, 1px) var(--theme-sig-keyboard-key-margin-bottom, 1px) 0 !important;
                text-align: center;
                min-width: var(--theme-sig-keyboard-key-min-width, 50px);
                min-height: var(--theme-sig-keyboard-key-min-height, 50px);
                line-height: var(--theme-sig-keyboard-key-line-height, 50px);
                border-radius: var(--theme-sig-keyboard-key-border-radius, 5px);
                cursor: pointer;
                vertical-align: middle;
                pointer-events: all;
            }

            :host([keyshadowactive]) .key {
                box-shadow: inset 1px 1px 0px 0px rgba(255, 255, 255, .4), inset -1px -1px 0px 0px rgba(0, 0, 0, .2);
            }
            
            .key.off {
                color: var(--theme-sig-keyboard-key-off-color, rgba(0,0,0,1));
                font-size: var(--theme-sig-keyboard-key-font-size, 18px);
                background-color: var(--theme-sig-keyboard-key-off-background-color, rgba(205,213,220,1));
                border-top-width: var(--theme-sig-keyboard-key-off-border-top-width, 1px);
                border-top-style: var(--theme-sig-keyboard-key-off-border-top-style, solid);
                border-top-color: var(--theme-sig-keyboard-key-off-border-top-color, rgba(155,179,195,1));
                border-left-width: var(--theme-sig-keyboard-key-off-border-left-width, 1px);
                border-left-style: var(--theme-sig-keyboard-key-off-border-left-style, solid);
                border-left-color: var(--theme-sig-keyboard-key-off-border-left-color, rgba(155,179,195,1));
                border-right-width: var(--theme-sig-keyboard-key-off-border-right-width, 1px);
                border-right-style: var(--theme-sig-keyboard-key-off-border-right-style, solid);
                border-right-color: var(--theme-sig-keyboard-key-off-border-right-color, rgba(155,179,195,1));
                border-bottom-width: var(--theme-sig-keyboard-key-off-border-bottom-width, 1px);
                border-bottom-style: var(--theme-sig-keyboard-key-off-border-bottom-style, solid);
                border-bottom-color: var(--theme-sig-keyboard-key-off-border-bottom-color, rgba(155,179,195,1));
                cursor: not-allowed;
                pointer-events: none;
            }
            
            .key.special {
                color: var(--theme-sig-keyboard-key-special-color, rgba(0,0,0,1));
                font-size: var(--theme-sig-keyboard-key-font-size, 18px);
                background-color: var(--theme-sig-keyboard-key-special-background-color, rgba(155,179,195,1));
                border-top-width: var(--theme-sig-keyboard-key-special-border-top-width, 1px);
                border-top-style: var(--theme-sig-keyboard-key-special-border-top-style, solid);
                border-top-color: var(--theme-sig-keyboard-key-special-border-top-color, rgba(155,179,195,1));
                border-left-width: var(--theme-sig-keyboard-key-special-border-left-width, 1px);
                border-left-style: var(--theme-sig-keyboard-key-special-border-left-style, solid);
                border-left-color: var(--theme-sig-keyboard-key-special-border-left-color, rgba(155,179,195,1));
                border-right-width: var(--theme-sig-keyboard-key-special-border-right-width, 1px);
                border-right-style: var(--theme-sig-keyboard-key-special-border-right-style, solid);
                border-right-color: var(--theme-sig-keyboard-key-special-border-right-color, rgba(155,179,195,1));
                border-bottom-width: var(--theme-sig-keyboard-key-special-border-bottom-width, 1px);
                border-bottom-style: var(--theme-sig-keyboard-key-special-border-bottom-style, solid);
                border-bottom-color: var(--theme-sig-keyboard-key-special-border-bottom-color, rgba(155,179,195,1));
            }

            .alpha .key.special,
            .full .key.special {
                 width: var(--theme-sig-keyboard-key-special-alpha-min-width, 50px);
            }

            .key.keypressed {
                color: var(--theme-sig-keyboard-key-pressed-color, rgba(0,0,0,1));
                font-size: var(--theme-sig-keyboard-key-font-size, 18px);
                background-color: var(--theme-sig-keyboard-key-pressed-background-color, rgba(239,239,239,1));
                border-top-width: var(--theme-sig-keyboard-key-pressed-border-top-width, 1px);
                border-top-style: var(--theme-sig-keyboard-key-pressed-border-top-style, solid);
                border-top-color: var(--theme-sig-keyboard-key-pressed-border-top-color, rgba(153,153,153,1));
                border-left-width: var(--theme-sig-keyboard-key-pressed-border-left-width, 1px);
                border-left-style: var(--theme-sig-keyboard-key-pressed-border-left-style, solid);
                border-left-color: var(--theme-sig-keyboard-key-pressed-border-left-color, rgba(255,255,255,1));
                border-right-width: var(--theme-sig-keyboard-key-pressed-border-right-width, 1px);
                border-right-style: var(--theme-sig-keyboard-key-pressed-border-right-style, solid);
                border-right-color: var(--theme-sig-keyboard-key-pressed-border-right-color, rgba(255,255,255,1));
                border-bottom-width: var(--theme-sig-keyboard-key-pressed-border-bottom-width, 1px);
                border-bottom-style: var(--theme-sig-keyboard-key-pressed-border-bottom-style, solid);
                border-bottom-color: var(--theme-sig-keyboard-key-pressed-border-bottom-color, rgba(255,255,255,1));
            }

            :host([keypressedshadowactive]) .key.keypressed {
                box-shadow: inset 1px 1px 0px 0px rgba(0, 0, 0, .4), inset -1px -1px 0px 0px rgba(255, 255, 255, .2);
            }
            
            .key.space {
                min-width: var(--theme-sig-keyboard-key-space-alpha-min-width, 280px);
            }
            
            .key.accept {
                color: var(--theme-sig-keyboard-key-accept-color, rgba(255,255,255,1));
                background-color: var(--theme-sig-keyboard-key-accept-background-color, rgba(0,153,0,1));
            }
            
            .key.cancel {
                color: var(--theme-sig-keyboard-key-cancel-color, rgba(255,255,255,1));
                background-color: var(--theme-sig-keyboard-key-cancel-background-color, rgba(136,0,0,1));
            }
            
            .layer.alt[selected] .key.alt,
            .layer.shift[selected] .key.shift,
            .layer.more[selected] .key.more {
                color: var(--theme-sig-keyboard-key-special-selected-color, rgba(255,255,255,1));
                background-color: var(--theme-sig-keyboard-key-special-selected-background-color, rgba(102,143,170,1));
                border-top-width: var(--theme-sig-keyboard-key-special-selected-border-top-width, 1px);
                border-top-style: var(--theme-sig-keyboard-key-special-selected-border-top-style, solid);
                border-top-color: var(--theme-sig-keyboard-key-special-selected-border-top-color, rgba(74,105,124,1));
                border-left-width: var(--theme-sig-keyboard-key-special-selected-border-left-width, 1px);
                border-left-style: var(--theme-sig-keyboard-key-special-selected-border-left-style, solid);
                border-left-color: var(--theme-sig-keyboard-key-special-selected-border-left-color, rgba(102,143,170,1));
                border-right-width: var(--theme-sig-keyboard-key-special-selected-border-right-width, 1px);
                border-right-style: var(--theme-sig-keyboard-key-special-selected-border-right-style, solid);
                border-right-color: var(--theme-sig-keyboard-key-special-selected-border-right-color, rgba(102,143,170,1));
                border-bottom-width: var(--theme-sig-keyboard-key-special-selected-border-bottom-width, 1px);
                border-bottom-style: var(--theme-sig-keyboard-key-special-selected-border-bottom-style, solid);
                border-bottom-color: var(--theme-sig-keyboard-key-special-selected-border-bottom-color, rgba(102,143,170,1));
            }
 
            :host([keyboardtype="numeric"]) #info {
                display: block;
            }
            
            :host(:not([isactive])) .inputholder {
                color: var(--theme-sig-keyboard-input-inactive-color, rgba(255,255,255,1));
                background-color: var(--theme-sig-keyboard-input-inactive-background-color, rgba(42, 42, 42, .5));
                border-bottom-color: var(--theme-sig-keyboard-input-inactive-bottom-color, rgba(0, 0, 0, 1));
            }
            
            :host([isinvalid]) .inputholder {
                color: var(--theme-sig-keyboard-input-invalid-color, rgba(255,255,255,1));
                background-color: var(--theme-sig-keyboard-input-invalid-background-color, rgba(255, 0, 0, .5));
                border-bottom-color: var(--theme-sig-keyboard-input-invalid-bottom-color, rgba(255, 0, 0, 1));
            }
            
            :host([isinvalid]) .key.enter,
            :host([isinvalid]) .key.key.accept,
            :host(:not([isactive])) .key.enter,
            :host(:not([isactive])) .key.key.accept {
                opacity: var(--theme-sig-keyboard-invalid-opacity, .3);
                pointer-events: none;
            }
            
            .symbol {
                @apply --symbol;
            }
            
            .u2000 {
                @apply --u2000;
                font-weight: bold;
            }
            
            .u2800 {
                @apply --u2800;
                font-weight: bold;
            }

            :host([ispassword]) #keyboardinput {
                font-family: caption !important;
            }
            
            :host([ignoremin]) #min {
                display: none;
            }
            
            :host([ignoremax]) #max {
                display: none;
            }
            
            #keyboard {
                pointer-events: none;
            }

            #disableOverlay {
                width:100%;
                height:100%;
                display:none;
                position:absolute;
                top:0px;
                background: var(--theme-sig-keyboard-disabled-overlay-background-color, rgba(42, 42, 42, 0.3));
            }

            :host([isdesignmode][showoverlay]) #disableOverlay {
                display:block;
            }

            :host(.sig-element-inactive) .key {
                pointer-events:none;
            }
        </style>
        <div id="sigkeyboard" class="sig-keyboard clearfix">
            <div id="keyshell" class="keyshell">
                <div id="layout" class="layout">
                    <div id="info" class="clearfix">
                        <span id="min">[[stringMin]][[keyboardmin]] &nbsp;&nbsp;&nbsp;&nbsp; </span>
                        <span id="max">[[stringMax]][[keyboardmax]]</span>
                    </div>
                    <div id="inputholder" class="inputholder">
                        <div id="inputfield" class="inputfield">
                            <input type="text" size="1" id="keyboardinput" class="keyboardinput notextselect"
                                value="{{keyboardvalue::input}}" on-blur="_setFocus" on-tap="_inputTap"
                                autocomplete="off" spellcheck="false" inputmode="none" readonly>
                        </div>
                        <div id="unittext" class="unittext" on-tap="_inputTap">
                           [[keyboardunittext]] 
                        </div>
                    </div>
                    <div id="keyboard"></div>
                    </div>
                </div>
            </div>
            <div id="disableOverlay"></div>`;
    }

    static get properties() {
        return {
            keyboardtype: {
                type: String,
                value: 'alpha',
                reflectToAttribute: true,
                observer: '_updateKeyboard'
            },
            keyboardvalue: {
                type: String,
                value: '',
                observer: '_change'
            },
            keyboardcurrentvalue: {
                type: String,
                value: '',
                observer: '_changeCurrValue'
            },
            keyboardmax: {
                type: Number,
                value: 0
            },
            keyboardmin: {
                type: Number,
                value: 0
            },
            keyboardmask: {
                type: String,
                value: '',
                observer: '_maskInput'
            },
            keyboardplaceholder: {
                type: String,
                value: '',
                observer: '_maskInput'
            },
            keyboardmaxlength: {
                type: Number,
                value: 0,
                observer: '_maskInput'
            },
            ispassword: {
                type: Boolean,
                value: false,
                reflectToAttribute: true,
                observer: '_makePassword'
            },
            isinvalid: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            isactive: {
                type: Boolean,
                value: false,
                reflectToAttribute: true,
                observer: '_toggleInput'
            },
            ignoremin: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            ignoremax: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            keyboardunittext: {
                type: String,
                value: '',
                observer: '_updateUnittext'
            },
            keyshadowactive: {
                type: Boolean,
                value: true,
                reflectToAttribute: true
            },
            keypressedshadowactive: {
                type: Boolean,
                value: true,
                reflectToAttribute: true
            },
            layoutmap: {
                type: String,
                value: '',
                observer: '_loadLayout'
            },
            showoverlay: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            stringMin: {
                type: String,
                value: '',
            },
            stringMax: {
                type: String,
                value: '',
            }
        }
    }

    constructor() {
        super();
        this.firstinput = true;
        this.keyboardcache = new Map();
        this.undoValue = '';
        this.keyboardRepeatInterval = 100; 
        this.keyboardRepeatDelay = 350; 
        this.currentCaretPosition = 0;
        this.keyboardlayout = {};
        this.interval = null;
        this.delay = null;
        this.keyIsDown = false;
        this.activeKey = null;
        this.keepLayout = false;
        this.layoutscache = new Map();
        this.disabled = false;
        this.dblClickTimeout = null;
        this.firstJSInputDone = false;
        this.keypressJSMousedownHandler = null;
        this.keypressJSMouseupHandler = null;
        this.keypressPolymerDownHandler = null;
        this.keypressPolymerUpHandler = null;
        this.titletext = '';
        this.datapointBound = false;
        this.datapointType = '';
        this.keyboardlang = 'en_fallback';
        this.activelayout = '';
        this.writeonincrement = false;
        this.keepLayoutOnDblClick = true;
        this.layoutcachetoggle = true;
        this.textApi = (window.sigApi && window.sigApi.textManager) ? window.sigApi.textManager : null;
        this.eventMediatorApi = (window.sigApi && window.sigApi.eventMediator) ? window.sigApi.eventMediator : null;
        this.eventsApi = (window.sigApi && window.sigApi.events) ? window.sigApi.events : null;
        this.keyboardApi = (window.sigApi && window.sigApi.keyboard) ? window.sigApi.keyboard : null;
        this.sanitizeInput = false;
        this.blindinput = false;
        this.BREAK_CHAR = '<br/>';
        this.OPENING_TAG_PLACEHOLDER = '\u2039'; 
        this.CLOSING_TAG_PLACEHOLDER = '\u203A'; 
        this.controlTextList = this.getControlTextListName();
        this.allowedTags = [
            {
                tag: "br",
                paired: false
            }
        ];

        this.HEX_PREFIX = '0x';
        this.DEFAULT_REGEX_VALIDATION = {
            numeric: '^-?([0-9]\\d*)(\\.\\d*)?$',
            ipv4: '^(?!.*\\.$)((?!0\\d)(1?\\d?\\d|25[0-5]|2[0-4]\\d)(\\.|$)){4}$',
            hex: '^(0x)?[0-9a-fA-F]+$',
            time: '^[0-9:]+$',
            date: '^[0-9.|-]+$'
        };

        if (this.textApi) {
            this.updateTexts = async () => {
                this.stringMin = await this.textApi.getTextAsync(this.controlTextList, 'min');
                this.stringMax = await this.textApi.getTextAsync(this.controlTextList, 'max');
            }

            if (!this.isdesignmode) this.sigAddEventListener('LANGUAGE_CHG', this.updateTexts, 'runtimeEvent');
            this.updateTexts();

        } else {
            this.stringMin = 'min: ';
            this.stringMax = 'max: ';
        }
    }

    designerOnLanguageChanged() {
        this.updateTexts();
    }

    async designerOnObjectStoreUpdate(data) {
        const textListChanged = await window.sigApi.textManager.isTextListUpdate(data, this.controlTextList);
        if (textListChanged) this.updateTexts();
    }


    connectedCallback() {
        super.connectedCallback();

        if (!this.isdesignmode) {

            if (this.context) {
                this.parentInstance = document.getElementById(this.context.instanceId);

                if (this.parentInstance instanceof HTMLElement && this.context.type === 'dashboard' ||
                    (this.context.type === 'window' && this.parentInstance.slot !== 'keyboard'))
                    this.disableKeyboard();
            }

            this._addKeypressEvents();
            this._addKeyEvents();
            this._addOtherEvents();
        }
    }

    disconnectedCallback() {
        super.disconnectedCallback();
        if (this.dblClickTimeout !== null) this.sigClearTimeout(this.dblClickTimeout);
        this.parentInstance = null;
        this.cleanup();
        this.textApi = null;
        this.eventMediatorApi = null;
        this.eventsApi = null;
        this.keyboardApi = null;
    }

    _updateUnittext(newval, oldval) {
        if (newval) {
            if (newval.length > 0) this.$.unittext.style.display = 'block';
        } else {
            this.$.unittext.style.display = 'none';
        }
    }

    _updateKeyboard() {
        if (this.isdesignmode && Object.keys(this.keyboardlayout).length !== 0) {
            this._build(this.$.keyboard);
            this._toggleLayer('normal');
        }
    }

    show(
        mask = '',
        placeholder = '',
        min = null,
        max = null,
        maxlength = 0,
        lang = this._getActiveLang(),
        password = false,
        ignoremin = false,
        ignoremax = false,
        titletext = '',
        unittext = '',
        writeonincrement = false,
        datapointBound = false,
        datapointtype = '',
        datetimeformat = '',
        sanitizeInput = false
    ) {
        this.cleanup();
        this.datapointBound = datapointBound;

        SigPolymer.afterNextRender(this, () => {
            if (!datapointBound) {
                switch (this.keyboardtype) {
                    case 'numeric':
                        if (min === undefined || min === null) ignoremin = true;
                        if (max === undefined || max === null) ignoremax = true;
                        break;
                    case 'ipv4':
                        mask = '099.099.099.099';
                        placeholder = '0.0.0.0';
                        maxlength = 15;
                        break;
                    case 'hex':
                        mask = 'FFFFFFFF'
                        maxlength = 8;
                        break;
                    case 'date':
                        switch (datetimeformat) {
                            case 'MM-DD':
                                mask = '00-00';
                                placeholder = 'MM-DD';
                                break;
                            case 'YY-MM-DD':
                                mask = '00-00-00';
                                placeholder = 'YY-MM-DD';
                                break;
                            case 'YYYY-MM-DD':
                                mask = '0000-00-00';
                                placeholder = 'YYYY-MM-DD';
                                break;
                            case 'YY-MM':
                                mask = '00-00';
                                placeholder = 'YY-MM';
                                break;
                            case 'MM.YY':
                                mask = '00.00';
                                placeholder = 'MM.YY';
                                break;
                            case 'DD.MM':
                                mask = '00.00';
                                placeholder = 'DD.MM';
                                break;
                            case 'DD.MM.YY':
                                mask = '00.00.00';
                                placeholder = 'DD.MM.YY';
                                break;
                            default:
                                mask = '00.00.0000';
                                placeholder = 'DD.MM.YYYY';
                                break;
                        }
                        maxlength = placeholder.length;
                        break;
                    case 'time':
                        switch (datetimeformat) {
                            case 'hh:mm':
                                mask = '00:00';
                                placeholder = 'HH:MM';
                                break;
                            case 'mm:ss':
                                mask = '00:00';
                                placeholder = 'MM:SS';
                                break;
                            default:
                                mask = '00:00:00';
                                placeholder = 'HH:MM:SS';
                                break;
                        }
                        maxlength = placeholder.length;
                        break;
                    default:
                        break;
                }
            }

            this.keyboardvalue = this.desanitizeInputValue(this.keyboardvalue);

            this.keyboardlang = lang;
            this.keyboardmask = mask;
            this.keyboardplaceholder = placeholder;
            this.keyboardmin = min;
            this.keyboardmax = max;
            this.keyboardmaxlength = maxlength;
            this.ispassword = password;
            this.ignoremin = ignoremin;
            this.ignoremax = ignoremax;
            this.isinvalid = false;
            this.undoValue = this.keyboardvalue;
            this.keyboardunittext = unittext;
            this.writeonincrement = writeonincrement;
            this.datapointtype = datapointtype;
            this.sanitizeInput = sanitizeInput;

            if (titletext === '') this.titletext = this.keyboardtype;
            else this.titletext = titletext;

            this._dispatchEvent('show')
        });
    }

    _activateInput() {
        this._log('Activate keyboard input after mouse is up');
        this.$.keyboardinput.removeAttribute('readonly');
        this.$.keyboardinput.classList.remove('notextselect');
        this._setFocusAndCursorAtTheEnd();
    }

    _deactivateInput() {
        this._log('Deactivate keyboard input to save caret position');
        this.isactive = false;
        this.$.keyboardinput.setAttribute('readonly', '');
        this.$.keyboardinput.classList.add('notextselect');
    }

    _simulateEvent(el, etype) {
        if (el.fireEvent) {
            el.fireEvent('on' + etype);
        } else {
            var evObj = document.createEvent('Events');
            evObj.initEvent(etype, true, false);
            el.dispatchEvent(evObj);
        }
    }

    _close(resolve = false) {
        this.cleanup();
        this._log(`Close the keyboard without updating any value.`);
        this._dispatchEvent('close');
        if (this.eventMediatorApi)
            this.eventMediatorApi.publish(this.eventsApi.getInternalEvent('SYSWIN_KEYBOARD_EXIT'), resolve);
    }

    _submit() {
        if (!this.isinvalid && this.isactive) {
            this._updateInput();
            this._dispatchEvent('submit');
            this._close(true);
        }
    }

    cleanup() {
        this.isactive = false;
        this.keyboardvalue = '';
        this.isinvalid = false;
        this.firstinput = true;
        this.undoValue = '';
        this.ispassword = false;
        this.keyboardmask = '';
        this.keyboardplaceholder = '';
        this.keyboardmaxlength = 0;
        this.keyboardmin = 0;
        this.keyboardmax = 0;
        this.currentCaretPosition = 0;
        this.ignoremin = false;
        this.ignoremax = false;
        this.formatedValue = undefined;
        this.writeonincrement = false;
        this.keyboardcurrentvalue = '';
        this.datapointBound = false;
        this.datapointtype = '';
        $(this.$.keyboardinput).unmask();

        if (this.delay !== null) SigPolymer.timeOut.cancel(this.delay);
        if (this.interval !== null) SigPolymer.timeOut.cancel(this.interval);
        this.interval = null;
        this.delay = null;
        this.keyIsDown = false;
        this.activeKey = null;
        this.keyboardunittext = '';

        this._toggleLayer('normal');

        if (this.parentInstance && (!this.parentInstance.options || !this.parentInstance.options.animateIn))
            this._setFocusAndCursorAtTheEnd();
    }

    _keydown(e) {
        const key = e.keyCode;
        const char = e.key;
        switch (key) {
            case 13:
                if (e.shiftKey === true && this.isactive) this._insertNewLine();
                else this._submit();
                break;
            case 27:
                break;
            case 109:
            case 189:
                if (!this.firstinput && this.keyboardtype === 'numeric') {
                    e.preventDefault();
                    this._makeNegative();
                }
                else this._checkFirstInput('physical', key, char);
                break;
            default:
                if (this.firstinput) this._checkFirstInput('physical', key, char);
                break;
        }
    }

    _changeCurrValue(newval, oldval) {
        if (!this.isactive) this.keyboardvalue = newval;
    }

    _change(newval, oldval) {
        if (typeof newval === 'number') {
            this.keyboardvalue = newval.toString(10);
            return;
        }
        else if (this.keyboardtype === 'hex' && newval.startsWith(this.HEX_PREFIX)) {
            this.keyboardvalue = newval.replace(this.HEX_PREFIX, '');
            return;
        }

        if (oldval !== undefined && this.isactive) {
            let regexValidation = new RegExp();
            if (this.keyboardlayout.layouts &&
                this.keyboardlayout.layouts[this.keyboardtype].config &&
                this.keyboardlayout.layouts[this.keyboardtype].config.regexValidation) {
                const regexRule = this.keyboardlayout.layouts[this.keyboardtype].config.regexValidation;

                try {
                    if (regexRule.startsWith('^')) {
                        regexValidation = new RegExp(regexRule);
                    } else {
                        if (this.DEFAULT_REGEX_VALIDATION[regexRule]) {
                            regexValidation = new RegExp(this.DEFAULT_REGEX_VALIDATION[regexRule]);
                        } else {
                            this._log(`"regexValidation" parameter in layout type ${this.keyboardtype} is invalid, using empty regex rule.`, 'WARN');
                        }
                    }
                } catch (error) {
                    this._log(error.message, 'ERROR');
                }
            }

            this._log("Validating based on regex: " + regexValidation);
            if (regexValidation.test(newval)) {
                this.isinvalid = false;
                this._log("Regex Valid");
            } else {
                this.isinvalid = true;

                if (this.keyboardtype === 'numeric' && newval !== "" && newval !== "-")
                    this.keyboardvalue = oldval;
                this._log("Regex Invalid");
                return;
            }

            if (this.keyboardApi && typeof this.keyboardApi.validateInputFormat === 'function') {
                const formatResult = this.keyboardApi.validateInputFormat(newval, oldval);

                this.isinvalid = !formatResult.isvalid;

                if (this.isinvalid && formatResult && formatResult.revertChange && this.oldvalbackup !== newval) {
                    this.oldvalbackup = oldval;
                    this.keyboardvalue = oldval;
                    return;
                }
            } else {
                this._log("Input format validation skipped because the API method is not avaiable.", 'WARN');
            }

            let performChecks = true;
            if (this.keyboardlayout.layouts &&
                this.keyboardlayout.layouts[this.keyboardtype] &&
                this.keyboardlayout.layouts[this.keyboardtype].config &&
                this.keyboardlayout.layouts[this.keyboardtype].config.checkLimits === false)
                performChecks = false;

            if (!this.isinvalid && performChecks) {
                this._log("Checking Limits");

                const checkLimitTypes = ['numeric', 'time-rt', 'date-rt'];
                if (this.datapointBound && checkLimitTypes.includes(this.datapointtype)) {
                    if (this.datapointtype === 'numeric') newval = Number(newval);

                    if (this.keyboardApi) {
                        const [valid, formVal] = this.keyboardApi.checkLimits(newval);
                        if ((valid === 0 || (valid === 1 && this.ignoremax) || (valid === -1 && this.ignoremin))) {
                            this.isinvalid = false;
                            this.formatedValue = formVal;
                        } else {
                            this.isinvalid = true;
                        }
                    } else {
                        this._log('Keyboard API not found. Limits will not be validated', 'ERROR');
                    }
                } else {
                    switch (this.keyboardtype) {
                        case 'numeric':
                            this._log(`Validating numeric input`);
                            if (((this.keyboardmin !== null && this.keyboardmin !== undefined && Number(newval) >= this.keyboardmin) || this.ignoremin) &&
                                ((this.keyboardmax !== null && this.keyboardmax !== undefined && Number(newval) <= this.keyboardmax) || this.ignoremax)) {
                                this.isinvalid = false;
                            } else {
                                this.isinvalid = true;
                            }
                            break;
                        case 'date':
                            this._log(`Validating date input`);
                            if (this._isValidDate(newval) && newval.length === this.keyboardmaxlength) this.isinvalid = false;
                            else this.isinvalid = true;
                            break;
                        case 'time':
                            this._log(`Validating time input`);
                            if (this._isValidTime(newval) && newval.length === this.keyboardmaxlength) this.isinvalid = false;
                            else this.isinvalid = true;
                            break;
                        default:
                            this._log('No Validation');
                            break;
                    }
                }
            }
            if (this.keyboardmax !== '') {
                $(this.$.keyboardinput).trigger('keyup');
            }
        }
    }

    _keyPress(element) {
        let key = element.getAttribute('data-key');

        if (element.classList.contains('key')) {
            if (this.firstinput) this._checkFirstInput('virtual', key);

            switch (key) {
                case '{bksp}':
                    this._deleteChar();
                    break;
                case '{tab}':
                    this._insertTab();
                    break;
                case '{enter}':
                    this._submit();
                    break;
                case '{newline}':
                    this._insertNewLine();
                    break;
                case '{shift}':
                    this._keyDoubleClick(element,
                        (keepLayout) => this._toggleLayer('shift', keepLayout),
                        () => this._toggleLayer('shift'));
                    break;
                case '{accept}':
                    this._submit();
                    break;
                case '{alt}':
                    this._toggleLayer('alt');
                    break;
                case '{space}':
                    this._insertChar(' ');
                    break;
                case '{cancel}':
                    this._close();
                    break;
                case '{off}':
                    break;
                case '{more}':
                    this._toggleLayer('more');
                    break;
                case '{curup}':
                    this._moveCursor('up');
                    break;
                case '{curdow}':
                    this._moveCursor('down');
                    break;
                case '{curleft}':
                    this._moveCursor('left');
                    break;
                case '{curright}':
                    this._moveCursor('right');
                    break;
                case '{clear}':
                    this._clearInput();
                    break;
                case '{increase}':
                    this._increaseValue();
                    break;
                case '{decrease}':
                    this._decreaseValue();
                    break;
                case '{minus}':
                    this._makeNegative();
                    break;
                case '{undo}':
                    this._undo();
                    break;
                default:
                    this._insertChar(key);
                    if (!this.keepLayout) this._toggleLayer('normal');
                    break;
            }
        }
    }

    _keyPressDown(e) {
        this.sigPreventDefault(e);
        if (this.keyboardmask !== '') this._simulateEvent(this.$.keyboardinput, 'keydown');
        this._log('inside _keyPressDown');

        this.keyIsDown = true;
        const element = e.composedPath()[0];
        if (element.classList.contains('key')) {
            this._keyPress(element);
            if (!element.classList.contains('shift') && !(this.ispassword && this.blindinput)) element.classList.add('keypressed');
            if (this.interval === null && this.delay === null) {
                this.activeKey = element;
                const keyConfig = (element.dataset.keyConfig) ? JSON.parse(element.dataset.keyConfig) : null;
                const repeat = (keyConfig !== null && keyConfig.noRepeat !== undefined) ? !keyConfig.noRepeat : true;
                if (repeat) {
                    this.delay = SigPolymer.timeOut.run(() => {
                        this._log('Start Keyboard Delay');
                        this._doKeypressInterval(element);
                    }, this.keyboardRepeatDelay);
                }
            }
        }
    }

    _doKeypressInterval(element) {
        this._keyPress(element);
        SigPolymer.timeOut.cancel(this.interval);
        this.interval = SigPolymer.timeOut.run(() =>
            this._doKeypressInterval(element), this.keyboardRepeatInterval);
    }

    _keyPressUp(e) {
        this.sigPreventDefault(e, true);
        this._log('inside _keyPressUP');

        const element = e.composedPath()[0];
        if (element.classList.contains('key')) {
            if (!element.classList.contains('shift')) element.classList.remove('keypressed');
        }
        if (this.activeKey !== null) {
            if (!element.classList.contains('shift')) element.classList.remove('keypressed');
            this.activeKey = null;
            this.keyIsDown = false;
        }
        if (this.delay !== null) {
            SigPolymer.timeOut.cancel(this.delay);
            this.delay = null;
        }
        if (this.interval !== null) {
            SigPolymer.timeOut.cancel(this.interval);
            this.interval = null;
        }
    }

    _toggleLayer(switchlayer, keepLayout = true) {
        this.keepLayout = keepLayout;
        this._log(`Toggle layer "${switchlayer}"`);
        const layers = this.$.keyboard.querySelectorAll('div.layer');
        const currentlayer = this.$.keyboard.querySelector('div.layer[selected]');

        if (currentlayer !== null && currentlayer.classList.contains(switchlayer))
            switchlayer = 'normal';

        for (let layer of layers) {
            if (layer instanceof HTMLElement && layer.classList.contains(switchlayer)) layer.setAttribute('selected', '');
            else layer.removeAttribute('selected');
        }
    }

    _makePassword(newval, oldval) {
        if (newval !== undefined) {
            if (newval) {
                if (this.isChromeAndroid()) this.$.keyboardinput.style.webkitTextSecurity = "disc";
                else this.$.keyboardinput.setAttribute('type', 'password');
            } else {
                if (this.isChromeAndroid()) this.$.keyboardinput.style.webkitTextSecurity = "none";
                else this.$.keyboardinput.setAttribute('type', 'text');
            }
        }
    }

    _insertChar(char) {
        const pos = this._getCaretPosition();
        if (this.keyboardvalue.length + char.length - (pos.end - pos.start) <= this.keyboardmaxlength || this.keyboardmaxlength === 0) {
            if ((pos.end - pos.start) > 0) {
                this._updateValue(this._deleteString(this.keyboardvalue, pos.end, pos.end - pos.start), pos.start);
            }
            this._updateValue(this._insertString(this.keyboardvalue, pos.start, char), pos.start + char.length);
            this._log(`Inserted character "${char}" at position "${pos.start}"`);
        }
    }

    _deleteChar() {
        const pos = this._getCaretPosition();
        if (pos.start > 0 || pos.end > 0) {
            if (pos.end - pos.start > 0) {
                this._updateValue(this._deleteString(this.keyboardvalue, pos.end, pos.end - pos.start), pos.start);
            } else {
                this._updateValue(this._deleteString(this.keyboardvalue, pos.start, 1), pos.start - 1);
            }
            this._log(`Delete character at position "${pos.start}"`);
        }
    }

    _insertTab() {
        this._insertChar("    ");
    }

    _insertNewLine() {
        this._insertChar(this.BREAK_CHAR);
    }

    _moveCursor(move) {
        const pos = this._getCaretPosition();
        switch (move) {
            case 'left':
                if (pos.start > 0) {
                    this._setCaretPosition(pos.start - 1);
                    this._log(`Moved cursor left from position ${pos.start} to ${pos.start - 1}`);
                }
                break;
            case 'right':
                if (pos.start < this.keyboardvalue.length) {
                    this._setCaretPosition(pos.start + 1);
                    this._log(`Moved cursor right from position ${pos.start} to ${pos.start + 1}`);
                }
                break;
            case 'up':
                this._log(`Moved cursor up (not implemented for now)`);
                break;
            case 'down':
                this._log(`Moved cursor down (not implemented for now)`);
                break;
            default:
                break;
        }
    }

    _increaseValue() {
        switch (this.keyboardtype) {
            case 'numeric':
                const newVal = (this.keyboardApi) ? this.keyboardApi.incDataPoint(this.writeonincrement, this.keyboardvalue) : undefined;
                if (newVal !== undefined) {
                    this.keyboardvalue = newVal.toString();
                    if (this.writeonincrement) this._updateInput();
                }
                break;
        }
    }

    _decreaseValue() {
        switch (this.keyboardtype) {
            case 'numeric':
                const newVal = (this.keyboardApi) ? this.keyboardApi.decDataPoint(this.writeonincrement, this.keyboardvalue) : undefined;
                if (newVal !== undefined) {
                    this.keyboardvalue = newVal.toString();
                    if (this.writeonincrement) this._updateInput();
                }
                break;
        }
    }

    _makeNegative() {
        let value = this.keyboardvalue;
        if (isNaN(value) || value === '') {
            value = '-';
        } else {
            value = parseFloat(value) * -1;
            this._log(` Value multiplied with -1 new value is "${value}"`);
        }
        this.keyboardvalue = value.toString();
    }

    _insertString(str = '', pos = 0, value = '') {
        return str.substr(0, pos) + value + str.substr(pos);
    }

    _deleteString(str = '', pos = 0, chars = 1) {
        return str.substr(0, pos - chars) + str.substr(pos);
    }

    _undo() {
        this.keyboardvalue = this.undoValue;
    }

    _getCaretPosition(ctrl = this.$.keyboardinput) {
        if (document.selection) {
            ctrl.focus();
            let range = document.selection.createRange();
            let rangelen = range.text.length;
            range.moveStart('character', -ctrl.value.length);
            let start = range.text.length - rangelen;
            return {
                'start': start,
                'end': start + rangelen
            };
        }
        else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
            return {
                'start': ctrl.selectionStart,
                'end': ctrl.selectionEnd
            };
        } else {
            return {
                'start': 0,
                'end': 0
            };
        }
    }

    _setCaretPosition(start, end, ctrl = this.$.keyboardinput) {
        if (end === undefined) end = start;
        if (ctrl.setSelectionRange) {
            ctrl.focus();
            ctrl.setSelectionRange(start, end);
        }
        else if (ctrl.createTextRange) {
            var range = ctrl.createTextRange();
            range.collapse(true);
            range.moveEnd('character', end);
            range.moveStart('character', start);
            range.select();
        }
    }

    _updateInput() {
        if (!this.isinvalid && this.eventMediatorApi && this.eventsApi) {
            if (this.formatedValue === undefined) {
                const sanitizedValue = this.sanitizeInputValue(this.keyboardvalue);
                this.eventMediatorApi.publish(this.eventsApi.getInternalEvent('SYSWIN_KEYBOARD_VALUE'), sanitizedValue);
            } else {
                this.eventMediatorApi.publish(this.eventsApi.getInternalEvent('SYSWIN_KEYBOARD_VALUE'), this.formatedValue);
            }
        }
    }

    sanitizeInputValue(value = '') {
        return (this.sanitizeInput) ? this._parseValue(value, 0) : value;
    }

    desanitizeInputValue(value = '') {
        return (this.sanitizeInput) ? this._parseValue(value, 1) : value;
    }

    _parseValue(value, direction) {
        let parsedValue = value;

        if (direction === 0) {
            let temp = document.createElement("span");
            temp.textContent = value;
            parsedValue = temp.innerHTML;
            temp = null;

            this.allowedTags.forEach(tag => {
                if (!tag.paired) {
                    parsedValue = parsedValue.replace(new RegExp(`&lt;${tag.tag}/&gt;`, 'g'), `<${tag.tag}/>`);
                } else {
                    const openingTag = `&lt;${tag.tag}&gt;`;
                    const closingTag = `&lt;/${tag.tag}&gt;`;
                    if (parsedValue.split(openingTag).length === parsedValue.split(closingTag).length) {
                        parsedValue = parsedValue.replace(new RegExp(openingTag, 'g'), `<${tag.tag}>`);
                        parsedValue = parsedValue.replace(new RegExp(closingTag, 'g'), `</${tag.tag}>`);
                    }
                }
            });

            parsedValue = this._replaceWithFakeTags(parsedValue, 0);
        } else {
            parsedValue = this._replaceWithFakeTags(value, 1);
        }

        return parsedValue;
    }

    _replaceWithFakeTags(value, direction) {
        if (direction === 0) {
            return value.replace(new RegExp('&lt;', 'g'), this.OPENING_TAG_PLACEHOLDER)
                .replace(new RegExp('&gt;', 'g'), this.CLOSING_TAG_PLACEHOLDER);
        } else {
            return value.replace(new RegExp(this.OPENING_TAG_PLACEHOLDER, 'g'), "<")
                .replace(new RegExp(this.CLOSING_TAG_PLACEHOLDER, 'g'), ">");
        }
    }

    _updateValue(value, pos = -1) {
        this.keyboardvalue = value.toString();
        if (this.keyboardmask === "" && pos >= 0)
            this._setCaretPosition(pos);

        this._setFocus();
    }

    _setFocus() {
        if (!this.disabled) this.$.keyboardinput.focus();
    }

    _setFocusAndCursorAtTheEnd() {
        const input = this.$.keyboardinput;
        const tmp = input.value;
        input.focus();
        input.value = '';
        input.blur();
        input.focus();
        input.value = tmp;
    }

    _isValidTime(time) {
        let isValid = false;
        switch (this.keyboardplaceholder) {
            case 'HH:MM':
                if (/^([0-1][0-9]|2[0-3]):([0-5][0-9])?$/.test(time)) isValid = true;
                break;
            case 'MM:SS':
                if (/^([0-5][0-9])(:[0-5][0-9])?$/.test(time)) isValid = true;
                break;
            default:
                if (/^([0-1][0-9]|2[0-3]):([0-5][0-9])(:[0-5][0-9])?$/.test(time)) isValid = true;
        }
        return isValid;
    }

    _isValidDate(s) {
        let bits, y, m, d;
        switch (this.keyboardplaceholder) {
            case 'YYYY-MM-DD':
                bits = s.split('-');
                y = bits[0];
                m = bits[1];
                d = bits[2];
                break;
            case 'YY-MM-DD':
                bits = s.split('-');
                y = bits[0];
                m = bits[1];
                d = bits[2];
                break;
            case 'YY-MM':
                bits = s.split('-');
                y = bits[0];
                m = bits[1];
                d = 1;
                break;
            case 'MM-DD':
                bits = s.split('-');
                y = 0;
                m = bits[0];
                d = bits[1];
                break;
            case 'DD.MM':
                bits = s.split('.');
                y = 0;
                m = bits[1];
                d = bits[0];
                break;
            case 'MM.YY':
                bits = s.split('.');
                y = bits[1];
                m = bits[0];
                d = 1;
                break;
            default:
                bits = s.split('.');
                y = bits[2];
                m = bits[1];
                d = bits[0];
                break;
        }


        const daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

        if ((!(y % 4) && y % 100) || !(y % 400)) {
            daysInMonth[1] = 29;
        }
        return !(/\D/.test(String(d))) && d > 0 && d <= daysInMonth[--m];
    }

    _checkFirstInput(type, key, char) {
        if (this.firstinput && !this.isactive) {
            this.isactive = true;
            this.firstinput = false;

            switch (type) {
                case 'physical':
                    this.keyboardvalue = '';
                    if (char === undefined) { 
                        if (key >= 96 && key <= 108) { 
                            this.keyboardvalue = String.fromCharCode(key - 48);
                        } else if (key === 109 || key === 189) { 
                            this.keyboardvalue = '-';
                        } else {
                            this.keyboardvalue = String.fromCharCode(key);;
                        }
                    } else {
                        if (char.length <= 1) this.keyboardvalue = char;
                        else this.keyboardvalue = '';
                    }
                    break;
                case 'virtual':
                    let invalidKeys = [
                        '{clear}',
                        '{bksp}',
                        '{enter}',
                        '{accept}',
                        '{decrease}',
                        '{increase}',
                        '{curleft}',
                        '{curright}'
                    ];
                    if (invalidKeys.indexOf(key) < 0) this.keyboardvalue = '';
                    break;
                default:
                    this.keyboardvalue = '';
                    break;
            }
        }
    }

    _clearInput() {
        this._updateValue('', 0);
    }

    _inputTap() {
        if (!this.isactive) this.isactive = true;
    }

    _maskInput() {
        this.$.keyboardinput.setAttribute('placeholder', this.keyboardplaceholder);

        if (this.keyboardmaxlength > 0) {
            this.$.keyboardinput.setAttribute('maxlength', this.keyboardmaxlength);
        } else {
            this.$.keyboardinput.removeAttribute('maxlength');
        }

        if (this.keyboardmask !== '') {
            $.jMaskGlobals = {
                dataMask: false,
                watchInputs: false,
                translation: {
                    '0': { pattern: /\d/ },
                    '9': { pattern: /\d/, optional: true },
                    '#': { pattern: /\d/, recursive: true },
                    'A': { pattern: /[a-zA-Z0-9]/ },
                    'S': { pattern: /[a-zA-Z]/ },
                    '0x': { pattern: /[0x]/ },
                    'F': { pattern: /[A-Fa-f0-9]/ }
                }
            };

            const options = {
                onChange: (val) => this._updateValue(val)
            };
            $(this.$.keyboardinput).mask(this.keyboardmask, options);
        }
    }

    _toggleInput(newval, oldval) {
        this.undoValue = this.keyboardvalue;
        if (newval) this._activateInput();
        else this._deactivateInput();
    }

    _build(node, preCache = false) {
        this._log(`Building keyboard of type "${this.keyboardtype}" for language "${this.keyboardlang}" with layout "${this.activelayout}"`);
        const keyboardlayout = this.keyboardtype + "_" + this.keyboardlang;
        const keyboard = document.createDocumentFragment();

        if (this.keyboardcache.has(keyboardlayout)) {
            this._log(`Loading the keyboard from cache`);
            node.innerHTML = this.keyboardcache.get(keyboardlayout);
        } else {
            node.innerHTML = '';
            const layout = this._getKeyboardLayout();
            keyboard.appendChild(document.createElement('div'));
            keyboard.lastChild.className = this.keyboardtype;
            let currentLayer = keyboard.lastChild;
            let layernames = Object.getOwnPropertyNames(layout);
            let i = 0;
            for (const layer of Object.values(layout)) {
                currentLayer.appendChild(document.createElement('div'));
                currentLayer.lastChild.className = `layer ${layernames[i]}`;
                let currentRow = currentLayer.lastChild;
                i++;
                let rowcount = 0;

                for (const row of layer) {
                    let keys = row.split(' ');
                    currentRow.appendChild(document.createElement('div'));
                    currentRow.lastChild.classList.add('row', 'clearfix');
                    let currentKey = currentRow.lastChild;
                    let keycount = 0;
                    for (let key of keys) {
                        let keyclass = '';
                        let keytext = '';
                        let keyconfig = '';
                        switch (key) {
                            case '{bksp}':
                                keyclass = 'key special symbol bksp';
                                keytext = '\uf30a';
                                break;
                            case '{tab}':
                                keyclass = 'key special u2800 tab';
                                keytext = '\u2B72';
                                break;
                            case '{enter}':
                                keyclass = 'key special u2800 enter';
                                keytext = '\u2BA0';
                                keyconfig = { noRepeat: true };
                                break;
                            case '{shift}':
                                keyclass = 'key special u2800 shift';
                                keytext = '\u2B06';
                                keyconfig = { noRepeat: true };
                                break;
                            case '{accept}':
                                keyclass = 'key special symbol accept';
                                keytext = '\uf00c';
                                keyconfig = { noRepeat: true };
                                break;
                            case '{alt}':
                                keyclass = 'key special alt';
                                keytext = 'ALT';
                                keyconfig = { noRepeat: true };
                                break;
                            case '{space}':
                                keyclass = 'key space';
                                keytext = '\u00A0';
                                break;
                            case '{cancel}':
                                keyclass = 'key special symbol cancel';
                                keytext = '\uf00d';
                                keyconfig = { noRepeat: true };
                                break;
                            case '{off}':
                                keyclass = 'key off';
                                keytext = '\u00A0'
                                break;
                            case '{more}':
                                keyclass = 'key special u2000 more';
                                keytext = '\u2026';
                                keyconfig = { noRepeat: true };
                                break;
                            case '{curup}':
                                keyclass = 'key curup';
                                keytext = '\u2191';
                                break;
                            case '{curdow}':
                                keyclass = 'key curdown';
                                keytext = '\u2193';
                                break;
                            case '{curleft}':
                                keyclass = 'key curleft';
                                keytext = '\u2190';
                                break;
                            case '{curright}':
                                keyclass = 'key curright';
                                keytext = '\u2192';
                                break;
                            case '{clear}':
                                keyclass = 'key special u2000 clear';
                                keytext = '\u232B';
                                keyconfig = { noRepeat: true };
                                break;
                            case '{increase}':
                                keyclass = 'key special u2800 increase';
                                keytext = '\u2BC5';
                                break;
                            case '{decrease}':
                                keyclass = 'key special u2800 decrease';
                                keytext = '\u2BC6';
                                break;
                            case '{minus}':
                                keyclass = 'key minus';
                                keytext = '-';
                                break;
                            case '{undo}':
                                keyclass = 'key special symbol undo';
                                keytext = '\uf0e2';
                                keyconfig = { noRepeat: true };
                                break;
                            case '{newline}':
                                keyclass = 'key special symbol newline';
                                keytext = '\u21b2';
                                keyconfig = { noRepeat: true };
                                break;
                            default:
                                keyclass = 'key standard';
                                keytext = key;
                                break;
                        }
                        currentKey.appendChild(document.createElement('div'));
                        currentKey.lastChild.className = `${keyclass}`;
                        currentKey.lastChild.setAttribute('data-key', key);
                        currentKey.lastChild.textContent = keytext;
                        if (keyconfig instanceof Object) currentKey.lastChild.setAttribute('data-key-config', JSON.stringify(keyconfig));
                        keycount++;

                    }
                    rowcount++;
                }
            }

            let keyboardraw = document.createElement('div')
            keyboardraw.appendChild(keyboard);
            this.keyboardcache.set(keyboardlayout, keyboardraw.innerHTML);

            if (!preCache) node.innerHTML = keyboardraw.innerHTML;
        }
    }

    _getKeyboardLayout() {
        if (this.keyboardlayout &&
            this.keyboardlayout.hasOwnProperty('layouts') &&
            this.keyboardlayout.layouts.hasOwnProperty(this.keyboardtype) &&
            this.keyboardlayout.layouts[this.keyboardtype].layers) {
            return this.keyboardlayout.layouts[this.keyboardtype].layers;
        } else {
            this._log('Keyboard layout invalid', 'WARN');
            return {};
        }
    }

    _keyDoubleClick(el, onsingle, ondouble) {
        if (this.keepLayoutOnDblClick) {
            if (el.getAttribute("data-dblclick") === null) {
                el.setAttribute("data-dblclick", 1);
                if (this.dblClickTimeout === null) {
                    this.dblClickTimeout = this.sigSetTimeout(() => {
                        if (parseInt(el.getAttribute("data-dblclick"), 10) === 1) {
                            onsingle(false);
                        }
                        el.removeAttribute("data-dblclick");
                        this.dblClickTimeout = null;
                    }, 250);
                }
            } else {
                el.removeAttribute("data-dblclick");
                ondouble();
            }
        } else {
            onsingle(true);
        }
    }

    _loadLayout(newVal, oldVal) {
        SigPolymer.afterNextRender(this, async () => {
            let layoutToBuild = undefined;
            let render = true;
            if (newVal === null || newVal === undefined || (this.isdesignmode && newVal === '')) {
                this._log('No layoutmap selected, using internal fallback instead!', 'INFO');
            } else if (newVal !== '' && newVal !== null) {
                if (this.layoutscache.has(newVal) && !this.isdesignmode && this.layoutcachetoggle) {
                    layoutToBuild = this.layoutscache.get(newVal);
                } else {
                    try {
                        const layout = await (await fetch(newVal)).json();
                        if (this._isValidLayout(layout)) {
                            layoutToBuild = layout;
                            this.layoutscache.set(newVal, layout);
                            this._log(`Layout ${layout.name} loaded and built successfully.`, 'INFO');
                        } else {
                            this._log('Invalid layout format. See documentation for valid layout structure. Using internal fallback layout!', 'ERROR');
                        }
                    } catch (error) {
                        this._log(`Parsing of the JSON failed: ${error.message}. Using internal layout fallback instead.`, 'ERROR');
                    }
                }
            } else {
                render = false;
                this._log('Invalid layoutmap value. Building of layout skipped.', 'INFO');
            }

            if (render) this._buildLayout(layoutToBuild);
        });
    }


    _buildLayout(layout) {
        if (layout) {
            this.keyboardlayout = layout;
            this.keyboardlang = layout.lang;
            this.activelayout = layout.name;
        } else {
            this.keyboardlang = 'en';
            this.activelayout = 'Fallback (en)';
            this.keyboardlayout = {
                lang: 'en',
                name: 'English Fallback',
                autor: 'SIGMATEK GmbH & Co KG',
                version: '1.0',
                layouts: {
                    alpha: {
                        layers: {
                            normal: [
                                '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}',
                                '{tab} q w e r t y u i o p [ ]',
                                'a s d f g h j k l ; \' \\ {accept}', 
                                '{shift} z x c v b n m , . / {shift}',
                                '{alt} {undo} {curleft} {space} {curright} {clear} {more}'
                            ],
                            shift: [
                                '~ ! @ # $ % ^ & * ( ) _ + {bksp}',
                                '{tab} Q W E R T Y U I O P { }',
                                'A S D F G H J K L : " | {accept}', 
                                '{shift} Z X C V B N M < > ? {shift}',
                                '{alt} {undo} {curleft} {space} {curright} {clear} {more}'
                            ],
                            alt: [
                                '{off} ¡ ² ³ ¤ € ¼ ½ ¾ ‘ ’ ¥ × {bksp}',
                                '{tab} ä å é ® þ ü ú í ó ö « »',
                                'á ß ð {off} {off} {off} {off} {off} ø ¶ ´ ¬ {accept}',
                                '{shift} æ {off} © {off} {off} ñ µ ç {off} ¿ {shift}',
                                '{alt} {undo} {curleft} {space} {curright} {clear} {more}'
                            ],
                            more: [
                                '{off} ¹ {off} {off} £ {off} {off} {off} {off} {off} {off} {off} ÷ {bksp}',
                                '{tab} Ä Å É {off} Þ Ü Ú Í Ó Ö {off} {off}',
                                'Á § Ð {off} {off} {off} {off} {off} Ø ° ¨ ¦ {off}',
                                '{shift} Æ {off} ¢ {off} {off} Ñ {off} Ç {off} {off} {shift}',
                                '{alt} {undo} {curleft} {space} {curright} {clear} {more}'
                            ]
                        }
                    },
                    numeric: {
                        config: {
                            regexValidation: 'numeric'
                        },
                        layers: {
                            normal: [
                                '7 8 9 {bksp}',
                                '4 5 6 {increase}',
                                '1 2 3 {decrease}',
                                '. 0 {minus} {accept}'
                            ]
                        }
                    },
                    ipv4: {
                        config: {
                            regexValidation: 'ipv4'
                        },
                        layers: {
                            normal: [
                                '7 8 9 {bksp}',
                                '4 5 6 {undo}',
                                '1 2 3 {clear}',
                                '. 0 {off} {accept}'
                            ]
                        }
                    },
                    hex: {
                        config: {
                            regexValidation: 'hex'
                        },
                        layers: {
                            normal: [
                                'C D E F {bksp}',
                                '8 9 A B {undo}',
                                '4 5 6 7 {clear}',
                                '0 1 2 3 {accept}'
                            ]
                        }
                    },
                    time: {
                        config: {
                            regexValidation: 'time'
                        },
                        layers: {
                            normal: [
                                '7 8 9 {bksp}',
                                '4 5 6 {undo}',
                                '1 2 3 {clear}',
                                ': 0 {off} {accept}'
                            ]
                        }
                    },
                    date: {
                        config: {
                            regexValidation: 'date'
                        },
                        layers: {
                            normal: [
                                '7 8 9 {bksp}',
                                '4 5 6 {undo}',
                                '1 2 3 {clear}',
                                '. 0 - {accept}'
                            ]
                        }
                    },
                    full: {
                        layers: {
                            normal: [
                                "` 1 2 3 4 5 6 7 8 9 0 - = {bksp}",
                                "{tab} q w e r t y u i o p [ ]",
                                "a s d f g h j k l ; ' \u005C {accept}",
                                "{shift} z x c v b n m , . / {shift}",
                                "{alt} {undo} {curleft} {space} {curright} {clear} {more}"
                            ],
                            shift: [
                                "~ ! @ # $ % ^ & * ( ) _ + {bksp}",
                                "{tab} Q W E R T Y U I O P { }",
                                "A S D F G H J K L : \u0022 | {accept}",
                                "{shift} Z X C V B N M < > ? {shift}",
                                "{alt} {undo} {curleft} {space} {curright} {clear} {more}"
                            ],
                            alt: [
                                "{off} \u00A1 \u00B2 \u00B3 \u00A4 \u20AC \u00BC \u00BD \u00BE \u2018 \u2019 \u00A5 \u00D7 {bksp}",
                                "{tab} \u00E4 \u00E5 \u00E9 \u00AE \u00FE \u00FC \u00FA \u00ED \u00F3 \u00F6 \u00AB \u00BB",
                                "\u00E1 \u00DF \u00F0 {off} {off} {off} {off} {off} \u00F8 \u00B6 \u00B4 \u00AC {accept}",
                                "{shift} \u00E6 {off} \u00A9 {off} {off} \u00F1 \u00B5 \u00E7 {off} \u00BF {shift}",
                                "{alt} {undo} {curleft} {space} {curright} {clear} {more}"
                            ],
                            more: [
                                '{off} {off} {off} {off} {off} {off} {off} {off} {off} {off} {off} {off} {off} {off}',
                                "{off} {off} {curup} {off} {off} {increase} {off} {off} {off} {off} {off} {off} {off}",
                                "{off} {off} {curdow} {off} {off} {decrease} {off} {off} {off} {off} {off} {enter}",
                                "{shift} {off} {off} {off} {off} {off} {off} {off} {off} {off} {off} {off} {more}",
                                "{accept} {undo} {curleft} {space} {curright} {clear} {alt} {cancel}"
                            ]
                        }
                    }
                }
            };
        }
        this._build(this.$.keyboard);
        this._toggleLayer('normal');
    }

    _isValidLayout(layout) {
        if (layout &&
            layout.layouts &&
            layout.lang &&
            layout.name &&
            layout.layouts.alpha && layout.layouts.alpha.layers &&
            layout.layouts.numeric && layout.layouts.numeric.layers &&
            layout.layouts.ipv4 && layout.layouts.ipv4.layers &&
            layout.layouts.hex && layout.layouts.hex.layers &&
            layout.layouts.date && layout.layouts.date.layers &&
            layout.layouts.time && layout.layouts.time.layers &&
            layout.layouts.alpha.layers.normal &&
            layout.layouts.numeric.layers.normal &&
            layout.layouts.ipv4.layers.normal &&
            layout.layouts.hex.layers.normal &&
            layout.layouts.date.layers.normal &&
            layout.layouts.time.layers.normal)
            return true;
        return false;
    }

    enableKeyboard() {
        this._log("enableKeyboard:", this.id);

        if (this.disabled) {
            this.$.disableOverlay.style.display = 'none';
            this.disabled = false;
        }
    }

    disableKeyboard() {
        this._log("disableKeyboard:", this.id);

        if (!this.disabled) {
            this.$.disableOverlay.style.display = 'block';
            this.disabled = true;
            this.cleanup();
            this.$.keyboardinput.blur();
        }
    }


    _addKeypressEvents(node = this.$.keyboard) {
        if (node instanceof HTMLElement) {
            if (this.isiPhone()) {
                if (this.keypressJSMousedownHandler === null) {
                    this.keypressJSMousedownHandler = evt => {
                        if (evt.target === this && this.isiPhone() && !this.firstJSInputDone) {
                            this._keyPressDown(evt);
                        }
                    }
                    this.sigAddEventListener('mousedown', this.keypressJSMousedownHandler);
                }
                if (this.keypressJSMouseupHandler === null) {
                    this.keypressJSMouseupHandler = evt => {
                        if (evt.target === this && this.isiPhone() && !this.firstJSInputDone) {
                            this._keyPressUp(evt);
                            this.firstJSInputDone = true;
                        }
                    }
                    this.sigAddEventListener('mouseup', this.keypressJSMouseupHandler);
                }
            }

            if (this.keypressPolymerDownHandler === null) {
                this.keypressPolymerDownHandler = evt => {
                    if (!this.isiPhone() || this.firstJSInputDone) {
                        this._keyPressDown(evt);
                        if (this.keypressJSMousedownHandler !== null) {
                            this.sigRemoveEventListener('mousedown', this.keypressJSMousedownHandler);
                            this.keypressJSMousedownHandler = null;
                        }
                    }
                }
                this.sigAddEventListener('down', this.keypressPolymerDownHandler, 'polymerEvent', node);
            }

            if (this.keypressPolymerUpHandler === null) {
                this.keypressPolymerUpHandler = evt => {
                    if (!this.isiPhone() || this.firstJSInputDone) {
                        this._keyPressUp(evt);
                        if (this.keypressJSMouseupHandler !== null) {
                            this.sigRemoveEventListener('mouseup', this.keypressJSMouseupHandler);
                            this.keypressJSMouseupHandler = null;
                        }
                    }
                }
                this.sigAddEventListener('up', this.keypressPolymerUpHandler, 'polymerEvent', node);
            }
        } else {
            this._log('The given node to bind the keypress event handlers to is no valid HTML element!', 'ERROR');
        }
    }

    _addKeyEvents() {
        this.sigAddEventListener('keydown', evt => {
            if (!this.disabled && this.isactive) this._keydown(evt);
        }, 'jsEvent', this.$.keyboardinput);

        this.sigAddEventListener('keyup', e => {
            if (!this.disabled && !this.isactive && this.parentInstance && !this.parentInstance.hidden) {
                this._keydown(e);
                if (this.keyboardmask !== '') this._simulateEvent(this.$.keyboardinput, 'keydown');
            }
        }, 'jsEvent', window);
    }

    _addOtherEvents() {
        this.sigAddEventListener('SYSWIN_KEYBOARD_CLOSE', () => this.cleanup(), 'runtimeEvent');

        this.sigAddEventListener('jspaneldragstart', (e) => {
            if (this.context && e.detail === this.context.instanceId)
                this.currentCaretPosition = this._getCaretPosition().end;
        }, 'jsEvent', document);

        this.sigAddEventListener('jspaneldragstop', (e) => {
            if (this.context && e.detail === this.context.instanceId)
                this._setCaretPosition(this.currentCaretPosition);
        }, 'jsEvent', document);

        if (this.parentInstance && this.parentInstance.options && this.parentInstance.options.animateIn !== undefined) {
            this.sigAddEventListener("animationend", () => {
                SigPolymer.afterNextRender(this, () => {
                    if (!this.parentInstance.hidden)
                        this._setFocusAndCursorAtTheEnd();
                });
            }, 'jsEvent', this.parentInstance);
        }
    }
}
customElements.define(LasalRuntimeSigKeyboard.is, LasalRuntimeSigKeyboard);
