//>>built
define("dojox/mvc/StatefulModel", [
	"dojo/_base/lang",
	"dojo/_base/array",
	"dojo/_base/declare",
	"dojo/Stateful"
], function(lang, array, declare, Stateful){
	/*=====
		declare = dojo.declare;
		Stateful = dojo.Stateful;
	=====*/

	var StatefulModel = declare("dojox.mvc.StatefulModel", [Stateful], {
		// summary:
		//		The first-class native JavaScript data model based on dojo.Stateful
		//		that wraps any data structure(s) that may be relevant for a view,
		//		a view portion, a dijit or any custom view layer component.
		//
		//  description:
		//		A data model is effectively instantiated with a plain JavaScript
		//		object which specifies the initial data structure for the model.
		//
		//		|	var struct = {
		//		|		order	: "abc123",
		//		|		shipto	: {
		//		|			address	: "123 Example St, New York, NY",
		//		|			phone	: "212-000-0000"
		//		|		},
		//		|		items : [
		//		|			{ part : "x12345", num : 1 },
		//		|			{ part : "n09876", num : 3 }
		//		|		]
		//		|	};
		//		|
		//		|	var model = dojox.mvc.newStatefulModel({ data : struct });
		//
		//		The simple example above shows an inline plain JavaScript object
		//		illustrating the data structure to prime the model with, however
		//		the underlying data may be made available by other means, such as
		//		from the results of a dojo.store or dojo.data query.
		//
		//		To deal with stores providing immediate values or Promises, a
		//		factory method for model instantiation is provided. This method
		//		will either return an immediate model or a model Promise depending
		//		on the nature of the store.
		//
		//		|	var model = dojox.mvc.newStatefulModel({ store: someStore });
		//
		//		The created data model has the following properties:
		//
		//		- It enables dijits or custom components in the view to "bind" to
		//		  data within the model. A bind creates a bi-directional update
		//		  mechanism between the bound view and the underlying data:
		//			- The data model is "live" data i.e. it maintains any updates
		//			  driven by the view on the underlying data.
		//			- The data model issues updates to portions of the view if the
		//			  data they bind to is updated in the model. For example, if two
		//			  dijits are bound to the same part of a data model, updating the
		//			  value of one in the view will cause the data model to issue an
		//			  update to the other containing the new value.
		//
		//		- The data model internally creates a tree of dojo.Stateful
		//		  objects that matches the input, which is effectively a plain
		//		  JavaScript object i.e. "pure data". This tree allows dijits or
		//		  other view components to bind to any node within the data model.
		//		  Typically, dijits with simple values bind to leaf nodes of the
		//		  datamodel, whereas containers bind to internal nodes of the
		//		  datamodel. For example, a datamodel created using the object below
		//		  will generate the dojo.Stateful tree as shown:
		//
		//		|	var model = dojox.mvc.newStatefulModel({ data : {
		//		|		prop1	: "foo",
		//		|		prop2	: {
		//		|			leaf1	: "bar",
		//		|			leaf2	: "baz"
		//		|		}
		//		|	}});
		//		|
		//		|	// The created dojo.Stateful tree is illustrated below (all nodes are dojo.Stateful objects)
		//		|	//
		//		|	//	                o  (root node)
		//		|	//	               / \
		//		|	//	 (prop1 node) o   o (prop2 node)
		//		|	//	                 / \
		//		|	//	   (leaf1 node)	o   o (leaf2 node)
		//		|	//
		//		|	// The root node is accessed using the expression "model" (the var name above). The prop1
		//		|	// node is accessed using the expression "model.prop1", the leaf2 node is accessed using
		//		|	// the expression "model.prop2.leaf2" and so on.
		//
		//		- Each of the dojo.Stateful nodes in the model may store data as well
		//		  as associated "meta-data", which includes things such as whether
		//		  the data is required or readOnly etc. This meta-data differs from
		//		  that maintained by, for example, an individual dijit in that this
		//		  is maintained by the datamodel and may therefore be affected by
		//		  datamodel-level constraints that span multiple dijits or even
		//		  additional criteria such as server-side computations.
		//
		//		- When the model is backed by a dojo.store or dojo.data query, the
		//		  client-side updates can be persisted once the client is ready to
		//		  "submit" the changes (which may include both value changes or
		//		  structural changes - adds/deletes). The datamodel allows control
		//		  over when the underlying data is persisted i.e. this can be more
		//		  incremental or batched per application needs.
		//
		//		There need not be a one-to-one association between a datamodel and
		//		a view or portion thereof. For example, multiple datamodels may
		//		back the dijits in a view. Indeed, this may be useful where the
		//		binding data comes from a number of data sources or queries, for
		//		example. Just as well, dijits from multiple portions of the view
		//		may be bound to a single datamodel.
		//
		//		Finally, requiring this class also enables all dijits to become data
		//		binding aware. The data binding is commonly specified declaratively
		//		via the "ref" property in the "data-dojo-props" attribute value.
		//
		//		To illustrate, the following is the "Hello World" of such data-bound
		//		widget examples:
		//
		//		|	<script>
		//		|		dojo.require("dojox.mvc");
		//		|		dojo.require("dojo.parser");
		//		|		var model;
		//		|		dojo.addOnLoad(function(){
		//		|			model = dojox.mvc.newStatefulModel({ data : {
		//		|				hello : "Hello World"
		//		|			}});
		//		|			dojo.parser.parse();
		//		|		}
		//		|	</script>
		//		|
		//		|	<input id="helloInput" dojoType="dijit.form.TextBox"
		//		|		ref="model.hello">
		//
		//		or
		//
		//		|	<script>
		//		|		var model;
		//		|		require(["dojox/mvc", "dojo/parser"], function(dxmvc, parser){
		//		|			model = dojox.mvc.newStatefulModel({ data : {
		//		|				hello : "Hello World"
		//		|			}});
		//		|			parser.parse();
		//		|		});
		//		|	</script>
		//		|
		//		|	<input id="helloInput" data-dojo-type="dijit.form.TextBox"
		//		|		data-dojo-props="ref: 'model.hello'">
		//
		//		Such data binding awareness for dijits is added by extending the
		//		dijit._WidgetBase class to include data binding capabilities
		//		provided by dojox.mvc._DataBindingMixin, and this class declares a
		//		dependency on dojox.mvc._DataBindingMixin.
		//
		//		The presence of a data model and the data-binding capabilities
		//		outlined above support the flexible development of a number of MVC
		//		patterns on the client. As an example, CRUD operations can be
		//		supported with minimal application code.
	
		// data: Object
		//		The plain JavaScript object / data structure used to initialize
		//		this model. At any point in time, it holds the lasted saved model
		//		state.
		//		Either data or store property must be provided.
		data: null,

		// store: dojo.store.DataStore
		//		The data store from where to retrieve initial data for this model.
		//		An optional query may also be provided along with this store.
		//		Either data or store property must be provided.
		store: null,
	
		// valid: boolean
		//		Whether this model deems the associated data to be valid.
		valid: true,

		// value: Object
		//		The associated value (if this is a leaf node). The value of
		//		intermediate nodes in the model is not defined.
		value: "",

		//////////////////////// PUBLIC METHODS / API ////////////////////////

		reset: function(){
			// summary:
			//		Resets this data model values to its original state.
			//		Structural changes to the data model (such as adds or removes)
			//		are not restored.
			if(lang.isObject(this.data) && !(this.data instanceof Date) && !(this.data instanceof RegExp)){	
				for(var x in this){
					if(this[x] && lang.isFunction(this[x].reset)){
						this[x].reset();
					}
				}
			}else{
				this.set("value", this.data);
			}
		},

		commit: function(/*"dojo.store.DataStore?"*/ store){
			// summary:
			//		Commits this data model:
			//		- Saves the current state such that a subsequent reset will not
			//		  undo any prior changes.
			//		- Persists client-side changes to the data store, if a store
			//		  has been supplied as a parameter or at instantiation.
			//	store:
			//		dojo.store.DataStore
			//		Optional dojo.store.DataStore to use for this commit, if none
			//		provided but one was provided at instantiation time, that store
			//		will be used instead.
			this._commit();
			var ds = store || this.store;
			if(ds){
				this._saveToStore(ds);
			}
		},

		toPlainObject: function(){
			// summary:
			//		Produces a plain JavaScript object representation of the data
			//		currently within this data model.
			// returns:
			//		Object
			//		The plain JavaScript object representation of the data in this
			//		model.
			var ret = {};
			var nested = false;
			for(var p in this){
				if(this[p] && lang.isFunction(this[p].toPlainObject)){
					if(!nested && typeof this.get("length") === "number"){
						ret = [];
					}
					nested = true;
					ret[p] = this[p].toPlainObject();
				}
			}
			if(!nested){
				if(this.get("length") === 0){
					ret = [];
				}else{				
					ret = this.value;
				}
			}
			return ret;
		},

		add: function(/*String*/ name, /*dojo.Stateful*/ stateful){
			// summary:
			//		Adds a dojo.Stateful tree represented by the given
			//		dojox.mvc.StatefulModel at the given property name.
			//	name:
			//		The property name to use whose value will become the given
			//		dijit.Stateful tree.
			//	stateful:
			//		The dojox.mvc.StatefulModel to insert.
			// description:
			//		In case of arrays, the property names are indices passed
			//		as Strings. An addition of such a dojo.Stateful node
			//		results in right-shifting any trailing sibling nodes.
			var n, n1, elem, elem1, save = new StatefulModel({ data : "" });
			if(typeof this.get("length") === "number" && /^[0-9]+$/.test(name.toString())){
				n = name;
				if(!this.get(n)){
					if(this.get("length") == 0 && n == 0){ // handle the empty array case
						this.set(n, stateful);
					} else {
						n1 = n-1;
						if(!this.get(n1)){
							throw new Error("Out of bounds insert attempted, must be contiguous.");
						}
						this.set(n, stateful);
					}
				}else{
					n1 = n-0+1;
					elem = stateful;
					elem1 = this.get(n1);
					if(!elem1){
						this.set(n1, elem);
					}else{
						do{
							this._copyStatefulProperties(elem1, save);
							this._copyStatefulProperties(elem, elem1);
							this._copyStatefulProperties(save, elem);
							this.set(n1, elem1); // for watchers
							elem1 = this.get(++n1);
						}while(elem1);
						this.set(n1, elem);
					}
				}
				this.set("length", this.get("length") + 1);
			}else{
				this.set(name, stateful);
			}
		},

		remove: function(/*String*/ name){
			// summary:
			//		Removes the dojo.Stateful tree at the given property name.
			//	name:
			//		The property name from where the tree will be removed.
			// description:
			//		In case of arrays, the property names are indices passed
			//		as Strings. A removal of such a dojo.Stateful node
			//		results in left-shifting any trailing sibling nodes.
			var n, elem, elem1;
			if(typeof this.get("length") === "number" && /^[0-9]+$/.test(name.toString())){
				n = name;
				elem = this.get(n);
				if(!elem){
					throw new Error("Out of bounds delete attempted - no such index: " + n);
				}else{
					this._removals = this._removals || [];
					this._removals.push(elem.toPlainObject());
					n1 = n-0+1;
					elem1 = this.get(n1);
					if(!elem1){
						this.set(n, undefined);
						delete this[n];
					}else{
						while(elem1){
							this._copyStatefulProperties(elem1, elem);
							elem = this.get(n1++);
							elem1 = this.get(n1);
						}
						this.set(n1-1, undefined);
						delete this[n1-1];
					}
					this.set("length", this.get("length") - 1);
				}
			}else{
				elem = this.get(name);
				if(!elem){
					throw new Error("Illegal delete attempted - no such property: " + name);
				}else{
					this._removals = this._removals || [];
					this._removals.push(elem.toPlainObject());
					this.set(name, undefined);
					delete this[name];
				}
			}
		},

		valueOf: function(){
			// summary:
			//		Returns the value representation of the data currently within this data model.
			// returns:
			//		Object
			//		The object representation of the data in this model.
			return this.toPlainObject();
		},

		toString: function(){
			// summary:
			//		Returns the string representation of the data currently within this data model.
			// returns:
			//		String
			//		The object representation of the data in this model.
			return this.value === "" && this.data ? this.data.toString() : this.value.toString();
		},

		//////////////////////// PRIVATE INITIALIZATION METHOD ////////////////////////

		constructor: function(/*Object*/ args){
			// summary:
			//		Instantiates a new data model that view components may bind to.
			//		This is a private constructor, use the factory method
			//		instead: dojox.mvc.newStatefulModel(args)
			//	args:
			//		The mixin properties.
			// description:
			//		Creates a tree of dojo.Stateful objects matching the initial
			//		data structure passed as input. The mixin property "data" is
			//		used to provide a plain JavaScript object directly representing
			//		the data structure.
			// tags:
			//		private
			var data = (args && "data" in args) ? args.data : this.data; 
			this._createModel(data);
		},

		//////////////////////// PRIVATE METHODS ////////////////////////

		_createModel: function(/*Object*/ obj){
			// summary:
			//		Create this data model from provided input data.
			//	obj:
			//		The input for the model, as a plain JavaScript object.
			// tags:
			//		private
			if(lang.isObject(obj) && !(obj instanceof Date) && !(obj instanceof RegExp) && obj !== null){
				for(var x in obj){
					var newProp = new StatefulModel({ data : obj[x] });
					this.set(x, newProp);
				}
				if(lang.isArray(obj)){
					this.set("length", obj.length);
				}
			}else{
				this.set("value", obj);
			}
		},

		_commit: function(){
			// summary:
			//		Commits this data model, saves the current state into data to become the saved state, 
			//		so a reset will not undo any prior changes.  
			// tags:
			//		private
			for(var x in this){
				if(this[x] && lang.isFunction(this[x]._commit)){
					this[x]._commit();
				}
			}
			this.data = this.toPlainObject();
		},

		_saveToStore: function(/*"dojo.store.DataStore"*/ store){
			// summary:
			//		Commit the current values to the data store:
			//		- remove() any deleted entries
			//		- put() any new or updated entries
			//	store:
			//		dojo.store.DataStore to use for this commit.
			// tags:
			//		private
			if(this._removals){
				array.forEach(this._removals, function(d){
					store.remove(store.getIdentity(d));
				}, this);
				delete this._removals;
			}
			var dataToCommit = this.toPlainObject();
			if(lang.isArray(dataToCommit)){
				array.forEach(dataToCommit, function(d){
					store.put(d);
				}, this);
			}else{
				store.put(dataToCommit);
			}
		},

		_copyStatefulProperties: function(/*dojo.Stateful*/ src, /*dojo.Stateful*/ dest){
			// summary:
			//		Copy only the dojo.Stateful properties from src to dest (uses
			//		duck typing).
			//	src:
			//		The source object for the copy.
			//	dest:
			//		The target object of the copy.
			// tags:
			//		private
			for(var x in src){
				var o = src.get(x);
				if(o && lang.isObject(o) && lang.isFunction(o.get)){
					dest.set(x, o);
				}
			}
		}
	});

	return StatefulModel;
});
