// Import dependencies
import './moment/min/moment-with-locales.js';
import { SigPolymer } from '../../sigmatek/sig-element/sig-element.js';

/****************************************************************************************************
 * @class DatepickerCylinder
 ****************************************************************************************************/
export class DatepickerCylinder {

    /****************************************************************************************************
     *Creates an instance of DatepickerCylinder.
     * @param {*} targetElement
     * @param {*} options
     ****************************************************************************************************/
    constructor(targetElement, options) {
        //Save the options
        this.targetElement = targetElement;
        this.options = this._getOptions(options);
        //set the current date, make sure it is a moment object
        this.currentDate = moment(this.startDate);
        //the name is used to identify the datepicker, and link it to the inut
        this.name = this._getRandomName();

        this.datepicker = undefined;
        this.yearControl = undefined;
        this.monthControl = undefined;
        this.dayControl = undefined;
        this.hourControl = undefined;
        this.minuteControl = undefined;
        this.secondControl = undefined;
        this.ampmControl = undefined;
        this.isAmpm = false;
        
        this.componentsToRender = 0;
        this.renderedComponents = 0;

        //Initialize the datepicker
        this._init();
        this.manuallyopened = false;
        this.isRendered = false;

        this._offsetParent = undefined;
    }
    
    /****************************************************************************************************
     * Initializes the datepicker
     ****************************************************************************************************/
    _init() {
        this.datepicker = this._getDatepicker();
        this._initIndividualSelectors();
        this._checkForInputsReady();
        this.targetElement.setAttribute('datepicker', this.name);
        this.targetElement.sigAddEventListener('tap', () => {
            this.open();
        }, 'jsEvent', this.targetElement);
    }

    /****************************************************************************************************
     * listens for the cylinder rendered events, and calls the componentRendered function
     * the componentRendered function then determines if all components have been rendered
     *
     ****************************************************************************************************/
    _checkForInputsReady() {
        if(this.yearControl) {
            //as runtime does not initialize properties we have to call callback
            this.yearControl.sigOnRequiredPropertiesReady();
            this.yearControl.sigAddEventListener('cylinder-rendered', () => {
                this._componentRendered();
            }, 'jsEvent', this.yearControl)
        }
        if(this.monthControl) {
            //as runtime does not initialize properties we have to call callback
            this.monthControl.sigOnRequiredPropertiesReady();            
            this.monthControl.sigAddEventListener('cylinder-rendered', () => {
                this._componentRendered();
            }, 'jsEvent', this.monthControl)
        }
        if(this.dayControl) {
            //as runtime does not initialize properties we have to call callback
            this.dayControl.sigOnRequiredPropertiesReady();
            this.dayControl.sigAddEventListener('cylinder-rendered', () => {
                this._componentRendered();
            }, 'jsEvent', this.dayControl)
        }
        if(this.hourControl) {
            //as runtime does not initialize properties we have to call callback
            this.hourControl.sigOnRequiredPropertiesReady();
            this.hourControl.sigAddEventListener('cylinder-rendered', () => {
                this._componentRendered();
            }, 'jsEvent', this.hourControl)
        }
        if(this.minuteControl) {
            //as runtime does not initialize properties we have to call callback
            this.minuteControl.sigOnRequiredPropertiesReady();
            this.minuteControl.sigAddEventListener('cylinder-rendered', () => {
                this._componentRendered();
            }, 'jsEvent', this.minuteControl)
        }
        if(this.secondControl) {
            //as runtime does not initialize properties we have to call callback
            this.secondControl.sigOnRequiredPropertiesReady();
            this.secondControl.sigAddEventListener('cylinder-rendered', () => {
                this._componentRendered();
            }, 'jsEvent', this.secondControl)
        }
        if(this.ampmControl) {
            //as runtime does not initialize properties we have to call callback
            this.ampmControl.sigOnRequiredPropertiesReady();
            this.ampmControl.sigAddEventListener('cylinder-rendered', () => {
                this._componentRendered();
            }, 'jsEvent', this.ampmControl)
        }
    }

    /****************************************************************************************************
     * Calls the allInputsRendered Callback
     ****************************************************************************************************/
    _componentRendered() {
        this.renderedComponents += 1;
        //cylinder goes through the render proccess twice
        this.renderdebouncer = SigPolymer.debouncer.debounce(this.renderdebouncer, SigPolymer.timeOut.after(200), () => {
            this.options.allInputsRenderedCB();
        })
    }

    /****************************************************************************************************
     *initializes all the compponents of the date/time picker
     *
     ****************************************************************************************************/
    _initIndividualSelectors() {
        this.yearControl = this.datepicker.querySelector('.year');
        this.monthControl = this.datepicker.querySelector('.month');
        this.dayControl = this.datepicker.querySelector('.day');
        this.hourControl = this.datepicker.querySelector('.hour');
        this.minuteControl = this.datepicker.querySelector('.minute');
        this.secondControl = this.datepicker.querySelector('.second');
        this.ampmControl = this.datepicker.querySelector('.ampm');

        this.okButton = this.datepicker.querySelector('#ok');
        this.cancelButton = this.datepicker.querySelector('#cancel');

        if(this.yearControl) {
            this._initYearControl();
            this._setYearControl();
        }
        if(this.monthControl) {
            this._initMonthControl();
            this._setMonthControl();
        }
        if(this.dayControl) {
            this._initDayControl()
            this._setDayControl();
        }
        if(this.hourControl) {
            this._initHourControl()
            this._setHourControl();
        }
        if(this.minuteControl) {
            this._initMinuteControl()
            this._setMinuteControl();
        }
        if(this.secondControl) {
            this._initSecondControl()
            this._setSecondControl();
        }
        if(this.ampmControl) {
            this._initAmpmControl();
            this._setAmpmControl()
        }
        if(this.okButton) {
            this._initOkButton();
        }
        if(this.cancelButton) {
            this._initCancelButton();
        }
    }

    /****************************************************************************************************
     * adds the eventHandling to the ok button
     *
     ****************************************************************************************************/
    _initOkButton() {
        this.targetElement.sigAddEventListener('tap', () => {
            this._okButtonTap();
        }, 'polymerEvent', this.okButton);
    }

    /****************************************************************************************************
     *adds the eventHandling to the cancel Button
     *
     ****************************************************************************************************/
    _initCancelButton() {
        this.targetElement.sigAddEventListener('tap', () => {
            this.cancelButtonTap();
        }, 'polymerEvent', this.cancelButton);
    }

    /****************************************************************************************************
     *eventHandler for the ok button
     *
     ****************************************************************************************************/
    _okButtonTap() {
        this.targetElement.$.testInput.iskeyboardopen = false;
        this.targetElement.value = this._getDate();
        
        this._close();
    }
    /****************************************************************************************************
     *event Handler for the cancel button
     *
     ****************************************************************************************************/
    cancelButtonTap() {
        this._close();
    }

    /****************************************************************************************************
     *closes the date/time picker
     *
     ****************************************************************************************************/
    _close() {
        this.datepicker.style.display = 'none';
        if(!this._offsetParent) {
            this._offsetParent = this.datepicker.parentElement.offsetParent
        }
        if(!this._offsetParent) {
            this._offsetParent = this.datepicker.parentElement.parentNode.host;
        }
        this._offsetParent.style.display = 'none';
        this.datepicker.dispatchEvent(new Event('closed'));
    }

    /****************************************************************************************************
     *opens the date/time picker
     *
     ****************************************************************************************************/
    open() {
        this.datepicker.style.removeProperty('display');
        if(!this._offsetParent) {
            this._offsetParent = this.datepicker.parentElement.offsetParent
        }
        if(!this._offsetParent) {
            this._offsetParent = this.datepicker.parentElement.parentNode.host;
        }
        this._offsetParent.style.removeProperty('display');
    }

    /****************************************************************************************************
     * sets the options for the year-cylinder, adds the eventlistener
     *
     ****************************************************************************************************/
    _initYearControl() {
        this._setCylinderSelectVariables(this.yearControl)
        this.yearControl.sigAddEventListener('value-changed', (e) => {
            this._updateSelectors(e);
        }, 'jsEvent', this.yearControl)
    }
    /****************************************************************************************************
     *sets the options for the month-cylinder, adds the eventlistener
     *
     ****************************************************************************************************/
    _initMonthControl() {
        this._setCylinderSelectVariables(this.monthControl)
        this.monthControl.sigAddEventListener('value-changed', (e) => {
            this._updateSelectors(e);
        }, 'jsEvent', this.monthControl)
    }
    /****************************************************************************************************
     *sets the options for the day-cylinder, adds the eventlistener
     *
     ****************************************************************************************************/
    _initDayControl() {
        this._setCylinderSelectVariables(this.dayControl)
        this.dayControl.sigAddEventListener('value-changed', (e) => {
            this._updateSelectors(e);
        }, 'jsEvent', this.dayControl)
    }

    /****************************************************************************************************
     *sets the options for the hour-cylinder, adds the eventlistener
     *
     ****************************************************************************************************/
    _initHourControl() {
        this._setCylinderSelectVariables(this.hourControl)
        this.hourControl.sigAddEventListener('value-changed', (e) => {
            this._updateSelectors(e);
        }, 'jsEvent', this.hourControl)
    }

    /****************************************************************************************************
     *sets the options for the minute-cylinder, adds the eventlistener
     *
     ****************************************************************************************************/
    _initMinuteControl() {
        this._setCylinderSelectVariables(this.minuteControl)
        this.minuteControl.sigAddEventListener('value-changed', (e) => {
            this._updateSelectors(e);
        }, 'jsEvent', this.minuteControl)
    }

    /****************************************************************************************************
     *sets the options for the second-cylinder, adds the eventlistener
     *
     ****************************************************************************************************/
    _initSecondControl() {
        this._setCylinderSelectVariables(this.secondControl)
        this.secondControl.sigAddEventListener('value-changed', (e) => {
            this._updateSelectors(e);
        }, 'jsEvent', this.secondControl)
    }
    /****************************************************************************************************
     *sets the options for the ampm-cylinder, adds the eventlistener
     *
     ****************************************************************************************************/
    _initAmpmControl() {
        this._setCylinderSelectVariables(this.ampmControl)
        this.ampmControl.sigAddEventListener('value-changed', (e) => {
            this._updateSelectors(e);
        }, 'jsEvent', this.ampmControl)
    }

    /****************************************************************************************************
     *sets the options for the cylinder
     *
     * @param {*} cylinder
     * @param {*} [options=this.options.wrapperOptions]
     ****************************************************************************************************/
    _setCylinderSelectVariables(cylinder, options = this.options.wrapperOptions) {
        for(let key in options) {
            cylinder[key] = options[key];
        }
    }


    /****************************************************************************************************
     * sets the cylinders to a valid date
     *
     * @param {*} e
     ****************************************************************************************************/
    _updateSelectors(e) {
        this.currentDate = this._getMoment();
        if(!this.currentDate.isValid()) {
            console.log('invalid Date')
            const tmpDate = moment([2010, 11, 1]);
            this.currentDate = tmpDate.set('date', tmpDate.daysInMonth());
        }

        if(this.currentDate.isBefore(this.options.minDate)) {
            this.currentDate = this.options.minDate
        }

        if(this.currentDate.isAfter(this.options.maxDate)) {
            this.currentDate = this.options.maxDate
        }

        
        this._setYearControl();
        this._setDayControl();
        this._setMonthControl();
        this._setHourControl();
        this._setMinuteControl();
        this._setSecondControl();
        this._setAmpmControl();
        if(this.options.setFontFunction) {
            this.options.setFontFunction();
        }
    }

    /****************************************************************************************************
     *
     *
     * @returns a moment object with the currently selected dates
     ****************************************************************************************************/
    _getMoment() {
        let dateArray = [];
        let dateFormat = '';
        if(this.yearControl) {
            let year = this.yearControl.value || this.currentDate.get('year');
            dateArray.push(year);
            dateFormat += 'YYYY';
        }

        if(this.monthControl) {
            let month = (this.monthControl.value || this.monthControl.value === 0) ? this.monthControl.value : this.currentDate.get('month');
            dateArray.push(month+1);
            dateFormat += 'MM';
        }

        if(this.dayControl) {
            let day = this.dayControl.value || this.currentDate.get('date');
            dateArray.push(day);
            dateFormat += 'DD';
        }

        if(this.hourControl) {
            let addHours = 0;
            if(this.ampmControl && this.ampmControl.value === 1) {
                addHours = 12;
            }
            let hour = this.hourControl.value/*  || this.currentDate.get('hour') */;
            dateArray.push(hour + addHours);
            dateFormat += this.isAmpm ? 'hh' : 'HH';
        }

        if(this.minuteControl) {
            let minute = this.minuteControl.value/*  || this.currentDate.get('minute') */;
            dateArray.push(minute);
            dateFormat += 'mm';
        }

        if(this.secondControl) {
            let second = this.secondControl.value/*  || this.currentDate.get('second') */;
            dateArray.push(second);
            dateFormat += 'ss';
        }

        if(this.ampmControl) {
            let second = this.ampmControl.value/*  || this.currentDate.get('second') */;
            dateArray.push(second);
            dateFormat += 'A';
        }

        let tempMoment = moment(dateArray, dateFormat);
        if(tempMoment.isValid()) {
            return tempMoment
        } else {
            if(this.yearControl && this.monthControl && this.dayControl) {
                //If we have an invalid date we try to correct it to the closest valid date
                let year = this.yearControl.value || this.currentDate.get('year');
                let month = (this.monthControl.value || this.monthControl.value === 0) ? this.monthControl.value : this.currentDate.get('month');
                let day = this.dayControl.value || this.currentDate.get('date');

                //first we need to make sure that we have y,m,d
                if(year >= 0 && month >= 0 && day >= 0) {
                    //Reducing the month until we have a valid month
                    while(!moment([year, month]).isValid()) {
                        month -= 1;
                    }
                    if(day > moment([year, month]).daysInMonth()) {
                        tempMoment = moment([year, month, moment([year, month]).daysInMonth()],dateFormat);
                    } else {
                        tempMoment = moment([year, month, day],dateFormat)
                    }

                }
            } else {
                return moment([2010,11,1],dateFormat);
            }
            
        }
        return tempMoment;
    }

    /****************************************************************************************************
     * selects the year,
     * also sets the min and max year
     * @returns
     ****************************************************************************************************/
    _setYearControl() {
        if(!this.yearControl) {
            return;
        }
        this.yearControl.min = this.options.minDate.get('year');
        this.yearControl.max = this.options.maxDate.get('year');
        this.yearControl.value = this.currentDate.get('year');
        
    }

    /****************************************************************************************************
     * selects the month
     * also sets the months list
     * @returns
     ****************************************************************************************************/
    _setMonthControl() {
        if(!this.monthControl) {
            return;
        }
        let monthsList = this._getMonths();
        if(this.currentDate.get('year') === this.options.maxDate.get('year')) {
            monthsList = monthsList.filter(val => {
               let maxMonth = this.options.maxDate.get('month');
               return val.value <= maxMonth; 
            })
        }
        if(this.currentDate.get('year') === this.options.minDate.get('year')) {
            monthsList = monthsList.filter(val => {
               let minMonth = this.options.minDate.get('month');
               return val.value >= minMonth; 
            })
        }
        this.monthControl.source = monthsList;
        this.monthControl.min = monthsList[0].value;
        this.monthControl.max = monthsList[monthsList.length - 1].value;
        this.monthControl._minChanged();
        this.monthControl.dontreloadsource = true;
        this.monthControl.value = this.currentDate.get('month') ;
    }

    /****************************************************************************************************
     * selects the day
     * also sets the min and max day
     * @returns
     ****************************************************************************************************/
    _setDayControl(){
        if(!this.dayControl) {
            return;
        }
        
        let minDay = 1;
        let maxDay = this.currentDate.daysInMonth();
        if(this.currentDate.get('year') === this.options.maxDate.get('year')) {
            if(this.currentDate.get('month') === this.options.maxDate.get('month')) {
                maxDay = this.options.maxDate.get('date');
            }
        }

        if(this.currentDate.get('year') === this.options.minDate.get('year')) {
            if(this.currentDate.get('month') === this.options.minDate.get('month')) {
                minDay = this.options.minDate.get('date');
            }
        }

        this.dayControl.min = minDay;
        this.dayControl.max = maxDay;
        this.dayControl.value = this.currentDate.get('date');
    }

    /****************************************************************************************************
     * selects the hour, sets the possible hours
     *
     * @returns
     ****************************************************************************************************/
    _setHourControl(){
        if(!this.hourControl) {
            return;
        }
        let mod = this.isAmpm ? 12 : 24;
        this.hourControl.source = this._getHours();
        this.hourControl.dontreloadsource = true;
        this.hourControl.min = 0; 
        this.hourControl.max = mod - 1;
        this.hourControl.value = this.currentDate.get('hour') % mod;
    }

    /****************************************************************************************************
     *
     *
     * @returns an array of objects ,representing the possible hours
     ****************************************************************************************************/
    _getHours() {
        const ret = [];
        let start = 0;
        let end = 24;
        if(this.isAmpm) {
            start = 0;
            end = 12;
        }
        for(let i = start; i < end; i++) {
            let value = i;
            let text = i;
            if(this.isAmpm && i === 0) {
                text = 12;
            }
            ret.push({
                value,
                text
            })
        }
        return ret;
    }

    /****************************************************************************************************
     *sets the minute control
     *
     * @returns
     ****************************************************************************************************/
    _setMinuteControl(){
        if(!this.minuteControl) {
            return;
        }
        this.minuteControl.min = 0;
        this.minuteControl.max = 59;
        this.minuteControl.value = this.currentDate.get('minute');
    }

    /****************************************************************************************************
     *sets the second control
     *
     * @returns
     ****************************************************************************************************/
    _setSecondControl(){
        if(!this.secondControl) {
            return;
        }
        this.secondControl.min = 0;
        this.secondControl.max = 59;
        this.secondControl.value = this.currentDate.get('second');
    }

    /****************************************************************************************************
     *sets the ampm coontrol
     *
     * @returns
     ****************************************************************************************************/
    _setAmpmControl() {
        if(!this.ampmControl){
            return;
        }

        this.ampmControl.source = [{
            value: 0,
            text: 'AM'
        },
        {
            value:1,
            text: 'PM'
        }];
        this.ampmControl.dontreloadsource = true;
        this.ampmControl.value = this.currentDate.get('hour') >= 12 ? 1:0;
    }

    /****************************************************************************************************
     * Returns a source array for a cylinder select, containing the months
     * @returns
     ****************************************************************************************************/
    _getMonths() {
        const months = moment(this.currentDate).locale(this.options.language)._locale._monthsShort;
        let monthList = months.map((val, idx) => {
            return {
                value: idx,
                text: val
            }
        })
        return monthList;
    }

    /****************************************************************************************************
     * @returns {HTMLElement} the datepicker element
     ****************************************************************************************************/
    _getDatepicker() {
        const datepickerContainer = document.createElement('div')
        datepickerContainer.classList.add('datepickerContainer');
        datepickerContainer.setAttribute('name', this.name);
        datepickerContainer.style.overflow = 'visible'
        const formatValues = this._getFormatValues(this.options.dateformat);
        let cylinder = `<sig-control-cylinder-select class="$$CLASSNAME" style="height:${this.options.pickerHeight};width:${this.options.pickerWidth}"></sig-control-cylinder-select>`
        let datepickerHTML = `<div id="cylinderContainer">`
        this.componentsToRender = 0;
        for(let i = 0; i < formatValues.length; i++) {
            datepickerHTML += cylinder.replace('$$CLASSNAME', formatValues[i]);
            this.componentsToRender += 2;
        }
        datepickerHTML += `</div>`;

        let controlButtonHTML = `
            <div id="controlContainer">
                <sig-control-button id="cancel" sigbtntext="Cancel"></sig-control-button>    
                <sig-control-button id="ok" sigbtntext="OK"></sig-control-button>
            </div>    
        `

        datepickerContainer.innerHTML = `
            ${datepickerHTML}
            ${controlButtonHTML}
        `;
        return datepickerContainer;
    }

    /****************************************************************************************************
     * decodes the dateformat, and returns an array ['day', 'month', 'year'] containing the 
     * needed values
     *
     * @param {*} dateformat
     * @returns
     ****************************************************************************************************/
    _getFormatValues(dateformat) {
        let sel = [];
        let lastC = ''
        for(let i=0;i<dateformat.length;i++) {
            let c = dateformat.charAt(i);
            if(c!==lastC) {
                if(c=='d' || c == 'D') {
                    if(sel.indexOf('day') === -1) {
                        sel.push('day');
                    }
                }
                if(c=='M'){
                    if(sel.indexOf('month') === -1) {
                        sel.push('month');
                    }
                }
                if(c=='y' || c == 'Y') {
                    if(sel.indexOf('year') === -1) {
                        sel.push('year');
                    }
                }
                if(c=='h' || c == 'H') {
                    if(sel.indexOf('hour') === -1) {
                        sel.push('hour');
                    }
                }
                if(c=='m') {
                    if(sel.indexOf('minute') === -1) {
                        sel.push('minute');
                    }
                }
                if(c=='s' || c=='s') {
                    if(sel.indexOf('second') === -1) {
                        sel.push('second');
                    }
                }
                if(c=='A') {
                    if(sel.indexOf('ampm') === -1) {
                        sel.push('ampm');
                    }
                    this.isAmpm = true;
                }
            }
            lastC = c;
        }

        if(sel.indexOf('day') !== -1 &&
            sel.indexOf('month') === -1) {
                sel.push('month');
            }
        if((sel.indexOf('day') !== -1 || sel.indexOf('month') !== -1) &&
            sel.indexOf('year') === -1) {
                sel.push('year');
            }
        return sel;
    }

    /****************************************************************************************************
     * Generates the name of the datepicker
     * @returns
     ****************************************************************************************************/
    _getRandomName() {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

        for (var i = 0; i < 10; i++)
        {
           text += possible.charAt(Math.floor(Math.random() * possible.length));
        }

        return '_datepicker' + text;
     }

    /****************************************************************************************************
     * 
      * @param {Object} options the options passed to the constructor
      * @returns {Object} the options merged with the default options
     ****************************************************************************************************/
    _getOptions(options) {
        const defaultOptions = {
            dateformat: 'DD.MM.YYYY',
            minDate: moment(0),
            maxDate: moment(10000000000000),
            startDate: moment(),
            language: 'de',
            pickerHeight: '200px',
            pickerWidth: '50px',
            pickerMargin: '30px',
            allInputsRenderedCB: () => {},
            wrapperOptions: {}
        }
        let finalOptions = {}
        Object.assign(finalOptions, defaultOptions, options);
        return finalOptions;
    }

    /****************************************************************************************************
     *
     *
     * @returns a moment object of the current date
     ****************************************************************************************************/
    _getUnformatedDate() {
        
        return this._getMoment();
    }

    /****************************************************************************************************
     *
     *
     * @param {*} [format=this.options.dateformat]
     * @returns a moment object of the current date, formatted
     ****************************************************************************************************/
    _getDate(format=this.options.dateformat) {
        let usedFormat;
        if(this.targetElement.tagName === 'SIG-CONTROL-DATEPICKER-CYLINDER') {
            usedFormat = 'DD.MM.YYYY';
        } else if (this.targetElement.tagName === 'SIG-CONTROL-TIMEPICKER-CYLINDER') {
            usedFormat = 'HH:mm:ss';
        } else if (format) {
            usedFormat = format;
        } else {
            usedFormat = 'DD.MM.YYYY';
        }
        /* if(format) {
            usedFormat = format;
        } */

        let date = this._getMoment();

        return date.locale(this.options.language).format(usedFormat);
    }

    /****************************************************************************************************
     *sets the current date, updates all cylinders
     *
     * @param {*} [date=this.options.startDate]
     * @param {*} [format=this.options.dateformat]
     ****************************************************************************************************/
    _setDate(date=this.options.startDate, format=this.options.dateformat) {

        if(this.yearControl) {
            this.yearControl.value = date.get('year');
        }
        if(this.monthControl) {
            this.monthControl.value = date.get('month');
        }
        if(this.dayControl) {
            this.dayControl.value = date.get('date');
        }
        if(this.hourControl) {
            let negHours = (this.isAmpm && this.ampmControl.value === 1) ? 12 : 0
            let mod = this.isAmpm ? 12 : 24;
            this.hourControl.value = date.get('hour') %mod;
        }
        if(this.minuteControl) {
            this.minuteControl.value = date.get('minute');
        }
        if(this.secondControl) {
            this.secondControl.value = date.get('second');
        }
        if(this.ampmControl) {
            this.ampmControl.value = date.get('hour') >= 12 ? 1 : 0;
        }

        //this.targetElement.value = date.locale(this.options.language).format(format);
    }

    /****************************************************************************************************
     * called after the element is detached from the DOM
     * can be called multiple times during the lifetime of an element
     *use this to cleanup your "mess" ;-)
     ****************************************************************************************************/
    cleanUp() {
        this.targetElement = null;

        this.datepicker = null;
        this.yearControl = null;
        this.monthControl = null;
        this.dayControl = null;
        this.hourControl = null;
        this.minuteControl = null;
        this.secondControl = null;
        this.ampmControl = null;

        this.cancelButton = null;
        this.okButton = null;

        this._offsetParent = null;
    }
}
