/**
 * DevExtreme (ui/calendar/ui.calendar.js)
 * Version: 18.1.7 (build 18332)
 * Build date: Wed Nov 28 2018
 *
 * Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
"use strict";
var $ = require("../../core/renderer"),
    Guid = require("../../core/guid"),
    registerComponent = require("../../core/component_registrator"),
    noop = require("../../core/utils/common").noop,
    typeUtils = require("../../core/utils/type"),
    extend = require("../../core/utils/extend").extend,
    Button = require("../button"),
    Editor = require("../editor/editor"),
    Swipeable = require("../../events/gesture/swipeable"),
    Navigator = require("./ui.calendar.navigator"),
    Views = require("./ui.calendar.views"),
    translator = require("../../animation/translator"),
    browser = require("../../core/utils/browser"),
    dateUtils = require("../../core/utils/date"),
    dateSerialization = require("../../core/utils/date_serialization"),
    devices = require("../../core/devices"),
    config = require("../../core/config"),
    fx = require("../../animation/fx"),
    windowUtils = require("../../core/utils/window"),
    messageLocalization = require("../../localization/message"),
    FunctionTemplate = require("../widget/function_template");
var CALENDAR_CLASS = "dx-calendar",
    CALENDAR_BODY_CLASS = "dx-calendar-body",
    CALENDAR_CELL_CLASS = "dx-calendar-cell",
    CALENDAR_FOOTER_CLASS = "dx-calendar-footer",
    CALENDAR_TODAY_BUTTON_CLASS = "dx-calendar-today-button",
    CALENDAR_HAS_FOOTER_CLASS = "dx-calendar-with-footer",
    CALENDAR_VIEWS_WRAPPER_CLASS = "dx-calendar-views-wrapper",
    CALENDAR_VIEW_CLASS = "dx-calendar-view",
    FOCUSED_STATE_CLASS = "dx-state-focused",
    ANIMATION_DURATION_SHOW_VIEW = 250,
    POP_ANIMATION_FROM = .6,
    POP_ANIMATION_TO = 1,
    CALENDAR_INPUT_STANDARD_PATTERN = "yyyy-MM-dd",
    CALENDAR_DATE_VALUE_KEY = "dxDateValueKey",
    LEVEL_COMPARE_MAP = {
        month: 3,
        year: 2,
        decade: 1,
        century: 0
    };
var Calendar = Editor.inherit({
    _activeStateUnit: "." + CALENDAR_CELL_CLASS,
    _getDefaultOptions: function() {
        return extend(this.callBase(), {
            hoverStateEnabled: true,
            activeStateEnabled: true,
            currentDate: new Date,
            value: null,
            dateSerializationFormat: void 0,
            min: new Date(1e3, 0),
            max: new Date(3e3, 0),
            firstDayOfWeek: void 0,
            zoomLevel: "month",
            maxZoomLevel: "month",
            minZoomLevel: "century",
            showTodayButton: false,
            cellTemplate: "cell",
            disabledDates: null,
            onCellClick: null,
            onContouredChanged: null,
            hasFocus: function(element) {
                return element.hasClass(FOCUSED_STATE_CLASS)
            }
        })
    },
    _defaultOptionsRules: function() {
        return this.callBase().concat([{
            device: function() {
                return "desktop" === devices.real().deviceType && !devices.isSimulator()
            },
            options: {
                focusStateEnabled: true
            }
        }])
    },
    _supportedKeys: function() {
        return extend(this.callBase(), {
            rightArrow: function(e) {
                e.preventDefault();
                if (e.ctrlKey) {
                    this._waitRenderView(1)
                } else {
                    this._moveCurrentDate(1 * this._getRtlCorrection())
                }
            },
            leftArrow: function(e) {
                e.preventDefault();
                if (e.ctrlKey) {
                    this._waitRenderView(-1)
                } else {
                    this._moveCurrentDate(-1 * this._getRtlCorrection())
                }
            },
            upArrow: function(e) {
                e.preventDefault();
                if (e.ctrlKey) {
                    this._navigateUp()
                } else {
                    if (fx.isAnimating(this._view.$element())) {
                        return
                    }
                    this._moveCurrentDate(-1 * this._view.option("colCount"))
                }
            },
            downArrow: function(e) {
                e.preventDefault();
                if (e.ctrlKey) {
                    this._navigateDown()
                } else {
                    if (fx.isAnimating(this._view.$element())) {
                        return
                    }
                    this._moveCurrentDate(1 * this._view.option("colCount"))
                }
            },
            home: function(e) {
                e.preventDefault();
                var zoomLevel = this.option("zoomLevel");
                var currentDate = this.option("currentDate");
                var min = this._dateOption("min");
                var date = dateUtils.sameView(zoomLevel, currentDate, min) ? min : dateUtils.getViewFirstCellDate(zoomLevel, currentDate);
                this._moveToClosestAvailableDate(date, 1)
            },
            end: function(e) {
                e.preventDefault();
                var zoomLevel = this.option("zoomLevel");
                var currentDate = this.option("currentDate");
                var max = this._dateOption("max");
                var date = dateUtils.sameView(zoomLevel, currentDate, max) ? max : dateUtils.getViewLastCellDate(zoomLevel, currentDate);
                this._moveToClosestAvailableDate(date, -1)
            },
            pageUp: function(e) {
                e.preventDefault();
                this._waitRenderView(-1)
            },
            pageDown: function(e) {
                e.preventDefault();
                this._waitRenderView(1)
            },
            tab: noop,
            enter: function(e) {
                if (!this._isMaxZoomLevel()) {
                    this._navigateDown()
                } else {
                    var value = this._updateTimeComponent(this.option("currentDate"));
                    this._dateValue(value, e)
                }
            }
        })
    },
    _getSerializationFormat: function(optionName) {
        var value = this.option(optionName || "value");
        if (this.option("dateSerializationFormat")) {
            return this.option("dateSerializationFormat")
        }
        if (typeUtils.isNumeric(value)) {
            return "number"
        }
        if (!typeUtils.isString(value)) {
            return
        }
        return dateSerialization.getDateSerializationFormat(value)
    },
    _convertToDate: function(value, optionName) {
        return dateSerialization.deserializeDate(value)
    },
    _dateValue: function(value, dxEvent) {
        if (dxEvent) {
            this._saveValueChangeEvent(dxEvent)
        }
        this._dateOption("value", value)
    },
    _dateOption: function(optionName, optionValue) {
        if (1 === arguments.length) {
            return this._convertToDate(this.option(optionName), optionName)
        }
        var serializationFormat = this._getSerializationFormat(optionName);
        this.option(optionName, dateSerialization.serializeDate(optionValue, serializationFormat))
    },
    _moveCurrentDate: function(offset, baseDate) {
        var newDate, currentDate = baseDate || new Date(this.option("currentDate")),
            maxDate = this.option("max"),
            minDate = this.option("min"),
            zoomLevel = this.option("zoomLevel"),
            isAvailableDateFound = false;
        while (!isAvailableDateFound) {
            newDate = new Date(currentDate);
            switch (zoomLevel) {
                case "month":
                    newDate.setDate(currentDate.getDate() + offset);
                    break;
                case "year":
                    newDate.setMonth(currentDate.getMonth() + offset);
                    break;
                case "decade":
                    newDate.setFullYear(currentDate.getFullYear() + offset);
                    break;
                case "century":
                    newDate.setFullYear(currentDate.getFullYear() + 10 * offset)
            }
            var offsetCorrection = 2 * offset / Math.abs(offset);
            if (Math.abs(offset) > 1 && !dateUtils.sameView(zoomLevel, currentDate, newDate)) {
                if ("decade" === zoomLevel) {
                    newDate.setFullYear(currentDate.getFullYear() + offset - offsetCorrection)
                }
                if ("century" === zoomLevel) {
                    newDate.setFullYear(currentDate.getFullYear() + 10 * (offset - offsetCorrection))
                }
            }
            if (this._view.isDateDisabled(newDate) && newDate <= new Date(maxDate) && newDate >= new Date(minDate)) {
                currentDate = newDate
            } else {
                isAvailableDateFound = true
            }
        }
        this.option("currentDate", newDate)
    },
    _moveToClosestAvailableDate: function(baseDate, offset) {
        if (this._view.isDateDisabled(baseDate)) {
            this._moveCurrentDate(offset, baseDate)
        } else {
            this.option("currentDate", baseDate)
        }
    },
    _init: function() {
        this.callBase();
        this._correctZoomLevel();
        this._initCurrentDate();
        this._initActions()
    },
    _correctZoomLevel: function() {
        var minZoomLevel = this.option("minZoomLevel"),
            maxZoomLevel = this.option("maxZoomLevel"),
            zoomLevel = this.option("zoomLevel");
        if (LEVEL_COMPARE_MAP[maxZoomLevel] < LEVEL_COMPARE_MAP[minZoomLevel]) {
            return
        }
        if (LEVEL_COMPARE_MAP[zoomLevel] > LEVEL_COMPARE_MAP[maxZoomLevel]) {
            this.option("zoomLevel", maxZoomLevel)
        } else {
            if (LEVEL_COMPARE_MAP[zoomLevel] < LEVEL_COMPARE_MAP[minZoomLevel]) {
                this.option("zoomLevel", minZoomLevel)
            }
        }
    },
    _initCurrentDate: function() {
        var currentDate = this._getNormalizedDate(this._dateOption("value")) || this._getNormalizedDate(this.option("currentDate"));
        this.option("currentDate", currentDate)
    },
    _getNormalizedDate: function(date) {
        date = dateUtils.normalizeDate(date, this._getMinDate(), this._getMaxDate());
        return typeUtils.isDefined(date) ? new Date(date) : date
    },
    _initActions: function() {
        this._cellClickAction = this._createActionByOption("onCellClick");
        this._onContouredChanged = this._createActionByOption("onContouredChanged")
    },
    _initTemplates: function() {
        this.callBase();
        this._defaultTemplates.cell = new FunctionTemplate(function(options) {
            var data = options.model;
            $(options.container).append($("<span>").text(data && data.text || String(data)))
        }, this)
    },
    _updateCurrentDate: function(date) {
        if (fx.isAnimating(this._$viewsWrapper)) {
            fx.stop(this._$viewsWrapper, true)
        }
        var min = this._getMinDate(),
            max = this._getMaxDate();
        if (min > max) {
            this.option("currentDate", new Date);
            return
        }
        var normalizedDate = this._getNormalizedDate(date);
        if (date.getTime() !== normalizedDate.getTime()) {
            this.option("currentDate", new Date(normalizedDate));
            return
        }
        var offset = this._getViewsOffset(this._view.option("date"), normalizedDate);
        if (0 !== offset && !this._isMaxZoomLevel() && this._isOtherViewCellClicked) {
            offset = 0
        }
        if (this._view && 0 !== offset && !this._suppressNavigation) {
            this._navigate(offset, normalizedDate)
        } else {
            this._renderNavigator();
            this._setViewContoured(normalizedDate);
            this._updateAriaId(normalizedDate)
        }
    },
    _setViewContoured: function(date) {
        if (this.option("hasFocus")(this._focusTarget())) {
            this._view.option("contouredDate", date)
        }
    },
    _getMinDate: function() {
        if (this.min) {
            return this.min
        }
        this.min = this._dateOption("min") || new Date(1e3, 0);
        return this.min
    },
    _getMaxDate: function() {
        if (this.max) {
            return this.max
        }
        this.max = this._dateOption("max") || new Date(3e3, 0);
        return this.max
    },
    _getViewsOffset: function(startDate, endDate) {
        var zoomLevel = this.option("zoomLevel");
        if ("month" === zoomLevel) {
            return this._getMonthsOffset(startDate, endDate)
        }
        var zoomCorrection;
        switch (zoomLevel) {
            case "century":
                zoomCorrection = 100;
                break;
            case "decade":
                zoomCorrection = 10;
                break;
            default:
                zoomCorrection = 1
        }
        return parseInt(endDate.getFullYear() / zoomCorrection) - parseInt(startDate.getFullYear() / zoomCorrection)
    },
    _getMonthsOffset: function(startDate, endDate) {
        var yearOffset = endDate.getFullYear() - startDate.getFullYear(),
            monthOffset = endDate.getMonth() - startDate.getMonth();
        return 12 * yearOffset + monthOffset
    },
    _waitRenderView: function(offset) {
        if (this._alreadyViewRender) {
            return
        }
        this._alreadyViewRender = true;
        var date = this._getDateByOffset(offset * this._getRtlCorrection());
        this._moveToClosestAvailableDate(date, offset);
        setTimeout(function() {
            this._alreadyViewRender = false
        }.bind(this))
    },
    _getRtlCorrection: function() {
        return this.option("rtlEnabled") ? -1 : 1
    },
    _getDateByOffset: function(offset, date) {
        date = new Date(date || this.option("currentDate"));
        var currentDay = date.getDate();
        var difference = dateUtils.getDifferenceInMonth(this.option("zoomLevel")) * offset;
        date.setDate(1);
        date.setMonth(date.getMonth() + difference);
        var lastDay = dateUtils.getLastMonthDate(date).getDate();
        date.setDate(currentDay > lastDay ? lastDay : currentDay);
        return date
    },
    _focusTarget: function() {
        return this.$element()
    },
    _initMarkup: function() {
        this._renderSubmitElement();
        this.callBase();
        var $element = this.$element();
        $element.addClass(CALENDAR_CLASS);
        this._renderBody();
        $element.append(this.$body);
        this._renderViews();
        this._renderNavigator();
        $element.append(this._navigator.$element());
        this._renderSwipeable();
        this._renderFooter();
        this.setAria({
            role: "listbox",
            label: messageLocalization.format("dxCalendar-ariaWidgetName")
        });
        this._updateAriaSelected();
        this._updateAriaId();
        if (this._view.isDateDisabled(this.option("currentDate"))) {
            this._moveCurrentDate(1)
        }
    },
    _render: function() {
        this.callBase();
        this._setViewContoured(this.option("currentDate"))
    },
    _renderBody: function() {
        if (!this._$viewsWrapper) {
            this.$body = $("<div>").addClass(CALENDAR_BODY_CLASS);
            this._$viewsWrapper = $("<div>").addClass(CALENDAR_VIEWS_WRAPPER_CLASS);
            this.$body.append(this._$viewsWrapper)
        }
    },
    _renderViews: function() {
        this.$element().addClass(CALENDAR_VIEW_CLASS + "-" + this.option("zoomLevel"));
        var currentDate = this.option("currentDate");
        this._view = this._renderSpecificView(currentDate);
        this._view.option("_keyboardProcessor", this._viewKeyboardProcessor);
        if (windowUtils.hasWindow()) {
            var beforeDate = this._getDateByOffset(-1, currentDate);
            this._beforeView = this._isViewAvailable(beforeDate) ? this._renderSpecificView(beforeDate) : null;
            var afterDate = this._getDateByOffset(1, currentDate);
            afterDate.setDate(1);
            this._afterView = this._isViewAvailable(afterDate) ? this._renderSpecificView(afterDate) : null
        }
        this._translateViews()
    },
    _renderSpecificView: function(date) {
        var specificView = Views[this.option("zoomLevel")],
            $view = $("<div>").appendTo(this._$viewsWrapper),
            config = this._viewConfig(date);
        return new specificView($view, config)
    },
    _viewConfig: function(date) {
        var disabledDates = this.option("disabledDates");
        disabledDates = typeUtils.isFunction(disabledDates) ? this._injectComponent(disabledDates.bind(this)) : disabledDates;
        return {
            date: date,
            min: this._getMinDate(),
            max: this._getMaxDate(),
            firstDayOfWeek: this.option("firstDayOfWeek"),
            value: this._dateOption("value"),
            rtl: this.option("rtlEnabled"),
            disabled: this.option("disabled") || config().designMode,
            tabIndex: void 0,
            focusStateEnabled: this.option("focusStateEnabled"),
            hoverStateEnabled: this.option("hoverStateEnabled"),
            disabledDates: disabledDates,
            onCellClick: this._cellClickHandler.bind(this),
            cellTemplate: this._getTemplateByOption("cellTemplate"),
            allowValueSelection: this._isMaxZoomLevel()
        }
    },
    _injectComponent: function(func) {
        var that = this;
        return function(params) {
            extend(params, {
                component: that
            });
            return func(params)
        }
    },
    _isViewAvailable: function(date) {
        var zoomLevel = this.option("zoomLevel");
        var min = dateUtils.getViewMinBoundaryDate(zoomLevel, this._getMinDate());
        var max = dateUtils.getViewMaxBoundaryDate(zoomLevel, this._getMaxDate());
        return dateUtils.dateInRange(date, min, max)
    },
    _translateViews: function() {
        translator.move(this._view.$element(), {
            left: 0,
            top: 0
        });
        this._beforeView && translator.move(this._beforeView.$element(), {
            left: this._getViewPosition(-1),
            top: 0
        });
        this._afterView && translator.move(this._afterView.$element(), {
            left: this._getViewPosition(1),
            top: 0
        })
    },
    _getViewPosition: function(coefficient) {
        var rtlCorrection = this.option("rtlEnabled") && !browser.msie ? -1 : 1;
        return 100 * coefficient * rtlCorrection + "%"
    },
    _cellClickHandler: function(e) {
        var zoomLevel = this.option("zoomLevel"),
            nextView = dateUtils.getViewDown(zoomLevel);
        var isMaxZoomLevel = this._isMaxZoomLevel();
        if (nextView && !isMaxZoomLevel) {
            this._navigateDown(e.event.currentTarget)
        } else {
            var newValue = this._updateTimeComponent(e.value);
            this._dateValue(newValue, e.event);
            this._cellClickAction(e)
        }
    },
    _updateTimeComponent: function(date) {
        var result = new Date(date);
        var currentValue = this._dateOption("value");
        if (currentValue) {
            result.setHours(currentValue.getHours());
            result.setMinutes(currentValue.getMinutes());
            result.setSeconds(currentValue.getSeconds());
            result.setMilliseconds(currentValue.getMilliseconds())
        }
        return result
    },
    _isMaxZoomLevel: function() {
        return this.option("zoomLevel") === this.option("maxZoomLevel")
    },
    _navigateDown: function(cell) {
        var zoomLevel = this.option("zoomLevel");
        if (this._isMaxZoomLevel()) {
            return
        }
        var nextView = dateUtils.getViewDown(zoomLevel);
        if (!nextView) {
            return
        }
        var newCurrentDate = this._view.option("contouredDate") || this._view.option("date");
        if (cell) {
            newCurrentDate = $(cell).data(CALENDAR_DATE_VALUE_KEY)
        }
        this._isOtherViewCellClicked = true;
        this.option("currentDate", newCurrentDate);
        this.option("zoomLevel", nextView);
        this._isOtherViewCellClicked = false;
        this._renderNavigator();
        this._animateShowView();
        this._setViewContoured(this._getNormalizedDate(newCurrentDate))
    },
    _renderNavigator: function() {
        if (!this._navigator) {
            this._navigator = new Navigator($("<div>"), this._navigatorConfig())
        }
        this._navigator.option("text", this._view.getNavigatorCaption());
        this._updateButtonsVisibility()
    },
    _navigatorConfig: function() {
        return {
            text: this._view.getNavigatorCaption(),
            onClick: this._navigatorClickHandler.bind(this),
            onCaptionClick: this._navigateUp.bind(this),
            rtlEnabled: this.option("rtlEnabled")
        }
    },
    _navigatorClickHandler: function(e) {
        var currentDate = this._getDateByOffset(e.direction, this.option("currentDate"));
        this._moveToClosestAvailableDate(currentDate, 1 * e.direction);
        this._updateNavigatorCaption(-e.direction * this._getRtlCorrection())
    },
    _navigateUp: function() {
        var zoomLevel = this.option("zoomLevel"),
            nextView = dateUtils.getViewUp(zoomLevel);
        if (!nextView || this._isMinZoomLevel(zoomLevel)) {
            return
        }
        var contouredDate = this._view.option("contouredDate");
        this.option("zoomLevel", nextView);
        this.option("currentDate", contouredDate || this._view.option("date"));
        this._renderNavigator();
        this._animateShowView().done(function() {
            this._setViewContoured(contouredDate)
        }.bind(this))
    },
    _isMinZoomLevel: function(zoomLevel) {
        var min = this._getMinDate(),
            max = this._getMaxDate();
        return dateUtils.sameView(zoomLevel, min, max) || this.option("minZoomLevel") === zoomLevel
    },
    _updateButtonsVisibility: function() {
        this._navigator.toggleButton("next", !typeUtils.isDefined(this._getRequiredView("next")));
        this._navigator.toggleButton("prev", !typeUtils.isDefined(this._getRequiredView("prev")))
    },
    _renderSwipeable: function() {
        if (!this._swipeable) {
            this._swipeable = this._createComponent(this.$element(), Swipeable, {
                onStart: this._swipeStartHandler.bind(this),
                onUpdated: this._swipeUpdateHandler.bind(this),
                onEnd: this._swipeEndHandler.bind(this),
                itemSizeFunc: this._viewWidth.bind(this)
            })
        }
    },
    _swipeStartHandler: function(e) {
        fx.stop(this._$viewsWrapper, true);
        e.event.maxLeftOffset = this._getRequiredView("next") ? 1 : 0;
        e.event.maxRightOffset = this._getRequiredView("prev") ? 1 : 0
    },
    _getRequiredView: function(name) {
        var view;
        var isRtl = this.option("rtlEnabled");
        if ("next" === name) {
            view = isRtl ? this._beforeView : this._afterView
        } else {
            if ("prev" === name) {
                view = isRtl ? this._afterView : this._beforeView
            }
        }
        return view
    },
    _swipeUpdateHandler: function(e) {
        var offset = e.event.offset;
        translator.move(this._$viewsWrapper, {
            left: offset * this._viewWidth(),
            top: 0
        });
        this._updateNavigatorCaption(offset)
    },
    _swipeEndHandler: function(e) {
        var targetOffset = e.event.targetOffset,
            moveOffset = !targetOffset ? 0 : targetOffset / Math.abs(targetOffset);
        if (0 === moveOffset) {
            this._animateWrapper(0, ANIMATION_DURATION_SHOW_VIEW);
            return
        }
        var date = this._getDateByOffset(-moveOffset * this._getRtlCorrection());
        if (this._isDateInInvalidRange(date)) {
            if (moveOffset >= 0) {
                date = new Date(this._getMinDate())
            } else {
                date = new Date(this._getMaxDate())
            }
        }
        this.option("currentDate", date)
    },
    _viewWidth: function() {
        if (!this._viewWidthValue) {
            this._viewWidthValue = this.$element().width()
        }
        return this._viewWidthValue
    },
    _updateNavigatorCaption: function(offset) {
        offset *= this._getRtlCorrection();
        var view = this._view;
        if (offset > .5 && this._beforeView) {
            view = this._beforeView
        } else {
            if (offset < -.5 && this._afterView) {
                view = this._afterView
            }
        }
        this._navigator.option("text", view.getNavigatorCaption())
    },
    _isDateInInvalidRange: function(date) {
        if (this._view.isBoundary(date)) {
            return
        }
        var min = this._getMinDate(),
            max = this._getMaxDate(),
            normalizedDate = dateUtils.normalizeDate(date, min, max);
        return normalizedDate === min || normalizedDate === max
    },
    _renderFooter: function() {
        var showTodayButton = this.option("showTodayButton");
        if (showTodayButton) {
            var $todayButton = this._createComponent($("<a>"), Button, {
                focusStateEnabled: false,
                text: messageLocalization.format("dxCalendar-todayButtonText"),
                onClick: function() {
                    this._toTodayView()
                }.bind(this),
                integrationOptions: {}
            }).$element().addClass(CALENDAR_TODAY_BUTTON_CLASS);
            this._$footer = $("<div>").addClass(CALENDAR_FOOTER_CLASS).append($todayButton);
            this.$element().append(this._$footer)
        }
        this.$element().toggleClass(CALENDAR_HAS_FOOTER_CLASS, showTodayButton)
    },
    _renderSubmitElement: function() {
        this._$submitElement = $("<input>").attr("type", "hidden").appendTo(this.$element());
        this._setSubmitValue(this.option("value"))
    },
    _setSubmitValue: function(value) {
        var dateValue = this._convertToDate(value);
        this._$submitElement.val(dateSerialization.serializeDate(dateValue, CALENDAR_INPUT_STANDARD_PATTERN))
    },
    _getSubmitElement: function() {
        return this._$submitElement
    },
    _animateShowView: function() {
        fx.stop(this._view.$element(), true);
        return this._popAnimationView(this._view, POP_ANIMATION_FROM, POP_ANIMATION_TO, ANIMATION_DURATION_SHOW_VIEW).promise()
    },
    _popAnimationView: function(view, from, to, duration) {
        return fx.animate(view.$element(), {
            type: "pop",
            from: {
                scale: from,
                opacity: from
            },
            to: {
                scale: to,
                opacity: to
            },
            duration: duration
        })
    },
    _navigate: function(offset, value) {
        if (0 !== offset && 1 !== Math.abs(offset) && this._isViewAvailable(value)) {
            var newView = this._renderSpecificView(value);
            if (offset > 0) {
                this._afterView && this._afterView.$element().remove();
                this._afterView = newView
            } else {
                this._beforeView && this._beforeView.$element().remove();
                this._beforeView = newView
            }
            this._translateViews()
        }
        var rtlCorrection = this._getRtlCorrection(),
            offsetSign = offset > 0 ? 1 : offset < 0 ? -1 : 0,
            endPosition = -rtlCorrection * offsetSign * this._viewWidth();
        var viewsWrapperPosition = this._$viewsWrapper.position().left;
        if (viewsWrapperPosition !== endPosition) {
            if (this._preventViewChangeAnimation) {
                this._wrapperAnimationEndHandler(offset, value)
            } else {
                this._animateWrapper(endPosition, ANIMATION_DURATION_SHOW_VIEW).done(this._wrapperAnimationEndHandler.bind(this, offset, value))
            }
        }
    },
    _animateWrapper: function(to, duration) {
        return fx.animate(this._$viewsWrapper, {
            type: "slide",
            from: {
                left: this._$viewsWrapper.position().left
            },
            to: {
                left: to
            },
            duration: duration
        })
    },
    _toTodayView: function() {
        var today = new Date;
        if (this._isMaxZoomLevel()) {
            this._dateOption("value", today);
            return
        }
        this._preventViewChangeAnimation = true;
        this.option("zoomLevel", this.option("maxZoomLevel"));
        this._dateOption("value", today);
        this._animateShowView();
        this._preventViewChangeAnimation = false
    },
    _wrapperAnimationEndHandler: function(offset, newDate) {
        this._rearrangeViews(offset);
        this._translateViews();
        this._resetLocation();
        this._renderNavigator();
        this._setViewContoured(newDate);
        this._updateAriaId(newDate)
    },
    _rearrangeViews: function(offset) {
        if (0 === offset) {
            return
        }
        var viewOffset, viewToCreateKey, viewToRemoveKey;
        if (offset < 0) {
            viewOffset = 1;
            viewToCreateKey = "_beforeView";
            viewToRemoveKey = "_afterView"
        } else {
            viewOffset = -1;
            viewToCreateKey = "_afterView";
            viewToRemoveKey = "_beforeView"
        }
        if (!this[viewToCreateKey]) {
            return
        }
        var destinationDate = this[viewToCreateKey].option("date");
        if (this[viewToRemoveKey]) {
            this[viewToRemoveKey].$element().remove()
        }
        if (offset === viewOffset) {
            this[viewToRemoveKey] = this._view
        } else {
            this[viewToRemoveKey] = this._renderSpecificView(this._getDateByOffset(viewOffset, destinationDate));
            this._view.$element().remove()
        }
        this._view = this[viewToCreateKey];
        var dateByOffset = this._getDateByOffset(-viewOffset, destinationDate);
        this[viewToCreateKey] = this._isViewAvailable(dateByOffset) ? this._renderSpecificView(dateByOffset) : null
    },
    _resetLocation: function() {
        translator.move(this._$viewsWrapper, {
            left: 0,
            top: 0
        })
    },
    _clean: function() {
        this.callBase();
        this._clearViewWidthCache();
        delete this._$viewsWrapper;
        delete this._navigator;
        delete this._$footer
    },
    _clearViewWidthCache: function() {
        delete this._viewWidthValue
    },
    _disposeViews: function() {
        this._view.$element().remove();
        this._beforeView && this._beforeView.$element().remove();
        this._afterView && this._afterView.$element().remove();
        delete this._view;
        delete this._beforeView;
        delete this._afterView
    },
    _refreshViews: function() {
        this._disposeViews();
        this._renderViews()
    },
    _visibilityChanged: function() {
        this._translateViews()
    },
    _focusInHandler: function() {
        this.callBase.apply(this, arguments);
        this._view.option("contouredDate", this.option("currentDate"))
    },
    _focusOutHandler: function() {
        this.callBase.apply(this, arguments);
        this._view.option("contouredDate", null)
    },
    _updateViewsValue: function(value) {
        var newValue = value ? new Date(value) : null;
        this._view.option("value", newValue);
        this._beforeView && this._beforeView.option("value", newValue);
        this._afterView && this._afterView.option("value", newValue)
    },
    _updateAriaSelected: function(value, previousValue) {
        value = value || this._dateOption("value");
        var $prevSelectedCell = this._view._getCellByDate(previousValue);
        var $selectedCell = this._view._getCellByDate(value);
        this.setAria("selected", void 0, $prevSelectedCell);
        this.setAria("selected", true, $selectedCell);
        if (value && this.option("currentDate").getTime() === value.getTime()) {
            this._updateAriaId(value)
        }
    },
    _updateAriaId: function(value) {
        value = value || this.option("currentDate");
        var ariaId = "dx-" + new Guid;
        var $newCell = this._view._getCellByDate(value);
        this.setAria("id", ariaId, $newCell);
        this.setAria("activedescendant", ariaId);
        this._onContouredChanged(ariaId)
    },
    _suppressingNavigation: function(callback, args) {
        this._suppressNavigation = true;
        callback.apply(this, args);
        delete this._suppressNavigation
    },
    _optionChanged: function(args) {
        var value = args.value;
        var previousValue = args.previousValue;
        switch (args.name) {
            case "width":
                this.callBase(args);
                this._clearViewWidthCache();
                break;
            case "min":
            case "max":
                this.min = void 0;
                this.max = void 0;
                this._suppressingNavigation(this._updateCurrentDate, [this.option("currentDate")]);
                this._refreshViews();
                this._renderNavigator();
                break;
            case "firstDayOfWeek":
                this._refreshViews();
                this._updateButtonsVisibility();
                break;
            case "currentDate":
                this.setAria("id", void 0, this._view._getCellByDate(previousValue));
                this._updateCurrentDate(value);
                break;
            case "zoomLevel":
                this.$element().removeClass(CALENDAR_VIEW_CLASS + "-" + previousValue);
                this._correctZoomLevel();
                this._refreshViews();
                this._renderNavigator();
                this._updateAriaId();
                break;
            case "minZoomLevel":
            case "maxZoomLevel":
                this._correctZoomLevel();
                this._updateButtonsVisibility();
                break;
            case "value":
                value = this._convertToDate(value);
                previousValue = this._convertToDate(previousValue);
                this._updateAriaSelected(value, previousValue);
                this.option("currentDate", typeUtils.isDefined(value) ? new Date(value) : new Date);
                this._updateViewsValue(value);
                this._setSubmitValue(value);
                this.callBase(args);
                break;
            case "disabled":
                this._view.option("disabled", value);
                this.callBase(args);
                break;
            case "onCellClick":
                this._view.option("onCellClick", value);
                break;
            case "onContouredChanged":
                this._onContouredChanged = this._createActionByOption("onContouredChanged");
                break;
            case "disabledDates":
            case "dateSerializationFormat":
            case "cellTemplate":
            case "showTodayButton":
                this._invalidate();
                break;
            case "hasFocus":
                break;
            default:
                this.callBase(args)
        }
    }
});
registerComponent("dxCalendar", Calendar);
module.exports = Calendar;
