/**
 * This plugin can be applied to any `Component` (although almost always to a `Container`)
 * to make it fill the browser viewport. This plugin is used internally by the more familiar
 * `Ext.container.Viewport` class.
 *
 * The `Viewport` container is commonly used but it can be an issue if you need to fill the
 * viewport with a container that derives from another class (e.g., `Ext.tab.Panel`). Prior
 * to this plugin, you would have to do this:
 *
 *      Ext.create('Ext.container.Viewport', {
 *          layout: 'fit', // full the viewport with the tab panel
 *
 *          items: [{
 *              xtype: 'tabpanel',
 *              items: [{
 *                  ...
 *              }]
 *          }]
 *      });
 *
 * With this plugin you can create the `tabpanel` as the viewport:
 *
 *      Ext.create('Ext.tab.Panel', {
 *          plugins: {
 *              viewport: true
 *          },
 *
 *          items: [{
 *              ...
 *          }]
 *      });
 *
 * More importantly perhaps is that as a plugin, the view class can be reused in other
 * contexts such as the content of a `{@link Ext.window.Window window}`.
 *
 * The Viewport renders itself to the document body, and automatically sizes itself to the size of
 * the browser viewport and manages window resizing. There may only be one Viewport created
 * in a page.
 *
 * ## Responsive Design
 *
 * This plugin enables {@link Ext.mixin.Responsive#responsiveConfig} for the components
 * by requiring `Ext.Responsive`.
 *
 * @since 5.0.0
 */
Ext.define('Ext.plugin.Viewport', {
    extend: 'Ext.plugin.Abstract',
 
    requires: [
        'Ext.Responsive'
    ],
 
    alias: 'plugin.viewport',
 
    setCmp: function(cmp) {
        this.cmp = cmp;
 
        if (cmp && !cmp.isViewport) {
            this.decorate(cmp);
 
            if (cmp.renderConfigs) {
                cmp.flushRenderConfigs();
            }
 
            cmp.setupViewport();
        }
    },
 
    destroy: function() {
        var el = this.cmp.el;
 
        this.callParent();
 
        // Remove the injected overrides so that the bodyEl singleton
        // can be reused by subsequent code (eg, unit tests)
        if (el) {
            delete el.setHeight;
            delete el.setWidth;
        }
    },
 
    statics: {
        decorate: function(target) {
            Ext.applyIf(target.prototype || target, {
                ariaRole: 'application',
 
                viewportCls: Ext.baseCSSPrefix + 'viewport'
            });
 
            Ext.override(target, {
                isViewport: true,
 
                preserveElOnDestroy: true,
 
                initComponent: function() {
                    this.callParent();
                    this.setupViewport();
                },
 
                // Because we don't stamp the size until onRender, our size model
                // won't return correctly. As we're always going to be configured,
                // just return the value here
                getSizeModel: function() {
                    var configured = Ext.layout.SizeModel.configured;
 
                    return configured.pairsByHeightOrdinal[configured.ordinal];
                },
 
                handleViewportResize: function() {
                    var me = this,
                        Element = Ext.dom.Element,
                        width = Element.getViewportWidth(),
                        height = Element.getViewportHeight();
 
                    if (width !== me.width || height !== me.height) {
                        me.setSize(width, height);
                    }
                },
 
                setupViewport: function() {
                    var me = this,
                        el = document.body;
 
                    if (!me.$responsiveId) {
                        me.setResponsiveConfig(true);
                        Ext.mixin.Responsive.register(me);
                        me.setupResponsiveContext();
                    }
 
                    // Here in the (perhaps unlikely) case that the body dom el doesn't yet have
                    // an id, we want to give it the same id as the viewport component so getCmp
                    // lookups will be able to find the owner component.
                    //
                    // Note that nothing says that components that use configured elements
                    // have to have matching ids (they probably won't), but this is at least making
                    // the attempt so that getCmp *may* be able to find the component.
                    // However, in these cases, it's really better to use Component#from
                    // to find the owner component.
                    if (!el.id) {
                        el.id = me.id;
                    }
 
                    // In addition, stamp on the data-componentid so lookups using Component's
                    // from will work.
                    el.setAttribute('data-componentid', me.id);
 
                    if (!me.ariaStaticRoles[me.ariaRole]) {
                        el.setAttribute('role', me.ariaRole);
                    }
 
                    el = me.el = Ext.getBody();
 
                    Ext.fly(document.documentElement).addCls(me.viewportCls);
                    el.setHeight = el.setWidth = Ext.emptyFn;
                    el.dom.scroll = 'no';
                    me.allowDomMove = false;
                    me.renderTo = el;
 
                    if (Ext.supports.Touch) {
                        me.addMeta('apple-mobile-web-app-capable', 'yes');
                    }
 
                    // Get the DOM disruption over with before the Viewport renders
                    // and begins a layout
                    Ext.scrollbar.size();
 
                    // Clear any dimensions, we will size later on in onRender
                    me.width = me.height = undefined;
 
                    // ... but take the measurements now because doing that in onRender
                    // will cause a costly reflow which we just forced with getScrollbarSize()
                    me.initialViewportHeight = Ext.Element.getViewportHeight();
                    me.initialViewportWidth = Ext.Element.getViewportWidth();
                },
 
                afterLayout: function(layout) {
                    if (Ext.supports.Touch) {
                        document.body.scrollTop = 0;
                    }
 
                    this.callParent([layout]);
                },
 
                onRender: function() {
                    var me = this,
                        overflowEl = me.getOverflowEl(), // eslint-disable-line no-unused-vars
                        body = Ext.getBody();
 
                    me.callParent(arguments);
 
                    // The global scroller is our scroller.
                    // We must provide a non-scrolling one if we are not configured to scroll,
                    // otherwise the deferred ready listener in Scroller will create
                    // one with scroll: true
                    Ext.setViewportScroller(me.getScrollable() || {
                        x: false,
                        y: false,
                        element: body
                    });
 
                    // If we are not scrolling the body, the body has to be overflow:hidden
                    if (me.getOverflowEl() !== body) {
                        body.setStyle('overflow', 'hidden');
                    }
 
                    // Important to start life as the proper size (to avoid extra layouts)
                    // But after render so that the size is not stamped into the body,
                    // although measurement has to take place before render to avoid
                    // causing a reflow.
                    me.width = me.initialViewportWidth;
                    me.height = me.initialViewportHeight;
 
                    me.initialViewportWidth = me.initialViewportHeight = null;
                },
 
                initInheritedState: function(inheritedState, inheritedStateInner) {
                    var me = this,
                        root = Ext.rootInheritedState;
 
                    if (inheritedState !== root) {
                        // We need to go at this again but with the rootInheritedState object. Let
                        // any derived class poke on the proper object!
                        me.initInheritedState(
                            me.inheritedState = root,
                            me.inheritedStateInner = Ext.Object.chain(root)
                        );
                    }
                    else {
                        me.callParent([inheritedState, inheritedStateInner]);
                    }
                },
 
                doDestroy: function() {
                    var me = this,
                        root = Ext.rootInheritedState,
                        scroller = me.scrollable,
                        key;
 
                    // We set the global body scroller aboce in onRender.
                    // Just relinquish it here and allow it to live on.
                    if (scroller) {
                        // Return the body scroller to default; X and Y scrolling
                        scroller.setConfig({
                            x: true,
                            y: true
                        });
                        me.scrollable = null;
                    }
 
                    // Clear any properties from the inheritedState so we don't pollute the
                    // global namespace. If we have a rtl flag set, leave it alone because it's
                    // likely we didn't write it
                    for (key in root) {
                        if (key !== 'rtl') {
                            delete root[key];
                        }
                    }
 
                    delete me.el.setHeight;
                    delete me.el.setWidth;
                    me.removeUIFromElement();
                    me.el.removeCls(me.baseCls);
                    Ext.fly(document.body.parentNode).removeCls(me.viewportCls);
 
                    me.callParent();
                },
 
                addMeta: function(name, content) {
                    var meta = document.createElement('meta');
 
                    meta.setAttribute('name', name);
                    meta.setAttribute('content', content);
                    Ext.getHead().appendChild(meta, true);
                },
 
                privates: {
                    // override here to prevent an extraneous warning
                    applyTargetCls: function(targetCls) {
                        var el = this.el;
 
                        if (el === this.getTargetEl()) {
                            this.el.addCls(targetCls);
                        }
                        else {
                            this.callParent([targetCls]);
                        }
                    },
 
                    // Override here to prevent tabIndex set/reset on the body
                    disableTabbing: function() {
                        var el = this.el;
 
                        if (el) {
                            el.saveTabbableState({
                                skipSelf: true
                            });
                        }
                    },
 
                    enableTabbing: function() {
                        var el = this.el;
 
                        if (el) {
                            el.restoreTabbableState({ skipSelf: true });
                        }
                    },
 
                    updateResponsiveState: function() {
                        // By providing this method we are in sync with the layout suspend/resume as
                        // well as other changes to configs that need to happen during this pulse of
                        // size change.
 
                        // Since we are not using the Viewport plugin beyond applying its methods on
                        // to our prototype, we need to be Responsive ourselves and call this here:
                        this.handleViewportResize();
                        this.callParent();
                    }
 
                }
            });
        }
    }
}, function(Viewport) {
    Viewport.prototype.decorate = Viewport.decorate;
});