/**
 * @class Ext.ux.DataViewTransition
 * Transition plugin for DataViews
 */
/* eslint-disable vars-on-top, one-var */
Ext.ux.DataViewTransition = Ext.extend(Object, {
    /**
     * @property defaults
     * @type Object
     * Default configuration options for all DataViewTransition instances
     */
    defaults: {
        duration: 750,
        idProperty: 'id'
    },

    /**
     * Creates the plugin instance, applies defaults
     * @constructor
     * @param {Object} config Optional config object
     */
    constructor: function(config) {
        Ext.apply(this, config || {}, this.defaults);
    },

    /**
     * Initializes the transition plugin. Overrides the dataview's default refresh function
     * @param {Ext.view.View} dataview The dataview
     */
    init: function(dataview) {
        /**
         * @property dataview
         * @type Ext.view.View
         * Reference to the DataView this instance is bound to
         */
        this.dataview = dataview;

        var idProperty = this.idProperty;

        dataview.blockRefresh = true;
        dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
            this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
                element.id = element.dom.id = Ext.util.Format.format(
                    "{0}-{1}", dataview.id, dataview.store.getAt(index).get(idProperty)
                );
            }, this);
        }, dataview);

        /**
         * @property dataviewID
         * @type String
         * The string ID of the DataView component. This is used internally when animating
         * child objects
         */
        this.dataviewID = dataview.id;

        /**
         * @property cachedStoreData
         * @type Object
         * A cache of existing store data, keyed by id. This is used to determine
         * whether any items were added or removed from the store on data change
         */
        this.cachedStoreData = {};

        // var store = dataview.store;

        // catch the store data with the snapshot immediately
        this.cacheStoreData(dataview.store.snapshot);

        dataview.store.on('datachanged', function(store) {
            var parentEl = dataview.getTargetEl(),
                calcItem = store.getAt(0),
                added = this.getAdded(store),
                removed = this.getRemoved(store),
                previous = this.getRemaining(store);

            // hide old items
            Ext.each(removed, function(item) {
                Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
                    remove: false,
                    duration: duration,
                    opacity: 0,
                    useDisplay: true
                });
            }, this);

            // store is empty
            if (calcItem == undefined) { // eslint-disable-line eqeqeq
                this.cacheStoreData(store);

                return;
            }

            var el = Ext.get(this.dataviewID + "-" + calcItem.get(this.idProperty)),

                // calculate the number of rows and columns we have
                itemWidth = el.getMargin('lr') + el.getWidth(),
                itemHeight = el.getMargin('bt') + el.getHeight(),
                dvWidth = parentEl.getWidth(),
                columns = Math.floor(dvWidth / itemWidth);

            // make sure the correct styles are applied to the parent element
            parentEl.applyStyles({
                display: 'block',
                position: 'relative'
            });

            // stores the current top and left values for each element (discovered below)
            var oldPositions = {},
                newPositions = {},
                elCache = {};

            // find current positions of each element and save a reference in the elCache
            Ext.iterate(previous, function(id, item) {
                // eslint-disable-next-line no-redeclare
                var id = item.get(this.idProperty),
                    el = elCache[id] = Ext.get(this.dataviewID + '-' + id);

                oldPositions[id] = {
                    top: el.getY() - parentEl.getY() - el.getMargin('t') - parentEl.getPadding('t'),
                    left: el.getX() - parentEl.getX() - el.getMargin('l') - parentEl.getPadding('l')
                };
            }, this);

            // set absolute positioning on all DataView items. We need to set position, left and 
            // top at the same time to avoid any flickering
            Ext.iterate(previous, function(id, item) {
                var oldPos = oldPositions[id],
                    el = elCache[id];

                if (el.getStyle('position') !== 'absolute') {
                    elCache[id].applyStyles({
                        position: 'absolute',
                        left: oldPos.left + "px",
                        top: oldPos.top + "px",

                        // we set the width here to make ListViews work correctly.
                        // This is not needed for DataViews
                        width: el.getWidth(!Ext.isIE || Ext.isStrict),
                        height: el.getHeight(!Ext.isIE || Ext.isStrict)
                    });
                }
            });

            // get new positions
            var index = 0;

            Ext.iterate(store.data.items, function(item) {
                var id = item.get(idProperty),
                    column = index % columns,
                    row = Math.floor(index / columns),
                    top = row * itemHeight,
                    left = column * itemWidth;

                newPositions[id] = {
                    top: top,
                    left: left
                };

                index ++;
            }, this);

            // do the movements
            var startTime = new Date(),
                duration = this.duration,
                dataviewID = this.dataviewID,

                doAnimate = function() {
                    var elapsed = new Date() - startTime,
                        fraction = elapsed / duration,
                        id;

                    if (fraction >= 1) {
                        for (id in newPositions) {
                            Ext.fly(dataviewID + '-' + id).applyStyles({
                                top: newPositions[id].top + "px",
                                left: newPositions[id].left + "px"
                            });
                        }

                        Ext.TaskManager.stop(task);
                    }
                    else {
                    // move each item
                        for (id in newPositions) {
                            if (!previous[id]) {
                                continue;
                            }

                            var oldPos = oldPositions[id],
                                newPos = newPositions[id],
                                oldTop = oldPos.top,
                                newTop = newPos.top,
                                oldLeft = oldPos.left,
                                newLeft = newPos.left,
                                diffTop = fraction * Math.abs(oldTop - newTop),
                                diffLeft = fraction * Math.abs(oldLeft - newLeft),
                                midTop = oldTop > newTop ? oldTop - diffTop : oldTop + diffTop,
                                midLeft = oldLeft > newLeft
                                    ? oldLeft - diffLeft
                                    : oldLeft + diffLeft;

                            Ext.fly(dataviewID + '-' + id).applyStyles({
                                top: midTop + "px",
                                left: midLeft + "px"
                            });
                        }
                    }
                },

                task = {
                    run: doAnimate,
                    interval: 20,
                    scope: this
                };

            Ext.TaskManager.start(task);

            //<debug>
            var count = 0;

            for (var k in added) { // eslint-disable-line no-unused-vars
                count++;
            }

            if (Ext.global.console && Ext.global.console.log) {
                Ext.global.console.log('added:', count);
            }
            //</debug>

            // show new items
            Ext.iterate(added, function(id, item) {
                Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).applyStyles({
                    top: newPositions[item.get(this.idProperty)].top + "px",
                    left: newPositions[item.get(this.idProperty)].left + "px"
                });

                Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
                    remove: false,
                    duration: duration,
                    opacity: 1
                });
            }, this);

            this.cacheStoreData(store);
        }, this);
    },

    /**
     * Caches the records from a store locally for comparison later
     * @param {Ext.data.Store} store The store to cache data from
     */
    cacheStoreData: function(store) {
        this.cachedStoreData = {};

        store.each(function(record) {
            this.cachedStoreData[record.get(this.idProperty)] = record;
        }, this);
    },

    /**
     * Returns all records that were already in the DataView
     * @return {Object} All existing records
     */
    getExisting: function() {
        return this.cachedStoreData;
    },

    /**
     * Returns the total number of items that are currently visible in the DataView
     * @return {Number} The number of existing items
     */
    getExistingCount: function() {
        var count = 0,
            items = this.getExisting();

        for (var k in items) { // eslint-disable-line no-unused-vars
            count++;
        }

        return count;
    },

    /**
     * Returns all records in the given store that were not already present
     * @param {Ext.data.Store} store The updated store instance
     * @return {Object} Object of records not already present in the dataview in format {id: record}
     */
    getAdded: function(store) {
        var added = {};

        store.each(function(record) {
            // eslint-disable-next-line eqeqeq
            if (this.cachedStoreData[record.get(this.idProperty)] == undefined) {
                added[record.get(this.idProperty)] = record;
            }
        }, this);

        return added;
    },

    /**
     * Returns all records that are present in the DataView but not the new store
     * @param {Ext.data.Store} store The updated store instance
     * @return {Array} Array of records that used to be present
     */
    getRemoved: function(store) {
        var removed = [];

        for (var id in this.cachedStoreData) {
            if (store.findExact(this.idProperty, Number(id)) === -1) {
                removed.push(this.cachedStoreData[id]);
            }
        }

        return removed;
    },

    /**
     * Returns all records that are already present and are still present in the new store
     * @param {Ext.data.Store} store The updated store instance
     * @return {Object} Object of records that are still present from last time in format
     * {id: record}
     */
    getRemaining: function(store) {
        var remaining = {};

        store.each(function(record) {
            /* eslint-disable-next-line eqeqeq */
            if (this.cachedStoreData[record.get(this.idProperty)] != undefined) {
                remaining[record.get(this.idProperty)] = record;
            }
        }, this);

        return remaining;
    }
});
