/**
 * @private
 */
Ext.define('Ext.util.sizemonitor.Abstract', {
 
    mixins: ['Ext.mixin.Templatable'],
 
    requires: [
        'Ext.TaskQueue'
    ],
 
    config: {
        element: null,
 
        callback: Ext.emptyFn,
 
        scope: null,
 
        args: []
    },
 
    width: null,
 
    height: null,
 
    contentWidth: null,
 
    contentHeight: null,
 
    constructor: function(config) {
        var me = this;
 
        me.refresh = me.refresh.bind(me);
 
        me.info = {
            width: 0,
            height: 0,
            contentWidth: 0,
            contentHeight: 0,
            flag: 0
        };
 
        me.initElement();
 
        me.initConfig(config);
 
        me.bindListeners(true);
    },
 
    bindListeners: Ext.emptyFn,
 
    applyElement: function(element) {
        if (element) {
            return Ext.get(element);
        }
    },
 
    updateElement: function(element) {
        element.append(this.detectorsContainer, true);
        element.addCls(Ext.baseCSSPrefix + 'size-monitored');
    },
 
    applyArgs: function(args) {
        return args.concat([this.info]);
    },
 
    refreshMonitors: Ext.emptyFn,
 
    forceRefresh: function() {
        Ext.TaskQueue.requestRead('refresh', this);
    },
 
    getContentBounds: function() {
        return this.detectorsContainer.getBoundingClientRect();
    },
 
    getContentWidth: function() {
        return this.detectorsContainer.clientWidth;
    },
 
    getContentHeight: function() {
        return this.detectorsContainer.clientHeight;
    },
 
    refreshSize: function() {
        var element = this.getElement();
 
        if (!element || element.destroyed) {
            return false;
        }
 
        // eslint-disable-next-line vars-on-top
        var me = this,
            size = element.measure(),
            width = size.width,
            height = size.height,
            contentWidth = me.getContentWidth(),
            contentHeight = me.getContentHeight(),
            currentContentWidth = me.contentWidth,
            currentContentHeight = me.contentHeight,
            info = me.info,
            resized = false,
            flag;
 
        me.width = width;
        me.height = height;
        me.contentWidth = contentWidth;
        me.contentHeight = contentHeight;
 
        flag = ((currentContentWidth !== contentWidth ? 1 : 0) +
                (currentContentHeight !== contentHeight ? 2 : 0));
 
        if (flag > 0) {
            info.width = width;
            info.height = height;
            info.contentWidth = contentWidth;
            info.contentHeight = contentHeight;
            info.flag = flag;
 
            resized = true;
            me.getCallback().apply(me.getScope(), me.getArgs());
        }
 
        return resized;
    },
 
    refresh: function() {
        if (this.destroying || this.destroyed) {
            return;
        }
 
        this.refreshSize();
 
        // We should always refresh the monitors regardless of whether or not refreshSize
        // resulted in a new size.  This avoids race conditions in situations such as
        // panel placeholder expand where we layout the panel in its expanded state momentarily
        // just so we can measure its animation destination, then immediately collapse it.
        // In such a scenario refreshSize() will be acting on the original size since it
        // is asynchronous, so it will not detect a size change, but we still need to
        // ensure that the monitoring elements are in sync, or else the next resize event
        // will not fire.
        Ext.TaskQueue.requestWrite('refreshMonitors', this);
    },
 
    destroy: function() {
        var me = this,
            element = me.getElement();
 
        me.bindListeners(false);
 
        if (element && !element.destroyed) {
            element.removeCls(Ext.baseCSSPrefix + 'size-monitored');
        }
 
        delete me._element;
 
        // This is a closure so Base destructor won't null it
        me.refresh = null;
 
        me.callParent();
    }
});