/**
 * DataView makes it easy to create lots of components dynamically, usually based off a {@link Ext.data.Store Store}.
 * It's great for rendering lots of data from your server backend or any other data source and is what powers
 * components like {@link Ext.List}.
 *
 * Use DataView whenever you want to show sets of the same component many times, for examples in apps like these:
 *
 * * List of messages in an email app
 * * Showing latest news/tweets
 * * Tiled set of albums in an HTML5 music player
 *
 * <h2>Creating a Simple DataView</h2>
 *
 * At its simplest, a DataView is just a Store full of data and a simple template that we use to render each item:
 *
 *     var touchTeam = Ext.create('Ext.DataView', {
 *         store: {
 *             fields: ['name', 'age'],
 *             data: [
 *                 {name: 'Jamie Avins',  age: 100},
 *                 {name: 'Rob Dougan',   age: 21},
 *                 {name: 'Tommy Maintz', age: 24},
 *                 {name: 'Jacky Nguyen', age: 24},
 *                 {name: 'Ed Spencer',   age: 26}
 *             ]
 *         },
 *
 *         itemConfig: {
 *             tpl: '{name} is {age} years old'
 *         }
 *     });
 *
 * Here we just defined everything inline so it's all local with nothing being loaded from a server. For each of the 5
 * data items defined in our Store, DataView will render a {@link Ext.Component Component} and pass in the name and age
 * data. The component will use the tpl we provided above, rendering the data in the curly bracket placeholders we
 * provided.
 *
 * Because DataView is integrated with Store, any changes to the Store are immediately reflected on the screen. For
 * example, if we add a new record to the Store it will be rendered into our DataView:
 *
 *     touchTeam.getStore().add({
 *         name: 'Abe Elias',
 *         age: 33
 *     });
 *
 * We didn't have to manually update the DataView, it's just automatically updated. The same happens if we modify one
 * of the existing records in the Store:
 *
 *     touchTeam.getStore().getAt(0).set('age', 42);
 *
 * This will get the first record in the Store (Jamie), change the age to 42 and automatically update what's on the
 * screen.
 *
 * <h2>Loading data from a server</h2>
 *
 * We often want to load data from our server or some other web service so that we don't have to hard code it all
 * locally. Let's say we want to load all of the latest tweets about Sencha Touch into a DataView, and for each one
 * render the user's profile picture, user name and tweet message. To do this all we have to do is modify the
 * {@link #store} and {@link #itemConfig} a little:
 *
 *     Ext.create('Ext.DataView', {
 *         fullscreen: true,
 *         store: {
 *             autoLoad: true,
 *             fields: ['from_user', 'text', 'profile_image_url'],
 *
 *             proxy: {
 *                 type: 'jsonp',
 *                 url: 'http://search.twitter.com/search.json?q=Sencha Touch',
 *
 *                 reader: {
 *                     type: 'json',
 *                     root: 'results'
 *                 }
 *             }
 *         },
 *
 *         itemConfig: {
 *             tpl: '<img src="{profile_image_url}" /><h2>{from_user}</h2><p>{text}</p>'
 *         }
 *     });
 *
 * The Store no longer has hard coded data, instead we've provided a {@link Ext.data.proxy.Proxy Proxy}, which fetches
 * the data for us. In this case we used a JSON-P proxy so that we can load from Twitter's JSON-P search API. We also
 * specified the fields present for each tweet, and used Store's {@link Ext.data.Store#autoLoad autoLoad} configuration
 * to load automatically. Finally, we configured a Reader to decode the response from Twitter, telling it to expect
 * JSON and that the tweets can be found in the 'results' part of the JSON response.
 *
 * The last thing we did is update our template to render the image, twitter username and message. All we need to do
 * now is add a little CSS to style the list the way we want it and we end up with this
 *
 * <<<<<<<<<<<<<< SCREENSHOT HERE >>>>>>>>>>>>>
 *
 */
Ext.define('Ext.dataview.ComponentView', {
    extend: 'Ext.dataview.DataView',

    alternateClassName: 'Ext.ComponentView',

    xtype: 'componentview',

    requires: [
        'Ext.dataview.DataItem'
    ],

    config: {
        // @inherit
        defaultType: 'dataitem',

        /**
         * @cfg {Object} itemConfig
         * A configuration object that is passed to every item created by the DataView. Because each item that a
         * DataView renders is a Component, we can pass configuration options to each component to easily customize how
         * each child component behaves.
         * @accessor
         */
        itemConfig: {},

        maxItemCache: 20
    },

    constructor: function() {
        this.itemCache = [];
        this.callParent(arguments);
    },

    //@private
    doInitialize: function() {
        var me = this,
            triggerObj = {
                delegate: '> ' + me.getDefaultType(),
                scope: me
            },
            clearObj = {
                scope: me
            };

        clearObj[me.getTriggerCtEvent()] = 'onContainerTrigger';
        me.element.on(clearObj);

        triggerObj[me.getTriggerEvent()] = 'onItemTrigger';
        me.on(triggerObj);

        me.on({
            delegate: '> ' + me.getDefaultType(),
            scope   : me,

            touchstart: 'onItemTouchStart',
            touchend: 'onItemTouchEnd',
            tap: 'onItemTap',
            touchmove: 'onItemTouchMove',
            doubletap: 'onItemDoubleTap',
            swipe: 'onItemSwipe'
        });
    },

    // apply to the selection model to maintain visual UI cues
    onItemTrigger: function(item, record, e) {
        this.selectWithEvent(record, e);
    },

    // apply to the selection model to maintain visual UI cues
    onContainerTrigger: function() {
        var me = this;
        if (me.getDeselectOnContainerClick() && me.getStore()) {
            me.deselectAll();
        }
    },

    // invoked by the selection model to maintain visual UI cues
    doItemSelect: function(me, record) {
        var item = me.getViewItems()[me.getStore().indexOf(record)];
        item.removeCls(me.getPressedCls());
        item.addCls(me.getSelectedCls());
    },

    doItemDeSelect: function(me, record) {
        var item = me.getViewItems()[me.getStore().indexOf(record)];
        if (item) {
            item.removeCls([me.getPressedCls(), me.getSelectedCls()]);
        }
    },

    doAddPressedCls: function(record) {
        var me = this,
        item = me.getViewItems()[me.getStore().indexOf(record)];
        item.addCls(me.getPressedCls());
    },

    onItemTouchStart: function(item, record, e) {
        var me = this,
            pressedDelay = me.getPressedDelay();
        if (record) {
            if (pressedDelay > 0) {
                me.pressedTimeout = Ext.defer(me.doAddPressedCls, pressedDelay, me, [record]);
            }
            else {
                me.doAddPressedCls(record);
            }
        }
        item.on({
            touchmove: 'onItemTouchMove',
            scope   : me,
            single: true
        });
        me.fireAction('itemtouchstart', [me, me.getViewItems().indexOf(item), item, e], 'doItemTouchStart');
    },

    onItemTouchEnd: function(item, record, e) {
        var me = this;
        if (this.hasOwnProperty('pressedTimeout')) {
            clearTimeout(this.pressedTimeout);
            delete this.pressedTimeout;
        }
        if (record) {
            cmp = me.getViewItems()[me.getStore().indexOf(record)];
            cmp.removeCls(me.getPressedCls());
        }
        item.un({
            touchmove: 'onItemTouchMove',
            scope   : me
        });
        me.fireAction('itemtouchend', [me, me.getViewItems().indexOf(item), item, e], 'doItemTouchEnd');
    },

    onItemTouchMove: function(item, record, e) {
        var me = this;
        if (me.hasOwnProperty('pressedTimeout')) {
            clearTimeout(me.pressedTimeout);
            delete me.pressedTimeout;
        }
        if (record) {
            me.getViewItems()[me.getStore().indexOf(record)].removeCls(me.getPressedCls());
        }
    },

    onItemTap: function(item, record, e) {
        var me = this;
        me.fireAction('itemtap', [me, me.getViewItems().indexOf(item), item, e], 'doItemTap');
    },

    onItemDoubleTap: function(item, record, e) {
        var me = this;
        me.fireAction('itemdoubletap', [me, me.getViewItems().indexOf(item), item, e], 'doItemDoubleTap');
    },

    onItemSwipe: function(item, record, e) {
        var me = this;
        me.fireAction('itemswipe', [me, me.getViewItems().indexOf(item), item, e], 'doItemSwipe');
    },

    moveItemsToCache: function(from, to) {
        var me = this,
            maxItemCache = me.getMaxItemCache(),
            items = me.getViewItems(),
            itemCache = me.itemCache,
            cacheLn = itemCache.length,
            i = to - from,
            item;

        for (; i >= 0; i--) {
            item = items[from + i];
            if (cacheLn !== maxItemCache) {
                me.remove(item, false);
                item.removeCls([me.getPressedCls(), me.getSelectedCls()]);
                itemCache.push(item);
                cacheLn++;
            }
            else {
                item.destroy();
            }
        }
    },

    moveItemsFromCache: function(records) {
        var me = this,
            ln = records.length,
            xtype = me.getDefaultType(),
            itemConfig = me.getItemConfig(),
            itemCache = me.itemCache,
            cacheLn = itemCache.length,
            items = [],
            i = 0,
            item, record;

        for (; i < ln; i++) {
            record = records[i];
            if (cacheLn) {
                cacheLn--;
                item = itemCache.pop();
                item.setRecord(record);
                items.push(item);
            }
            else {
                items.push(me.getDataItemConfig(xtype, record, itemConfig));
            }
        }
        return items;
    },

    getViewItems: function() {
        return this.getInnerItems();
    },

    updateListItem: function(record, item) {
        if (item.setRecord) {
            item.setRecord(record);
        }
    },

    doCreateItems: function(records, ln) {
        this.add(this.moveItemsFromCache(records, ln));
    },

    getDataItemConfig: function(xtype, record, itemConfig) {
        return {
            xtype: xtype,
            record: record,
            defaults: itemConfig
        };
    }
});