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

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

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

    /****************************************************************************************************
     * Return the component style and template as a template literal
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    static get template() {

        return SigPolymer.html`
        <style include="sig-element-css">
            :host {
                /* Basic styling of the component */
                background-color: var(--theme-sig-control-carousel-background-color, transparent);
                border-width: var(--theme-sig-control-carousel-border-width, 0px);
                border-color: var(--theme-sig-control-carousel-border-color, transparent);
                border-style: var(--theme-sig-control-carousel-border-style, none);

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

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

            /* If the composites are positions absolutely, it makes working with them difficoult */
            :host ::slotted(sig-composite-control) {
                position: relative !important;
                margin-bottom: var(--theme-sig-control-carousel-composite-container-margin-right, 10px);

            }

            :host(:not([vertical])) ::slotted(sig-composite-control) {
                /* display: inline-block !important; */
                margin-right: var(--theme-sig-control-carousel-composite-container-margin-right, 10px);
            }

            .clearfix:after {
                @apply --clearfix;
            }

            #outerContainer {
                height: 100%;
                width: 100%;
                background-color: transparent;
                overflow: hidden;
            }

            :host([isdesignmode]) {
                background: repeating-linear-gradient(  to right,
                                                        darkgray 0%, darkgray calc(20% - calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)),
                                                        transparent calc(20% - calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)), transparent calc(20% + calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)),
                                                        lightgray calc(20% + calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)), lightgray calc(40% - calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)),
                                                        transparent calc(40% - calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)), transparent calc(40% + calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)));
            }

            :host([isdesignmode])::before {
                position: absolute;
                top: 50%;
                right: auto;
                bottom: auto;
                left: 5%;
                transform: translateY(-50%);
                content: "";
                width: 0;
                height: 0;
                border-top: calc(var(--theme-sig-control-carousel-arrow-width, 100px) * 0.5) solid transparent;
                border-right: var(--theme-sig-control-carousel-arrow-height, 60px) solid #037CA9;
                border-bottom: calc(var(--theme-sig-control-carousel-arrow-width, 100px) * 0.5) solid transparent;
                border-left: none;
            }

            :host([isdesignmode])::after {
                position: absolute;
                top: 50%;
                right: 5%;
                bottom: auto;
                left: auto;
                transform: translateY(-50%);
                content: "";
                width: 0;
                height: 0;
                border-top: calc(var(--theme-sig-control-carousel-arrow-width, 100px) * 0.5) solid transparent;
                border-right: none;
                border-bottom: calc(var(--theme-sig-control-carousel-arrow-width, 100px) * 0.5) solid transparent;
                border-left: var(--theme-sig-control-carousel-arrow-height, 60px) solid #037CA9;
            }

            :host([isdesignmode][vertical]) {
                background: repeating-linear-gradient(  to bottom,
                                                        darkgray 0%, darkgray calc(20% - calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)),
                                                        transparent calc(20% - calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)), transparent calc(20% + calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)),
                                                        lightgray calc(20% + calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)), lightgray calc(40% - calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)),
                                                        transparent calc(40% - calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)), transparent calc(40% + calc(var(--theme-sig-control-carousel-composite-container-margin-right, 10px) / 2)));
            }

            :host([isdesignmode][vertical])::before {
                position: absolute;
                top: 3%;
                right: auto;
                bottom: auto;
                left: 50%;
                transform: translateX(-50%);
                content: "";
                width: 0;
                height: 0;
                border-top: none;
                border-right: calc(var(--theme-sig-control-carousel-arrow-width, 100px) * 0.5) solid transparent;
                border-bottom: var(--theme-sig-control-carousel-arrow-height, 60px) solid #037CA9;
                border-left: calc(var(--theme-sig-control-carousel-arrow-width, 100px) * 0.5) solid transparent;
            }

            :host([isdesignmode][vertical])::after {
                position: absolute;
                top: auto;
                right: auto;
                bottom: 5%;
                left: 50%;
                transform: translateX(-50%);
                content: "";
                width: 0;
                height: 0;
                border-top: var(--theme-sig-control-carousel-arrow-height, 60px) solid #037CA9;
                border-right: calc(var(--theme-sig-control-carousel-arrow-width, 100px) * 0.5) solid transparent;
                border-bottom: none;
                border-left: calc(var(--theme-sig-control-carousel-arrow-width, 100px) * 0.5) solid transparent;
            }

            :host([vertical]) #outerContainer {
                padding-left: 0px;
            }

            #container {
                height: 100%;
                position: absolute;
                white-space: nowrap;
            }

            :host([vertical]) #container {
                height: auto;
            }

            .compositeContainer {
                display: inline-block;
                white-space: initial;
            }

            :host([vertical]) .compositeContainer {
                display:flex;
                margin-right: 0px;
            }

            /** All the !important, bevause the styles are set inline, and we want to overrule that */
            sig-composite-control {
                position: relative !important;
                display: inline-block !important;
                transform: none !important;
            }

            .compositeContainer.notRendered {
                margin-right: var(--theme-sig-control-carousel-composite-container-margin-right, 10px);
                margin-bottom: var(--theme-sig-control-carousel-composite-container-margin-right, 10px);
            }
        </style>
        <div id="outerContainer">
            <div id="container"></div>
        </div>
        `
    }

    /****************************************************************************************************
     * Return the component style and template as a template literal
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    static get is() {
        return "sig-control-carousel";
    }

    /****************************************************************************************************
     * define the main properties of the component
     * reflected property names should always be lower case so it can be used directly on the element as an attribute
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    static get properties() {
        return {
            value: {
                type: Number,
                value: 0,
                observer: '_valueChanged',
                notify: true
            },
            absolutevalue: {
                type: Number,
                value: undefined,
                observer: '_absoluteValueChanged',
                notify: true
            },
            compositescheme: {
                type: Object,
                value: () => { return {} }
            },
            datapointscheme: {
                type: Object,
                value: () => { return {} }
            },
            snapin: {
                type: Boolean,
                value: false
            },
            vertical: {
                type: Boolean,
                value: false,
                reflectToAttribute: true
            },
            dimension: {
                type: String,
                computed: '_cDimension(vertical)'
            },
            direction: {
                type: String,
                computed: '_cDirection(vertical)'
            },
            propagationtime: {
                type: Number,
                value: 500,
            },
            infinite: {
                type: Boolean,
                value: true
            },
            minscroll: {
                type: Number,
                value: null,
                observer: '_setMinMaxTransform'
            },
            maxscroll: {
                type: Number,
                value: null,
                observer: '_setMinMaxTransform'
            }
        }
    }

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

        //default value for flick scroll
        this.flickscroll = true;

        this.compositeControls = [];
        this.renderedComposites = 0;
        this.datapointSchemeObj = [];

        this.trackStart = undefined;
        this.currentTransform = 0;

        this.maxTransform = undefined;
        this.infiniteTransform = undefined;

        this.evtQueue = [];
        this.evtQueueBusy = false;

        this.evtBubbledUp = false;

        this.disableTrack = false;

        this.easing = {
            easeOutCubic: function (pos) {
                return (Math.pow((pos - 1), 3) + 1);
            },
            easeOutQuart: function (pos) {
                return -(Math.pow((pos - 1), 4) - 1);
            },
        };

        this.touchData = {
            startY: 0,
            yArr: []
        };

        this.moving = false;

        this.minTransformLimit = 0;
        this.maxTransformLimit = 0;
        
        this.checkbitDebouncer = undefined

        this.checkbitObserver = () => {
            if(!this.rendered) return;
            this.sigClearTimeout(this.checkbitDebouncer);
            this.checkbitDebouncer = this.sigSetTimeout(() => {
                let tmpMaxTransform = this.maxTransform;
                let maxTransform = this._getMaxTransform_new();
                this._populateContainer();
                if(tmpMaxTransform !== maxTransform && this.infinite) {
                    this._appendInfiniteElements();
                }
                SigPolymer.afterNextRender(this, () => {
                    //if no absolute value is configured we use normal value to update
                    if (this.absolutevalue !== undefined) {
                        this._absoluteValueChanged();
                    }
                    else {
                        this._valueChanged();
                    }
                })
                
            }, 100);
        }
    }

    /****************************************************************************************************
     * Called after property default values are set and local DOM is initialized.
     *
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    ready() {
        super.ready();
    }

     /****************************************************************************************************
     * gets called if element was connected to dom
     *
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    connectedCallback() {        
        super.connectedCallback();
        this._addEventListeners();
        this._renderCallback();
    }

    /****************************************************************************************************
     * Handles the touch and mouse up events
     *
     * @param {*} e
     * @return {*} 
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    _upHandler(e) {
        if(e.type === 'touchend') {
            this.touchmode = true;
        }
        if(this.touchmode && e.type === 'mouseup') {
            e.stopPropagation();
            return;
        }
        //if the event shouldn't be messed with, we do nothing
        if (e.dontStop) {
            return;
        }
        if ((this.trackStart === undefined && !e.dontStop) || this.propagationtime === 0) {
            //If we didn't track, and the event wasn't already stopped by us,
            //the evnt is stopped and added to the queue to preserve the event order
            e.stopPropagation();
            let newEvt = new e.constructor(e.type, e);
            newEvt.dontStop = true;
            let el = e.composedPath()[0].closest('.sig-control-button-base');
            if (el === null) {
                el = e.composedPath()[0].closest('.sig-control-image-button-base');
            }
            if (el === null) {
                el = e.composedPath()[0]
            }

            this.evtQueue.push({
                el,
                evt: newEvt
            })
            this._doEvtQueue();
            return;
        } else {
            e.stopPropagation();
            this._endTrack(e);
            return;
        }
    }

    /****************************************************************************************************
     * Handles the touch and mouse down events
     *
     * @param {*} e
     * @return {*} 
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    _downHandler(e) {        
        if(e.type === 'touchstart') {
        this.touchmode = true;
        }
        if(this.touchmode && e.type === 'mousedown') {
            e.stopPropagation();
            return;
        }
        //per default events are stopped from propagation
        if (!e.dontStop) {
            //Stop propagating the event if it is the first time it is encountered
            e.stopPropagation();

            //If it is a fresh touch down event
            //we make sure the component knows that this is a fresh down event
            //to ensure scrolling keeps on working
            if (!e.noBubbleUp) {
                this.evtBubbledUp = false;
            }

            //Set dontStop variable, so that event propagation isn't stopped when it is fired again
            e.dontStop = true;
            //A new event, dispatched from this.$.container, so the container gets the event,
            //While the contents dont (since the event for the contents would be stopped via stopPropagation)
            const newEvt = new e.constructor(e.type, e);
            newEvt.dontStop = true;
            newEvt.noBubbleUp = true;
            this.$.container.dispatchEvent(newEvt);

            //After a fixed timeout dispatch the event again, so the containers contents can do their event Handling
            //If a track move starts before the doEvtQueue was called, 
            //this.evtQueue is reset
            if(!e.evtDontHandleDown) {
                this.evtQueue.push({
                    el: e.composedPath()[0],
                    evt: e
                });
            }
            
            this.sigSetTimeout(() => {
                this._doEvtQueue();
            }, this.propagationtime);
        }
    }

    /****************************************************************************************************
     * Adds the event listeners for static content
     *
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    _addEventListeners() {
        const container = this.$.container;
        this.sigAddEventListener('track', (e) => {
            this._handleTrack(e);
        }, 'polymerEvent', container);

        //Touchmove needs to be handled seperately; otherwise it can happen 
        //That a slider moves as well as the whole scroll area
        this.sigAddEventListener('touchmove', (e) => {
            if (this.evtBubbledUp) {
                return;
            }
            if (e.dontStop) {
            } else if (!e.dontStop || ((this.trackStart !== undefined) && e.target !== this)) {
                e.stopPropagation();
                const newEvent = new e.constructor('touchmove', e);
                newEvent.dontStop = true;
                container.dispatchEvent(newEvent);
            }
        }, 'jsEvent', container, {capture: true})

        //Js Event Handler because Polymer doesn't support event capturing as far as I know
        // => Necessary to use js event handler, gesture mixin is not possible 
        const downHandler = this._downHandler.bind(this);
        this.sigAddEventListener('mousedown', downHandler, 'jsEvent', this.$.container, { capture: true });
        this.sigAddEventListener('touchstart', downHandler, 'jsEvent', this.$.container, { capture: true });

        const upHandler = this._upHandler.bind(this);
        this.sigAddEventListener('mouseup', upHandler, 'jsEvent', this.$.container, { capture: true });
        this.sigAddEventListener('touchend', upHandler, 'jsEvent', this.$.container, { capture: true });
        
        this.sigAddEventListener('carouseldisabletrack', () => {
            this.disableTrack = true;
        }, 'jsEvent', document);
        this.sigAddEventListener('carouselenabletrack', () => {
            this.disableTrack = false;
        }, 'jsEvent', document);
    }

    /****************************************************************************************************
     * Handles the track events
     *
     * @param {*} e
     * @return {*} 
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    _handleTrack(e) {
        //If the event bubbled up, dont handle the track
        //Otherwise there are problems if controls within the composite use track events
        if (this.evtBubbledUp) {
            return;
        }
        //correct the event corrdinates for rotation
        e = this._getRotationCorrectedEvent(e);
        if (e.detail.state === 'start') {
            
            let tmpMaxTransform = this.maxTransform;
            let maxTransform = this._getMaxTransform_new();
            if(tmpMaxTransform !== maxTransform && this.infinite) {
                this._appendInfiniteElements();
            }

            //since we are now moving the track, we dont need to propagate events down
            //so we clear the queue
            this.evtQueue = [];
            //the starting x position of the track event
            this.trackStart = e.detail.x / this._getScaleFactor();
            this._stop();
            this.touchData.yArr = [];
        } else if (e.detail.state === 'track') {
            if (this.trackStart === undefined) {
                return;
            }
            //calculate the new transform
            let transform = this._getTrackTransform(e.detail.x /this._getScaleFactor());
            transform = Math.min(this.minTransformLimit[this.direction], transform);
            transform = Math.max(this.maxTransformLimit[this.direction], transform);
            if(!this.disableTrack) {
                //change the current transform of the bar
                this._moveTo(transform);

                //Save the touchdata for the flick scroll on touch end
                this.touchData.yArr.push([e.detail.x, new Date().getTime()]);
                if (this.touchData.yArr.length > 5) {
                    this.touchData.yArr.shift();
                }
                this._renderVisibleCCs();
            }
        } else {
            this._endTrack(e);
        }
    }

    /****************************************************************************************************
     * Handles the track-end events
     *
     * @param {*} e
     * @return {*} 
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    _endTrack(e) {
        if (this.trackStart === undefined) {
            this.trackStart = undefined
            return;
        }
        if(this.touchData.yArr.length === 0) {
            this._moveTo(this.currentTransform);
            this.trackStart = undefined;
            return;
        }
        let transform = this._getTrackTransform(this.touchData.yArr[this.touchData.yArr.length - 1][0] / this._getScaleFactor());
        transform = Math.min(this.minTransformLimit[this.direction], transform);
        transform = Math.max(this.maxTransformLimit[this.direction], transform);
        this._moveTo(transform);
        this.currentTransform = transform;

        //check if flick scroll is activated
        if (this.flickscroll === true) {
            this._flickScroll();
        }
        //check if snapin should be done
        else if (this.snapin === true) {
            //animate the scrolling to the reached target composite control -> 
            //0.8 seconds for animation seems smooth so i don't see a point in making it configurable
            this._animateToScroll(transform, this._getSnappedTransform(transform), 0.8);
        }
        this.trackStart = undefined;
    }

    /****************************************************************************************************
     * Calculates the speed of the flick, and calls the function that starts the animation
     *
     * @return {*} 
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    _flickScroll() {
        //The start and end position of the scroll motion
        const startPos = this.touchData.yArr[0][0];
        const endPos = this.touchData.yArr[this.touchData.yArr.length - 1][0];

        //The start and end time of the scroll motion
        const startT = this.touchData.yArr[0][1];
        const endT = this.touchData.yArr[this.touchData.yArr.length - 1][1]

        const currentT = new Date().getTime();
        // If the last move event is too far in the past a flick scroll shouldn't happen
        if(currentT - endT > 100) {
            this._animateMoveByInitV(0);
            return;
        }
        let t = endT - startT;
        //Avoid division by 0
        if (isNaN(t) || t === 0) {
            t = 1;
        }
        //10000 trial and error; otherwise the scrolling is slow
        const v = ((endPos - startPos) / 100) * 10000 / t;

        this._animateMoveByInitV(v);
    }

    /****************************************************************************************************
     * Gets the transform value of the container
     * makes sure the transform stays within bounds
     * 
     * @param {Number} x - The x coordinates of the event
     * @return {Number} - The value by which the container shlould be transformed
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _getTrackTransform(x) {

        let transform = this.currentTransform - this.trackStart + x;
        if (this.infinite) {
            transform = this._normalizeScroll(transform);
        } else {
            if (transform > 0) {
                transform = 0;
            }
            let maxTransform = this._getMaxTransform_new()
            if (transform < maxTransform) {
                transform = maxTransform;
            }
        }
        return transform;
    }

    /****************************************************************************************************
     * Normalizes the scroll value to a value between 
     * 0 and the number of elements 
     * 
     * @param {Number} scroll - A Number rpresenting the scroll position
     * @return {Number} - A Number between 0 and the source length
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _normalizeScroll(scroll) {
        let normalizedScroll = scroll;
        const transform = this.infiniteTransform ? this.infiniteTransform : this._getMaxTransform_new();
        while (normalizedScroll > 0) {
            normalizedScroll += transform;
        }
        normalizedScroll = normalizedScroll % transform;
        return normalizedScroll;
    }

    /****************************************************************************************************
     * Calculates the max transform
     * 
     * @return {Number} - the max transform
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _getMaxTransform_new(force = false) {
        let compositeBounds = [];

        if(!this.ccSchemeLength) {
            this.ccSchemeLength = this.compositeControls.length;
        }
        
        if(force && this.maxTransform) {
            return this.maxTransform;
        }

        let maxTransform = 0;
        if(!this.compositeMargin && this.compositeMargin !== 0) {
            this.compositeMargin = 10;
            const compositeContainer = this.$.container.querySelector('.compositeContainer');
            let margin = ''
            if(this.vertical) {
                margin = 'margin-bottom'
            } else {
                margin = 'margin-right'
            }

            if(compositeContainer) {
                this.compositeMargin = parseFloat(getComputedStyle(compositeContainer).getPropertyValue(margin));
            }

            this.compositeMargin = parseFloat(getComputedStyle(this).getPropertyValue('--theme-sig-control-carousel-composite-container-margin-right'));
            if(isNaN(this.compositeMargin)) {
                // If no composite Margin is set, we use the default value
                this.compositeMargin = 10;
            }
        }

        for(let i = 0; i < this.ccSchemeLength; i++) {
            let checkbit = this.checkbitObj[i].getValue();
            compositeBounds[i] = {};
            compositeBounds[i].start = maxTransform;
            if(checkbit) {
                maxTransform -= parseInt(this.compositeControls[i].cc.getControlsJson().props[`--theme-sig-element-${this.dimension}`].value);
                maxTransform -= this.compositeMargin 
            }
            compositeBounds[i].end = maxTransform;            
        }   

        if (this.infinite) {
            this.infiniteTransform = maxTransform;
        }

        maxTransform += this.bounds[this.dimension];

        if(this.snapin) {
            for(let i = 0; i<compositeBounds.length; i++) {
                if(compositeBounds[i].start <= maxTransform) {
                    if(compositeBounds[i].start !== compositeBounds[i].end) {
                        maxTransform = compositeBounds[i].start
                    }
                    break;
                }
            }
        }

        this.$.container.style['min-'+this.dimension] = -maxTransform+this.bounds[this.dimension] + 'px';
        this.maxTransform = maxTransform;
        return maxTransform;
    }

    /****************************************************************************************************
     * Checks a composite control for a checkbit
     *
     * @param {Object} compositeControl - the composite control that needs to be checked
     * @returns {Boolean} - the value of the checkbit; true, if no checkbit is set
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _checkCheckbit(compositeControl) {
        if(compositeControl.vm) {
            let ccDomRef = compositeControl.vm.getDomRef();
            return ccDomRef.checkbit;
        }
        let props = compositeControl.cc.getControlsJson().props;
        let checkbit = false;
        if(props.checkbit) {
            checkbit = sigApi.checkbitManager.getCheckbitById(props.checkbit.value.split(':')[1])
            checkbit = checkbit ? checkbit.getValue() : false;
        }
        if(!props.checkbit || checkbit) {
            return true;
        } else {
            return false;
        }
    }
    
    /****************************************************************************************************
     * returns the new transform, if the component is set to snapin
     * 
     * @param {Number} transform - a transform Value
     * @return {Number} the new actual transform
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _getSnappedTransform(transform) {
        //Make since the offsets of the elements are positive,
        //and the transforms are negative, make sure everything is positive for easier calculation
        transform = Math.abs(transform);
        const controls = this.$.container.querySelectorAll('.compositeContainer');

        let compositeContainerMargin;
        //The value that will be returned
        let ret = transform;
        if (this.vertical) {
            compositeContainerMargin = parseFloat(getComputedStyle(this.$.outerContainer.querySelector('.compositeContainer')).getPropertyValue('margin-bottom'))
        } else {
            compositeContainerMargin = parseFloat(getComputedStyle(this.$.outerContainer.querySelector('.compositeContainer')).getPropertyValue('margin-right'))
        }

        //Loop through the control to find the first one that is partially visible
        for (let i = 0; i < controls.length; i++) {
            const control = controls[i];

            const controlBounds = {
                height: controls[i].offsetHeight,
                width: controls[i].offsetWidth,
                top: controls[i].offsetTop,
                left: controls[i].offsetLeft
            }

            if ((controlBounds[this.direction]) <= transform
                && ((controlBounds[this.direction] + controlBounds[this.dimension]) + compositeContainerMargin) >= transform) {
                let offset = 0;
                // 2 * controlBounds[direction] means 2 * the top or left position
                // controlBounds[dimension] means the width or height
                // Transform is the value how much the control has been transformed
                const delta = (2 * controlBounds[this.direction] + controlBounds[this.dimension]) / transform;
                if (delta < 2) {
                    ret = -controls[i + 1][`offset${this.direction.charAt(0).toUpperCase() + this.direction.slice(1)}`]
                } else {
                    if (i === 0) {
                        ret = 0;
                    } else {
                        ret = -(controlBounds[this.direction]);
                    }
                }
                break;
            }
        }

        //If there is no max transform, just return the calculated value
        //Same goes if the control is scrolling infinitely
        if ((!this.maxTransform && this.maxTransform !== 0) || this.infinite) {
            return ret;
        } else if (ret !== undefined && (this.maxTransform || this.maxTransform === 0)) {
            // if there is a max transform, make sure the value isn't outside of that
            return Math.max(ret, this.maxTransform);
        } else {
            //If we don't hav anything useable return the transform value
            return -transform;
        }
    }

    /****************************************************************************************************
     * corrects the polymer track event for rotation
    * 
    * @param {Event} e - the Polymer.Gestures track event
    * @returns {Object} - the needed event variables corrected for rotation
    * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _getRotationCorrectedEvent(e) {
        const rotation = this._getRealRotation(parseInt(this.realrotation, 10));
        let ret = {};
        ret.detail = {};
        ret.detail.state = e.detail.state;
        if (this.vertical) {
            if (rotation === 90) {
                ret.detail.x = -e.detail.x;
            } else if (rotation === 180) {
                ret.detail.x = -e.detail.y;
            } else if (rotation === 270) {
                ret.detail.x = e.detail.x;
            } else {
                ret.detail.x = e.detail.y
            }
        } else {
            if (rotation === 90) {
                ret.detail.x = e.detail.y;
            } else if (rotation === 180) {
                ret.detail.x = -e.detail.x;
            } else if (rotation === 270) {
                ret.detail.x = -e.detail.y;
            } else {
                //if no rotation occured, the event itself is fine;
                return e;
            }
        }
        return ret;
    }

    /****************************************************************************************************
     * Callback when the control has useable dimensions
     * 
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    async _renderCallback() {

        if (!this.isdesignmode) {

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

            await this._getCompositeControls();

            // load the schemes needed for overriding
            this.datapointSchemeObj = await this._getDatapointScheme('datapointscheme');

            this.checkbitObj = await this._getCheckbitObject();

            //Adds the composite controls to the container
            this._populateContainer();

            //Set the max transform here, and NEVER touch it again
            //If we scroll infinitely, the max transform is wrong after adding additional controls
            this.maxTransform = this._getMaxTransform_new();

            //If we scroll infinitely, we need to add CCs and datapoints at the end
            if (this.infinite) {
                this._appendInfiniteElements()
            }

            //After we have the composite controls, and the datapoint scheme

            this.rendered = true;
            this._setMinMaxTransform();
            let vm = sigApi.vm.getVmById(this.id);
            //if no absolute value configured we use normal value to update
            if (this.absolutevalue !== undefined) {
                this._absoluteValueChanged();
            }
            else {
                this._valueChanged();
            }    

            if(!this.renderRemainingCCsStarted) {
                this.renderRemainingCCsStarted = true;
                this.sigSetTimeout(() => {
                    this._renderRemainingCCs();
                },2000)
            }
        }   
    }


    /****************************************************************************************************
     * create promise to get checkbit informations
     *
     * @return {Promise} 
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    _getCheckbitObject() {
        return new Promise((resolve, reject) => {
            let ret = [];
            for(let cc of this.compositeControls) {
                let checkbit = cc.cc.getControlsJson().props.checkbit;
                if(checkbit) {
                    ret.push(sigApi.properties.createProperty({
                        src: sigApi.SIG_CONST.PROP_SRC_CHECKBIT,
                        dest: sigApi.SIG_CONST.PROP_DEST_NONE,
                        value: checkbit.value}));
                } else {
                    ret.push({
                        getValue: () => {return true}
                    })
                }
            }
            for(let dp in this.datapointSchemeObj) {
                if(ret[dp].overload) {
                    ret[dp].overload(this.datapointSchemeObj[dp]);
                }
            }
    
            for(let checkbit in ret) {
                if(ret[checkbit].addObserver) {
                    ret[checkbit].addObserver(this.checkbitObserver);
                }
                    
            }
            SigPolymer.afterNextRender(this, () => {
                resolve(ret);
            })
        })
    }

    /****************************************************************************************************
     * Appends Elements needed for infinite scrolling
     *
     * @returns
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _appendInfiniteElements() {
        if (!this.infinite) {
            return;
        }
        if(this.ccSchemeLength) {
            for(let i = this.compositeControls.length ;i > this.ccSchemeLength; i=this.compositeControls.length) {
                let slotName = `${this.id}${i-1}`
                sigApi.vm.removeCC(sigApi.vm.getVmById(this.id), slotName);
                this.$.container.removeChild(this.slotWrappers[slotName]);
                delete this.slotWrappers[slotName];
                this.compositeControls.pop();
            }

            for(let i = this.datapointSchemeObj.length; i>this.ccSchemeLength; i=this.datapointSchemeObj.length) {
                this.datapointSchemeObj.pop();
            }

            for(let i = this.checkbitObj.length; i>this.ccSchemeLength; i=this.checkbitObj.length) {
                this.checkbitObj.pop();
            }
        }

        const compositeBounds = this.compositeControls.map((composite, idx) => {
            let checkbit = this.checkbitObj[idx].getValue();
            if(checkbit) {
                return parseInt(composite.cc.getControlsJson().props[`--theme-sig-element-${this.dimension}`].value);
            } else {
                return 0;
            }      
        })
        let len = 0;
        for(let i = 0; i < compositeBounds.length; i++) {
            len += compositeBounds[i];
        }
        if(len === 0) {
            // All CCs are disabled -> no need to add infinite CCs
            return;
        }
        const slots = this.$.container.querySelectorAll('slot');

        let addedSize = 0;
        const container = this.$.container;
        const parent = window.sigApi.vm.getVmById(this.id)

        let i = 0;
        while (addedSize < this.bounds[this.dimension]) {
            this.compositeControls.push({
                vm: null,
                cc: this.compositeControls[i % this.compositeControls.length].cc
            });
            if(!!this.datapointSchemeObj[i] || this.datapointSchemeObj[i] === 0) {
                this.datapointSchemeObj.push(this.datapointSchemeObj[i]);
            }
            if(!!this.checkbitObj[i]) {
                this.checkbitObj.push(this.checkbitObj[i]);
            }
            let compositeControl = this.compositeControls[this.compositeControls.length - 1];
            addedSize += compositeBounds[i % compositeBounds.length];
            this._addPlaceHolderToContainer(container, this.compositeControls.length - 1, compositeControl);           
            i++;
        }
    }

    /****************************************************************************************************
     * gets the scheme data, and returns it
     * 
     * @param {String} - The Name of the scheme that should be returned
     * @return {Array} - the scheme data
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    async _getDatapointScheme(schemeName) {
        let ret = [];
        try {
            let schemeData = await sigApi.properties.getSchemeBranchDataWithProperty(this.id, schemeName)

            for (let branch of schemeData) {
                //In each branch there should be a refPropObj
                //If refPropObj.src === 6 that means that refPropObj.value is the datapoint id
                if (branch.resultPropertyObject && branch.resultPropertyObject.src === 6) {
                    const dpId = branch.resultPropertyObject.value;
                    const dataPoint = await window.sigApi.datapointManager.getDatapoint(dpId);

                    if (dataPoint !== undefined && dataPoint !== null) {
                        // check if scheme value is already the class itself and store - otherwise trace the given datapoint back to class
                        if (dataPoint.getTypeId() === window.sigApi.SIG_CONST.TYPE_ID_CLASS || dataPoint.getTypeId() === window.sigApi.SIG_CONST.TYPE_ID_OBJECT) {
                            ret.push(dataPoint.getId());
                        } else {
                            //check if getScope method already exists -> otherwise use old one
                            //we have to do it this way, cause this version of carousel is a bugfix but the runtime does not include getScope yet
                            //to avoid making two versions we check if getScope exists
                            if (dataPoint.getScope) {
                                //scope should be the object that we want to access
                                ret.push(dataPoint.getScope());                                
                            }
                            else {
                                //scope should be the object that we want to access
                                ret.push(dataPoint.__.getScope());                                  
                            }
                        }
                    }
                }
            }
        } catch (err) {
            this._log('_getDatapointScheme(): No scheme', 'DEBUG');
        } 
        return ret;
    }

    /****************************************************************************************************
     * Adds the composite control to the container
     * 
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _populateContainer() {
        const container = this.$.container;
        container.innerHTML = '';
        const parent = window.sigApi.vm.getVmById(this.id)
        for (let [idx, compositeControl] of this.compositeControls.entries()) {
            this._addPlaceHolderToContainer(container, idx, compositeControl)
        }
    }

    /****************************************************************************************************
     *
     * @param {*} container
     * @param {*} idx
     * @param {*} compositeControl
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    _addPlaceHolderToContainer(container, idx, compositeControl) {
        if(!this.slotWrappers) this.slotWrappers = {};

        const slotWrapper = document.createElement('div');
        const ccWrapper = document.createElement('slot');
        slotWrapper.append(ccWrapper);
        container.append(slotWrapper);

        const slotName = `${this.id}${idx}`;
        ccWrapper.name = slotName;
        const checkbit = this.checkbitObj[idx].getValue();
        slotWrapper.classList.add('compositeContainer');
        if(!checkbit) {
            slotWrapper.style.height = '0px';
            slotWrapper.style.width = '0px';
            slotWrapper.style.marginRight = '0px';
            slotWrapper.style.marginBottom = '0px';
        } else {
            slotWrapper.style.height = compositeControl.cc.getControlsJson().props['--theme-sig-element-height'].value;
            slotWrapper.style.width = compositeControl.cc.getControlsJson().props['--theme-sig-element-width'].value;
            slotWrapper.style.removeProperty('margin-right');
            slotWrapper.style.removeProperty('margin-bottom');
        }
        slotWrapper.classList.add('notRendered');
        slotWrapper.dataset.slotName = slotName;
        this.slotWrappers[slotName] = slotWrapper;
    }

    /****************************************************************************************************
     * gets the compositecontrol that are in the scheme
     *
     * @return {*} 
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    async _getCompositeControls() {
        //If we alerady have the composite controls, we don't need to reload them
        if (this.compositeControls.length > 0) {
            return true;
        }
        // Get the scheme Data
        const schemeData = await window.sigApi.properties.getSchemeDataWithProperty(this.id, 'compositescheme');
        // Map only the relevant information
        this.compositeControls = schemeData.map((entry) => {
            return {
                vm: null,
                cc: entry.value
            };
        })
    }

    /****************************************************************************************************
     * Returns the scalefactor for the component
     * 
     * @param {Boolean} forceUpdate - Forces the scalefactor to update; otherwise an already calculated scalefactor is úsed
     * @returns {Number} - The scalefactor of the component
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _getScaleFactor(forceUpdate = false) {
        if (this.scaleFactor === undefined || this.scaleFactor === null || this.isdesignmode || forceUpdate) {
            // getBoundingClientRect().height does not return the scaled with on webkit
            const rotation = this._getRealRotation(parseInt(this.realrotation, 10));
            if (rotation === 90 || rotation === 270) {
                this.scaleFactor = this.getBoundingClientRect().height / this.offsetWidth;
            } else {
                this.scaleFactor = this.getBoundingClientRect().width / this.offsetWidth;
            }
        }
        return this.scaleFactor;
    }

    /****************************************************************************************************
     * @param {Number} rotation - the rotation you want to convert
     * @return {Number} - the rotation between 0 and 360 deg
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _getRealRotation(rotation = 0) {
        return (rotation % 360 + 360) % 360;
    }

    /****************************************************************************************************
     * Works through an queue array
     * the array elements look like this
     * {
     *      el: the element that dispatches the event
     *      evt: the event that is to be dispatched
     * }
     * 
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _doEvtQueue() {
        //Do nothing if the queue is already running
        if (this.evtQueueBusy) {
            return;
        }
        //Do nothing if there are no elements in the queue
        if (this.evtQueue.length === 0) {
            return;
        }

        //Since the queue is for the events that were stopped from propagating down
        //we now set the evtBubbledUp to true
        //since those events are coming from below
        this.evtBubbledUp = true;
        this.evtQueueBusy = true;

        //while there are elements in the queue, dispatch the events
        while (this.evtQueue.length > 0) {
            const evt = this.evtQueue.shift();
            try {
                evt.el.dispatchEvent(evt.evt);
            } catch (e) {
                this._log("_doEvtQueue(): " + e.message, 'ERROR');
            }
        }
        this.evtQueueBusy = false;
    }

    /****************************************************************************************************
     * Jumps to the scroll value
     * 
     * @param {init} scroll 
     * @return the scroll position after it was normalized
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _moveTo(scroll) {
        if (this.infinite) {
            scroll = this._normalizeScroll(scroll);
        }
        this._getSnappedTransform();
        if (this.vertical) {
            this.$.container.style.top = `${scroll}px`;
        } else {
            this.$.container.style.left = `${scroll}px`;
        }
        return scroll;
    }

    /****************************************************************************************************
     * Animates from initScroll to finalScroll in t time
     * 
     * @param initscroll    Starting position
     * @param finalScroll   End position
     * @param t             Time
     * @param easingName    Name of the easing function
     * @param around        determines if the animation goes past the max transform, and starts again at the beginning
     * @return {Promise}    Resolves when the scrolling is finished
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _animateToScroll(initScroll, finalScroll, t, easingName = 'easeOutCubic', around = undefined) {
        // If there is no need to animate, just move to the end position
        if (initScroll === finalScroll || t === 0) {
            this._moveTo(finalScroll);
            this.value = this._getValueFromTransform();
            return;
        }

        t = Math.min(t, 1);
        let start = new Date().getTime() / 1000; //Start Time
        let pass = 0; // Passed time

        if (this.snapin) {
            if (this.infinite) {
                const scrolls = Math.floor(finalScroll / this.infiniteTransform);
                const snappedTransform = this._getSnappedTransform(this._normalizeScroll(finalScroll));
                finalScroll = scrolls * this.infiniteTransform + snappedTransform;
            } else {
                finalScroll = this._getSnappedTransform(finalScroll);
            }
        }

        let totalScrollLen = finalScroll - initScroll; //Total length of the scroll

        //returns a promise that resolves when the passed time reached t
        return new Promise((resolve, reject) => {
            this.moving = true;
            let tick = () => {
                pass = new Date().getTime() / 1000 - start; // the time passed since the start

                if (pass < t) {
                    //if the passed time is less than t
                    //move a bit
                    let transform = initScroll + this.easing[easingName](pass / t) * totalScrollLen
                    transform = Math.min(this.minTransformLimit[this.direction], transform);
                    transform = Math.max(this.maxTransformLimit[this.direction], transform);
                    this.currentTransform = this._moveTo(transform);
                    this.moveT = requestAnimationFrame(tick);
                    this._renderVisibleCCs();
                } else {
                    //else resolve the promise
                    //cancel the animation frame
                    // Move to the final scroll position
                    //code after the resolve still gets executed
                    resolve();
                    this._stop();
                    let transform = initScroll + totalScrollLen
                    transform = Math.min(this.minTransformLimit[this.direction], transform);
                    transform = Math.max(this.maxTransformLimit[this.direction], transform);
                    this.currentTransform = this._moveTo(transform);
                    this.value = this._getValueFromTransform();
                }
            };
            tick();
        });
    }

    /****************************************************************************************************
     * Gets the index of the leftest element that is still visible
     * 
     * @return {Number} - the index of the leftest element that is still visible
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _getValueFromTransform() {
        const controls = this.$.container.querySelectorAll('.compositeContainer');
        let validCCs = this.checkbitObj.reduce((acc=[], val, idx)=>{
            if(val.getValue()) acc.push(idx)
            return acc;
        }, []);
        for (let i = 0; i < validCCs.length; i++) {
            const control = controls[validCCs[i]];
            const controlBounds = {
                height: control.offsetHeight,
                width: control.offsetWidth,
                top: control.offsetTop,
                left: control.offsetLeft
            }
            if ((controlBounds[this.direction] + controlBounds[this.dimension] > (this.currentTransform * -1))) {
                return i;
            }
        }
        return this.value;
    }

    /****************************************************************************************************
     * Stops the _animatToScroll animation
     * 
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _stop() {
        this.moving = false;
        cancelAnimationFrame(this.moveT);
    }

    /****************************************************************************************************
     * Scrolles with a starting speed initV
     * 
     * @param {Number} initv
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    async _animateMoveByInitV(initV) {

        let initScroll; //Start position
        let finalScroll; //EndPosition

        let totalScrollLen;
        let acceleration;
        let time;

        if (this.infinite) {
            initScroll = this.currentTransform; // Start position
            acceleration = initV > 0 ? -10 : 10; // Acceleration
            time = Math.abs(initV / acceleration); // Time until speed reaches 0
            totalScrollLen = initV * time + acceleration * time * time / 2; //The length of the scroll
            finalScroll = Math.round(this.currentTransform + totalScrollLen); // Rounded up to ensure an integer
            const finalBeforeNormalize = finalScroll;
            if (this.snapin) {
                const scrolls = Math.floor(finalScroll / this.infiniteTransform);
                const snappedTransform = this._getSnappedTransform(this._normalizeScroll(finalScroll));
                finalScroll = scrolls * this.infiniteTransform + snappedTransform;
            }
            await this._animateToScroll(this.currentTransform, finalScroll, time, 'easeOutCubic');

        } else if (this.currentTransform > 0 || this.currentTransform < this._getMaxTransform_new()) {
            //If the scroll current scrollposition is outside of the bounds
            acceleration = this.exceedA;
            initScroll = this.currentTransform;
            //..set the final scroll to either 0 or the maxTransform ...
            finalScroll = this.currentTransform > 0 ? this._getTrackTransform(this.trackStart) : this._getMaxTransform_new();
            if (this.snapin) {
                finalScroll = this._getSnappedTransform(finalScroll);
            }
            totalScrollLen = initScroll - finalScroll;

            time = Math.sqrt(Math.abs(totalScrollLen / acceleration));
            initV = acceleration * time;
            initV = this.currentTransform > 0 ? -initV : initV;
            // ... and animate to that position
            await this._animateToScroll(this.currentTransform, finalScroll, time);
        } else {
            initScroll = this.currentTransform; // Start position
            acceleration = initV > 0 ? -10 : 10; // Acceleration
            time = Math.abs(initV / acceleration); // Time until speed reaches 0
            totalScrollLen = initV * time + acceleration * time * time / 2; //The length of the scroll
            finalScroll = Math.round(this.currentTransform + totalScrollLen); // Rounded up to ensure an integer
            if (this.infinite) {
                finalScroll = this._normalizeScroll(finalScroll);
            } else {
                finalScroll = Math.min(0, finalScroll);
                finalScroll = Math.max(this._getMaxTransform_new(), finalScroll);
            }

            if (this.snapin) {
                finalScroll = this._getSnappedTransform(finalScroll);
            }
            totalScrollLen = finalScroll - initScroll;
            time = Math.sqrt(Math.abs(totalScrollLen / acceleration));
            //Animate to the final scroll
            await this._animateToScroll(this.currentTransform, finalScroll, time, 'easeOutCubic');
        }
    }

    /****************************************************************************************************
     * Observer for this.value
     * scrolls to the element on position [this.value]
     * 
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _valueChanged() {
        if(!this.rendered) return;
        //If we dont have composite controls, we cannot do anything here
        if(!this.compositeControls.length) return;
        // If we dont have a valid valuem we cannot do anything here
        if(this.value === undefined ||this.value === null) return;
        // If we don't have a composite control, meaning that the value is lower than 0 or higher than the ammount of 
        // CCs, we cannot jump to the CC, so we return

        let validCCs = this.checkbitObj.reduce((acc=[], val, idx)=>{
            if(val.getValue()) acc.push(idx)
            return acc;
        }, []);
        if(!this.compositeControls[validCCs[this.value] % this.ccSchemeLength]) return;
        let checkbit = this.checkbitObj[validCCs[this.value] % this.ccSchemeLength].getValue();

        if((this.minscroll || this.minscroll === 0) && this.value < this.minscroll) {
            //in case of change of value we also need to update abs value
            this.avoidAbsValueUpdate = false;
            this.value = this.minscroll;
            return;
        } else if((this.maxscroll || this.maxscroll === 0) && this.value > this.maxscroll) {
            //in case of change of value we also need to update abs value
            this.avoidAbsValueUpdate = false;            
            this.value = this.maxscroll;
            return;
        }
        const controls = this.$.container.querySelectorAll('.compositeContainer');
        if (controls[validCCs[this.value] % this.ccSchemeLength]) {
            let control = controls[validCCs[this.value] % this.ccSchemeLength]
            const controlBounds = {
                height: control.offsetHeight,
                width: control.offsetWidth,
                top: control.offsetTop,
                left: control.offsetLeft
            };
            let compositeContainerMargin = parseFloat(getComputedStyle(this.$.outerContainer.querySelector('.compositeContainer')).getPropertyValue('margin-right'))
            //The value that will be returned
            let ret = undefined;
            if (this.vertical) {
                compositeContainerMargin = parseFloat(getComputedStyle(this.$.outerContainer.querySelector('.compositeContainer')).getPropertyValue('margin-bottom'))
            }
            //calculating if the control is located on the left sight and at least visible -> behavoir is not optimal but as we need to stay compatible we cannot change it
            const alreadyThere = (controlBounds[this.direction] - compositeContainerMargin <= (this.currentTransform * -1)) && (controlBounds[this.direction] + controlBounds[this.dimension] > (this.currentTransform * -1))
            if (alreadyThere && !this.snapin) {
                //Nothing if we dont have to snap or the element is already showing
                this._renderVisibleCCs();
            } else if (!alreadyThere || (alreadyThere && this.snapin)) {

                let transform = this._getSnappedTransform(controlBounds[this.direction])
                transform = Math.min(this.minTransformLimit[this.direction], transform);
                transform = Math.max(this.maxTransformLimit[this.direction], transform);
                
                this._moveTo(transform);
                this.currentTransform = transform;
                this._renderVisibleCCs();
            }
        }
        //check if absolute value should be updated
        if (!this.avoidAbsValueUpdate) {
            this.absolutevalue = validCCs[this.value];
        }
    }

    /****************************************************************************************************
     * gets called if the absolute index changed
     *
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _absoluteValueChanged() {
        //check if valid value
        if (this.absolutevalue !== undefined) {
            //check if composite controls are available
            if (this.compositeControls.length > 0) {
                //we use absolute index only to manipulate the relative index
                //scrolling to position is done in observer of relative index
                //check if absolute value valid
                if (this.absolutevalue < this.compositeControls.length) {
                    //check if checkbit of composite control is true
                    if (this.checkbitObj[this.absolutevalue].getValue()) {
                        //get relative index of composite control
                        let relativeIdx = 0;
                        if (this.absolutevalue > 0) {
                            for (let i=0;i<this.absolutevalue;i++) {
                                if (this.checkbitObj[i].getValue()) {
                                    relativeIdx += 1;
                                }
                            }                            
                        }
                        //avoid writing absolute value again
                        this.avoidAbsValueUpdate = true;
                        //write relative index to get to position of control
                        this.value = relativeIdx;
                        //reset avoid info
                        this.avoidAbsValueUpdate = false;
                    }
                }    
            }
        }
    }

    /****************************************************************************************************
     * Depending if the control is vertical or not, the interesting dimension is the height or the width
     * 
     * @param {Boolean} vertical - shows if the component is shown horizontal or vertical
     * @return {String} - the dimension we want
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _cDimension(vertical) {
        return vertical ? 'height' : 'width'
    }

    /****************************************************************************************************
     * Depending if the control is vertical or not, the interesting direction is from top or left
     * 
     * @param {Boolean} vertical - shows if the component is horizontal or vertical
     * @return {String} - the direction we want
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _cDirection(vertical) {
        return vertical ? 'top' : 'left'
    }

    /**********************************************************************************************************************************************************
     * Calculates and sets the css vars for the size of the arrows. needed for the designmode
     *
     * @memberof LasalRuntimeSigCarouselElement
    ***********************************************************************************************************************************************************/
    _calcArrowSizes() {
        let locWidth = 0;
        let locHeight = 0;
        if (this.vertical) {
            locWidth = this.offsetWidth * 0.6;
            locHeight = locWidth * 0.6;
            if (locWidth > 100) {
                locWidth = 100;
            }
            if (locHeight > 60) {
                locHeight = 60;
            }
        } else {
            locWidth = this.offsetHeight * 0.6;
            locHeight = locWidth * 0.6;
            if (locWidth > 100) {
                locWidth = 100;
            }
            if (locHeight > 60) {
                locHeight = 60;
            }
        }
        this.updateStyles({
            '--theme-sig-control-carousel-arrow-height': String(locHeight) + 'px',
            '--theme-sig-control-carousel-arrow-width': String(locWidth) + 'px'
        });
    }

    /****************************************************************************************************
     * function is called if component gets resized in designmode
     *
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    designerOnBoundsChanged() {
        SigPolymer.afterNextRender(this, function() {
            this._calcArrowSizes();
        });
    }

    /****************************************************************************************************
     * Sets the min and max transform values
     * those values are used to limit the carousel to only show the entries between min and max scroll
     *
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _setMinMaxTransform() {
        if(!this.rendered) return;
        if(this.minscroll === undefined || this.minscroll === null) {
            this.minTransformLimit = {
                top: 0,
                left: 0
            }
            if(this.infinite) {
                this.minTransformLimit = {
                    top: Number.MAX_SAFE_INTEGER,
                    left: Number.MAX_SAFE_INTEGER
                }
            }
        } else {
            const ccContainers = this.$.container.querySelectorAll('.compositeContainer');
            if (ccContainers !== undefined && ccContainers !== null && ccContainers.length !== 0) {
                let cMinScroll = this.minscroll;

                // convert minscroll to valid array index
                if (cMinScroll >= ccContainers.length) cMinScroll = (ccContainers.length - 1);
                else if (cMinScroll < 0) cMinScroll = 0;

                let control = ccContainers[cMinScroll];
                let min = {
                    height: control.offsetHeight,
                    width: control.offsetWidth,
                    top: control.offsetTop,
                    left: control.offsetLeft
                }
                min.top = Math.abs(min.top) * -1;
                min.left = Math.abs(min.left) * -1;
                this.minTransformLimit = min;
            }
        }
        if(this.maxscroll === undefined || this.maxscroll === null) {
            this.maxTransformLimit = {
                top: Number.MIN_SAFE_INTEGER,
                left: Number.MIN_SAFE_INTEGER
            }
        } else {
            const ccContainers = this.$.container.querySelectorAll('.compositeContainer');
            if (ccContainers !== undefined && ccContainers !== null && ccContainers.length !== 0) {
                let cMaxScroll = this.maxscroll;

                // convert maxscroll to valid array index
                if (cMaxScroll >= ccContainers.length) cMaxScroll = (ccContainers.length - 1);
                else if (cMaxScroll < 0) cMaxScroll = 0;

                let control = ccContainers[cMaxScroll];
                let max = {
                    height: control.offsetHeight,
                    width: control.offsetWidth,
                    left: control.offsetLeft,
                    top: control.offsetTop
                }
                max.top = Math.abs(max.top) * -1;
                max.left = Math.abs(max.left) * -1;
                this.maxTransformLimit = max;
            }
        }

        if((this.minscroll || this.minscroll === 0) && this.minscroll > this.value) {
            this.value = this.minscroll;
        } else if((this.maxscroll || this.maxscroll === 0) && this.maxscroll < this.value) {
            this.value = this.maxscroll;
        }
    }

    /****************************************************************************************************
     * Renders the currently visible CCs all at once
     *
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _renderVisibleCCs() {
        const direction = parseFloat(this.$.container.style[this.direction]) || 0;
        const currentTransform = this.currentTransform;
        let container = this.$.container.querySelectorAll('.compositeContainer');
        if(!this.bounds || (this.bounds.height === 0 && this.bounds.width === 0)) {
            this.bounds = this.getControlBounds();
        }
        let dimension = this.bounds[this.dimension];
        if(!this.ccParent) {
            this.ccParent = window.sigApi.vm.getVmById(this.id);
        }
        let parent =this.ccParent;
        let containerOffsets = [];
        for(let cont in container) {
            containerOffsets.push(container[cont][`offset${this.direction.charAt(0).toUpperCase() + this.direction.slice(1)}`] * -1);
        }
        for(let cont in container) {
            if(this.compositeControls && this.compositeControls[cont] && this.compositeControls[cont].vm) {
                // We dont need to check anything if the cc is already rendered
                continue;
            }
            if(!container[cont] || isNaN(parseFloat(cont))) {
                continue;
            }
            if(this.checkbitObj[cont] && !this.checkbitObj[cont].getValue()) {
                continue;
            }
            if((direction - dimension) < containerOffsets[cont] && (direction + dimension) > containerOffsets[cont] && this.checkbitObj[cont].getValue()) {
                if(!this.compositeControls[cont].vm) {
                    if (window.sigApi.vm.appendNewCC(parent, this.compositeControls[cont].cc.getName(), container[cont].dataset.slotName, this.datapointSchemeObj[cont])) {
                        //get cc viewmodel
                        this.compositeControls[cont].vm = parent.getChildByName(this.compositeControls[cont].cc.getName());                                  
                    }
                }

                container[cont].style.removeProperty('height');
                container[cont].style.removeProperty('width');
                container[cont].classList.remove('notRendered');
            }
        }
        this.style.removeProperty('visibility');
        if(!this.renderRemainingCCsStarted) {
            this.renderRemainingCCsStarted = true;
            this.renderTimeout = this.sigSetTimeout(() => {
                this._renderRemainingCCs();
            },2000)
        }
    }
    
    /****************************************************************************************************
     * Renders the composite controls in the background
     *
     * @param {Number} from - the first index that should be rendered
     * @param {number} [ammount=1]
     * @memberof LasalRuntimeSigCarouselElement
     ****************************************************************************************************/
    _renderRemainingCCs(from, ammount = 1) {
        if(!this.ccParent) {
            this.ccParent = window.sigApi.vm.getVmById(this.id);
        }
        let parent =this.ccParent;
        if(!from && from !== 0) {
            from = 0;
        }
        let container = this.$.container.querySelectorAll('.compositeContainer');
        if(container[from]) {
            container[from].style.removeProperty('height');
            container[from].style.removeProperty('width');
            container[from].classList.remove('notRendered');
            if(!this.compositeControls[from].vm && this.checkbitObj[from].getValue()) {
                if (window.sigApi.vm.appendNewCC(parent, this.compositeControls[from].cc.getName(), container[from].dataset.slotName, this.datapointSchemeObj[from])) {
                    //get cc viewmodel
                    this.compositeControls[from].vm = parent.getChildByName(this.compositeControls[from].cc.getName());
                    if (this.compositeControls[from].vm !== null) {
                        const ccDomRef =  this.compositeControls[from].vm.getDomRef();
                        ccDomRef._onCheckbitChange = (newval, oldval) => {
                            if (newval === false) {
                                ccDomRef.classList.add('sig-element-checkbit');
                                ccDomRef.style.removeProperty('visibility');
                                ccDomRef.style.removeProperty('width');
                                ccDomRef.style.removeProperty('height');
                            } else {
                                ccDomRef.classList.remove('sig-element-checkbit');
                                ccDomRef.style.removeProperty('visibility');
                                ccDomRef.style.removeProperty('width');
                                ccDomRef.style.removeProperty('height');
                            }
        
                            if(this.checkbitDebouncer) {
                                this.sigClearTimeout(this.checkbitDebouncer);
                            } 
                            this.checkbitDebouncer = setTimeout(() => {
                                let tmpMaxTransform = this.maxTransform;
                                let maxTransform = this._getMaxTransform_new();
                                if(tmpMaxTransform !== maxTransform && this.infinite) {
                                    this._appendInfiniteElements();
                                }
                                SigPolymer.afterNextRender(this, () => {
                                    //if no absolute value configured we use normal value to update
                                    if (this.absolutevalue !== undefined) {
                                        this._absoluteValueChanged();
                                    }
                                    else {
                                        this._valueChanged();
                                    }
                                })
                                
                            }, 100);
                        }  
                    }
                    else {
                        this._log('_renderRemainingCCs(): failed to get cc vm', 'ERROR');
                    }
                }
            }
            this.renderTimeout = this.sigSetTimeout(() => {
                this._renderRemainingCCs(from+1);
            },2000)
        }
    }

    /****************************************************************************************************
     * gets called if control gets disconnected from the DOM
     *
     * @memberof LasalRuntimeSigCarouselElement
    ****************************************************************************************************/
    disconnectedCallback() {
        super.disconnectedCallback();
        if(this.renderTimeout) {
            this.sigClearTimeout(this.renderTimeout);
        }
        for(let checkbit in this.checkbitObj) {
            if(this.checkbitObj[checkbit].destroy) {
                this.checkbitObj[checkbit].destroy()
            }
        }
    }
}
customElements.define(LasalRuntimeSigCarouselElement.is, LasalRuntimeSigCarouselElement);
