/*
 * Note that this control will most likely remain as an example, and not as a core Ext form
 * control.  However, the API will be changing in a future release and so should not yet be
 * treated as a final, stable API at this time.
 */

/**
 * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
 */
Ext.define('Ext.ux.form.ItemSelector', {
	extend: 'Ext.ux.form.MultiSelect',
	alias: ['widget.itemselectorfield', 'widget.itemselector'],
	alternateClassName: ['Ext.ux.ItemSelector'],
	/**
	 * @cfg {Boolean} [hideNavIcons=false] True to hide the navigation icons
	 */
	hideNavIcons: false,

	/**
	 * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector
	 * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used
	 * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}.
	 * This can be overridden with a custom Array to change which buttons are displayed or their order.
	 */
	buttons: ['top', 'up', 'add', 'remove', 'down', 'bottom'],

	/**
	 * @cfg {Object} buttonsText The tooltips for the {@link #buttons}.
	 * Labels for buttons.
	 */
	buttonsText: {
		top: "Move to Top",
		up: "Move Up",
		add: "Add to Selected",
		remove: "Remove from Selected",
		down: "Move Down",
		bottom: "Move to Bottom"
	},

	layout: {
		type: 'hbox',
		align: 'stretch'
	},

	initComponent: function() {
		var me = this;

		me.ddGroup = me.id + '-dd';
		me.callParent();

		// bindStore must be called after the fromField has been created because
		// it copies records from our configured Store into the fromField's Store
		me.bindStore(me.store);
	},

	createList: function(title) {
		var me = this;

		return Ext.create('Ext.ux.form.MultiSelect', {
			// We don't want the multiselects themselves to act like fields,
			// so override these methods to prevent them from including
			// any of their values
			submitValue: false,
			getSubmitData: function() {
				return null;
			},
			getModelData: function() {
				return null;
			},
			flex: 1,
			dragGroup: me.ddGroup,
			dropGroup: me.ddGroup,
			title: title,
			store: {
				model: me.store.model,
				data: []
			},
			displayField: me.displayField,
			valueField: me.valueField,
			disabled: me.disabled,
			listConfig: {
				tpl: me.tpl
			},
			listeners: {
				boundList: {
					scope: me,
					itemdblclick: me.onItemDblClick,
					drop: me.syncValue
				}
			}
		});
	},

	setupItems: function() {
		var me = this;

		me.fromField = me.createList(me.fromTitle);
		me.toField = me.createList(me.toTitle);

		return [
			me.fromField,
			{
				xtype: 'container',
				margin: '0 4',
				layout: {
					type: 'vbox',
					pack: 'center'
				},
				items: me.createButtons()
			},
			me.toField
		];
	},

	createButtons: function() {

		var me = this;
		var buttons = [];

		if (!me.hideNavIcons) {
			Ext.Array.each(me.buttons, function(name) {
				buttons.push({
					xtype: 'button',
					tooltip: me.buttonsText[name],
					handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
					cls: Ext.baseCSSPrefix + 'form-itemselector-btn',
					iconCls: Ext.baseCSSPrefix + 'form-itemselector-' + name,
					navBtn: true,
					scope: me,
					margin: '4 0 0 0'
				});
			});
		}
		return buttons;
	},

	/**
	 * Get the selected records from the specified list.
	 *
	 * Records will be returned *in store order*, not in order of selection.
	 * @param {Ext.view.BoundList} list The list to read selections from.
	 * @return {Ext.data.Model[]} The selected records in store order.
	 *
	 */
	getSelections: function(list) {
		var store = list.getStore();

		return Ext.Array.sort(list.getSelectionModel().getSelection(), function(a, b) {
			a = store.indexOf(a);
			b = store.indexOf(b);

			if (a < b) {
				return -1;
			}
			else if (a > b) {
				return 1;
			}
			return 0;
		});
	},

	onTopBtnClick: function() {
		var list = this.toField.boundList,
			store = list.getStore(),
			selected = this.getSelections(list);

		store.suspendEvents();
		store.remove(selected, true);
		store.insert(0, selected);
		store.resumeEvents();
		list.refresh();
		this.syncValue();
		list.getSelectionModel().select(selected);
	},

	onBottomBtnClick: function() {
		var list = this.toField.boundList,
			store = list.getStore(),
			selected = this.getSelections(list);

		store.suspendEvents();
		store.remove(selected, true);
		store.add(selected);
		store.resumeEvents();
		list.refresh();
		this.syncValue();
		list.getSelectionModel().select(selected);
	},

	onUpBtnClick: function() {
		var list = this.toField.boundList,
			store = list.getStore(),
			selected = this.getSelections(list),
			rec,
			i = 0,
			len = selected.length,
			index = 0;

		// Move each selection up by one place if possible
		store.suspendEvents();
		for (; i < len; ++i, index++) {
			rec = selected[i];
			index = Math.max(index, store.indexOf(rec) - 1);
			store.remove(rec, true);
			store.insert(index, rec);
		}
		store.resumeEvents();
		list.refresh();
		this.syncValue();
		list.getSelectionModel().select(selected);
	},

	onDownBtnClick: function() {
		var list = this.toField.boundList,
			store = list.getStore(),
			selected = this.getSelections(list),
			rec,
			i = selected.length - 1,
			index = store.getCount() - 1;

		// Move each selection down by one place if possible
		store.suspendEvents();
		for (; i > -1; --i, index--) {
			rec = selected[i];
			index = Math.min(index, store.indexOf(rec) + 1);
			store.remove(rec, true);
			store.insert(index, rec);
		}
		store.resumeEvents();
		list.refresh();
		this.syncValue();
		list.getSelectionModel().select(selected);
	},

	onAddBtnClick: function() {
		var me = this,
			selected = me.getSelections(me.fromField.boundList);

		me.moveRec(true, selected);
		me.toField.boundList.getSelectionModel().select(selected);
	},

	onRemoveBtnClick: function() {
		var me = this,
			selected = me.getSelections(me.toField.boundList);

		me.moveRec(false, selected);
		me.fromField.boundList.getSelectionModel().select(selected);
	},

	moveRec: function(add, recs) {
		var me = this,
			fromField = me.fromField,
			toField = me.toField,
			fromStore = add ? fromField.store : toField.store,
			toStore = add ? toField.store : fromField.store;

		fromStore.suspendEvents();
		toStore.suspendEvents();
		fromStore.remove(recs);
		toStore.add(recs);
		fromStore.resumeEvents();
		toStore.resumeEvents();

		fromField.boundList.refresh();
		toField.boundList.refresh();

		me.syncValue();
	},

	// Synchronizes the submit value with the current state of the toStore
	syncValue: function() {
		var me = this;
		me.mixins.field.setValue.call(me, me.setupValue(me.toField.store.getRange()));
	},

	onItemDblClick: function(view, rec) {
		this.moveRec(view === this.fromField.boundList, rec);
	},

	setValue: function(value) {
		var me = this,
			fromField = me.fromField,
			toField = me.toField,
			fromStore = fromField.store,
			toStore = toField.store,
			selected;

		// Wait for from store to be loaded
		if (!me.fromStorePopulated) {
			me.fromField.store.on({
				load: Ext.Function.bind(me.setValue, me, [value]),
				single: true
			});
			return;
		}

		value = me.setupValue(value);
		me.mixins.field.setValue.call(me, value);

		selected = me.getRecordsForValue(value);

		// Clear both left and right Stores.
		// Both stores must not fire events during this process.
		fromStore.suspendEvents();
		toStore.suspendEvents();
		fromStore.removeAll();
		toStore.removeAll();

		// Reset fromStore
		me.populateFromStore(me.store);

		// Copy selection across to toStore
		Ext.Array.each(selected, function(rec) {
			// In the from store, move it over
			if (fromStore.indexOf(rec) > -1) {
				fromStore.remove(rec);
			}
			toStore.add(rec);
		});

		// Stores may now fire events
		fromStore.resumeEvents();
		toStore.resumeEvents();

		// Refresh both sides and then update the app layout
		Ext.suspendLayouts();
		fromField.boundList.refresh();
		toField.boundList.refresh();
		Ext.resumeLayouts(true);
	},

	onBindStore: function(store, initial) {
		var me = this;

		if (me.fromField) {
			me.fromField.store.removeAll();
			me.toField.store.removeAll();

			// Add everything to the from field as soon as the Store is loaded
			if (store.getCount()) {
				me.populateFromStore(store);
			}
			else {
				me.store.on('load', me.populateFromStore, me);
			}
		}
	},

	populateFromStore: function(store) {
		var fromStore = this.fromField.store;

		// Flag set when the fromStore has been loaded
		this.fromStorePopulated = true;

		fromStore.add(store.getRange());

		// setValue waits for the from Store to be loaded
		fromStore.fireEvent('load', fromStore);
	},

	onEnable: function() {
		var me = this;

		me.callParent();
		me.fromField.enable();
		me.toField.enable();

		Ext.Array.each(me.query('[navBtn]'), function(btn) {
			btn.enable();
		});
	},

	onDisable: function() {
		var me = this;

		me.callParent();
		me.fromField.disable();
		me.toField.disable();

		Ext.Array.each(me.query('[navBtn]'), function(btn) {
			btn.disable();
		});
	},

	onDestroy: function() {
		this.bindStore(null);
		this.callParent();
	}
});
