/**
 * This class is intended to be extended or created via the
 * {@link Ext.Component#componentLayout layout} configuration property.
 * See {@link Ext.Component#componentLayout} for additional details.
 * @private
 */
Ext.define('Ext.layout.component.Component', {
    extend: 'Ext.layout.Layout',

    type: 'component',

    isComponentLayout: true,

    nullBox: {},

    usesContentHeight: true,
    usesContentWidth: true,
    usesHeight: true,
    usesWidth: true,

    widthCache: {},
    heightCache: {},

    beginLayoutCycle: function(ownerContext, firstCycle) {
        var me = this,
            owner = me.owner,
            ownerCtContext = ownerContext.ownerCtContext,
            heightModel = ownerContext.heightModel,
            widthModel = ownerContext.widthModel,
            body = owner.el.dom === document.body,
            lastBox = owner.lastBox || me.nullBox,
            lastSize = owner.el.lastBox || me.nullBox,
            dirty = !body,
            isTopLevel = ownerContext.isTopLevel,
            ownerLayout, v, width, height, scroller;

        me.callParent([ownerContext, firstCycle]);

        if (firstCycle) {
            scroller = owner.getScrollable && owner.getScrollable();

            if (scroller) {
                scroller.flushOnDomScrollEnd();
            }

            if (me.usesContentWidth) {
                ++ownerContext.consumersContentWidth;
            }

            if (me.usesContentHeight) {
                ++ownerContext.consumersContentHeight;
            }

            if (me.usesWidth) {
                ++ownerContext.consumersWidth;
            }

            if (me.usesHeight) {
                ++ownerContext.consumersHeight;
            }

            if (ownerCtContext && !ownerCtContext.hasRawContent) {
                ownerLayout = owner.ownerLayout;

                if (ownerLayout) {
                    if (ownerLayout.usesWidth) {
                        ++ownerContext.consumersWidth;
                    }

                    if (ownerLayout.usesHeight) {
                        ++ownerContext.consumersHeight;
                    }
                }
            }
        }

        // we want to publish configured dimensions as early as possible and since this is
        // a write phase...
        if (widthModel.configured) {
            // If the owner.el is the body, owner.width is not dirty (we don't want to write
            // it to the body el). For other el's, the width may already be correct in the
            // DOM (e.g., it is rendered in the markup initially). If the width is not
            // correct in the DOM, this is only going to be the case on the first cycle.
            width = owner[widthModel.names.width];

            if (isTopLevel && widthModel.calculatedFrom) {
                width = lastBox.width;
            }

            if (!body) {
                dirty = me.setWidthInDom ||
                        (firstCycle ? width !== lastSize.width : widthModel.constrained);
            }

            ownerContext.setWidth(width, dirty);
        }
        else if (isTopLevel) {
            if (widthModel.calculated) {
                v = lastBox.width;
                ownerContext.setWidth(v, /* dirty= */v !== lastSize.width);
            }
            else if (widthModel.calculatedFromNatural) {
                owner.el.dom.style.width = owner.width;
            }

            v = lastBox.x;
            ownerContext.setProp('x', v, /* dirty= */v !== lastSize.x);
        }

        if (heightModel.configured) {
            height = owner[heightModel.names.height];

            if (isTopLevel && heightModel.calculatedFrom) {
                height = lastBox.height;
            }

            if (!body) {
                dirty = firstCycle ? height !== lastSize.height : heightModel.constrained;
            }

            ownerContext.setHeight(height, dirty);
        }
        else if (isTopLevel) {
            if (heightModel.calculated) {
                v = lastBox.height;
                ownerContext.setHeight(v, v !== lastSize.height);
            }
            else if (heightModel.calculatedFromNatural) {
                owner.el.dom.style.height = owner.height;
            }

            v = lastBox.y;
            ownerContext.setProp('y', v, /* dirty= */v !== lastSize.y);
        }
    },

    finishedLayout: function(ownerContext) {
        var me = this,
            elementChildren = ownerContext.children,
            owner = me.owner,
            len, i, elContext, lastBox, props;

        // NOTE: In the code below we cannot use getProp because that will generate
        // a layout dependency

        // Set lastBox on managed child Elements.
        // So that ContextItem.constructor can snag the lastBox for use by its undo method.
        if (elementChildren) {
            len = elementChildren.length;

            for (i = 0; i < len; i++) {
                elContext = elementChildren[i];
                elContext.el.lastBox = elContext.props;
            }
        }

        // Cache the size from which we are changing so that notifyOwner
        // can notify the owningComponent with all essential information
        ownerContext.previousSize = me.lastComponentSize;

        // Cache the currently layed out size
        me.lastComponentSize = owner.el.lastBox = props = ownerContext.props;

        // lastBox is a copy of the defined props to allow save/restore of these (panel
        // collapse needs this)
        lastBox = owner.lastBox || (owner.lastBox = {});
        lastBox.x = props.x;
        lastBox.y = props.y;
        lastBox.width = props.width;
        lastBox.height = props.height;
        lastBox.invalid = false;

        me.callParent([ownerContext]);
    },

    notifyOwner: function(ownerContext) {
        var me = this,
            currentSize = me.lastComponentSize,
            prevSize = ownerContext.previousSize;

        me.owner.afterComponentLayout(
            currentSize.width, currentSize.height,
            prevSize ? prevSize.width : undefined,
            prevSize ? prevSize.height : undefined
        );
    },

    /**
     * Returns the owner component's resize element.
     * @return {Ext.dom.Element}
     */
    getTarget: function() {
        return this.owner.el;
    },

    /**
     * Returns the element into which rendering must take place.
     * Defaults to the owner Component's encapsulating element.
     *
     * May be overridden in Component layout managers which implement an inner element.
     * @return {Ext.dom.Element}
     */
    getRenderTarget: function() {
        return this.owner.el;
    },

    cacheTargetInfo: function(ownerContext) {
        var me = this,
            targetInfo = me.targetInfo,
            target;

        if (!targetInfo) {
            target = ownerContext.getEl('getTarget', me);

            me.targetInfo = targetInfo = {
                padding: target.getPaddingInfo(),
                border: target.getBorderInfo()
            };
        }

        return targetInfo;
    },

    measureAutoDimensions: function(ownerContext, dimensions) {
        // Subtle But Important:
        // 
        // We don't want to call getProp/hasProp et.al. unless we in fact need that value
        // for our results! If we call it and don't need it, the layout manager will think
        // we depend on it and will schedule us again should it change.

        var me = this,
            owner = me.owner,
            containerLayout = owner.layout,
            heightModel = ownerContext.heightModel,
            widthModel = ownerContext.widthModel,
            boxParent = ownerContext.boxParent,
            isBoxParent = ownerContext.isBoxParent,
            target = ownerContext.target,
            props = ownerContext.props,
            isContainer,
            ret = {
                gotWidth: false,
                gotHeight: false,
                isContainer: (isContainer = !ownerContext.hasRawContent)
            },
            hv = dimensions || 3,
            zeroWidth, zeroHeight,
            needed = 0,
            got = 0,
            ready, size, temp, key, cache, dirty;

        // Note: this method is called *a lot*, so we have to be careful not to waste any
        // time or make useless calls or, especially, read the DOM when we can avoid it.

        //---------------------------------------------------------------------
        // Width

        if (widthModel.shrinkWrap && ownerContext.consumersContentWidth) {
            ++needed;
            zeroWidth = !(hv & 1);

            if (isContainer) {
                // as a componentLayout for a container, we rely on the container layout to
                // produce contentWidth...
                if (zeroWidth) {
                    ret.contentWidth = 0;
                    ret.gotWidth = true;
                    ++got;
                }
                else if ((ret.contentWidth = ownerContext.getProp('contentWidth')) !== undefined) {
                    ret.gotWidth = true;
                    ++got;
                }
            }
            else {
                size = props.contentWidth;

                if (typeof size === 'number') { // if (already determined)
                    ret.contentWidth = size;
                    ret.gotWidth = true;
                    ++got;
                }
                else {
                    if (zeroWidth) {
                        ready = true;
                    }
                    else if (!ownerContext.hasDomProp('containerChildrenSizeDone')) {
                        ready = false;
                    }
                    else if (isBoxParent || !boxParent || boxParent.widthModel.shrinkWrap) {
                        // if we have no boxParent, we are ready, but a shrinkWrap boxParent
                        // artificially provides width early in the measurement process so
                        // we are ready to go in that case as well...
                        ready = true;
                    }
                    else {
                        // lastly, we have a boxParent that will be given a width, so we
                        // can wait for that width to be set in order to properly measure
                        // whatever is inside...
                        ready = boxParent.hasDomProp('width');
                    }

                    if (ready) {
                        if (zeroWidth) {
                            temp = 0;
                        }
                        else if (containerLayout && containerLayout.measureContentWidth) {
                            // Allow the container layout to do the measurement since it
                            // may have a better idea of how to do it even with no items:
                            temp = containerLayout.measureContentWidth(ownerContext);
                        }
                        else {
                            if (target.cacheWidth) {
                                // if all instances of a given xtype/UI are the same size,
                                // only read the DOM once to measure the first instance.
                                // Thereafter, retrieve the width from the cache.
                                key = target.xtype + '-' + target.ui;
                                cache = me.widthCache;
                                temp = cache[key] ||
                                       (cache[key] = me.measureContentWidth(ownerContext));
                            }
                            else {
                                temp = me.measureContentWidth(ownerContext);
                            }
                        }

                        if (!isNaN(ret.contentWidth = temp)) {
                            ownerContext.setContentWidth(temp, true);
                            ret.gotWidth = true;
                            ++got;
                        }
                    }
                }
            }
        }
        else if (widthModel.natural && ownerContext.consumersWidth) {
            ++needed;
            size = props.width;
            // zeroWidth does not apply

            if (typeof size === 'number') { // if (already determined)
                ret.width = size;
                ret.gotWidth = true;
                ++got;
            }
            else {
                if (isBoxParent || !boxParent) {
                    ready = true;
                }
                else {
                    // lastly, we have a boxParent that will be given a width, so we
                    // can wait for that width to be set in order to properly measure
                    // whatever is inside...
                    ready = boxParent.hasDomProp('width');
                }

                if (ready) {
                    if (!isNaN(ret.width = me.measureOwnerWidth(ownerContext))) {
                        // if minWidth/maxWidth was specified, we need to mark this as dirty
                        // so the new ret.width is applied to this context.
                        dirty = !!((target.minWidth || target.maxWidth) &&
                            typeof target.width !== 'number');
                        ownerContext.setWidth(ret.width, dirty);
                        ret.gotWidth = true;
                        ++got;
                    }
                }
            }
        }

        //---------------------------------------------------------------------
        // Height

        if (heightModel.shrinkWrap && ownerContext.consumersContentHeight) {
            ++needed;
            zeroHeight = !(hv & 2);

            if (isContainer) {
                // don't ask unless we need to know...
                if (zeroHeight) {
                    ret.contentHeight = 0;
                    ret.gotHeight = true;
                    ++got;
                }
                // eslint-disable-next-line no-cond-assign, max-len
                else if ((ret.contentHeight = ownerContext.getProp('contentHeight')) !== undefined) {
                    ret.gotHeight = true;
                    ++got;
                }
            }
            else {
                size = props.contentHeight;

                if (typeof size === 'number') { // if (already determined)
                    ret.contentHeight = size;
                    ret.gotHeight = true;
                    ++got;
                }
                else {
                    if (zeroHeight) {
                        ready = true;
                    }
                    else if (!ownerContext.hasDomProp('containerChildrenSizeDone')) {
                        ready = false;
                    }
                    else if (owner.noWrap) {
                        ready = true;
                    }
                    else if (!widthModel.shrinkWrap) {
                        // fixed width, so we need the width to determine the height...
                        // eslint-disable-next-line max-len
                        ready = (ownerContext.bodyContext || ownerContext).hasDomProp('width');// && (!ownerContext.bodyContext || ownerContext.bodyContext.hasDomProp('width'));
                    }
                    else if (isBoxParent || !boxParent || boxParent.widthModel.shrinkWrap) {
                        // if we have no boxParent, we are ready, but an autoWidth boxParent
                        // artificially provides width early in the measurement process so
                        // we are ready to go in that case as well...
                        ready = true;
                    }
                    else {
                        // lastly, we have a boxParent that will be given a width, so we
                        // can wait for that width to be set in order to properly measure
                        // whatever is inside...
                        ready = boxParent.hasDomProp('width');
                    }

                    if (ready) {
                        if (zeroHeight) {
                            temp = 0;
                        }
                        else if (containerLayout && containerLayout.measureContentHeight) {
                            // Allow the container layout to do the measurement since it
                            // may have a better idea of how to do it even with no items:
                            temp = containerLayout.measureContentHeight(ownerContext);
                        }
                        else {
                            if (target.cacheHeight) {
                                // if all instances of a given xtype/UI are the same size,
                                // only read the DOM once to measure the first instance.
                                // Thereafter, retrieve the height from the cache.
                                key = target.xtype + '-' + target.ui;
                                cache = me.heightCache;
                                temp = cache[key] ||
                                       (cache[key] = me.measureContentHeight(ownerContext));
                            }
                            else {
                                temp = me.measureContentHeight(ownerContext);
                            }
                        }

                        if (!isNaN(ret.contentHeight = temp)) {
                            ownerContext.setContentHeight(temp, true);
                            ret.gotHeight = true;
                            ++got;
                        }
                    }
                }
            }
        }
        else if (heightModel.natural && ownerContext.consumersHeight) {
            ++needed;
            size = props.height;
            // zeroHeight does not apply

            if (typeof size === 'number') { // if (already determined)
                ret.height = size;
                ret.gotHeight = true;
                ++got;
            }
            else {
                if (isBoxParent || !boxParent) {
                    ready = true;
                }
                else {
                    // lastly, we have a boxParent that will be given a width, so we
                    // can wait for that width to be set in order to properly measure
                    // whatever is inside...
                    ready = boxParent.hasDomProp('width');
                }

                if (ready) {
                    if (!isNaN(ret.height = me.measureOwnerHeight(ownerContext))) {
                        // if minHeight/maxHeight was specified, we need to mark this as dirty
                        // so the new ret.height is applied to this context.
                        dirty = !!((target.minHeight || target.maxHeight) &&
                            typeof target.height !== 'number');
                        ownerContext.setHeight(ret.height, dirty);
                        ret.gotHeight = true;
                        ++got;
                    }
                }
            }
        }

        if (boxParent) {
            ownerContext.onBoxMeasured();
        }

        ret.gotAll = got === needed;

        // see if we can avoid calling this method by storing something on ownerContext.
        return ret;
    },

    measureContentWidth: function(ownerContext) {
        // contentWidth includes padding, but not border, framing or margins
        return ownerContext.el.getWidth() - ownerContext.getFrameInfo().width;
    },

    measureContentHeight: function(ownerContext) {
        // contentHeight includes padding, but not border, framing or margins
        return ownerContext.el.getHeight() - ownerContext.getFrameInfo().height;
    },

    measureOwnerHeight: function(ownerContext) {
        return ownerContext.el.getHeight();
    },

    measureOwnerWidth: function(ownerContext) {
        return ownerContext.el.getWidth();
    }
});