define("dojox/app/View", ["dojo/_base/declare", "dojo/_base/lang", "dojo/Deferred", "dojo/when", "require", "dojo/dom-attr", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin", "./model"],
function(declare, lang, Deferred, when, require, dattr, TemplatedMixin, WidgetsInTemplateMixin, Model){
	// module:
	//		dojox/app/View
	// summary:
	//		dojox/app view object, each view can have one parent view and several children views.

	return declare("dojox.app.View", null, {
		constructor: function(params){
			// summary:
			//		init view object. A user can use configuration file or programing type to create a view instance.
			//
			// example:
			//		|	use configuration file
			//		|
			// 		|	// load view definition from views/simple.js by default
			//		|	"simple":{
			//		|		"template": "./templates/simple.html",
			//		|		"dependencies":["dojox/mobile/TextBox"]
			//		|	}
			//		|
			//		|	"home":{
			//		|		"template": "./templates/home.html",
			//		|		"definition": "none",	// identify no view definition
			//		|		"dependencies":["dojox/mobile/TextBox"]
			//		|	}
			//		|	"main":{
			//		|		"template": "./templates/main.html",
			//		|		"definition": "./views/main.js", // identify load view definition from views/main.js
			//		|		"dependencies":["dojox/mobile/TextBox"]
			//		|	}
			//
			// example:
			//		|	var viewObj = new View({
			//		|		app: this.app,
			//		|		id: this.id,
			//		|		name: this.name,
			//		|		parent: this,
			//		|		templateString: this.templateString,
			//		|		definition: this.definition
			//		|	});
			//		|	viewObj.start(); // start view
			//
			// params:
			//		view parameters, include:
			//
			//		- app: the app
			//		- id: view id
			//		- name: view name
			//		- template: view template url. If templateString not empty, ignore this parameter.
			//		- templateString: view template string
			//		- definition: view definition url
			//		- parent: parent view
			//		- children: children views
			this.id = "";
			this.name = "";
			this.templateString = "";
			this.template = "";
			this.definition = "";
			this.parent = null;
			this.children = {};
			this.selectedChild = null;
			// private
			this._started = false;
			this._definition = null;

			lang.mixin(this, params);
			// mixin views configuration to current view instance.
			if(this.parent.views){
				lang.mixin(this, this.parent.views[this.name]);
			}
		},

		_loadViewDefinition: function(){
			// summary:
			//		Load view definition by configuration or by default.
			// tags:
			//		private
			//
			var _definitionDef = new Deferred();
			var path;

			if(this.definition && (this.definition === "none")){
				_definitionDef.resolve(true);
				return _definitionDef;
			}else if(this.definition){
				path = this.definition.replace(/(\.js)$/, "");
			}else{
				path = this.id.split("_");
				path.shift();
				path = path.join("/");
				path = "./views/" + path;
			}

			var requireSignal;
			try{
				var loadFile = path;
				var index = loadFile.indexOf("./");
				if(index >= 0){
					loadFile = path.substring(index+2);
				}
				requireSignal = require.on("error", function(error){
					if (_definitionDef.isResolved() || _definitionDef.isRejected()) {
						return;
					}
					if(error.info[0] && (error.info[0].indexOf(loadFile)>= 0)){
						_definitionDef.resolve(false);
						requireSignal.remove();
					}
				});

				if(path.indexOf("./") == 0){
					path = "app/"+path;
				}

				require([path], function(definition){
					_definitionDef.resolve(definition);
					requireSignal.remove();
				});
			}catch(ex){
				_definitionDef.resolve(false);
				requireSignal.remove();
			}
			return _definitionDef;
		},

		_loadViewTemplate: function(){
			// summary:
			//		load view HTML template and dependencies.
			// tags:
			//		private
			//

			if(this.templateString){
				return true;
			}else{
				if(!this.dependencies){
					this.dependencies = [];
				}
				var tpl = this.template;
				if(tpl.indexOf("./") == 0){
					tpl = "app/"+tpl;
				}
				var deps = this.template ? this.dependencies.concat(["dojo/text!"+tpl]) : this.dependencies.concat([]);
				var def = new Deferred();
				if(deps.length > 0){
					var requireSignal;
					try{
						requireSignal = require.on("error", lang.hitch(this, function(error){
							if(def.isResolved() || def.isRejected()){
								return;
							}
							if(error.info[0] && error.info[0].indexOf(this.template)>=0 ){
								def.resolve(false);
								requireSignal.remove();
							}
						}));
						require(deps, function(){
							def.resolve.call(def, arguments);
							requireSignal.remove();
						});
					}catch(ex){
						def.resolve(false);
						requireSignal.remove();
					}
				}else{
					def.resolve(true);
				}

				var loadViewDeferred = new Deferred();
				when(def, lang.hitch(this, function(){
					this.templateString = this.template ? arguments[0][arguments[0].length - 1] : "<div></div>";
					loadViewDeferred.resolve(this);
				}));
				return loadViewDeferred;
			}
		},

		// start view
		start: function(){
			// summary:
			//		start view object.
			//		load view template, view definition implement and startup all widgets in view template.
			if(this._started){
				return this;
			}

			var _definitionDef = this._loadViewDefinition();
			var _templateDef = this._loadViewTemplate();

			this._startDef = new Deferred();
			when(_definitionDef, lang.hitch(this, function(definition){
				this._definition = definition;
				when(_templateDef, lang.hitch(this, function(){
					// call setupModel, after setupModel startup will be called after startup the loadViewDeferred will be resolved
					this._setupModel();
				}));
			}));
			return this._startDef;
		},

		_setupModel: function(){
			// summary:
			//		Load views model if it is not already loaded then call _startup.
			// tags:
			//		private
			
			if (!this.loadedModels) {
				var loadModelLoaderDeferred = new Deferred();
				var createPromise;
				try{
					createPromise = Model(this.models, this.parent, this.app);
				}catch(ex){
					loadModelLoaderDeferred.reject("load model error.");
					return loadModelLoaderDeferred.promise;
				}
				if(createPromise.then){  // model returned a promise, so set loadedModels and call startup after the .when
					when(createPromise, lang.hitch(this, function(newModel){
						if(newModel){
							this.loadedModels = newModel;
						}
						this._startup();
					}),
					function(){
						loadModelLoaderDeferred.reject("load model error.")
					});
				}else{ // model returned the actual model not a promise, so set loadedModels and call _startup
					this.loadedModels = createPromise;
					this._startup();
				}
			}else{ // loadedModels already created so call _startup
				this._startup();				
			}		
		},

		_startup: function(){
			// summary:
			//		startup widgets in view template.
			// tags:
			//		private

			this._widget = this.render(this.templateString);
			// bind view level data model
			this.domNode = this._widget.domNode;
			this.parent.domNode.appendChild(this.domNode);

			//start widget
			this._widget.startup();

			// set widget attributes
			dattr.set(this.domNode, "id", this.id);
			dattr.set(this.domNode, "data-app-region", "center");
			dattr.set(this.domNode, "style", "width:100%; height:100%");
			this._widget.region = "center";

			//mixin view lifecycle implement
			if (this._definition) {
				lang.mixin(this, this._definition);
			}

			// call view assistant's init() method to initialize view
			this.app.log("  > in app/View calling init() name=[",this.name,"], parent.name=[",this.parent.name,"]");
			this.init();
			this._started = true;
			if(this._startDef){
				this._startDef.resolve(this);
			}
		},

		render: function(templateString){
			// summary:
			//		rendering view template HTML
			// templateString:
			//		template string
			var widgetTemplate = new TemplatedMixin();
			var widgetInTemplate = new WidgetsInTemplateMixin();
			// set the loadedModels here to be able to access the model on the parse.
			if(this.loadedModels){
				widgetInTemplate.loadedModels = this.loadedModels;
			}
			lang.mixin(widgetTemplate, widgetInTemplate);
			widgetTemplate.templateString = templateString;
			widgetTemplate.buildRendering();
			return widgetTemplate;
		},
		
		init: function(){
			// summary:
			//		view life cycle init()
		},

		beforeActivate: function(){
			// summary:
			//		view life cycle beforeActivate()
		},

		afterActivate: function(){
			// summary:
			//		view life cycle afterActivate()
		},

		beforeDeactivate: function(){
			// summary:
			//		view life cycle beforeDeactivate()
		},

		afterDeactivate: function(){
			// summary:
			//		view life cycle afterDeactivate()
		},

		destroy: function(){
			// summary:
			//		view life cycle destroy()
		}
	});
});
