/**
 * This class is the base for `Binding` and `MultiBinding`.
 * @private
 */
Ext.define('Ext.app.bind.BaseBinding', {
    extend: 'Ext.util.Schedulable',

    calls: 0,

    kind: 20,

    defaultOptions: {},

    lastValue: undefined,

    /**
     * @cfg {Boolean} [single=false]
     * This option instructs the binding to call its `destroy` method immediately after
     * delivering the initial value.
     * @since 5.0.0
     */

    constructor: function (owner, callback, scope, options) {
        var me = this;

        me.options = options;
        me.owner = owner;
        me.scope = scope;
        me.callback = callback;

        //<debug>
        if (!callback) {
            Ext.Error.raise('Callback is required');
        }
        //</debug>

        // If given a string callback name, preserve the late binding:
        me.lateBound = Ext.isString(callback);
        if (options && options.deep) {
            me.deep = true;
        }

        me.callParent();
    },

    destroy: function () {
        var me = this,
            owner = me.owner;

        me.callParent();
        if (owner) {
            owner.onBindDestroy();
        }
        me.scope = me.callback = me.owner = null;
    },

    privates: {
        getScheduler: function () {
            var owner = this.owner;
            return owner && owner.getScheduler();
        },

        getSession: function () {
            var owner = this.owner;
            return owner.isSession ? owner : owner.getSession();
        },

        notify: function (value) {
            var me = this,
                options = me.options || me.defaultOptions,
                previous = me.lastValue;

            // We want to deliver if:
            // 1) We've never been called
            // 2) We're a deep binding, which means that our object reference may not have changed,
            //    but something under us has changed. For example a link stub or a model field binding
            // 3) If the value has changed
            // 4) If the value is an array. It's difficult to tell if the underlying data changed
            if (!me.calls || me.deep || previous !== value || Ext.isArray(value)) {
                ++me.calls;
                me.lastValue = value;

                if (me.lateBound) {
                    // Interestingly, lateBound-ness may be more efficient since it does
                    // not use the "call" method.
                    me.scope[me.callback](value, previous, me);
                } else {
                    me.callback.call(me.scope, value, previous, me);
                }

                if (options.single) {
                    me.destroy();
                }
            }
        }
    }
});
