
class SmartDateFormat
{
    constructor(format)
    {
        this._day = null;
        this._month = null;
        this._year = null;
        
        this.loadFormat(format);
        
        this._parseState = new SmartDateParseState();
    }
    
    loadFormat(format)
    {
        let index = 0;
        let lowFormat = format.toLowerCase();
        
        this._format = format;
        this._parts = [];
        
        while (index < format.length) {
            index = this._scanPart(index, format, lowFormat);
        }
    }
    
    _scanPart(index, format, lowFormat)
    {
        switch (lowFormat[index]) {
            case SmartDateFormat.DAY_ANCHOR:
                return this._scanDay(index, lowFormat);
                
            case SmartDateFormat.MONTH_ANCHOR:
                return this._scanMonth(index, lowFormat);
                
            case SmartDateFormat.YEAR_ANCHOR:
                return this._scanYear(index, lowFormat);
        }
        
        return this._scanStatic(index, format, lowFormat);
    }
    
    _scanDay(startIndex, lowFormat)
    {
        this._checkLastIsStatic();
        
        this._day = new SmartDateDayPart();
        
        this._parts.push(this._day);
        
        return this._avoidAnchor(SmartDateFormat.DAY_ANCHOR, startIndex, lowFormat);
    }
    
    _scanMonth(startIndex, lowFormat)
    {
        this._checkLastIsStatic();
        
        this._month = new SmartDateMonthPart();
        
        this._parts.push(this._month);
        
        return this._avoidAnchor(SmartDateFormat.MONTH_ANCHOR, startIndex, lowFormat);
    }
    
    _scanYear(startIndex, lowFormat)
    {
        this._checkLastIsStatic();
        
        this._year = new SmartDateYearPart();
        
        this._parts.push(this._year);
        
        return this._avoidAnchor(SmartDateFormat.YEAR_ANCHOR, startIndex, lowFormat);
    }
    
    _avoidAnchor(anchor, startIndex, lowFormat)
    {
        let endingIndex = startIndex;
        
        while (endingIndex < lowFormat.length && lowFormat[endingIndex] === anchor) {
            endingIndex++;
        }
        
        return endingIndex;
    }
    
    _checkLastIsStatic()
    {
        if (this._parts.length === 0) {
            return;
        }
        
        if (!this._parts[this._parts.length - 1].isStatic()) {
            throw new Error('Dynamic date parts must be separated by static substrings');
        }
    }
    
    _scanStatic(startIndex, format, lowFormat)
    {
        let endingIndex = startIndex;
        
        while (endingIndex < format.length && this._isStaticAnchorSymbol(lowFormat[endingIndex])) {
            endingIndex++;
        }
        
        this._parts.push(new SmartDateStaticPart(format.substring(startIndex, endingIndex)));
        
        return endingIndex;
    }
    
    _isStaticAnchorSymbol(symbol)
    {
        return symbol !== SmartDateFormat.DAY_ANCHOR
            && symbol !== SmartDateFormat.MONTH_ANCHOR
            && symbol !== SmartDateFormat.YEAR_ANCHOR;
    }
    
    getPlaceHolder()
    {
        let placeHolder = '';
        
        for (let i = 0; i < this._parts.length; i++) {
            placeHolder += this._parts[i].placeHolder();
        }
        
        return placeHolder;
    }
    
    /**
     * @param {Date} date
     * @returns {String}
     */
    formatDate(date)
    {
        var formattedDate = '';
        
        for (let i = 0; i < this._parts.length; i++) {
            if (this._parts[i].isStatic()) {
                formattedDate += this._parts[i].placeHolder();
            }
            else {
                formattedDate += this._parts[i].formatDate(date);
            }
        }
        
        return formattedDate;
    }
    
    parse(value, autocomplete)
    {
        this._resetParseState();
        
        this._parseState.start(value);
        
        this._parseParts(autocomplete);
        
        this._correctDate();
    }
    
    _resetParseState()
    {
        for (let i = 0; i < this._parts.length; i++) {
            this._parts[i].resetState();
        }
    }
    
    _parseParts(autocomplete)
    {
        let index = 0;
        
        while (index < this._parts.length && (autocomplete || !this._parseState.isOver())) {
            this._parts[index].parse(this._parseState);
            
            if (!this._parts[index].isComplete()) {
                return;
            }
            
            index++;
        }
    }
    
    _correctDate()
    {
        if (!this._day.isComplete() || !this._month.isComplete()) {
            return;
        }
        
        this._day.setMaximalValue(this._getMonthSize(this._month.numericValue() - 1, !this._year.isComplete() || this._isLeaPYear(this._year.numericValue())));
    }
    
    getParsedDate()
    {
        if (!this.isComplete()) {
            return null;
        }
        
        return new Date(this._year.numericValue(), this._month.numericValue() - 1, this._day.numericValue());
    }
    
    _getMonthSize(monthIndex, isLeapYear)
    {
        if (monthIndex < 0 || monthIndex >= SmartDateMonthPart.MAXIMAL_VALUE) {
            return SmartDateMonthPart.MAXIMAL_VALUE;
        }
        
        if (monthIndex === 1 && isLeapYear) { // February
            return SmartDateFormat.BASIC_MONTH_SIZES[monthIndex] + 1;
        }
        
        return SmartDateFormat.BASIC_MONTH_SIZES[monthIndex];
    }
    
    _isLeaPYear(year)
    {
        return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
    }
    
    getCorrectedValue()
    {
        let index = 0;
        let value = '';
        
        while (index < this._parts.length && !this._parts[index].isNoState()) {
            value += this._parts[index].cleanedValue();
            index++;
        }
        
        return value;
    }
    
    isComplete()
    {
        return this._parts[this._parts.length - 1].isComplete();
    }
}

SmartDateFormat.DAY_ANCHOR = 'd';
SmartDateFormat.DAY_PLACEHOLDER = 'ДД';

SmartDateFormat.MONTH_ANCHOR = 'm';
SmartDateFormat.MONTH_PLACEHOLDER = 'ММ';

SmartDateFormat.YEAR_ANCHOR = 'y';
SmartDateFormat.YEAR_PLACEHOLDER = 'ГГГГ';

SmartDateFormat.BASIC_MONTH_SIZES = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
