class SmartDateField
{
    constructor(field, options)
    {
        this._dateYmdParser = new RegExp('^([0-9]{4})[,\._\/\-]+([0-9]{1,2})[,\._\/\-]+([0-9]{1,2})$');
        this._dateDmyParser = new RegExp('^([0-9]{1,2})[,\._\/\-]+([0-9]{1,2})[,\._\/\-]+([0-9]{4})$');
                
        this._loadOptions(options);
        
        this._formatter = new SmartDateFormat(this._format, this._minimalDate, this._maximalDate);
        
        this._lastCorrectValue = (this._defaultDate === null ? null : this._formatter.formatDate(this._defaultDate));

        this._initField(field);

        this._subscribeEvents();
    }
    
    
    _loadDefaultOptions()
    {
        this._format = 'd.m.y';
        this._minimalDate = new Date(1900, 0, 1);
        this._maximalDate = new Date(2099, 11, 31);
        this._defaultDate = null;
    }
    
    _loadOptions(options)
    {
        this._loadDefaultOptions();
        
        if (typeof(options) !== 'object' || options == null) {
            return;
        }
        
        if (typeof(options.format) === 'string'
            && options.format.indexOf('d') >= 0
            && options.format.indexOf('m') >= 0
            && options.format.indexOf('y') >= 0) {
            this._format = options.format;
        }
        
        if (typeof(options.minimal) !== 'undefined') {
            let date = this._parseDateValue(options.minimal);
            this._minimalDate = date;
        }
        
        if (typeof(options.maximal) !== 'undefined') {
            let date = this._parseDateValue(options.maximal);
            this._maximalDate = date;
        }
        
        if (typeof(options.default) !== 'undefined') {
            let date = this._parseDateValue(options.default);
            this._defaultDate = date;
        }
        
        this._correctDateOptions();
    }
    
    _correctDateOptions()
    {
        if (this._minimalDate.valueOf() > this._maximalDate.valueOf()) {
            let temporaryDate = this._minimalDate;
            this._minimalDate = this._maximalDate;
            this._maximalDate = temporaryDate;
        }
        
        if (this._defaultDate === null) {
            return;
        }
        
        if (this._defaultDate.valueOf() < this._minimalDate.valueOf()) {
            this._defaultDate = this._minimalDate;
        }
        
        if (this._defaultDate.valueOf() > this._maximalDate.valueOf()) {
            this._defaultDate = this._maximalDate;
        }
    }
    
    _parseDateValue(date)
    {
        if (typeof(date) === 'object' && date !== null && date instanceof Date) {
            return date;
        }
        
        if (typeof(date) !== 'string' || date.trim() === '') {
            return null;
        }
        
        let parsed = this._dateYmdParser.exec(date);
        
        if (parsed !== null) {
            return new Date(Number(parsed[1]), Number(parsed[2]) - 1, Number(parsed[3]));
        }
        
        parsed = this._dateDmyParser.exec(date);
        
        if (parsed === null) {
            return null;
        }
        
        return new Date(Number(parsed[3]), Number(parsed[2]) - 1, Number(parsed[1]));
    }
 
    _initField(field)
    {
        this._field = field;
        this._field.placeholder = this._formatter.getPlaceHolder();
        
        if (this._isEmptyValue(this._field.value)) {
            this._field.value = (this._defaultDate === null ? '' : this._formatter.formatDate(this._defaultDate));
        }
        else {
            this.correctValue(true);
        }
    }
    
    _subscribeEvents()
    {
        let _this = this;
        
        let correct = function (event) {
            if (event === null || event.type !== 'keyup') {
                _this.correctValue(true);
            }
            
            if (_this._isServiceCode(event.keyCode)) {
                return;
            }
            
            if (event.keyCode === 27) { // Escape
                _this.returnLastCorrectValue();
            }
            
            _this.correctValue(true);
        };
        
        this._field.addEventListener('change', correct);
        this._field.addEventListener('keyup', correct);
        
        this._field.addEventListener('blur', function () {
            _this.correctValue(true);
            _this.returnLastCorrectValue();
        });
    }
    
    _isServiceCode(code) {
        return code === 8 /* backspace */
            || code === 46 /* delete */
            || code === 16 /* shift */
            || code === 17 /* shift */
            || code === 18 /* shift */
            || code === 20 /* caps lock */
            || code === 35 /* end */
            || code === 36 /* home */
            || code === 37 /* left */
            || code === 39 /* right */
            || code === 46 /* delete */;
    }
    
    _isEmptyValue(value)
    {
        return value.trim() === '';
    }
    
    getDefaultValue()
    {
        return this._defaultDate === null ? '' : this._formatter.formatDate(this._defaultDate);
    }
    
    correctValue(autocomplete)
    {
        this._formatter.parse(this._field.value, autocomplete);
        
        let parsedValue = this._getLimitedParsedValue();
        
        if (this._formatter.isComplete()) {
            this._lastCorrectValue = parsedValue;
        }
        
        if (this._field.value === parsedValue) {
            return;
        }
        
        this._setValueToFieled(parsedValue);
    }
    
    setValue(value)
    {
        let date = this._parseDateValue(value);
        
        if (date === null || date.valueOf() < this._minimalDate.valueOf() || date.valueOf() > this._maximalDate.valueOf()) {
            return;
        }
        
        this._lastCorrectValue = this._formatter.formatDate(date);
        this._field.value = this._lastCorrectValue;
    }
    
    _getLimitedParsedValue()
    {
        if (!this._formatter.isComplete()) {
            return this._formatter.getCorrectedValue();
        }
        
        let date = this._formatter.getParsedDate();
            
        if (date.valueOf() < this._minimalDate.valueOf()) {
            return this._formatter.formatDate(this._minimalDate);
        }
        else if (date.valueOf() > this._maximalDate.valueOf()) {
            return this._formatter.formatDate(this._maximalDate);
        }
        
        return this._formatter.getCorrectedValue();
    }
    
    _setValueToFieled(newValue)
    {
        let position = this._field.selectionStart;
        let isAtEnd = (position === this._field.value.length);
        
        this._field.value = newValue;
        
        if (!isAtEnd && position < this._field.value.length) {
            this._field.selectionStart = position;
            this._field.selectionEnd = position;
        }
    }
    
    returnLastCorrectValue()
    {
        if (this._lastCorrectValue !== null) {
            this._field.value = this._lastCorrectValue;
        }
    }
}

SmartDateField.initBy = function (id) {
    return SmartDateField.initFor(document.getElementById(id));
};

SmartDateField.initFor = function (field, options) {
    if (typeof(field) !== 'object'
            || field === null
            || field.tagName.toLowerCase() !== 'input'
            || field.type.toLowerCase() !== 'text') {
        return;
    }
    
    if (typeof(field._smartDate) === 'object' && field._smartDate !== null && field._smartDate instanceof SmartDateField) {
        return;
    }
    
    field._smartDate = new SmartDateField(field, options);
};

jQuery.fn.smartdate = function (options) {
    let collection = [];
    let field = null;

    for (let i = 0; i < this.length; i++) {
        field = SmartDateField.initFor(this[i], options);
        
        if (field !== null) {
            collection.push(field);
        }
    }
    
    return jQuery(this);
};

jQuery.fn.setDateValue = function (value) {
    for (let i = 0; i < this.length; i++) {
        if (typeof(this[i]._smartDate) === 'object' && this[i]._smartDate !== null && this[i]._smartDate instanceof SmartDateField) {
            this[i]._smartDate.setValue(value);
        }
    }
    
    return jQuery(this);
};
