/**
 *  Component layout for {@link Ext.view.Table}
 *  @private
 *
 */
Ext.define('Ext.view.TableLayout', {
    extend: 'Ext.layout.component.Auto',
 
    alias: 'layout.tableview',
    type: 'tableview',
 
    beginLayout: function(ownerContext) {
        var me = this,
            owner = me.owner,
            ownerGrid = owner.ownerGrid,
            partner = owner.lockingPartner,
            partnerContext = ownerContext.lockingPartnerContext,
            context = ownerContext.context,
            scrollable = ownerGrid.getScrollable(),
            partnerVisible;
 
        partnerVisible = partner && partner.grid.isVisible() &&
                         !(partner.grid.collapsed || partner.grid.floatedFromCollapse);
 
        // Flag whether we need to do row height synchronization.
        // syncRowHeightOnNextLayout is a one time flag used when some code knows it has changed
        // data height and that the upcoming layout must sync row heights even if the grid
        // is configured not to for general row rendering.
        ownerContext.doSyncRowHeights =
            partnerVisible && (ownerGrid.syncRowHeight || ownerGrid.syncRowHeightOnNextLayout);
 
        // The reason for checking .config here is that by setting the overflow on the context, it
        // overwrites the value in the scrollable. As such, all we're trying to do here is capture
        // the initial intent to see if the user configured the scroller as x: false.
        // It's not perfect but will cover 99% of cases.
        ownerContext.allowScrollX = scrollable && scrollable.config && scrollable.config.x;
 
        if (!me.columnFlusherId) {
            me.columnFlusherId = me.id + '-columns';
            me.rowHeightFlusherId = me.id + '-rows';
        }
 
        me.callParent([ ownerContext ]);
 
        // If we are in a twinned grid (locked view) then set up bidirectional links with
        // the other side's layout context. If the locked or normal side is hidden then
        // we should treat it as though we were laying out a single grid, so don't setup
        // the partners.
        // This is typically if a grid is configured with locking but starts with no locked columns.
        if (partnerVisible) {
            if (!partnerContext && partner.componentLayout.isRunning()) {
                // eslint-disable-next-line max-len
                (partnerContext = ownerContext.lockingPartnerContext = context.getCmp(partner)).lockingPartnerContext = ownerContext;
 
                // Set up opposite side's link if not already aware.
                if (!partnerContext.lockingPartnerContext) {
                    partnerContext.lockingPartnerContext = ownerContext;
                }
            }
 
            if (ownerContext.doSyncRowHeights) {
                if (partnerContext && !partnerContext.rowHeightSynchronizer) {
                    partnerContext.rowHeightSynchronizer =
                        partnerContext.target.syncRowHeightBegin();
                }
 
                ownerContext.rowHeightSynchronizer = me.owner.syncRowHeightBegin();
            }
        }
 
        // Grab a ContextItem for the header container (and make sure the TableLayout can
        // reach us as well):
        (ownerContext.headerContext = context.getCmp(me.headerCt)).viewContext = ownerContext;
    },
 
    beginLayoutCycle: function(ownerContext, firstCycle) {
        this.callParent([ ownerContext, firstCycle ]);
 
        if (ownerContext.syncRowHeights) {
            ownerContext.target.syncRowHeightClear(ownerContext.rowHeightSynchronizer);
            ownerContext.syncRowHeights = false;
        }
    },
 
    calculate: function(ownerContext) {
        var me = this,
            context = ownerContext.context,
            lockingPartnerContext = ownerContext.lockingPartnerContext,
            headerContext = ownerContext.headerContext,
            ownerCtContext = ownerContext.ownerCtContext,
            state = ownerContext.state,
            owner = me.owner,
            bodyDom = owner.body.dom,
            columnsChanged = headerContext.getProp('columnsChanged'),
            overflowable, columnFlusher, otherSynchronizer, synchronizer, rowHeightFlusher,
            bodyHeight, ctSize, overflowY, overflowX, scrollbarHeight;
 
        // Shortcut when empty grid - let the base handle it.
        // EXTJS-14844: Even when no data rows (all.getCount() === 0) there may be
        // summary rows to size.
        if (!owner.all.getCount() && (!bodyDom || !owner.body.child('table', true))) {
            ownerContext.setProp('viewOverflowY', false);
            me.callParent([ ownerContext ]);
 
            return;
        }
 
        // BufferedRenderer#beforeTableLayout reads, so call it in a read phase.
        if (me.calcCount === 1 && me.owner.bufferedRenderer) {
            me.owner.bufferedRenderer.beforeTableLayout(ownerContext);
        }
 
        if (columnsChanged === undefined) {
            // We cannot proceed when we have rows but no columnWidths determined...
            me.done = false;
 
            return;
        }
 
        if (columnsChanged) {
            if (!(columnFlusher = state.columnFlusher)) {
                // Since the columns have changed, we need to write the widths to the DOM.
                // Queue (and possibly replace) a pseudo ContextItem, who's flush method
                // routes back into this class.
                context.queueFlush(state.columnFlusher = columnFlusher = {
                    ownerContext: ownerContext,
                    columnsChanged: columnsChanged,
                    layout: me,
                    id: me.columnFlusherId,
                    flush: me.flushColumnWidths
                }, true);
            }
 
            if (!columnFlusher.flushed) {
                // We have queued the columns to be written, but they are still pending, so
                // we cannot proceed.
                me.done = false;
 
                return;
            }
        }
 
        // They have to turn row height synchronization on, or there may be variable row heights
        // Either no columns changed, or we have flushed those changes.. which means the
        // column widths in the DOM are correct. Now we can proceed to syncRowHeights (if
        // we are locking) or wrap it up by determining our vertical overflow.
        if (ownerContext.doSyncRowHeights) {
            if (!(rowHeightFlusher = state.rowHeightFlusher)) {
                // When we are locking, both sides need to read their row heights in a read
                // phase (i.e., right now).
                if (!(synchronizer = state.rowHeights)) {
                    state.rowHeights = synchronizer = ownerContext.rowHeightSynchronizer;
                    me.owner.syncRowHeightMeasure(synchronizer);
                    ownerContext.setProp('rowHeights', synchronizer);
                }
 
                if (!(otherSynchronizer = lockingPartnerContext.getProp('rowHeights'))) {
                    me.done = false;
 
                    return;
                }
 
                // Queue (and possibly replace) a pseudo ContextItem, who's flush method
                // routes back into this class.
                context.queueFlush(state.rowHeightFlusher = rowHeightFlusher = {
                    ownerContext: ownerContext,
                    synchronizer: synchronizer,
                    otherSynchronizer: otherSynchronizer,
                    layout: me,
                    id: me.rowHeightFlusherId,
                    flush: me.flushRowHeights
                }, true);
            }
 
            if (!rowHeightFlusher.flushed) {
                me.done = false;
 
                return;
            }
        }
 
        me.callParent([ ownerContext ]);
 
        if (!ownerContext.heightModel.shrinkWrap) {
            if (!ownerCtContext.heightModel.shrinkWrap) {
                overflowable = true;
                // We are placed in a fit layout of the gridpanel (our ownerCt), so we need to
                // consult its containerSize when we are not shrink-wrapping to see if our
                // content will overflow vertically.
                ctSize = ownerCtContext.target.layout.getContainerSize(ownerCtContext);
 
                if (!ctSize.gotHeight) {
                    me.done = false;
 
                    return;
                }
 
                bodyHeight = bodyDom.offsetHeight;
 
                if (bodyHeight > ctSize.height) {
                    overflowY = true;
                }
            }
        }
 
        // Adjust the presence of X scrollability depending upon whether the headers
        // overflow, and scrollbars take up space.
        // This has two purposes.
        //
        // For lockable assemblies, if there is horizontal overflow in the normal side,
        // The locked side (which shrinkwraps the columns) must be set to overflow: scroll
        // in order that it has acquires a matching horizontal scrollbar.
        //
        // If no locking, then if there is no horizontal overflow, we set overflow-x: hidden
        // This avoids "pantom" scrollbars which are only caused by the presence of another
        // scrollbar.
        scrollbarHeight = Ext.scrollbar.height();
 
        if (me.done && ownerContext.allowScrollX && scrollbarHeight) {
            // No locking sides, ensure X scrolling is on if there is overflow,
            // but not if there is no overflow
            // This eliminates "phantom" scrollbars which are only caused by other scrollbars.
            // Locking horizontal scrollbars are handled in Ext.grid.locking.Lockable#afterLayout
            if (!owner.lockingPartner) {
                if (owner.isAutoTree) {
                    overflowX = true;
                }
                else {
                    overflowX = !!ownerContext.headerContext.state.boxPlan.tooNarrow;
                }
 
                ownerContext.setProp('overflowX', overflowX);
            }
 
            // If the overflowY was set to false but then adding a horizontal scrollbar
            // will overflow the view vertically we need to set overflowY to true
            if (overflowX && bodyHeight && overflowable) {
                overflowY = (bodyHeight + scrollbarHeight) > ctSize.height;
            }
        }
 
        if (me.done || overflowY != null) {
            ownerContext.setProp('viewOverflowY', !!overflowY);
        }
    },
 
    measureContentHeight: function(ownerContext) {
        var owner = this.owner,
            bodyDom = owner.body.dom,
            emptyEl = owner.emptyEl,
            bodyHeight = 0;
 
        if (emptyEl) {
            bodyHeight += emptyEl.offsetHeight;
        }
 
        if (bodyDom) {
            bodyHeight += bodyDom.offsetHeight;
        }
 
        // This will have been figured out by now because the columnWidths have been
        // published...
        if (ownerContext.headerContext.state.boxPlan.tooNarrow) {
            bodyHeight += Ext.scrollbar.height();
        }
 
        return bodyHeight;
    },
 
    flushColumnWidths: function() {
        // NOTE: The "this" pointer here is the flusher object that was queued.
        var flusher = this,
            me = flusher.layout,
            ownerContext = flusher.ownerContext,
            columnsChanged = flusher.columnsChanged,
            owner = ownerContext.target,
            len = columnsChanged.length,
            column, i, colWidth, lastBox;
 
        if (ownerContext.state.columnFlusher !== flusher) {
            return;
        }
 
        // Set column width corresponding to each header
        for (= 0; i < len; i++) {
            if (!(column = columnsChanged[i])) {
                continue;
            }
 
            colWidth = column.props.width;
            owner.body.select(owner.getColumnSizerSelector(column.target)).setWidth(colWidth);
 
            // Allow columns which need to perform layouts on resize queue a layout
            if (column.target.onCellsResized) {
                column.target.onCellsResized(colWidth);
            }
 
            // Enable the next go-round of headerCt's ColumnLayout change check to
            // read true, flushed lastBox widths that are in the Table
            lastBox = column.lastBox;
 
            if (lastBox) {
                lastBox.width = colWidth;
            }
        }
 
        flusher.flushed = true;
 
        if (!me.pending) {
            ownerContext.context.queueLayout(me);
        }
    },
 
    flushRowHeights: function() {
        // NOTE: The "this" pointer here is the flusher object that was queued.
        var flusher = this,
            me = flusher.layout,
            ownerContext = flusher.ownerContext;
 
        if (ownerContext.state.rowHeightFlusher !== flusher) {
            return;
        }
 
        ownerContext.target.syncRowHeightFinish(flusher.synchronizer, flusher.otherSynchronizer);
 
        flusher.flushed = true;
 
        ownerContext.syncRowHeights = true;
 
        if (!me.pending) {
            ownerContext.context.queueLayout(me);
        }
    },
 
    finishedLayout: function(ownerContext) {
        var me = this,
            ownerGrid = me.owner.ownerGrid,
            nodeContainer = Ext.fly(me.owner.getNodeContainer()),
            scroller = this.owner.getScrollable(),
            buffered;
 
        me.callParent([ ownerContext ]);
 
        if (nodeContainer) {
            nodeContainer.setWidth(ownerContext.headerContext.props.contentWidth);
        }
 
        // Inform any buffered renderer about completion of the layout of its view
        buffered = me.owner.bufferedRenderer;
 
        if (buffered) {
            buffered.afterTableLayout(ownerContext);
        }
 
        if (ownerGrid) {
            ownerGrid.syncRowHeightOnNextLayout = false;
        }
 
        if (scroller && !scroller.isScrolling) {
            // BufferedRenderer only sets nextRefreshStartIndex to zero when preserveScrollOnReload
            // is false. And if variableRowHeight is true, restoring the scroller will be handled
            // by the bufferedRenderer
            if (buffered) {
                if (buffered.nextRefreshStartIndex === 0 || me.owner.hasVariableRowHeight()) {
                    return;
                }
            }
 
            scroller.restoreState();
        }
    },
 
    getLayoutItems: function() {
        return this.owner.getRefItems();
    },
 
    isValidParent: function() {
        return true;
    }
});