/**
 * This component provides a grid holding selected items from a second store of potential
 * members. The `store` of this component represents the selected items. The `searchStore`
 * represents the potentially selected items.
 *
 * The default view defined by this class is intended to be easily replaced by deriving a
 * new class and overriding the appropriate methods. For example, the following is a very
 * different view that uses a date range and a data view:
 *
 *      Ext.define('App.view.DateBoundSearch', {
 *          extend: 'Ext.view.MultiSelectorSearch',
 *
 *          makeDockedItems: function () {
 *              return {
 *                  xtype: 'toolbar',
 *                  items: [{
 *                      xtype: 'datefield',
 *                      emptyText: 'Start date...',
 *                      flex: 1
 *                  },{
 *                      xtype: 'datefield',
 *                      emptyText: 'End date...',
 *                      flex: 1
 *                  }]
 *              };
 *          },
 *
 *          makeItems: function () {
 *              return [{
 *                  xtype: 'dataview',
 *                  itemSelector: '.search-item',
 *                  selModel: 'rowselection',
 *                  store: this.store,
 *                  scrollable: true,
 *                  tpl:
 *                      '<tpl for=".">' +
 *                          '<div class="search-item">' +
 *                              '<img src="{icon}">' +
 *                              '<div>{name}</div>' +
 *                          '</div>' +
 *                      '</tpl>'
 *              }];
 *          },
 *
 *          getSearchStore: function () {
 *              return this.items.getAt(0).getStore();
 *          },
 *
 *          selectRecords: function (records) {
 *              var view = this.items.getAt(0);
 *              return view.getSelectionModel().select(records);
 *          }
 *      });
 *
 * **Important**: This class assumes there are two components with specific `reference`
 * names assigned to them. These are `"searchField"` and `"searchGrid"`. These components
 * are produced by the `makeDockedItems` and `makeItems` method, respectively. When
 * overriding these it is important to remember to place these `reference` values on the
 * appropriate components.
 */
Ext.define('Ext.view.MultiSelectorSearch', {
    extend: 'Ext.panel.Panel',
 
    xtype: 'multiselector-search',
 
    /**
     * @cfg layout
     * @inheritdoc
     */
    layout: 'fit',
 
    /**
     * @cfg floating
     * @inheritdoc
     */
    floating: true,
 
    /**
     * @cfg alignOnScroll
     * @inheritdoc
     */
    alignOnScroll: false,
 
    /**
     * @cfg minWidth
     * @inheritdoc
     */
    minWidth: 200,
 
    /**
     * @cfg minHeight
     * @inheritdoc
     */
    minHeight: 200,
 
    /**
     * @cfg border
     * @inheritdoc
     */
    border: true,
 
    /**
     * @cfg keyMap
     * @inheritdoc
     */
    keyMap: {
        scope: 'this',
        ESC: 'hide'
    },
 
    platformConfig: {
        desktop: {
            resizable: true
        },
        'tablet && rtl': {
            resizable: {
                handles: 'sw'
            }
        },
        'tablet && !rtl': {
            resizable: {
                handles: 'se'
            }
        }
    },
 
    /**
     * @cfg defaultListenerScope
     * @inheritdoc
     */
    defaultListenerScope: true,
 
    /**
     * @cfg referenceHolder
     * @inheritdoc
     */
    referenceHolder: true,
 
    /**
     * @cfg {String} field
     * A field from your grid's store that will be used for filtering your search results.
     */
 
    /**
     * @cfg store
     * @inheritdoc Ext.panel.Table#cfg-store
     */
 
    /**
     * @cfg {String} searchText
     * This text is displayed as the "emptyText" of the search `textfield`.
     */
    searchText: 'Search...',
 
    initComponent: function() {
        var me = this,
            owner = me.owner,
            items = me.makeItems(),
            i, item, records, store;
 
        me.dockedItems = me.makeDockedItems();
        me.items = items;
 
        store = Ext.data.StoreManager.lookup(me.store);
 
        for (= items.length; i--;) {
            if ((item = items[i]).xtype === 'grid') {
                item.store = store;
                item.isSearchGrid = true;
                item.selModel = item.selModel || {
                    type: 'checkboxmodel',
                    pruneRemoved: false,
                    listeners: {
                        selectionchange: 'onSelectionChange'
                    }
                };
 
                Ext.merge(item, me.grid);
 
                if (!item.columns) {
                    item.hideHeaders = true;
                    item.columns = [{
                        flex: 1,
                        dataIndex: me.field
                    }];
                }
 
                break;
            }
        }
 
        me.callParent();
 
        records = me.getOwnerStore().getRange();
 
        if (!owner.convertSelectionRecord.$nullFn) {
            for (= records.length; i--;) {
                records[i] = owner.convertSelectionRecord(records[i]);
            }
        }
 
        if (store.isLoading() || (store.loadCount === 0 && !store.getCount())) {
 
            // If it is NOT a preloaded store, then unless a Session is being used,
            // The newly loaded records will NOT match any in the ownerStore.
            // So we must match them by ID in order to select the same dataset.
            store.on('load', function() {
                if (!me.destroyed) {
                    me.selectRecords(records);
                }
            }, null, { single: true });
        }
        else {
            me.selectRecords(records);
        }
    },
 
    getOwnerStore: function() {
        return this.owner.getStore();
    },
 
    afterShow: function() {
        var searchField;
 
        this.callParent(arguments);
 
        // Do not focus if this was invoked by a touch gesture
        if (!this.invocationEvent || this.invocationEvent.pointerType !== 'touch') {
            searchField = this.lookupReference('searchField');
 
            if (searchField) {
                searchField.focus();
            }
        }
 
        this.invocationEvent = null;
    },
 
    /**
     * Returns the store that holds search results. By default this comes from the
     * "search grid". If this aspect of the view is changed sufficiently so that the
     * search grid cannot be found, this method should be overridden to return the proper
     * store.
     * @return {Ext.data.Store} 
     */
    getSearchStore: function() {
        var searchGrid = this.lookupReference('searchGrid');
 
        return searchGrid.getStore();
    },
 
    makeDockedItems: function() {
        return [{
            xtype: 'textfield',
            reference: 'searchField',
            dock: 'top',
            hideFieldLabel: true,
            emptyText: this.searchText,
            cls: Ext.baseCSSPrefix + 'multiselector-search-input',
            triggers: {
                clear: {
                    cls: Ext.baseCSSPrefix + 'form-clear-trigger',
                    handler: 'onClearSearch',
                    hidden: true
                }
            },
            listeners: {
                specialKey: 'onSpecialKey',
                change: {
                    fn: 'onSearchChange',
                    buffer: 300
                }
            }
        }];
    },
 
    onSpecialKey: function(field, event) {
        if (event.getKey() === event.TAB && event.shiftKey) {
            event.preventDefault();
            this.owner.searchTool.focus();
        }
    },
 
    makeItems: function() {
        return [{
            xtype: 'grid',
            reference: 'searchGrid',
            trailingBufferZone: 2,
            leadingBufferZone: 2,
            viewConfig: {
                deferEmptyText: false,
                emptyText: 'No results.'
            }
        }];
    },
 
    getMatchingRecords: function(records) {
        var searchGrid = this.lookupReference('searchGrid'),
            store = searchGrid.getStore(),
            selections = [],
            i, record, len;
 
        records = Ext.isArray(records) ? records : [records];
 
        for (= 0, len = records.length; i < len; i++) {
            record = store.getById(records[i].getId());
 
            if (record) {
                selections.push(record);
            }
        }
 
        return selections;
    },
 
    selectRecords: function(records) {
        var searchGrid = this.lookupReference('searchGrid');
 
        // match up passed records to the records in the search store so that the right
        // internal ids are used
        records = this.getMatchingRecords(records);
 
        return searchGrid.getSelectionModel().select(records);
    },
 
    deselectRecords: function(records) {
        var searchGrid = this.lookupReference('searchGrid');
 
        // match up passed records to the records in the search store so that the right
        // internal ids are used
        records = this.getMatchingRecords(records);
 
        return searchGrid.getSelectionModel().deselect(records);
    },
 
    search: function(text) {
        var me = this,
            filter = me.searchFilter,
            filters = me.getSearchStore().getFilters();
 
        if (text) {
            filters.beginUpdate();
 
            if (filter) {
                filter.setValue(text);
            }
            else {
                me.searchFilter = filter = new Ext.util.Filter({
                    id: 'search',
                    property: me.field,
                    value: text
                });
            }
 
            filters.add(filter);
 
            filters.endUpdate();
        }
        else if (filter) {
            filters.remove(filter);
        }
    },
 
    privates: {
        onClearSearch: function() {
            var searchField = this.lookupReference('searchField');
 
            searchField.setValue(null);
            searchField.focus();
        },
 
        onSearchChange: function(searchField) {
            var value = searchField.getValue(),
                trigger = searchField.getTrigger('clear');
 
            trigger.setHidden(!value);
            this.search(value);
        },
 
        onSelectionChange: function(selModel, selection) {
            var owner = this.owner,
                store = owner.getStore(),
                data = store.data,
                remove = 0,
                map = {},
                add, i, id, record;
 
            for (= selection.length; i--;) {
                record = selection[i];
                id = record.id;
                map[id] = record;
 
                if (!data.containsKey(id)) {
                    (add || (add = [])).push(owner.convertSearchRecord(record));
                }
            }
 
            for (= data.length; i--;) {
                record = data.getAt(i);
 
                if (!map[record.id]) {
                    (remove || (remove = [])).push(record);
                }
            }
 
            if (add || remove) {
                data.splice(data.length, remove, add);
            }
        }
    }
});