define([
	"dojo/_base/lang",
	"dojo/Stateful"
], function(lang, Stateful){
	function update(/*dojox/mvc/StatefulArray*/ a){
		// summary:
		//		Set all array elements as stateful so that watch function runs.
		// a: dojox/mvc/StatefulArray
		//		The array.

		// Notify change of elements.
		if(a._watchElementCallbacks){
			a._watchElementCallbacks(undefined, [], []);
		}

		return a; // dojox/mvc/StatefulArray
	}

	var StatefulArray = function(/*Anything[]*/ a){
		// summary:
		//		An inheritance of native JavaScript array, that adds dojo/Stateful capability.
		// description:
		//		Supported methods are:
		//
		//		- pop() - watchElements() notification is done for the removed elements. watch() notification is done for the length.
		//		- push() - watchElements() notification is done for the added elements. watch() notification is done for the length.
		//		- reverse() - watchElements() notification is done, indicating that the change affects all elements.
		//		- shift() - watchElements() notification is done for the removed elements. watch() notification is done for the length.
		//		- sort() - watchElements() notification is done, indicating that the change affects all elements.
		//		- splice() - watchElements() notification is done for the removed/added elements. watch() notification is done for the length. Returns an instance of StatefulArray instead of the native array.
		//		- unshift() - watchElements() notification is done for the added elements. watch() notification is done for the length.
		//		- concat() - Returns an instance of StatefulArray instead of the native Array.
		//		- join() - The length as well as the elements are obtained via stateful getters, instead of direct access.
		//		- slice() - The length as well as the elements are obtained via stateful getters, instead of direct access.
		//		- Setting an element to this array via set() - watch() notification is done for the new element as well as the new length.
		//		- Setting a length to this array via set() - watchElements() notification is done for the removed/added elements. watch() notification is done for the new length.

		var array = lang._toArray(a || []);
		var ctor = StatefulArray;
		array.constructor = ctor;
		return lang.mixin(array, {
			pop: function(){
				return this.splice(this.get("length") - 1, 1)[0];
			},
			push: function(){
				this.splice.apply(this, [this.get("length"), 0].concat(lang._toArray(arguments)));
				return this.get("length");
			},
			reverse: function(){
				return update([].reverse.apply(this, lang._toArray(arguments)));
			},
			shift: function(){
				return this.splice(0, 1)[0];
			},
			sort: function(){
				return update([].sort.apply(this, lang._toArray(arguments)));
			},
			splice: function(/*Number*/ idx, /*Number*/ n){
				// summary:
				//		Removes and then adds some elements to an array.
				//		watchElements() notification is done for the removed/added elements.
				//		watch() notification is done for the length.
				//		Returns an instance of StatefulArray instead of the native array.
				// idx: Number
				//		The index where removal/addition should be done.
				// n: Number
				//		How many elements to be removed at idx.
				// varargs: Anything[]
				//		The elements to be added to idx.
				// returns: dojox/mvc/StatefulArray
				//		The removed elements.

				var l = this.get("length");

				idx += idx < 0 ? l : 0;

				var p = Math.min(idx, l),
				 removals = this.slice(idx, idx + n),
				 adds = lang._toArray(arguments).slice(2);

				// Do the modification in a native manner except for setting additions
				[].splice.apply(this, [idx, n].concat(new Array(adds.length)));

				// Set additions in a stateful manner
				for(var i = 0; i < adds.length; i++){
					this[p + i] = adds[i];
				}

				// Notify change of elements.
				if(this._watchElementCallbacks){
					this._watchElementCallbacks(idx, removals, adds);
				}

				// Notify change of length.
				// Not calling the setter for "length" though, given removal/addition of array automatically changes the length.
				if(this._watchCallbacks){
					this._watchCallbacks("length", l, l - removals.length + adds.length);
				}

				return removals; // dojox/mvc/StatefulArray
			},
			unshift: function(){
				this.splice.apply(this, [0, 0].concat(lang._toArray(arguments)));
				return this.get("length");
			},
			concat: function(/*Array*/ a){
				return new StatefulArray([].concat.apply(this, arguments));
			},
			join: function(/*String*/ sep){
				// summary:
				//		Returns a string joining string elements in a, with a separator.
				// sep: String
				//		The separator.

				var list = [];
				for(var l = this.get("length"), i = 0; i < l; i++){
					list.push(this.get(i));
				}
				return list.join(sep); // String
			},
			slice: function(/*Number*/ start, /*Number*/ end){
				// summary:
				//		Returns partial elements of an array.
				// start: Number
				//		The index to begin with.
				// end: Number
				//		The index to end at. (a[end] won't be picked up)

				var l = this.get("length");

				start += start < 0 ? l : 0;
				end = (end === void 0 ? l : end) + (end < 0 ? l : 0);

				var slice = [];
				for(var i = start || 0; i < Math.min(end, this.get("length")); i++){
					slice.push(this.get(i));
				}
				return new StatefulArray(slice); // dojox/mvc/StatefulArray
			},
			watchElements: function(/*Function*/ callback){
				// summary:
				//		Watch for change in array elements.
				// callback: Function
				//		The callback function, which should take: The array index, the removed elements, and the added elements.

				var callbacks = this._watchElementCallbacks, _self = this;
				if(!callbacks){
					callbacks = this._watchElementCallbacks = function(idx, removals, adds){
						for(var list = [].concat(callbacks.list), i = 0; i < list.length; i++){
							list[i].call(_self, idx, removals, adds);
						}
					};
					callbacks.list = [];
				}

				callbacks.list.push(callback);

				var h = {};
				h.unwatch = h.remove = function(){
					for(var list = callbacks.list, i = 0; i < list.length; i++){
						if(list[i] == callback){
							list.splice(i, 1);
							break;
						}
					}
				};
				return h; // dojo/handle
			}
		}, Stateful.prototype, {
			set: function(/*Number|String*/ name, /*Anything*/ value){
				// summary:
				//		Sets a new value to an array.
				// name: Number|String
				//		The property name.
				// value: Anything
				//		The new value.

				if(name == "length"){
					var old = this.get("length");
					if(old < value){
						this.splice.apply(this, [old, 0].concat(new Array(value - old)));
					}else if(value < old){
						this.splice.apply(this, [value, old - value]);
					}
					return this;
				}else{
					var oldLength = this.length;
					Stateful.prototype.set.call(this, name, value);
					if(oldLength != this.length){
						Stateful.prototype.set.call(this, "length", this.length);
					}
					return this;
				}
			},
			isInstanceOf: function(cls){
				return Stateful.prototype.isInstanceOf.apply(this, arguments) || cls == StatefulArray;
			}
		});
	};

	StatefulArray._meta = {bases: [Stateful]}; // For isInstanceOf()
	return lang.setObject("dojox.mvc.StatefulArray", StatefulArray);
});
