/**
 * DevExtreme (ui/scheduler/workspaces/ui.scheduler.work_space.js)
 * Version: 20.1.10
 * Build date: Fri Jan 15 2021
 *
 * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
"use strict";

function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
        throw new TypeError("Cannot call a class as a function")
    }
}

function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) {
            descriptor.writable = true
        }
        Object.defineProperty(target, descriptor.key, descriptor)
    }
}

function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) {
        _defineProperties(Constructor.prototype, protoProps)
    }
    if (staticProps) {
        _defineProperties(Constructor, staticProps)
    }
    return Constructor
}
var $ = require("../../../core/renderer");
var domAdapter = require("../../../core/dom_adapter");
var eventsEngine = require("../../../events/core/events_engine");
var dataUtils = require("../../../core/element_data");
var dateUtils = require("../../../core/utils/date");
var typeUtils = require("../../../core/utils/type");
var windowUtils = require("../../../core/utils/window");
var getPublicElement = require("../../../core/utils/dom").getPublicElement;
var extend = require("../../../core/utils/extend").extend;
var each = require("../../../core/utils/iterator").each;
var getBoundingRect = require("../../../core/utils/position").getBoundingRect;
var messageLocalization = require("../../../localization/message");
var dateLocalization = require("../../../localization/date");
var toMs = dateUtils.dateToMilliseconds;
var Widget = require("../../widget/ui.widget");
var abstract = Widget.abstract;
var noop = require("../../../core/utils/common").noop;
var isDefined = require("../../../core/utils/type").isDefined;
var publisherMixin = require("../ui.scheduler.publisher_mixin");
var eventUtils = require("../../../events/utils");
var pointerEvents = require("../../../events/pointer");
var errors = require("../../widget/ui.errors");
var clickEvent = require("../../../events/click");
var contextMenuEvent = require("../../../events/contextmenu");
var dragEvents = require("../../../events/drag");
var Scrollable = require("../../scroll_view/ui.scrollable");
var HorizontalGroupedStrategy = require("./ui.scheduler.work_space.grouped.strategy.horizontal");
var VerticalGroupedStrategy = require("./ui.scheduler.work_space.grouped.strategy.vertical");
var tableCreator = require("../ui.scheduler.table_creator");
var VerticalShader = require("../shaders/ui.scheduler.current_time_shader.vertical");
var AppointmentDragBehavior = require("../appointmentDragBehavior");
var FIXED_CONTAINER_CLASS = require("../constants").FIXED_CONTAINER_CLASS;
var timeZoneUtils = require("../utils.timeZone");
var COMPONENT_CLASS = "dx-scheduler-work-space";
var GROUPED_WORKSPACE_CLASS = "dx-scheduler-work-space-grouped";
var VERTICAL_GROUPED_WORKSPACE_CLASS = "dx-scheduler-work-space-vertical-grouped";
var WORKSPACE_VERTICAL_GROUP_TABLE_CLASS = "dx-scheduler-work-space-vertical-group-table";
var WORKSPACE_WITH_BOTH_SCROLLS_CLASS = "dx-scheduler-work-space-both-scrollbar";
var WORKSPACE_WITH_COUNT_CLASS = "dx-scheduler-work-space-count";
var WORKSPACE_WITH_GROUP_BY_DATE_CLASS = "dx-scheduler-work-space-group-by-date";
var WORKSPACE_WITH_ODD_CELLS_CLASS = "dx-scheduler-work-space-odd-cells";
var TIME_PANEL_CLASS = "dx-scheduler-time-panel";
var TIME_PANEL_CELL_CLASS = "dx-scheduler-time-panel-cell";
var TIME_PANEL_ROW_CLASS = "dx-scheduler-time-panel-row";
var ALL_DAY_PANEL_CLASS = "dx-scheduler-all-day-panel";
var ALL_DAY_TABLE_CLASS = "dx-scheduler-all-day-table";
var ALL_DAY_CONTAINER_CLASS = "dx-scheduler-all-day-appointments";
var ALL_DAY_TITLE_CLASS = "dx-scheduler-all-day-title";
var ALL_DAY_TITLE_HIDDEN_CLASS = "dx-scheduler-all-day-title-hidden";
var ALL_DAY_TABLE_CELL_CLASS = "dx-scheduler-all-day-table-cell";
var ALL_DAY_TABLE_ROW_CLASS = "dx-scheduler-all-day-table-row";
var WORKSPACE_WITH_ALL_DAY_CLASS = "dx-scheduler-work-space-all-day";
var WORKSPACE_WITH_COLLAPSED_ALL_DAY_CLASS = "dx-scheduler-work-space-all-day-collapsed";
var WORKSPACE_WITH_MOUSE_SELECTION_CLASS = "dx-scheduler-work-space-mouse-selection";
var HORIZONTAL_SIZES_CLASS = "dx-scheduler-cell-sizes-horizontal";
var VERTICAL_SIZES_CLASS = "dx-scheduler-cell-sizes-vertical";
var HEADER_PANEL_CLASS = "dx-scheduler-header-panel";
var HEADER_PANEL_CELL_CLASS = "dx-scheduler-header-panel-cell";
var HEADER_ROW_CLASS = "dx-scheduler-header-row";
var GROUP_ROW_CLASS = "dx-scheduler-group-row";
var GROUP_HEADER_CLASS = "dx-scheduler-group-header";
var GROUP_HEADER_CONTENT_CLASS = "dx-scheduler-group-header-content";
var DATE_TABLE_CLASS = "dx-scheduler-date-table";
var DATE_TABLE_CELL_CLASS = "dx-scheduler-date-table-cell";
var DATE_TABLE_ROW_CLASS = "dx-scheduler-date-table-row";
var DATE_TABLE_FOCUSED_CELL_CLASS = "dx-scheduler-focused-cell";
var DATE_TABLE_DROPPABLE_CELL_CLASS = "dx-scheduler-date-table-droppable-cell";
var SCHEDULER_HEADER_SCROLLABLE_CLASS = "dx-scheduler-header-scrollable";
var SCHEDULER_SIDEBAR_SCROLLABLE_CLASS = "dx-scheduler-sidebar-scrollable";
var SCHEDULER_DATE_TABLE_SCROLLABLE_CLASS = "dx-scheduler-date-table-scrollable";
var SCHEDULER_WORKSPACE_DXPOINTERDOWN_EVENT_NAME = eventUtils.addNamespace(pointerEvents.down, "dxSchedulerWorkSpace");
var SCHEDULER_CELL_DXDRAGENTER_EVENT_NAME = eventUtils.addNamespace(dragEvents.enter, "dxSchedulerDateTable");
var SCHEDULER_CELL_DXDROP_EVENT_NAME = eventUtils.addNamespace(dragEvents.drop, "dxSchedulerDateTable");
var SCHEDULER_CELL_DXDRAGLEAVE_EVENT_NAME = eventUtils.addNamespace(dragEvents.leave, "dxSchedulerDateTable");
var SCHEDULER_CELL_DXCLICK_EVENT_NAME = eventUtils.addNamespace(clickEvent.name, "dxSchedulerDateTable");
var SCHEDULER_CELL_DXPOINTERDOWN_EVENT_NAME = eventUtils.addNamespace(pointerEvents.down, "dxSchedulerDateTable");
var SCHEDULER_CELL_DXPOINTERUP_EVENT_NAME = eventUtils.addNamespace(pointerEvents.up, "dxSchedulerDateTable");
var SCHEDULER_CELL_DXPOINTERMOVE_EVENT_NAME = eventUtils.addNamespace(pointerEvents.move, "dxSchedulerDateTable");
var CELL_DATA = "dxCellData";
var DATE_TABLE_CELL_BORDER = 1;
var DATE_TABLE_MIN_CELL_WIDTH = 75;
var DAY_MS = toMs("day");
var HOUR_MS = toMs("hour");
var SCHEDULER_DRAG_AND_DROP_SELECTOR = ".".concat(DATE_TABLE_CLASS, " td, .").concat(ALL_DAY_TABLE_CLASS, " td");
var ScrollSemaphore = function() {
    function ScrollSemaphore() {
        _classCallCheck(this, ScrollSemaphore);
        this.counter = 0
    }
    _createClass(ScrollSemaphore, [{
        key: "isFree",
        value: function() {
            return 0 === this.counter
        }
    }, {
        key: "take",
        value: function() {
            this.counter++
        }
    }, {
        key: "release",
        value: function() {
            this.counter--;
            if (this.counter < 0) {
                this.counter = 0
            }
        }
    }]);
    return ScrollSemaphore
}();
var formatWeekday = function(date) {
    return dateLocalization.getDayNames("abbreviated")[date.getDay()]
};
var SchedulerWorkSpace = Widget.inherit({
    _supportedKeys: function() {
        var clickHandler = function(e) {
            e.preventDefault();
            e.stopPropagation();
            if (this._focusedCells && this._focusedCells.length) {
                var $itemElement = $(this.option("focusedElement"));
                var $cellElement = $itemElement.length ? $itemElement : this._focusedCells;
                e.target = this._focusedCells;
                this._showPopup = true;
                this._cellClickAction({
                    event: e,
                    cellElement: $(this._focusedCells),
                    cellData: this.getCellData($cellElement)
                })
            }
        };
        var arrowPressHandler = function(e, cell) {
            e.preventDefault();
            e.stopPropagation();
            this._moveToCell(cell, e.shiftKey)
        };
        return extend(this.callBase(), {
            enter: clickHandler,
            space: clickHandler,
            downArrow: function(e) {
                var $cell = this._getCellFromNextRow("next", e.shiftKey);
                arrowPressHandler.call(this, e, $cell)
            },
            upArrow: function(e) {
                var $cell = this._getCellFromNextRow("prev", e.shiftKey);
                arrowPressHandler.call(this, e, $cell)
            },
            rightArrow: function(e) {
                var $rightCell = this._getCellFromNextColumn("next", e.shiftKey);
                arrowPressHandler.call(this, e, $rightCell)
            },
            leftArrow: function(e) {
                var $leftCell = this._getCellFromNextColumn("prev", e.shiftKey);
                arrowPressHandler.call(this, e, $leftCell)
            }
        })
    },
    _isRTL: function() {
        return this.option("rtlEnabled")
    },
    _getFocusedCell: function() {
        return this._$focusedCell || this._$dateTable.find("." + DATE_TABLE_CELL_CLASS).eq(0)
    },
    _getAllFocusedCells: function() {
        return this._focusedCells || this._$dateTable.find("." + DATE_TABLE_CELL_CLASS).eq(0)
    },
    _getCellFromNextRow: function(direction) {
        var $currentCell = this._$focusedCell;
        if (isDefined($currentCell)) {
            var cellIndex = $currentCell.index();
            var $row = $currentCell.parent();
            var $cell = $row[direction]().children().eq(cellIndex);
            $cell = this._checkForViewBounds($cell);
            return $cell
        }
    },
    _checkForViewBounds: function($item) {
        if (!$item.length) {
            $item = this._$focusedCell
        }
        return $item
    },
    _getCellFromNextColumn: function(direction, isMultiSelection) {
        var $focusedCell = this._$focusedCell;
        if (!isDefined($focusedCell)) {
            return
        }
        var $nextCell;
        var $row = $focusedCell.parent();
        var nextColumnDirection = direction;
        var isDirectionNext = "next" === direction;
        var previousColumnDirection = isDirectionNext ? "prev" : "next";
        var isRTL = this._isRTL();
        var groupCount = this._getGroupCount();
        var isHorizontalGrouping = this._isHorizontalGroupedWorkSpace();
        var isGroupedByDate = this.isGroupedByDate();
        var totalCellCount = this._getTotalCellCount(groupCount);
        var rowCellCount = isMultiSelection && !isGroupedByDate ? this._getCellCount() : totalCellCount;
        var lastIndexInRow = rowCellCount - 1;
        var currentIndex = $focusedCell.index();
        var step = isGroupedByDate && isMultiSelection ? groupCount : 1;
        var isEdgeCell = this._isEdgeCell(isHorizontalGrouping ? totalCellCount - 1 : lastIndexInRow, currentIndex, step, direction);
        var sign = isRTL ? 1 : -1;
        var directionSign = isDirectionNext ? 1 : -1;
        var resultingSign = sign * directionSign;
        if (isEdgeCell || isMultiSelection && this._isGroupEndCell($focusedCell, direction)) {
            var nextIndex = currentIndex - resultingSign * step + resultingSign * rowCellCount;
            var rowDirection = isRTL ? previousColumnDirection : nextColumnDirection;
            $nextCell = $row[rowDirection]().children().eq(nextIndex);
            $nextCell = this._checkForViewBounds($nextCell)
        } else {
            $nextCell = $row.children().eq(currentIndex - resultingSign * step)
        }
        return $nextCell
    },
    _isEdgeCell: function(lastIndexInRow, cellIndex, step, direction) {
        var isRTL = this._isRTL();
        var isDirectionNext = "next" === direction;
        var rightEdgeCellIndex = isRTL ? 0 : lastIndexInRow;
        var leftEdgeCellIndex = isRTL ? lastIndexInRow : 0;
        var edgeCellIndex = isDirectionNext ? rightEdgeCellIndex : leftEdgeCellIndex;
        var isNextCellGreaterThanEdge = cellIndex + step > edgeCellIndex;
        var isNextCellLessThanEdge = cellIndex - step < edgeCellIndex;
        var isRightEdgeCell = isRTL ? isNextCellLessThanEdge : isNextCellGreaterThanEdge;
        var isLeftEdgeCell = isRTL ? isNextCellGreaterThanEdge : isNextCellLessThanEdge;
        return isDirectionNext ? isRightEdgeCell : isLeftEdgeCell
    },
    _isGroupEndCell: function($cell, direction) {
        if (this.isGroupedByDate()) {
            return false
        }
        var isDirectionNext = "next" === direction;
        var cellsInRow = this._getCellCount();
        var currentCellIndex = $cell.index();
        var result = currentCellIndex % cellsInRow;
        var endCell = isDirectionNext ? cellsInRow - 1 : 0;
        var startCell = isDirectionNext ? 0 : cellsInRow - 1;
        return this._isRTL() ? result === startCell : result === endCell
    },
    _moveToCell: function($cell, isMultiSelection) {
        isMultiSelection = isMultiSelection && this.option("allowMultipleCellSelection");
        this._setFocusedCell($cell, isMultiSelection);
        this._dateTableScrollable.scrollToElement($cell)
    },
    _setFocusedCell: function($cell, isMultiSelection) {
        if (!isDefined($cell) || !$cell.length) {
            return
        }
        this._releaseFocusedCell();
        this._focusedCells = [];
        if (isMultiSelection) {
            $cell = this._correctCellForGroup($cell);
            var orientation = "day" === this.option("type") && (!this.option("groups").length || "vertical" === this.option("groupOrientation")) ? "vertical" : "horizontal";
            var $targetCells = this._getCellsBetween($cell, this._$prevCell, orientation);
            this._focusedCells = $targetCells.toArray()
        } else {
            this._focusedCells = [$cell.get(0)];
            this._$prevCell = $cell
        }
        var $focusedCells = $(this._focusedCells);
        this._toggleFocusClass(true, $focusedCells);
        this.setAria("label", "Add appointment", $focusedCells);
        this._toggleFocusedCellClass(true, $cell);
        this._$focusedCell = $cell;
        var selectedCellData = this.getFocusedCellData();
        this.option("selectedCellData", selectedCellData);
        this._selectionChangedAction({
            selectedCellData: selectedCellData
        })
    },
    _correctCellForGroup: function($cell) {
        var $focusedCell = this._$focusedCell;
        var cellGroupIndex = this._getGroupIndexByCell($cell);
        var focusedCellGroupIndex = this._getGroupIndexByCell($focusedCell);
        var isDifferentTables = this._hasAllDayClass($cell) !== this._hasAllDayClass($focusedCell);
        return focusedCellGroupIndex !== cellGroupIndex || isDifferentTables ? $focusedCell : $cell
    },
    _getCellsBetween: function($first, $last, direction) {
        var isAllDayTable = this._hasAllDayClass($last);
        var $cells = this._getCells(isAllDayTable, direction);
        var firstIndex = $cells.index($first);
        var lastIndex = $cells.index($last);
        if (firstIndex > lastIndex) {
            var buffer = firstIndex;
            firstIndex = lastIndex;
            lastIndex = buffer
        }
        $cells = $cells.slice(firstIndex, lastIndex + 1);
        if (this._getGroupCount() > 1) {
            var result = [];
            var focusedGroupIndex = this._getGroupIndexByCell($first);
            each($cells, function(_, cell) {
                var groupIndex = this._getGroupIndexByCell($(cell));
                if (focusedGroupIndex === groupIndex) {
                    result.push(cell)
                }
            }.bind(this));
            $cells = $(result)
        }
        return $cells
    },
    _hasAllDayClass: function($cell) {
        return $cell.hasClass(ALL_DAY_TABLE_CELL_CLASS)
    },
    _getGroupIndexByCell: function($cell) {
        return this._groupedStrategy.getGroupIndexByCell($cell)
    },
    _toggleFocusedCellClass: function(isFocused, $element) {
        var $focusTarget = $element && $element.length ? $element : this._focusTarget();
        $focusTarget.toggleClass(DATE_TABLE_FOCUSED_CELL_CLASS, isFocused)
    },
    _releaseFocusedCell: function($cell) {
        $cell = $cell || $(this._focusedCells);
        if (isDefined($cell) && $cell.length) {
            this._toggleFocusClass(false, $cell);
            this._toggleFocusedCellClass(false, $cell);
            this.setAria("label", void 0, $cell)
        }
        this.option("selectedCellData", [])
    },
    _focusInHandler: function(e) {
        if ($(e.target).is(this._focusTarget()) && false !== this._isCellClick) {
            delete this._isCellClick;
            delete this._contextMenuHandled;
            this.callBase.apply(this, arguments);
            var $cell = this._getFocusedCell();
            this._setFocusedCell($cell)
        }
    },
    _focusOutHandler: function() {
        this.callBase.apply(this, arguments);
        if (!this._contextMenuHandled) {
            this._releaseFocusedCell()
        }
    },
    _focusTarget: function() {
        return this.$element()
    },
    _activeStateUnit: "." + DATE_TABLE_CELL_CLASS + ", ." + ALL_DAY_TABLE_CELL_CLASS,
    _getDefaultOptions: function() {
        return extend(this.callBase(), {
            currentDate: new Date,
            intervalCount: 1,
            startDate: null,
            firstDayOfWeek: void 0,
            startDayHour: 0,
            endDayHour: 24,
            hoursInterval: .5,
            activeStateEnabled: true,
            hoverStateEnabled: true,
            groups: [],
            showAllDayPanel: true,
            allDayExpanded: false,
            onCellClick: null,
            crossScrollingEnabled: false,
            dataCellTemplate: null,
            timeCellTemplate: null,
            resourceCellTemplate: null,
            dateCellTemplate: null,
            allowMultipleCellSelection: true,
            indicatorTime: new Date,
            indicatorUpdateInterval: 5 * toMs("minute"),
            shadeUntilCurrentTime: true,
            groupOrientation: "horizontal",
            selectedCellData: [],
            groupByDate: false
        })
    },
    _optionChanged: function(args) {
        switch (args.name) {
            case "startDayHour":
            case "endDayHour":
                this.invoke("validateDayHours");
                this._cleanWorkSpace();
                break;
            case "dateCellTemplate":
            case "resourceCellTemplate":
            case "dataCellTemplate":
            case "timeCellTemplate":
            case "hoursInterval":
            case "firstDayOfWeek":
            case "currentDate":
            case "startDate":
                this._cleanWorkSpace();
                break;
            case "groups":
                this._cleanView();
                this._removeAllDayElements();
                this._initGrouping();
                this.repaint();
                break;
            case "groupOrientation":
                this._initGroupedStrategy();
                this._createAllDayPanelElements();
                this._removeAllDayElements();
                this._cleanWorkSpace();
                this._toggleGroupByDateClass();
                break;
            case "showAllDayPanel":
                if (this._isVerticalGroupedWorkSpace()) {
                    this._cleanView();
                    this._removeAllDayElements();
                    this._initGrouping();
                    this.repaint()
                } else {
                    this._toggleAllDayVisibility()
                }
                break;
            case "allDayExpanded":
                this._changeAllDayVisibility();
                this._attachTablesEvents();
                this.headerPanelOffsetRecalculate();
                this._updateScrollable();
                break;
            case "onSelectionChanged":
                this._createSelectionChangedAction();
                break;
            case "onCellClick":
                this._createCellClickAction();
                break;
            case "onCellContextMenu":
                this._attachContextMenuEvent();
                break;
            case "intervalCount":
                this._cleanWorkSpace();
                this._toggleWorkSpaceCountClass();
                this._toggleFixedScrollableClass();
                break;
            case "groupByDate":
                this._cleanWorkSpace();
                this._toggleGroupByDateClass();
                break;
            case "crossScrollingEnabled":
                this._toggleHorizontalScrollClass();
                this._dateTableScrollable.option(this._dateTableScrollableConfig());
                break;
            case "width":
                this.callBase(args);
                this._dimensionChanged();
                break;
            case "allowMultipleCellSelection":
                break;
            case "selectedCellData":
                break;
            default:
                this.callBase(args)
        }
    },
    _cleanWorkSpace: function() {
        this._cleanView();
        this._toggleGroupedClass();
        this._toggleWorkSpaceWithOddCells();
        this._renderView()
    },
    _init: function() {
        this._headerSemaphore = new ScrollSemaphore;
        this._sideBarSemaphore = new ScrollSemaphore;
        this._dataTableSemaphore = new ScrollSemaphore;
        this.callBase();
        this._initGrouping();
        this._toggleHorizontalScrollClass();
        this._toggleWorkSpaceCountClass();
        this._toggleGroupByDateClass();
        this._toggleWorkSpaceWithOddCells();
        this.$element().addClass(COMPONENT_CLASS).addClass(this._getElementClass())
    },
    _initGrouping: function() {
        this._initGroupedStrategy();
        this._toggleGroupingDirectionClass();
        this._toggleGroupByDateClass()
    },
    _initGroupedStrategy: function() {
        var strategyName = this.option("groups").length ? this.option("groupOrientation") : this._getDefaultGroupStrategy();
        var Strategy = "vertical" === strategyName ? VerticalGroupedStrategy : HorizontalGroupedStrategy;
        this._groupedStrategy = new Strategy(this)
    },
    _getDefaultGroupStrategy: function() {
        return "horizontal"
    },
    _isVerticalGroupedWorkSpace: function() {
        return !!this.option("groups").length && "vertical" === this.option("groupOrientation")
    },
    _isHorizontalGroupedWorkSpace: function() {
        return !!this.option("groups").length && "horizontal" === this.option("groupOrientation")
    },
    _toggleHorizontalScrollClass: function() {
        this.$element().toggleClass(WORKSPACE_WITH_BOTH_SCROLLS_CLASS, this.option("crossScrollingEnabled"))
    },
    _toggleGroupByDateClass: function() {
        this.$element().toggleClass(WORKSPACE_WITH_GROUP_BY_DATE_CLASS, this.isGroupedByDate())
    },
    _toggleWorkSpaceCountClass: function() {
        this.$element().toggleClass(WORKSPACE_WITH_COUNT_CLASS, this._isWorkSpaceWithCount())
    },
    _isWorkSpaceWithCount: function() {
        return this.option("intervalCount") > 1
    },
    _toggleWorkSpaceWithOddCells: function() {
        this.$element().toggleClass(WORKSPACE_WITH_ODD_CELLS_CLASS, this._isWorkspaceWithOddCells())
    },
    _isWorkspaceWithOddCells: function() {
        return .5 === this.option("hoursInterval")
    },
    _toggleGroupingDirectionClass: function() {
        this.$element().toggleClass(VERTICAL_GROUPED_WORKSPACE_CLASS, this._isVerticalGroupedWorkSpace())
    },
    _getRealGroupOrientation: function() {
        return this._isVerticalGroupedWorkSpace() ? "vertical" : "horizontal"
    },
    _getTimePanelClass: function() {
        return TIME_PANEL_CLASS
    },
    _getDateTableClass: function() {
        return DATE_TABLE_CLASS
    },
    _getDateTableRowClass: function() {
        return DATE_TABLE_ROW_CLASS
    },
    _getDateTableCellClass: function(i, j) {
        var cellClass = DATE_TABLE_CELL_CLASS + " " + HORIZONTAL_SIZES_CLASS + " " + VERTICAL_SIZES_CLASS;
        return this._needApplyLastGroupCellClass() ? this._groupedStrategy.addAdditionalGroupCellClasses(cellClass, j + 1, i, j) : cellClass
    },
    _needApplyLastGroupCellClass: function() {
        return true
    },
    _getGroupRowClass: function() {
        return GROUP_ROW_CLASS
    },
    _getGroupHeaderClass: function(i) {
        var cellClass = GROUP_HEADER_CLASS;
        return this._groupedStrategy.addAdditionalGroupCellClasses(cellClass, i + 1)
    },
    _getGroupHeaderContentClass: function() {
        return GROUP_HEADER_CONTENT_CLASS
    },
    _initWorkSpaceUnits: function() {
        this._$headerPanel = $("<table>");
        this._$thead = $("<thead>").appendTo(this._$headerPanel);
        this._$fixedContainer = $("<div>").addClass(FIXED_CONTAINER_CLASS);
        this._$allDayContainer = $("<div>").addClass(ALL_DAY_CONTAINER_CLASS);
        this._initAllDayPanelElements();
        this._createAllDayPanelElements();
        this._$timePanel = $("<table>").addClass(this._getTimePanelClass());
        this._$dateTable = $("<table>");
        this._$groupTable = $("<div>").addClass(WORKSPACE_VERTICAL_GROUP_TABLE_CLASS)
    },
    _initAllDayPanelElements: function() {
        this._allDayTitles = [];
        this._allDayTables = [];
        this._allDayPanels = []
    },
    _createAllDayPanelElements: function() {
        var groupCount = this._getGroupCount();
        if (this._isVerticalGroupedWorkSpace() && 0 !== groupCount) {
            for (var i = 0; i < groupCount; i++) {
                var $allDayTitle = $("<div>").addClass(ALL_DAY_TITLE_CLASS).text(messageLocalization.format("dxScheduler-allDay"));
                this._allDayTitles.push($allDayTitle);
                this._$allDayTable = $("<table>");
                this._allDayTables.push(this._$allDayTable);
                this._$allDayPanel = $("<div>").addClass(ALL_DAY_PANEL_CLASS).append(this._$allDayTable);
                this._allDayPanels.push(this._$allDayPanel)
            }
        } else {
            this._$allDayTitle = $("<div>").addClass(ALL_DAY_TITLE_CLASS).text(messageLocalization.format("dxScheduler-allDay")).appendTo(this.$element());
            this._$allDayTable = $("<table>");
            this._$allDayPanel = $("<div>").addClass(ALL_DAY_PANEL_CLASS).append(this._$allDayTable)
        }
    },
    _initDateTableScrollable: function() {
        var $dateTableScrollable = $("<div>").addClass(SCHEDULER_DATE_TABLE_SCROLLABLE_CLASS);
        this._dateTableScrollable = this._createComponent($dateTableScrollable, Scrollable, this._dateTableScrollableConfig())
    },
    _dateTableScrollableConfig: function() {
        var config = {
            useKeyboard: false,
            bounceEnabled: false,
            updateManually: true,
            pushBackValue: 0
        };
        if (this._needCreateCrossScrolling()) {
            config = extend(config, this._createCrossScrollingConfig())
        }
        return config
    },
    _createCrossScrollingConfig: function() {
        var _this = this;
        var config = {};
        config.direction = "both";
        config.onScroll = function(e) {
            _this._dataTableSemaphore.take();
            _this._sideBarSemaphore.isFree() && _this._sidebarScrollable && _this._sidebarScrollable.scrollTo({
                top: e.scrollOffset.top
            });
            _this._headerSemaphore.isFree() && _this._headerScrollable && _this._headerScrollable.scrollTo({
                left: e.scrollOffset.left
            });
            _this._dataTableSemaphore.release()
        };
        config.onEnd = function() {
            _this.notifyObserver("updateResizableArea", {})
        };
        return config
    },
    _createWorkSpaceElements: function() {
        if (this.option("crossScrollingEnabled")) {
            this._createWorkSpaceScrollableElements()
        } else {
            this._createWorkSpaceStaticElements()
        }
    },
    _createWorkSpaceStaticElements: function() {
        if (this._isVerticalGroupedWorkSpace()) {
            this._dateTableScrollable.$content().append(this._$allDayContainer, this._$groupTable, this._$timePanel, this._$dateTable);
            this.$element().append(this._$fixedContainer, this._$headerPanel, this._dateTableScrollable.$element())
        } else {
            this._dateTableScrollable.$content().append(this._$timePanel, this._$dateTable);
            this.$element().append(this._$fixedContainer, this._$headerPanel, this._$allDayContainer, this._$allDayPanel, this._dateTableScrollable.$element())
        }
    },
    _createWorkSpaceScrollableElements: function() {
        this.$element().append(this._$fixedContainer);
        this._createHeaderScrollable();
        this._createSidebarScrollable();
        this.$element().append(this._dateTableScrollable.$element());
        this._headerScrollable.$content().append(this._$headerPanel);
        this._dateTableScrollable.$content().append(this._$dateTable);
        if (this._isVerticalGroupedWorkSpace()) {
            this._dateTableScrollable.$content().prepend(this._$allDayContainer);
            this._sidebarScrollable.$content().append(this._$groupTable, this._$timePanel)
        } else {
            this._headerScrollable.$content().append(this._$allDayContainer, this._$allDayPanel)
        }
        this._sidebarScrollable.$content().append(this._$timePanel)
    },
    _createHeaderScrollable: function() {
        var $headerScrollable = $("<div>").addClass(SCHEDULER_HEADER_SCROLLABLE_CLASS).appendTo(this.$element());
        this._headerScrollable = this._createComponent($headerScrollable, Scrollable, this._headerScrollableConfig())
    },
    _headerScrollableConfig: function() {
        var _this2 = this;
        var config = {
            useKeyboard: false,
            showScrollbar: false,
            direction: "horizontal",
            useNative: false,
            updateManually: true,
            bounceEnabled: false,
            pushBackValue: 0,
            onScroll: function(e) {
                _this2._headerSemaphore.take();
                _this2._dataTableSemaphore.isFree() && _this2._dateTableScrollable.scrollTo({
                    left: e.scrollOffset.left
                });
                _this2._headerSemaphore.release()
            }
        };
        return config
    },
    _createSidebarScrollable: function() {
        var _this3 = this;
        var $timePanelScrollable = $("<div>").addClass(SCHEDULER_SIDEBAR_SCROLLABLE_CLASS).appendTo(this.$element());
        this._sidebarScrollable = this._createComponent($timePanelScrollable, Scrollable, {
            useKeyboard: false,
            showScrollbar: false,
            direction: "vertical",
            useNative: false,
            updateManually: true,
            bounceEnabled: false,
            pushBackValue: 0,
            onScroll: function(e) {
                _this3._sideBarSemaphore.take();
                _this3._dataTableSemaphore.isFree() && _this3._dateTableScrollable.scrollTo({
                    top: e.scrollOffset.top
                });
                _this3._sideBarSemaphore.release()
            }
        })
    },
    _visibilityChanged: function(visible) {
        if (visible && this._isVerticalGroupedWorkSpace()) {
            this._setHorizontalGroupHeaderCellsHeight()
        }
        if (visible && this._needCreateCrossScrolling()) {
            this._setTableSizes()
        }
    },
    _attachTableClasses: function() {
        this._addTableClass(this._$dateTable, this._getDateTableClass());
        if (this._isVerticalGroupedWorkSpace()) {
            var groupCount = this._getGroupCount();
            for (var i = 0; i < groupCount; i++) {
                this._addTableClass(this._allDayTables[i], ALL_DAY_TABLE_CLASS)
            }
        } else {
            this._addTableClass(this._$allDayTable, ALL_DAY_TABLE_CLASS)
        }
    },
    _attachHeaderTableClasses: function() {
        this._addTableClass(this._$headerPanel, HEADER_PANEL_CLASS)
    },
    _addTableClass: function($el, className) {
        $el && !$el.hasClass(className) && $el.addClass(className)
    },
    _setTableSizes: function() {
        this._attachTableClasses();
        var cellWidth = this.getCellWidth();
        if (cellWidth < this.getCellMinWidth()) {
            cellWidth = this.getCellMinWidth()
        }
        var minWidth = this.getWorkSpaceMinWidth();
        var $headerCells = this._$headerPanel.find("tr").last().find("th");
        var width = cellWidth * $headerCells.length;
        if (width < minWidth) {
            width = minWidth
        }
        this._$headerPanel.width(width);
        this._$dateTable.width(width);
        this._$allDayTable && this._$allDayTable.width(width);
        this._attachHeaderTableClasses();
        if (this._isVerticalGroupedWorkSpace()) {
            this._setHorizontalGroupHeaderCellsHeight()
        }
    },
    getWorkSpaceMinWidth: function() {
        return this._groupedStrategy.getWorkSpaceMinWidth()
    },
    _dimensionChanged: function() {
        if (this.option("crossScrollingEnabled")) {
            this._setTableSizes()
        }
        this.headerPanelOffsetRecalculate();
        this._cleanCellDataCache();
        this._cleanAllowedPositions()
    },
    _needCreateCrossScrolling: function() {
        return this.option("crossScrollingEnabled")
    },
    _getElementClass: noop,
    _getRowCount: noop,
    _getCellCount: noop,
    _getRowCountWithAllDayRows: function() {
        var allDayRowCount = this.option("showAllDayPanel") ? 1 : 0;
        return this._getRowCount() + allDayRowCount
    },
    _initMarkup: function() {
        this._initWorkSpaceUnits();
        this._initDateTableScrollable();
        this._createWorkSpaceElements();
        this.callBase();
        if (!this.option("crossScrollingEnabled")) {
            this._attachTableClasses();
            this._attachHeaderTableClasses()
        }
        this._toggleGroupedClass();
        this._toggleFixedScrollableClass();
        this._renderView();
        this._attachEvents();
        this._setFocusOnCellByOption(this.option("selectedCellData"))
    },
    _render: function() {
        this.callBase();
        this._renderDateTimeIndication();
        this._setIndicationUpdateInterval()
    },
    _toggleGroupedClass: function() {
        this.$element().toggleClass(GROUPED_WORKSPACE_CLASS, this._getGroupCount() > 0)
    },
    _toggleFixedScrollableClass: noop,
    _renderView: function() {
        this._setFirstViewDate();
        this._applyCellTemplates(this._renderGroupHeader());
        this._renderDateHeader();
        this._renderTimePanel();
        if (this._isVerticalGroupedWorkSpace()) {
            var groupCount = this._getGroupCount();
            for (var i = 0; i < groupCount; i++) {
                this._renderAllDayPanel(i)
            }
        }
        this._renderDateTable();
        this._renderAllDayPanel();
        this._updateGroupTableHeight();
        this._shader = new VerticalShader(this)
    },
    _updateGroupTableHeight: function() {
        if (this._isVerticalGroupedWorkSpace() && windowUtils.hasWindow()) {
            this._setHorizontalGroupHeaderCellsHeight()
        }
    },
    _renderDateTimeIndication: noop,
    _setIndicationUpdateInterval: noop,
    _refreshDateTimeIndication: noop,
    _setFocusOnCellByOption: function(data) {
        var cells = [];
        this._releaseFocusedCell();
        for (var i = 0; i < data.length; i++) {
            var groups = data[i].groups;
            var groupIndex = this.option("groups").length && groups ? this._getGroupIndexByResourceId(groups) : 0;
            var allDay = !!data[i].allDay;
            var coordinates = this.getCoordinatesByDate(data[i].startDate, groupIndex, allDay);
            var $cell = this._getCellByCoordinates(coordinates, groupIndex);
            if (isDefined($cell)) {
                this._toggleFocusClass(true, $cell);
                cells.push($cell.get(0))
            }
        }
        this._focusedCells = cells
    },
    _getGroupIndexByResourceId: function(id) {
        var groups = this.option("groups");
        var resourceTree = this.invoke("createResourcesTree", groups);
        if (!resourceTree.length) {
            return 0
        }
        return this._getGroupIndexRecursively(resourceTree, id)
    },
    _getGroupIndexRecursively: function(resourceTree, id) {
        var _this4 = this;
        var currentKey = resourceTree[0].name;
        var currentValue = id[currentKey];
        return resourceTree.reduce(function(prevIndex, _ref) {
            var leafIndex = _ref.leafIndex,
                value = _ref.value,
                children = _ref.children;
            var areValuesEqual = currentValue === value;
            if (areValuesEqual && void 0 !== leafIndex) {
                return leafIndex
            }
            if (areValuesEqual) {
                return _this4._getGroupIndexRecursively(children, id)
            }
            return prevIndex
        }, 0)
    },
    _setFirstViewDate: function() {
        var firstDayOfWeek = isDefined(this._firstDayOfWeek()) ? this._firstDayOfWeek() : dateLocalization.firstDayOfWeekIndex();
        this._firstViewDate = dateUtils.getFirstWeekDate(this._getViewStartByOptions(), firstDayOfWeek);
        this._setStartDayHour(this._firstViewDate)
    },
    _getViewStartByOptions: function() {
        if (!this.option("startDate")) {
            return this.option("currentDate")
        } else {
            var startDate = dateUtils.trimTime(this._getStartViewDate());
            var currentDate = this.option("currentDate");
            var diff = startDate.getTime() <= currentDate.getTime() ? 1 : -1;
            var endDate = new Date(startDate.getTime() + this._getIntervalDuration() * diff);
            while (!this._dateInRange(currentDate, startDate, endDate, diff)) {
                startDate = endDate;
                endDate = new Date(startDate.getTime() + this._getIntervalDuration() * diff)
            }
            return diff > 0 ? startDate : endDate
        }
    },
    _getHeaderDate: function() {
        return this.getStartViewDate()
    },
    _getStartViewDate: function() {
        return this.option("startDate")
    },
    _dateInRange: function(date, startDate, endDate, diff) {
        return diff > 0 ? dateUtils.dateInRange(date, startDate, new Date(endDate.getTime() - 1)) : dateUtils.dateInRange(date, endDate, startDate, "date")
    },
    _getIntervalDuration: function() {
        return toMs("day") * this.option("intervalCount")
    },
    _setStartDayHour: function(date) {
        var startDayHour = this.option("startDayHour");
        if (isDefined(startDayHour)) {
            date.setHours(startDayHour, startDayHour % 1 * 60, 0, 0)
        }
    },
    _firstDayOfWeek: function() {
        return this.option("firstDayOfWeek")
    },
    _attachEvents: function() {
        this._createSelectionChangedAction();
        this._attachClickEvent();
        this._attachContextMenuEvent()
    },
    _attachClickEvent: function() {
        var that = this;
        var pointerDownAction = this._createAction(function(e) {
            that._pointerDownHandler(e.event)
        });
        this._createCellClickAction();
        var cellSelector = "." + DATE_TABLE_CELL_CLASS + ",." + ALL_DAY_TABLE_CELL_CLASS;
        var $element = this.$element();
        eventsEngine.off($element, SCHEDULER_WORKSPACE_DXPOINTERDOWN_EVENT_NAME);
        eventsEngine.off($element, SCHEDULER_CELL_DXCLICK_EVENT_NAME);
        eventsEngine.on($element, SCHEDULER_WORKSPACE_DXPOINTERDOWN_EVENT_NAME, function(e) {
            if (eventUtils.isMouseEvent(e) && e.which > 1) {
                e.preventDefault();
                return
            }
            pointerDownAction({
                event: e
            })
        });
        eventsEngine.on($element, SCHEDULER_CELL_DXCLICK_EVENT_NAME, cellSelector, function(e) {
            var $cell = $(e.target);
            that._cellClickAction({
                event: e,
                cellElement: getPublicElement($cell),
                cellData: that.getCellData($cell)
            })
        })
    },
    _createCellClickAction: function() {
        var that = this;
        this._cellClickAction = this._createActionByOption("onCellClick", {
            afterExecute: function(e) {
                that._moveToClosestNonStub(e.args[0].event)
            }
        })
    },
    _createSelectionChangedAction: function() {
        this._selectionChangedAction = this._createActionByOption("onSelectionChanged")
    },
    _moveToClosestNonStub: function(e) {
        var $target = $(e.target);
        if (this._showPopup && this._hasFocusClass($target)) {
            delete this._showPopup;
            this._showAddAppointmentPopup($target)
        }
    },
    _pointerDownHandler: function(e) {
        var $target = $(e.target);
        if (!$target.hasClass(DATE_TABLE_CELL_CLASS) && !$target.hasClass(ALL_DAY_TABLE_CELL_CLASS)) {
            this._isCellClick = false;
            return
        }
        this._isCellClick = true;
        if ($target.hasClass(DATE_TABLE_FOCUSED_CELL_CLASS)) {
            this._showPopup = true
        } else {
            this._setFocusedCell($target)
        }
    },
    _showAddAppointmentPopup: function($cell) {
        var firstCellData = this.getCellData($cell.first());
        var lastCellData = this.getCellData($cell.last());
        var args = {
            startDate: this.invoke("convertDateByTimezoneBack", firstCellData.startDate) || firstCellData.startDate,
            endDate: this.invoke("convertDateByTimezoneBack", lastCellData.endDate) || lastCellData.endDate
        };
        if (isDefined(lastCellData.allDay)) {
            args.allDay = lastCellData.allDay
        }
        extend(args, lastCellData.groups);
        this.notifyObserver("showAddAppointmentPopup", args)
    },
    _attachContextMenuEvent: function() {
        this._createContextMenuAction();
        var cellSelector = "." + DATE_TABLE_CELL_CLASS + ",." + ALL_DAY_TABLE_CELL_CLASS;
        var $element = this.$element();
        var eventName = eventUtils.addNamespace(contextMenuEvent.name, this.NAME);
        eventsEngine.off($element, eventName, cellSelector);
        eventsEngine.on($element, eventName, cellSelector, this._contextMenuHandler.bind(this))
    },
    _contextMenuHandler: function(e) {
        var $cell = $(e.target);
        this._contextMenuAction({
            event: e,
            cellElement: getPublicElement($cell),
            cellData: this.getCellData($cell)
        });
        this._contextMenuHandled = true
    },
    _createContextMenuAction: function() {
        this._contextMenuAction = this._createActionByOption("onCellContextMenu")
    },
    _getGroupHeaderContainer: function() {
        if (this._isVerticalGroupedWorkSpace()) {
            return this._$groupTable
        }
        return this._$thead
    },
    _getDateHeaderContainer: function() {
        return this._$thead
    },
    _renderGroupHeader: function() {
        var $container = this._getGroupHeaderContainer();
        var groupCount = this._getGroupCount();
        var cellTemplates = [];
        if (groupCount) {
            var groupRows = this._makeGroupRows(this.option("groups"), this.option("groupByDate"));
            this._attachGroupCountAttr(groupCount, groupRows);
            $container.append(groupRows.elements);
            cellTemplates = groupRows.cellTemplates
        } else {
            this._detachGroupCountAttr()
        }
        return cellTemplates
    },
    _applyCellTemplates: function(templates) {
        templates.forEach(function(template) {
            template()
        })
    },
    _detachGroupCountAttr: function() {
        var groupedAttr = this._groupedStrategy.getGroupCountAttr();
        this.$element().removeAttr(groupedAttr.attr)
    },
    _attachGroupCountAttr: function(groupRowCount, groupRows) {
        var groupedAttr = this._groupedStrategy.getGroupCountAttr(groupRowCount, groupRows);
        this.$element().attr(groupedAttr.attr, groupedAttr.count)
    },
    headerPanelOffsetRecalculate: function() {
        if (!this.option("resourceCellTemplate") && !this.option("dateCellTemplate")) {
            return
        }
        var headerPanelHeight = this.getHeaderPanelHeight();
        var headerHeight = this.invoke("getHeaderHeight");
        var allDayPanelHeight = this.supportAllDayRow() && this.option("showAllDayPanel") ? this._groupedStrategy.getAllDayTableHeight() : 0;
        headerPanelHeight && this._headerScrollable && this._headerScrollable.$element().height(headerPanelHeight + allDayPanelHeight);
        headerPanelHeight && this._dateTableScrollable.$element().css({
            paddingBottom: allDayPanelHeight + headerPanelHeight + "px",
            marginBottom: -1 * (parseInt(headerPanelHeight, 10) + allDayPanelHeight) + "px"
        });
        headerPanelHeight && this._sidebarScrollable && this._sidebarScrollable.$element().css({
            paddingBottom: allDayPanelHeight + headerPanelHeight + "px",
            marginBottom: -1 * (parseInt(headerPanelHeight, 10) + allDayPanelHeight) + "px"
        });
        this._$allDayTitle && this._$allDayTitle.css("top", headerHeight + headerPanelHeight + "px")
    },
    _makeGroupRows: function(groups, groupByDate) {
        var tableCreatorStrategy = this._isVerticalGroupedWorkSpace() ? tableCreator.VERTICAL : tableCreator.HORIZONTAL;
        return tableCreator.makeGroupedTable(tableCreatorStrategy, groups, {
            groupHeaderRowClass: this._getGroupRowClass(),
            groupRowClass: this._getGroupRowClass(),
            groupHeaderClass: this._getGroupHeaderClass.bind(this),
            groupHeaderContentClass: this._getGroupHeaderContentClass()
        }, this._getCellCount() || 1, this.option("resourceCellTemplate"), this._getGroupCount(), groupByDate)
    },
    _getDateHeaderTemplate: function() {
        return this.option("dateCellTemplate")
    },
    _renderDateHeader: function() {
        var $container = this._getDateHeaderContainer();
        var $headerRow = $("<tr>").addClass(HEADER_ROW_CLASS);
        var count = this._getCellCount();
        var cellTemplate = this._getDateHeaderTemplate();
        var repeatCount = this._calculateHeaderCellRepeatCount();
        var templateCallbacks = [];
        var groupByDate = this.isGroupedByDate();
        var colspan = groupByDate ? this._getGroupCount() : 1;
        var i;
        var j;
        if (!groupByDate) {
            for (j = 0; j < repeatCount; j++) {
                for (i = 0; i < count; i++) {
                    this._renderDateHeaderTemplate($headerRow, i, j * repeatCount + i, cellTemplate, templateCallbacks)
                }
            }
            $container.append($headerRow)
        } else {
            for (i = 0; i < count; i++) {
                var $cell = this._renderDateHeaderTemplate($headerRow, i, i * repeatCount, cellTemplate, templateCallbacks);
                $cell.attr("colSpan", colspan)
            }
            $container.prepend($headerRow)
        }
        this._applyCellTemplates(templateCallbacks);
        return $headerRow
    },
    _renderDateHeaderTemplate: function($container, i, calculatedIndex, cellTemplate, templateCallbacks) {
        var text = this._getHeaderText(i);
        var $cell = $("<th>").addClass(this._getHeaderPanelCellClass(i)).attr("title", text);
        if (cellTemplate && cellTemplate.render) {
            templateCallbacks.push(cellTemplate.render.bind(cellTemplate, {
                model: {
                    text: text,
                    date: this._getDateByIndex(i)
                },
                index: calculatedIndex,
                container: getPublicElement($cell)
            }))
        } else {
            $cell.text(text)
        }
        $container.append($cell);
        return $cell
    },
    _getHeaderPanelCellClass: function(i) {
        var cellClass = HEADER_PANEL_CELL_CLASS + " " + HORIZONTAL_SIZES_CLASS;
        return this._groupedStrategy.addAdditionalGroupCellClasses(cellClass, i + 1)
    },
    _calculateHeaderCellRepeatCount: function() {
        return this._groupedStrategy.calculateHeaderCellRepeatCount()
    },
    _renderAllDayPanel: function(index) {
        var cellCount = this._getCellCount();
        if (!this._isVerticalGroupedWorkSpace()) {
            cellCount *= this._getGroupCount() || 1
        }
        var cellTemplates = this._renderTableBody({
            container: this._allDayPanels.length ? getPublicElement(this._allDayTables[index]) : getPublicElement(this._$allDayTable),
            rowCount: 1,
            cellCount: cellCount,
            cellClass: this._getAllDayPanelCellClass.bind(this),
            rowClass: ALL_DAY_TABLE_ROW_CLASS,
            cellTemplate: this.option("dataCellTemplate"),
            getCellData: this._getAllDayCellData.bind(this),
            groupIndex: index
        }, true);
        this._toggleAllDayVisibility();
        this._applyCellTemplates(cellTemplates)
    },
    _getAllDayPanelCellClass: function(i, j) {
        var cellClass = ALL_DAY_TABLE_CELL_CLASS + " " + HORIZONTAL_SIZES_CLASS;
        return this._groupedStrategy.addAdditionalGroupCellClasses(cellClass, j + 1)
    },
    _getAllDayCellData: function(cell, rowIndex, cellIndex, groupIndex) {
        var startDate = this._getDateByCellIndexes(rowIndex, cellIndex);
        startDate = dateUtils.trimTime(startDate);
        var data = {
            startDate: startDate,
            endDate: new Date(startDate.getTime() + DAY_MS),
            allDay: true
        };
        var groups = this._getCellGroups(groupIndex || this._getGroupIndex(rowIndex, cellIndex));
        if (groups.length) {
            data.groups = {}
        }
        for (var i = 0; i < groups.length; i++) {
            data.groups[groups[i].name] = groups[i].id
        }
        return {
            key: CELL_DATA,
            value: data
        }
    },
    _toggleAllDayVisibility: function() {
        var showAllDayPanel = this.option("showAllDayPanel");
        this._$allDayPanel.toggle(showAllDayPanel);
        this._$allDayTitle && this._$allDayTitle.toggleClass(ALL_DAY_TITLE_HIDDEN_CLASS, !showAllDayPanel);
        this.$element().toggleClass(WORKSPACE_WITH_ALL_DAY_CLASS, showAllDayPanel);
        this._changeAllDayVisibility();
        this._updateScrollable()
    },
    _changeAllDayVisibility: function() {
        this.$element().toggleClass(WORKSPACE_WITH_COLLAPSED_ALL_DAY_CLASS, !this.option("allDayExpanded") && this.option("showAllDayPanel"))
    },
    _updateScrollable: function() {
        this._dateTableScrollable.update();
        this._headerScrollable && this._headerScrollable.update();
        this._sidebarScrollable && this._sidebarScrollable.update()
    },
    _renderTimePanel: function() {
        var _this5 = this;
        var repeatCount = this._groupedStrategy.calculateTimeCellRepeatCount();
        var startViewDate = this._getDateWithSkippedDST();
        var _getTimeText = function(i) {
            var index = i % _this5._getRowCount();
            if (index % 2 === 0) {
                return dateLocalization.format(_this5._getTimeCellDateCore(startViewDate, i), "shorttime")
            }
            return ""
        };
        this._renderTableBody({
            container: getPublicElement(this._$timePanel),
            rowCount: this._getTimePanelRowCount() * repeatCount,
            cellCount: 1,
            cellClass: this._getTimeCellClass.bind(this),
            rowClass: TIME_PANEL_ROW_CLASS,
            cellTemplate: this.option("timeCellTemplate"),
            getCellText: _getTimeText.bind(this),
            getCellDate: this._getTimeCellDate.bind(this),
            groupCount: this._getGroupCount(),
            allDayElements: this._insertAllDayRowsIntoDateTable() ? this._allDayTitles : void 0
        })
    },
    _getDateWithSkippedDST: function() {
        var result = new Date(this.getStartViewDate());
        if (timeZoneUtils.isTimezoneChangeInDate(result)) {
            result = new Date(result.setDate(result.getDate() + 1))
        }
        return result
    },
    _getTimePanelRowCount: function() {
        return this._getCellCountInDay()
    },
    _getCellCountInDay: function(skipRound) {
        var result = this._calculateDayDuration() / this.option("hoursInterval");
        return skipRound ? result : Math.ceil(result)
    },
    _calculateDayDuration: function() {
        return this.option("endDayHour") - this.option("startDayHour")
    },
    _getTimeCellClass: function(i) {
        var cellClass = TIME_PANEL_CELL_CLASS + " " + VERTICAL_SIZES_CLASS;
        return this._groupedStrategy.addAdditionalGroupCellClasses(cellClass, i, i)
    },
    _getTimeCellDateAdjustedDST: function(i) {
        var startViewDate = new Date(this.getStartViewDate());
        if (timeZoneUtils.isTimezoneChangeInDate(startViewDate)) {
            startViewDate = new Date(startViewDate.setDate(startViewDate.getDate() + 1))
        }
        return this._getTimeCellDateCore(startViewDate, i)
    },
    _getTimeCellDate: function(i) {
        return this._getTimeCellDateCore(this.getStartViewDate(), i)
    },
    _getTimeCellDateCore: function(startViewDate, i) {
        var result = new Date(startViewDate);
        var timeCellDuration = Math.round(this.getCellDuration());
        var cellCountInDay = this._getCellCountInDay(true);
        result.setMilliseconds(result.getMilliseconds() + timeCellDuration * (i % cellCountInDay));
        return result
    },
    _renderDateTable: function() {
        var groupCount = this._getGroupCount();
        this._renderTableBody({
            container: getPublicElement(this._$dateTable),
            rowCount: this._getTotalRowCount(groupCount),
            cellCount: this._getTotalCellCount(groupCount),
            cellClass: this._getDateTableCellClass.bind(this),
            rowClass: this._getDateTableRowClass(),
            cellTemplate: this.option("dataCellTemplate"),
            getCellData: this._getCellData.bind(this),
            allDayElements: this._insertAllDayRowsIntoDateTable() ? this._allDayPanels : void 0,
            groupCount: groupCount,
            groupByDate: this.option("groupByDate")
        })
    },
    _insertAllDayRowsIntoDateTable: function() {
        return this._groupedStrategy.insertAllDayRowsIntoDateTable()
    },
    _getTotalCellCount: function(groupCount) {
        return this._groupedStrategy.getTotalCellCount(groupCount)
    },
    _getTotalRowCount: function() {
        return this._groupedStrategy.getTotalRowCount()
    },
    _getCellData: function(cell, rowIndex, cellIndex) {
        var data = this._prepareCellData(rowIndex, cellIndex, cell);
        return {
            key: CELL_DATA,
            value: data
        }
    },
    _prepareCellData: function(rowIndex, cellIndex) {
        var startDate = this._getDateByCellIndexes(rowIndex, cellIndex);
        var endDate = this.calculateEndDate(startDate);
        var data = {
            startDate: startDate,
            endDate: endDate,
            allDay: this._getTableAllDay()
        };
        var groups = this._getCellGroups(this._getGroupIndex(rowIndex, cellIndex));
        if (groups.length) {
            data.groups = {}
        }
        for (var i = 0; i < groups.length; i++) {
            data.groups[groups[i].name] = groups[i].id
        }
        return data
    },
    _getGroupIndex: function(rowIndex, cellIndex) {
        return this._groupedStrategy.getGroupIndex(rowIndex, cellIndex)
    },
    _getTableAllDay: function() {
        return false
    },
    calculateEndDate: function(startDate) {
        var result = new Date(startDate);
        result.setMilliseconds(result.getMilliseconds() + Math.round(this._getInterval()));
        return result
    },
    _getGroupCount: function() {
        var groups = this.option("groups");
        var result = 0;
        for (var i = 0, len = groups.length; i < len; i++) {
            if (!i) {
                result = groups[i].items.length
            } else {
                result *= groups[i].items.length
            }
        }
        return result
    },
    _getPathToLeaf: function(leafIndex) {
        var tree = this.invoke("createResourcesTree", this.option("groups"));

        function findLeafByIndex(data, index) {
            for (var i = 0; i < data.length; i++) {
                if (data[i].leafIndex === index) {
                    return data[i]
                } else {
                    var _leaf = findLeafByIndex(data[i].children, index);
                    if (_leaf) {
                        return _leaf
                    }
                }
            }
        }

        function makeBranch(leaf, result) {
            result = result || [];
            result.push(leaf.value);
            if (leaf.parent) {
                makeBranch(leaf.parent, result)
            }
            return result
        }
        var leaf = findLeafByIndex(tree, leafIndex);
        return makeBranch(leaf).reverse()
    },
    _getCellGroups: function(groupIndex) {
        var result = [];
        if (this._getGroupCount()) {
            var groups = this.option("groups");
            if (groupIndex < 0) {
                return
            }
            var path = this._getPathToLeaf(groupIndex);
            for (var i = 0; i < groups.length; i++) {
                result.push({
                    name: groups[i].name,
                    id: path[i]
                })
            }
        }
        return result
    },
    _attachTablesEvents: function() {
        var _this6 = this;
        var that = this;
        var isPointerDown = false;
        var cellHeight;
        var cellWidth;
        var $element = this.$element();
        eventsEngine.off($element, SCHEDULER_CELL_DXDRAGENTER_EVENT_NAME);
        eventsEngine.off($element, SCHEDULER_CELL_DXDRAGLEAVE_EVENT_NAME);
        eventsEngine.off($element, SCHEDULER_CELL_DXDROP_EVENT_NAME);
        eventsEngine.off($element, SCHEDULER_CELL_DXPOINTERMOVE_EVENT_NAME);
        eventsEngine.off($element, SCHEDULER_CELL_DXPOINTERDOWN_EVENT_NAME);
        eventsEngine.on($element, SCHEDULER_CELL_DXDRAGENTER_EVENT_NAME, SCHEDULER_DRAG_AND_DROP_SELECTOR, {
            itemSizeFunc: function($element) {
                if (!cellHeight) {
                    cellHeight = getBoundingRect($element.get(0)).height
                }
                if (!cellWidth) {
                    cellWidth = getBoundingRect($element.get(0)).width
                }
                return {
                    width: cellWidth,
                    height: cellHeight
                }
            },
            checkDropTarget: function(target, event) {
                return !_this6._isOutsideScrollable(target, event)
            }
        }, function(e) {
            if (that._$currentTableTarget) {
                that.removeDroppableCellClass(that._$currentTableTarget)
            }
            that._$currentTableTarget = $(e.target);
            that._$currentTableTarget.addClass(DATE_TABLE_DROPPABLE_CELL_CLASS)
        });
        eventsEngine.on($element, SCHEDULER_CELL_DXDRAGLEAVE_EVENT_NAME, function(e) {
            if (!$element.find($(e.draggingElement)).length) {
                that.removeDroppableCellClass()
            }
        });
        eventsEngine.on($element, SCHEDULER_CELL_DXDROP_EVENT_NAME, SCHEDULER_DRAG_AND_DROP_SELECTOR, function(e) {
            that.removeDroppableCellClass($(e.target));
            cellHeight = 0;
            cellWidth = 0
        });
        eventsEngine.on($element, SCHEDULER_CELL_DXPOINTERDOWN_EVENT_NAME, SCHEDULER_DRAG_AND_DROP_SELECTOR, function(e) {
            if (eventUtils.isMouseEvent(e) && 1 === e.which) {
                isPointerDown = true;
                that.$element().addClass(WORKSPACE_WITH_MOUSE_SELECTION_CLASS);
                eventsEngine.off(domAdapter.getDocument(), SCHEDULER_CELL_DXPOINTERUP_EVENT_NAME);
                eventsEngine.on(domAdapter.getDocument(), SCHEDULER_CELL_DXPOINTERUP_EVENT_NAME, function() {
                    isPointerDown = false;
                    that.$element().removeClass(WORKSPACE_WITH_MOUSE_SELECTION_CLASS)
                })
            }
        });
        eventsEngine.on($element, SCHEDULER_CELL_DXPOINTERMOVE_EVENT_NAME, SCHEDULER_DRAG_AND_DROP_SELECTOR, function(e) {
            if (isPointerDown && that._dateTableScrollable && !that._dateTableScrollable.option("scrollByContent")) {
                e.preventDefault();
                e.stopPropagation();
                that._moveToCell($(e.target), true)
            }
        })
    },
    _getDateTables: function() {
        return this._$dateTable.add(this._$allDayTable)
    },
    _getDateTable: function() {
        return this._$dateTable
    },
    _getAllDayTable: function() {
        return this._$allDayTable
    },
    _getInterval: function() {
        if (void 0 === this._interval) {
            this._interval = this.option("hoursInterval") * HOUR_MS
        }
        return this._interval
    },
    _getHeaderText: function(headerIndex) {
        return dateLocalization.format(this._getDateByIndex(headerIndex), this._getFormat())
    },
    _getDateByIndex: abstract,
    _getFormat: abstract,
    _calculateCellIndex: function(rowIndex, cellIndex) {
        return this._groupedStrategy.calculateCellIndex(rowIndex, cellIndex)
    },
    _renderTableBody: function(options, delayCellTemplateRendering) {
        var result = [];
        if (!delayCellTemplateRendering) {
            this._applyCellTemplates(tableCreator.makeTable(options))
        } else {
            result = tableCreator.makeTable(options)
        }
        return result
    },
    _removeAllDayElements: function() {
        this._$allDayTable && this._$allDayTable.remove();
        this._$allDayTitle && this._$allDayTitle.remove()
    },
    _cleanView: function() {
        this._cleanCellDataCache();
        this._cleanAllowedPositions();
        this._$thead.empty();
        this._$dateTable.empty();
        this._shader && this._shader.clean();
        this._$timePanel.empty();
        this._$allDayTable && this._$allDayTable.empty();
        this._$groupTable.empty();
        delete this._hiddenInterval;
        delete this._interval
    },
    _clean: function() {
        eventsEngine.off(domAdapter.getDocument(), SCHEDULER_CELL_DXPOINTERUP_EVENT_NAME);
        this.callBase()
    },
    getWorkArea: function() {
        return this._dateTableScrollable.$content()
    },
    getScrollable: function() {
        return this._dateTableScrollable
    },
    getScrollableScrollTop: function() {
        return this._dateTableScrollable.scrollTop()
    },
    getGroupedScrollableScrollTop: function(allDay) {
        return this._groupedStrategy.getScrollableScrollTop(allDay)
    },
    getScrollableScrollLeft: function() {
        return this._dateTableScrollable.scrollLeft()
    },
    getScrollableOuterWidth: function() {
        return this._dateTableScrollable.scrollWidth()
    },
    getScrollableContainer: function() {
        return this._dateTableScrollable._container()
    },
    getHeaderPanelHeight: function() {
        return this._$headerPanel && this._$headerPanel.outerHeight(true)
    },
    getTimePanelWidth: function() {
        return this._$timePanel && getBoundingRect(this._$timePanel.get(0)).width
    },
    getGroupTableWidth: function() {
        return this._$groupTable ? this._$groupTable.outerWidth() : 0
    },
    getWorkSpaceLeftOffset: function() {
        return this._groupedStrategy.getLeftOffset()
    },
    getGroupedStrategy: function() {
        return this._groupedStrategy
    },
    _getCellCoordinatesByIndex: function(index) {
        var cellIndex = Math.floor(index / this._getRowCount());
        var rowIndex = index - this._getRowCount() * cellIndex;
        return {
            cellIndex: cellIndex,
            rowIndex: rowIndex
        }
    },
    _getDateByCellIndexes: function(rowIndex, cellIndex, patchedIndexes) {
        cellIndex = !patchedIndexes ? this._patchCellIndex(cellIndex) : cellIndex;
        var firstViewDate = this.getStartViewDate();
        var currentDate = new Date(firstViewDate.getTime() + this._getMillisecondsOffset(rowIndex, cellIndex) + this._getOffsetByCount(cellIndex));
        currentDate.setTime(currentDate.getTime() + dateUtils.getTimezonesDifference(firstViewDate, currentDate));
        return currentDate
    },
    _patchCellIndex: function(cellIndex) {
        if (this.isGroupedByDate()) {
            cellIndex = Math.floor(cellIndex / this._getGroupCount())
        }
        return cellIndex
    },
    _getOffsetByCount: function() {
        return 0
    },
    _getMillisecondsOffset: function(rowIndex, cellIndex) {
        return this._getInterval() * this._calculateCellIndex(rowIndex, cellIndex) + this._calculateHiddenInterval(rowIndex, cellIndex)
    },
    _calculateHiddenInterval: function(rowIndex, cellIndex) {
        var dayCount = cellIndex % this._getCellCount();
        return dayCount * this._getHiddenInterval()
    },
    _getHiddenInterval: function() {
        if (void 0 === this._hiddenInterval) {
            this._hiddenInterval = DAY_MS - this.getVisibleDayDuration()
        }
        return this._hiddenInterval
    },
    _getIntervalBetween: function(currentDate, allDay) {
        var firstViewDate = this.getStartViewDate();
        var startDayTime = this.option("startDayHour") * HOUR_MS;
        var timeZoneOffset = dateUtils.getTimezonesDifference(firstViewDate, currentDate);
        var fullInterval = currentDate.getTime() - firstViewDate.getTime() - timeZoneOffset;
        var days = this._getDaysOfInterval(fullInterval, startDayTime);
        var weekendsCount = this._getWeekendsCount(days);
        var result = (days - weekendsCount) * DAY_MS;
        if (!allDay) {
            result = fullInterval - days * this._getHiddenInterval() - weekendsCount * this.getVisibleDayDuration()
        }
        return result
    },
    _getWeekendsCount: function() {
        return 0
    },
    _getDaysOfInterval: function(fullInterval, startDayTime) {
        return Math.floor((fullInterval + startDayTime) / DAY_MS)
    },
    _getGroupIndexes: function(appointmentResources) {
        var result = [];
        if (appointmentResources && this.option("groups").length) {
            var tree = this.invoke("createResourcesTree", this.option("groups"));
            result = this.invoke("getResourceTreeLeaves", tree, appointmentResources)
        }
        return result
    },
    _updateIndex: function(index) {
        return index * this._getRowCount()
    },
    _getDroppableCell: function() {
        return this._getDateTables().find("." + DATE_TABLE_DROPPABLE_CELL_CLASS)
    },
    _getWorkSpaceWidth: function() {
        if (this._needCreateCrossScrolling()) {
            return getBoundingRect(this._$dateTable.get(0)).width
        }
        return getBoundingRect(this.$element().get(0)).width - this.getTimePanelWidth()
    },
    _getCellPositionByIndex: function(index, groupIndex, inAllDayRow) {
        var cellCoordinates = this._getCellCoordinatesByIndex(index);
        var $cell = this._getCellByCoordinates(cellCoordinates, groupIndex, inAllDayRow);
        var result = this._getCellPosition($cell);
        this.setCellDataCache(cellCoordinates, groupIndex, $cell);
        if (result) {
            result.rowIndex = cellCoordinates.rowIndex;
            result.cellIndex = cellCoordinates.cellIndex
        }
        return result
    },
    _getCellPosition: function($cell) {
        var position = $cell.position();
        if (this.option("rtlEnabled")) {
            position.left += getBoundingRect($cell.get(0)).width
        }
        return position
    },
    _getCellByCoordinates: function(cellCoordinates, groupIndex, inAllDayRow) {
        var indexes = this._groupedStrategy.prepareCellIndexes(cellCoordinates, groupIndex, inAllDayRow);
        return this._$dateTable.find("tr").eq(indexes.rowIndex).find("td").eq(indexes.cellIndex)
    },
    _getCells: function(allDay, direction) {
        var cellClass = allDay ? ALL_DAY_TABLE_CELL_CLASS : DATE_TABLE_CELL_CLASS;
        if ("vertical" === direction) {
            var result = [];
            for (var i = 1;; i++) {
                var cells = this.$element().find("tr .".concat(cellClass, ":nth-child(").concat(i, ")"));
                if (!cells.length) {
                    break
                }
                result = result.concat(cells.toArray())
            }
            return $(result)
        } else {
            return this.$element().find("." + cellClass)
        }
    },
    _setHorizontalGroupHeaderCellsHeight: function() {
        var height = getBoundingRect(this._$dateTable.get(0)).height;
        this._$groupTable.outerHeight(height)
    },
    _getDateTableBorder: function() {
        return DATE_TABLE_CELL_BORDER
    },
    _getDateTableBorderOffset: function() {
        return 2 * this._getDateTableBorder()
    },
    _getGroupHeaderCellsContent: function() {
        return this.$element().find("." + GROUP_HEADER_CONTENT_CLASS)
    },
    _getGroupHeaderCells: function() {
        return this.$element().find("." + GROUP_HEADER_CLASS)
    },
    _getScrollCoordinates: function(hours, minutes, date) {
        var currentDate = date || new Date(this.option("currentDate"));
        var startDayHour = this.option("startDayHour");
        var endDayHour = this.option("endDayHour");
        if (hours < startDayHour) {
            hours = startDayHour
        }
        if (hours >= endDayHour) {
            hours = endDayHour - 1
        }
        currentDate.setHours(hours, minutes, 0, 0);
        return this.getCoordinatesByDate(currentDate)
    },
    _isOutsideScrollable: function(target, event) {
        var $dateTableScrollableElement = this._dateTableScrollable.$element();
        var scrollableSize = getBoundingRect($dateTableScrollableElement.get(0));
        var window = windowUtils.getWindow();
        var isTargetInAllDayPanel = !$(target).closest($dateTableScrollableElement).length;
        var isOutsideHorizontalScrollable = event.pageX < scrollableSize.left || event.pageX > scrollableSize.left + scrollableSize.width + (window.scrollX || 0);
        var isOutsideVerticalScrollable = event.pageY < scrollableSize.top || event.pageY > scrollableSize.top + scrollableSize.height + (window.scrollY || 0);
        if (isTargetInAllDayPanel && !isOutsideHorizontalScrollable) {
            return false
        }
        return isOutsideVerticalScrollable || isOutsideHorizontalScrollable
    },
    setCellDataCache: function(cellCoordinates, groupIndex, $cell) {
        var cache = this.getCellDataCache();
        var data = this.getCellData($cell);
        var key = JSON.stringify({
            rowIndex: cellCoordinates.rowIndex,
            cellIndex: cellCoordinates.cellIndex,
            groupIndex: groupIndex
        });
        cache[key] = data
    },
    setCellDataCacheAlias: function(appointment, geometry) {
        var key = JSON.stringify({
            rowIndex: appointment.rowIndex,
            cellIndex: appointment.cellIndex,
            groupIndex: appointment.groupIndex
        });
        var aliasKey = JSON.stringify({
            top: geometry.top,
            left: geometry.left
        });
        var cache = this.getCellDataCache();
        if (cache[key]) {
            cache[aliasKey] = cache[key]
        }
    },
    getCellDataCache: function(key) {
        if (!this._cache) {
            this._cache = {}
        }
        return key ? this._cache[key] : this._cache
    },
    _cleanCellDataCache: function() {
        delete this._cache
    },
    _cleanAllowedPositions: function() {
        delete this._maxAllowedVerticalPosition;
        delete this._maxAllowedPosition
    },
    supportAllDayRow: function() {
        return true
    },
    keepOriginalHours: function() {
        return false
    },
    getFocusedCellData: function() {
        var $focusedCells = this._getAllFocusedCells();
        var result = [];
        if ($focusedCells.length > 1) {
            result = this._getMultipleCellsData($focusedCells)
        } else {
            var data = this.getCellData($focusedCells);
            data && result.push(data)
        }
        return result
    },
    _getMultipleCellsData: function($cells) {
        var data = [];
        for (var i = 0; i < $cells.length; i++) {
            data.push(dataUtils.data($cells[i], CELL_DATA))
        }
        return data
    },
    getCellData: function($cell) {
        var data = $cell[0] ? dataUtils.data($cell[0], CELL_DATA) : void 0;
        return extend(true, {}, data)
    },
    _getHorizontalMax: function(groupIndex) {
        groupIndex = this.isGroupedByDate() ? this._getGroupCount() - 1 : groupIndex;
        return this._groupedStrategy.getHorizontalMax(groupIndex)
    },
    getCoordinatesByDate: function(date, groupIndex, inAllDayRow) {
        groupIndex = groupIndex || 0;
        var index = this.getCellIndexByDate(date, inAllDayRow);
        var position = this._getCellPositionByIndex(index, groupIndex, inAllDayRow);
        var shift = this.getPositionShift(inAllDayRow ? 0 : this.getTimeShift(date), inAllDayRow);
        var horizontalHMax = this._getHorizontalMax(groupIndex, date);
        if (!position) {
            throw errors.Error("E1039")
        }
        var coordinates = {
            cellPosition: position.left + shift.cellPosition,
            top: position.top + shift.top,
            left: position.left + shift.left,
            rowIndex: position.rowIndex,
            cellIndex: position.cellIndex,
            hMax: horizontalHMax,
            vMax: this.getVerticalMax(groupIndex),
            groupIndex: groupIndex
        };
        return coordinates
    },
    getVerticalMax: function(groupIndex) {
        return this._groupedStrategy.getVerticalMax(groupIndex)
    },
    _getOffsetByAllDayPanel: function(groupIndex) {
        return this._groupedStrategy._getOffsetByAllDayPanel(groupIndex)
    },
    _getGroupTop: function(groupIndex) {
        return this._groupedStrategy._getGroupTop(groupIndex)
    },
    isGroupedByDate: function() {
        return this.option("groupByDate") && this._isHorizontalGroupedWorkSpace() && this._getGroupCount() > 0
    },
    getCellIndexByDate: function(date, inAllDayRow) {
        var timeInterval = inAllDayRow ? 864e5 : this._getInterval();
        var dateTimeStamp = this._getIntervalBetween(date, inAllDayRow);
        var index = Math.floor(dateTimeStamp / timeInterval);
        if (inAllDayRow) {
            index = this._updateIndex(index)
        }
        if (index < 0) {
            index = 0
        }
        return index
    },
    getPositionShift: function(timeShift, isAllDay) {
        return {
            top: timeShift * this.getCellHeight(),
            left: 0,
            cellPosition: 0
        }
    },
    getTimeShift: function(date) {
        var currentDayStart = new Date(date);
        var cellDuration = this.getCellDuration();
        var currentDayEndHour = new Date(new Date(date).setHours(this.option("endDayHour"), 0, 0));
        if (date.getTime() <= currentDayEndHour.getTime()) {
            currentDayStart.setHours(this.option("startDayHour"), 0, 0, 0)
        }
        var currentDateTime = date.getTime();
        var currentDayStartTime = currentDayStart.getTime();
        var minTime = this._firstViewDate.getTime();
        return currentDateTime > minTime ? (currentDateTime - currentDayStartTime) % cellDuration / cellDuration : 0
    },
    _isSkippedData: function() {
        return false
    },
    getCoordinatesByDateInGroup: function(date, appointmentResources, inAllDayRow) {
        var indexes = this._getGroupIndexes(appointmentResources);
        var result = [];
        if (this._isSkippedData(date)) {
            return result
        }
        if (indexes.length) {
            for (var i = 0; i < indexes.length; i++) {
                result.push(this.getCoordinatesByDate(date, indexes[i], inAllDayRow))
            }
        } else {
            result.push(this.getCoordinatesByDate(date, 0, inAllDayRow))
        }
        return result
    },
    getDroppableCellIndex: function() {
        var $droppableCell = this._getDroppableCell();
        var $row = $droppableCell.parent();
        var rowIndex = $row.index();
        return rowIndex * $row.find("td").length + $droppableCell.index()
    },
    getDataByDroppableCell: function() {
        var cellData = this.getCellData(this._getDroppableCell());
        var allDay = cellData.allDay;
        var startDate = cellData.startDate;
        var endDate = startDate && this.invoke("calculateAppointmentEndDate", allDay, startDate);
        return {
            startDate: startDate,
            endDate: endDate,
            allDay: allDay,
            groups: cellData.groups
        }
    },
    getDateRange: function() {
        return [this.getStartViewDate(), this.getEndViewDateByEndDayHour()]
    },
    getCellWidth: function() {
        var cell = this._getCells().first().get(0);
        return cell && getBoundingRect(cell).width
    },
    getCellMinWidth: function() {
        return DATE_TABLE_MIN_CELL_WIDTH
    },
    getRoundedCellWidth: function(groupIndex, startIndex, cellCount) {
        if (groupIndex < 0) {
            return 0
        }
        var $row = this.$element().find("." + this._getDateTableRowClass()).eq(0);
        var width = 0;
        var $cells = $row.find("." + DATE_TABLE_CELL_CLASS);
        var totalCellCount = this._getCellCount() * groupIndex;
        cellCount = cellCount || this._getCellCount();
        if (!typeUtils.isDefined(startIndex)) {
            startIndex = totalCellCount
        }
        for (var i = startIndex; i < totalCellCount + cellCount; i++) {
            width += getBoundingRect($($cells).eq(i).get(0)).width
        }
        return width / (totalCellCount + cellCount - startIndex)
    },
    getCellHeight: function() {
        var cell = this._getCells().first().get(0);
        return cell && getBoundingRect(cell).height
    },
    getAllDayHeight: function() {
        var cell = this._getCells(true).first().get(0);
        return this.option("showAllDayPanel") ? cell && getBoundingRect(cell).height || 0 : 0
    },
    getAllDayOffset: function() {
        return this._groupedStrategy.getAllDayOffset()
    },
    getMaxAllowedPosition: function() {
        if (!this._maxAllowedPosition) {
            var isRtl = this.option("rtlEnabled");
            this._maxAllowedPosition = [];
            this._$dateTable.find("tr").first().find("td:nth-child(" + this._getCellCount() + "n)").each(function(_, cell) {
                var maxPosition = $(cell).position().left;
                if (!isRtl) {
                    maxPosition += getBoundingRect(cell).width
                }
                this._maxAllowedPosition.push(Math.round(maxPosition))
            }.bind(this))
        }
        return this._maxAllowedPosition
    },
    getMaxAllowedVerticalPosition: function() {
        if (!this._maxAllowedVerticalPosition) {
            var that = this;
            this._maxAllowedVerticalPosition = [];
            var rows = this._getRowCount();
            this._$dateTable.find("tr:nth-child(" + rows + "n)").each(function(_, row) {
                var maxPosition = $(row).position().top + getBoundingRect(row).height;
                that._maxAllowedVerticalPosition.push(Math.round(maxPosition))
            })
        }
        return this._maxAllowedVerticalPosition
    },
    getFixedContainer: function() {
        return this._$fixedContainer
    },
    getAllDayContainer: function() {
        return this._$allDayContainer
    },
    getCellIndexByCoordinates: function(coordinates, allDay) {
        var cellCount = this._getTotalCellCount(this._getGroupCount());
        var cellWidth = Math.floor(this._getWorkSpaceWidth() / cellCount);
        var cellHeight = allDay ? this.getAllDayHeight() : this.getCellHeight();
        var leftOffset = this._isRTL() || this.option("crossScrollingEnabled") ? 0 : this.getWorkSpaceLeftOffset();
        var topIndex = Math.floor(Math.floor(coordinates.top) / Math.floor(cellHeight));
        var leftIndex = Math.floor((coordinates.left + 5 - leftOffset) / cellWidth);
        if (this._isRTL()) {
            leftIndex = cellCount - leftIndex - 1
        }
        return cellCount * topIndex + leftIndex
    },
    getStartViewDate: function() {
        return this._firstViewDate
    },
    getEndViewDate: function() {
        var dateOfLastViewCell = this.getDateOfLastViewCell();
        var endDateOfLastViewCell = this.calculateEndViewDate(dateOfLastViewCell);
        return this._adjustEndViewDateByDaylightDiff(dateOfLastViewCell, endDateOfLastViewCell)
    },
    getEndViewDateByEndDayHour: function() {
        var dateOfLastViewCell = this.getDateOfLastViewCell();
        var endTime = dateUtils.dateTimeFromDecimal(this.option("endDayHour"));
        var endDateOfLastViewCell = new Date(dateOfLastViewCell.setHours(endTime.hours, endTime.minutes));
        return this._adjustEndViewDateByDaylightDiff(dateOfLastViewCell, endDateOfLastViewCell)
    },
    calculateEndViewDate: function(dateOfLastViewCell) {
        return new Date(dateOfLastViewCell.getTime() + this.getCellDuration())
    },
    _adjustEndViewDateByDaylightDiff: function(startDate, endDate) {
        var daylightDiff = timeZoneUtils.getDaylightOffsetInMs(startDate, endDate);
        var endDateOfLastViewCell = new Date(endDate.getTime() - daylightDiff);
        return new Date(endDateOfLastViewCell.getTime() - toMs("minute"))
    },
    getDateOfLastViewCell: function() {
        var rowIndex = this._getRowCount() - 1;
        var cellIndex = this._getCellCount();
        if (this.isGroupedByDate()) {
            cellIndex = cellIndex * this._getGroupCount() - 1
        } else {
            cellIndex -= 1
        }
        return this._getDateByCellIndexes(rowIndex, cellIndex, true)
    },
    getCellDuration: function() {
        return 36e5 * this.option("hoursInterval")
    },
    getIntervalDuration: function(allDay) {
        return allDay ? toMs("day") : this.getCellDuration()
    },
    getVisibleDayDuration: function() {
        return this.option("hoursInterval") * this._getCellCountInDay() * HOUR_MS
    },
    getGroupBounds: function(coordinates) {
        var cellCount = this._getCellCount();
        var $cells = this._getCells();
        var cellWidth = this.getCellWidth();
        var result = this._groupedStrategy.getGroupBoundsOffset(cellCount, $cells, cellWidth, coordinates);
        if (this._isRTL()) {
            var startOffset = result.left;
            result.left = result.right - 2 * cellWidth;
            result.right = startOffset + 2 * cellWidth
        }
        return result
    },
    needRecalculateResizableArea: function() {
        return this._isVerticalGroupedWorkSpace() && 0 !== this.getScrollable().scrollTop()
    },
    getCellDataByCoordinates: function(coordinates, allDay) {
        var key = JSON.stringify({
            top: coordinates.top,
            left: coordinates.left
        });
        var data = this.getCellDataCache(key);
        if (data) {
            return data
        }
        var $cells = this._getCells(allDay);
        var cellIndex = this.getCellIndexByCoordinates(coordinates, allDay);
        var $cell = $cells.eq(cellIndex);
        return this.getCellData($cell)
    },
    getVisibleBounds: function() {
        var result = {};
        var $scrollable = this.getScrollable().$element();
        var cellHeight = this.getCellHeight();
        var scrolledCellCount = this.getScrollableScrollTop() / cellHeight;
        var totalCellCount = scrolledCellCount + $scrollable.height() / cellHeight;
        result.top = {
            hours: Math.floor(scrolledCellCount * this.option("hoursInterval")) + this.option("startDayHour"),
            minutes: scrolledCellCount % 2 ? 30 : 0
        };
        result.bottom = {
            hours: Math.floor(totalCellCount * this.option("hoursInterval")) + this.option("startDayHour"),
            minutes: Math.floor(totalCellCount) % 2 ? 30 : 0
        };
        return result
    },
    updateScrollPosition: function(date) {
        date = this.invoke("convertDateByTimezone", date);
        var bounds = this.getVisibleBounds();
        var startDateHour = date.getHours();
        var startDateMinutes = date.getMinutes();
        if (this.needUpdateScrollPosition(startDateHour, startDateMinutes, bounds, date)) {
            this.scrollToTime(startDateHour, startDateMinutes, date)
        }
    },
    needUpdateScrollPosition: function(hours, minutes, bounds) {
        var isUpdateNeeded = false;
        if (hours < bounds.top.hours || hours > bounds.bottom.hours) {
            isUpdateNeeded = true
        }
        if (hours === bounds.top.hours && minutes < bounds.top.minutes) {
            isUpdateNeeded = true
        }
        if (hours === bounds.bottom.hours && minutes > bounds.top.minutes) {
            isUpdateNeeded = true
        }
        return isUpdateNeeded
    },
    getGroupWidth: function(groupIndex) {
        var result = this._getCellCount() * this.getCellWidth();
        var position = this.getMaxAllowedPosition();
        var currentPosition = position[groupIndex];
        if (position.length && currentPosition) {
            if (this._isRTL()) {
                result = currentPosition - position[groupIndex + 1]
            } else {
                if (0 === groupIndex) {
                    result = currentPosition
                } else {
                    result = currentPosition - position[groupIndex - 1]
                }
            }
        }
        return result
    },
    scrollToTime: function(hours, minutes, date) {
        var min = this.getStartViewDate();
        var max = this.getEndViewDate();
        if (date < min || date > max) {
            errors.log("W1008", date);
            return
        }
        var coordinates = this._getScrollCoordinates(hours, minutes, date);
        var scrollable = this.getScrollable();
        scrollable.scrollBy({
            top: coordinates.top - scrollable.scrollTop(),
            left: 0
        })
    },
    getDistanceBetweenCells: function(startIndex, endIndex) {
        var result = 0;
        this.$element().find("." + this._getDateTableRowClass()).first().find("." + DATE_TABLE_CELL_CLASS).each(function(index) {
            if (index < startIndex || index > endIndex) {
                return true
            }
            result += getBoundingRect(this).width
        });
        return result
    },
    needApplyCollectorOffset: function() {
        return false
    },
    initDragBehavior: function(scheduler) {
        if (!this.dragBehavior && scheduler) {
            this.dragBehavior = new AppointmentDragBehavior(scheduler);
            this.dragBehavior.addTo(this.getWorkArea());
            this.dragBehavior.addTo(this.getAllDayContainer());
            this.dragBehavior.addTo(this._$allDayPanel)
        }
    },
    _isApplyCompactAppointmentOffset: function() {
        return this._supportCompactDropDownAppointments()
    },
    _supportCompactDropDownAppointments: function() {
        return true
    },
    _formatWeekday: function(date) {
        return formatWeekday(date)
    },
    _formatWeekdayAndDay: function(date) {
        return formatWeekday(date) + " " + dateLocalization.format(date, "day")
    },
    removeDroppableCellClass: function($cellElement) {
        ($cellElement || this._getDroppableCell()).removeClass(DATE_TABLE_DROPPABLE_CELL_CLASS)
    }
}).include(publisherMixin);
module.exports = SchedulerWorkSpace;
