define(["dojo/_base/declare", "dojo/_base/array", "dojo/sniff", "dojo/_base/lang", "dojo/Stateful"],
	function(declare, arr, has, lang, Stateful){

	return declare("dojox.widget.Selection", Stateful, {
		// summary:
		//		Base class for widgets that manage a list of selected data items.

		constructor: function(){
			this.selectedItems = [];
		},

		// selectionMode: String
		//		Valid values are:
		//
		//		1. "none": No selection can be done.
		//		2. "single": Only one item can be selected at a time.
		//		3. "multiple": Several item can be selected using the control key modifier.
		//		Default value is "single".
		selectionMode: "single",

		_setSelectionModeAttr: function(value){
			if(value != "none" && value != "single" && value != "multiple"){
				value = "single"; //default value
			}
			if(value != this.selectionMode){
				this.selectionMode = value;
				if(value == "none"){
					this.set("selectedItems", null);
				}else if(value == "single"){
					this.set("selectedItem", this.selectedItem); // null or last selected item
				}
			}
		},

		// selectedItem: Object
		//		In single selection mode, the selected item or in multiple selection mode the last selected item.
		//		Warning: Do not use this property directly, make sure to call set() or get() methods.
		selectedItem: null,

		_setSelectedItemAttr: function(value){
			if(this.selectedItem != value){
				this._set("selectedItem", value);
				this.set("selectedItems", value ? [value] : null);
			}
		},

		// selectedItems: Object[]
		//		The list of selected items.
		//		Warning: Do not use this property directly, make sure to call set() or get() methods.
		selectedItems: null,

		_setSelectedItemsAttr: function(value){
			var oldSelectedItems = this.selectedItems;

			this.selectedItems = value;
			this.selectedItem = null;

			if(oldSelectedItems != null && oldSelectedItems.length>0){
				this.updateRenderers(oldSelectedItems, true);
			}
			if(this.selectedItems && this.selectedItems.length>0){
				this.selectedItem = this.selectedItems[0];
				this.updateRenderers(this.selectedItems, true);
			}
		},

		_getSelectedItemsAttr: function(){
			return this.selectedItems == null ? [] : this.selectedItems.concat();
		},

		isItemSelected: function(item){
			// summary:
			//		Returns whether an item is selected or not.
			// item: Object
			//		The item to test the selection for.
			if(this.selectedItems == null || this.selectedItems.length== 0){
				return false;
			}

			return arr.some(this.selectedItems, lang.hitch(this, function(sitem){
				return this.getIdentity(sitem) == this.getIdentity(item);
			}));
		},

		getIdentity: function(item){
			// summary:
			//		This function must be implemented to return the id of a item.
			// item: Object
			//		The item to query the identity for.
		},

		setItemSelected: function(item, value){
			// summary:
			//		Change the selection state of an item.
			// item: Object
			//		The item to change the selection state for.
			// value: Boolean
			//		True to select the item, false to deselect it.

			if(this.selectionMode == "none" || item == null){
				return;
			}

			// copy is returned
			var sel = this.get("selectedItems");

			if(this.selectionMode == "single"){
				if(value){
					this.set("selectedItem", item);
				}else if(this.isItemSelected(item)){
					this.set("selectedItems", null);
				}
			}else{ // multiple
				if(value){
					if(this.isItemSelected(item)){
						return; // already selected
					}
					if(sel == null){
						sel = [item];
					}else{
						sel.unshift(item);
					}
					this.set("selectedItems", sel);
				}else{
					var res = arr.filter(sel, function(sitem){
						return sitem.id != item.id;
					});
					if(res == null || res.length == sel.length){
						return; // already not selected
					}
					this.set("selectedItems", res);
				}
			}
		},

		selectFromEvent: function(e, item, renderer, dispatch){
			// summary:
			//		Applies selection triggered by an user interaction
			// e: Event
			//		The source event of the user interaction.
			// item: Object
			//		The render item that has been selected/deselected.
			// renderer: Object
			//		The visual renderer of the selected/deselected item.
			// dispatch: Boolean
			//		Whether an event must be dispatched or not.
			// returns: Boolean
			//		Returns true if the selection has changed and false otherwise.
			// tags:
			//		protected

			if(this.selectionMode == "none"){
				return false;
			}

			var changed;
			var oldSelectedItem = this.get("selectedItem");
			var selected = item ? this.isItemSelected(item): false;
			var evtMultiSelectKey = has("mac") ? e.metaKey : e.ctrlKey;
			if(item == null){
				if(!evtMultiSelectKey && this.selectedItem != null){
					this.set("selectedItem", null);
					changed = true;
				}
			}else if(this.selectionMode == "multiple"){
				 if(evtMultiSelectKey){
					this.setItemSelected(item, !selected);
					changed = true;
				}else{
					this.set("selectedItem", item);
					changed = true;
				}
			}else{ // single
				if(evtMultiSelectKey){
					//if the object is selected deselects it.
					this.set("selectedItem", selected ? null : item);
					changed = true;
				}else{
					if(!selected){
						this.set("selectedItem", item);
						changed = true;
					}
				}
			}

			if(dispatch && changed){
				this.dispatchChange(oldSelectedItem, this.get("selectedItem"), renderer, e);
			}

			return changed;
		},

		dispatchChange: function(oldSelectedItem, newSelectedItem, renderer, triggerEvent){
			// summary:
			//		Dispatch a selection change event.
			// oldSelectedItem: Object
			//		The previously selectedItem.
			// newSelectedItem: Object
			//		The new selectedItem.
			// renderer: Object
			//		The visual renderer of the selected/deselected item.
			// triggerEvent: Event
			//		The event that lead to the selection of the item.
			this.onChange({
				oldValue: oldSelectedItem,
				newValue: newSelectedItem,
				renderer: renderer,
				triggerEvent: triggerEvent
			});
		},

		onChange: function(){
			// summary:
			//		Called when the selection changed.
			// tags:
			//		callback
		}
	});
});
