/**
 * This class manages one side of a `Matrix`.
 * @private
 */
Ext.define('Ext.data.matrix.Slice', {
    constructor: function(side, id) {
        /**
         * @property {String/Number} id
         * The id of the interested entity. Based on whether this slice is on the "left"
         * or "right" of the matrix, this id identities the respective entity.
         * @readonly
         */
        this.id = id;
 
        /**
         * @property {Ext.data.matrix.Side} side
         * The side of the matrix to which this slice belongs.
         */
        this.side = side;
 
        /**
         * 
         */
        this.members = {};
    },
 
    attach: function(store) {
        var me = this;
        
        //<debug>
        Ext.Assert.falsey(me.store, 'Store is already attached');
        //</debug>
 
        me.store = store;
        store.matrix = me;
 
        store.on('load', me.onStoreLoad, me, { single: true });
    },
 
    commit: function() {
        var members = this.members,
            id;
 
        for (id in members) {
            members[id][2] = 0;
        }
    },
 
    onStoreLoad: function(store) {
        this.update(store.getData().items, 0);
    },
 
    update: function(recordsOrIds, state) {
        //<debug>
        if (!(recordsOrIds instanceof Array)) {
            Ext.raise('Only array of records or record ids are supported');
        }
        //</debug>
 
        /* eslint-disable-next-line vars-on-top */
        var me = this,
            MatrixSlice = Ext.data.matrix.Slice,
            side = me.side,
            assocIndex = side.index,
            length = recordsOrIds.length,
            id = me.id,
            members = me.members,
            otherSide = side.inverse,
            otherSlices = otherSide.slices,
            assoc, call, i, item, otherId, otherSlice, record; // eslint-disable-line no-unused-vars
 
        for (= 0; i < length; ++i) {
            call = record = null;
            item = recordsOrIds[i];
            otherId = item.isEntity ? (record = item).id : item;
            assoc = members[otherId];
 
            // If we're in a created state and we're asking to remove it, don't move it to
            // removed state, just blow it away completely.
            if (state < 0 && assoc && assoc[2] === 1) {
                delete members[otherId];
                otherSlice = otherSlices[otherId];
                
                if (otherSlice) {
                    delete otherSlice.members[id];
                }
                
                call = 1;
            }
            else {
                if (!assoc) {
                    // Note - when we create a new matrix tuple we must catalog it on both
                    // sides of the matrix or risk losing it on only one side. To gather all
                    // of the tuples we need only visit one side.
                    assoc = [ otherId, otherId, state ];
                    assoc[assocIndex] = id;
 
                    members[otherId] = assoc;
                    otherSlice = otherSlices[otherId];
                    
                    if (!otherSlice) {
                        otherSlices[otherId] = otherSlice = new MatrixSlice(otherSide, otherId);
                    }
                    
                    otherSlice.members[id] = assoc;
                    call = 1;
                }
                else if (state !== assoc[2] && state !== 0 && !(state === 1 && assoc[2] === 0)) {
                    // If they aren't equal and we're setting it to 0, favour the current state,
                    // except in the case where it's trying to mark as added when we already have
                    // it as present
                    assoc[2] = state;
                    otherSlice = otherSlices[otherId];
                    // because the assoc exists the other side will have a slice
                    call = 1;
                }
            }
 
            if (call) {
                if (me.notify) {
                    me.notify.call(me.scope, me, otherId, state);
                }
                
                if (otherSlice && otherSlice.notify) {
                    otherSlice.notify.call(otherSlice.scope, otherSlice, id, state);
                }
            }
        }
    },
 
    updateId: function(newId) {
        var me = this,
            oldId = me.id,
            side = me.side,
            slices = side.slices,
            slice = slices[oldId],
            members = slice.members,
            index = side.index,
            otherSlices = side.inverse.slices,
            assoc, otherId, otherMembers;
 
        me.id = newId;
        slices[newId] = slice;
        delete slices[oldId];
 
        for (otherId in members) {
            assoc = members[otherId];
            assoc[index] = newId;
 
            otherMembers = otherSlices[otherId].members;
            otherMembers[newId] = otherMembers[oldId];
            delete otherMembers[oldId];
        }
    },
 
    destroy: function() {
        var me = this,
            store = me.store;
 
        if (store) {
            store.matrix = null;
            store.un('load', me.onStoreLoad, me);
        }
 
        me.notify = me.scope = me.store = me.side = me.members = null;
        me.callParent();
    }
});