Source: mvc/controllers/controller.js


define (
	[ 'mmirf/commonUtils', 'mmirf/helper', 'mmirf/logger', 'module' ],
	function (
			commonUtils, Helper, Logger, module
){

	/**
	 * @private
	 * @type mmir.tools.Logger
	 * @memberOf mmir.ctrl.Controller#
	 * @inner
	 * @member logger
	 */
	var logger = Logger.create(module);

	/**
	 * The Controller Class is a kind of interface-class which gives access to the methods of a controller and its helper. <br>
	 * Also holds information about views and partials associated with the controller.
	 *
	 * @constructor
	 * @class
	 * @name Controller
	 * @memberOf mmir.ctrl
	 * @param {String} name Name of the Controller
	 * @param {Object} jsonDef Information about the controllers, views, and partials
	 * @param {Function} instanceConstr the constructor for creating a new controller instance
	 *
	 */
	function Controller(name, jsonDef, instanceConstr){
//		console.log("controller name " + name);

		/**
		 * The json definition of the views and partials associated with the controller. Also contains paths to controller and its views/partials.
		 *
		 * @type Object
		 * @public
		 * @memberOf mmir.ctrl.Controller#
		 * @member def
		 */
		this.def = jsonDef;

		/**
		 * The name of the controller.
		 *
		 * @type String
		 * @public
		 * @memberOf mmir.ctrl.Controller#
		 * @member name
		 */
		this.name = name;

		var viewDefs = this.def.views;

		/**
		 * An array holding the names of all views associated with the controller.
		 *
		 * @type Array<String>
		 * @public
		 * @memberOf mmir.ctrl.Controller#
		 * @member views
		 */
		this.views = [];
		this.parseViews(viewDefs);

		// parsing the partials and saving the names in an array
		var partialDefs = this.def.partials;


		/**
		 * An array holding the names of all partials associated with the controller.
		 *
		 * @type Array<String>
		 * @public
		 * @memberOf mmir.ctrl.Controller#
		 * @member partials
		 */
		this.partials = [];
		this.parsePartials(partialDefs);


		/**
		 * The instance of the with the controller associated helper.
		 *
		 * @type mmir.ctrl.Helper
		 * @public
		 * @memberOf mmir.ctrl.Controller#
		 * @member helper
		 */
		this.helper;

		/**
		 * The layout (info) for this controller (if undefined, the default layout should be used)
		 * @type Object
		 * @memberOf mmir.ctrl.Controller#
		 * @member layout
		 */
		this.layout = this.def.layout;

		/**
		 * The definition of the controller object, i.e. its (application specific) implementation,
		 * containing all properties and functions of the controller.<br>
		 *
		 * A method of the controller implementation can be called via:
		 * <pre>
		 * 	this.instance.method(parameter);
		 * </pre>
		 *
		 * @type Object
		 * @protected
		 * @memberOf mmir.ctrl.Controller#
		 * @member impl
		 */
		this.impl = new instanceConstr(this);

		/**
		 * @deprecated use {@link #impl} instead
		 * @protected
		 * @memberOf mmir.ctrl.Controller#
		 * @member script
		 */
		this.script = this.impl;
	}


	/**
	 * This function loads the helper of the controller - if it exists.
	 *
	 * @function loadHelper
	 * @param {String} name Name of the Helper to be loaded
	 * @param {String} helperPath Path to the helper file to load
	 * @param {Object} ctx the context for the helper implementation, i.e. where the constructor (will) exists: ctx.<helper name>()
	 * @protected
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.loadHelper = function(name, helperPath, ctx){

		if(typeof ctx[name] === 'function' && ctx[name].name === name){
			//helper implementation was already loaded -> immediately create helper
			if(logger.isVerbose()) logger.v("[HELPER] already loaded "+name);//debug
			self.helper = new Helper(self, name, ctx[name]);
		}

		var self = this;

		if(typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD){
			helperPath = 'require://' + helperPath.replace(/\.js/i, '');
		} else {
			//TODO move check of helper existence to Controller.foundControllersCallBack ?

			//determine if there is a helper for the controller:
			var path = helperPath;
			var fileName = path;
			var lastPathSeparatorIndex = path.lastIndexOf('/');
			if(lastPathSeparatorIndex !== -1){
				path = path.substring(0,lastPathSeparatorIndex);
				fileName = fileName.substring( lastPathSeparatorIndex + 1 );
			}
			//get contents of the helper directory:
			var dirContents = commonUtils.listDir(path);
			if(!dirContents){
				logger.warn('Could not determine contents for directory "'+path+'"');
				return; ////////////////////// EARLY EXIT //////////////////////////////
			}
			else if(! commonUtils.isArray(dirContents) || dirContents.length < 1){
				logger.warn('Invalid information for contents of directory "'+path+'": '+dirContents);
				return; ////////////////////// EARLY EXIT //////////////////////////////
			}

			//check, if there is an implementation file for this helper:
			var helperIsSpecified = false;
			for(var i=0, size= dirContents.length; i < size; ++i){
				if(dirContents[i] === fileName){
					helperIsSpecified = true;
					break;
				}
			}

			if( ! helperIsSpecified){
				if(logger.isVerbose()) logger.v("[HELPER] no helper available (not implemented) at '"+ helperPath+"'");
				return; ////////////////////// EARLY EXIT //////////////////////////////
			}
		}

		//if there is a file: load the helper
		commonUtils.loadScript(helperPath, function(result){

				if(logger.isVerbose()) logger.v("[HELPER] load "+ helperPath);//debug

				var constr = ctx[name];

				//FIXME HACK for handling exported constructor
				if(typeof result === 'function' && result.name === name){
					constr = result;
				}

				self.helper = new Helper(self, name, constr);
			},
			function(exception) {
				// print out an error message
				logger.error("[WARN] Could not load helper -> '"+ helperPath + "'", exception); //failure
			}
		);
	};


	/**
	 * This function performs an action of a controller - which is represented by this instance of the Controller <br>
	 * class - by calling the method from the corresponding controller, e.g. assets/www/controllers/application.js
	 *
	 * @function perform
	 * @param {String} actionName Name of the method to be executed
	 * @param {Object} data Data to pass to the method of the controller as argument
	 * @returns {Object} The return value of the executed method
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.perform = function(actionName, data){

		if(logger.isVerbose()) logger.v("should perform '" + actionName + "' of '" + this.name + "'"+ ((typeof data !== 'undefined' && data !== null)? " with data: "+JSON.stringify(data): ""));//debug

		if(arguments.length > 2){
			return this.impl[actionName](data, arguments[2]);
		}
		else {
			return this.impl[actionName](data);
		}
	};

	/**
	 *
	 * This function performs an action of a controller, but only if an action with this name exists; otherwise nothing is done.
	 *
	 * In difference to perform(..), the method does not trigger an ERROR, if the action does not exist / is not implemented.
	 * As a consequence, this method refers to "optionally" implemented functions, whereas perform(..) refers to mandatory functions.
	 *
	 * @function performIfPresent
	 * @param {String} actionName Name of the method to be executed
	 * @param {Object} data Data to pass to the method of the controller as argument
	 * @returns {Object} The return value of the executed method
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.performIfPresent = function(actionName, data){
		if(typeof this.impl[actionName] === 'function'){

			if(logger.isVerbose()) logger.v("performing '" + actionName + "' of '" + this.name + "'"+ ((typeof data !== 'undefined' && data !== null)? " with data: "+JSON.stringify(data): ""));//debug

			return this.perform.apply(this, arguments);

		} else if(typeof this.impl[actionName] !== 'undefined'){
			if(logger.isVerbose()) logger.info("could not perform '" + actionName + "' of '" + this.name + "'"+ ((typeof data !== 'undefined' && data !== null)? " with data: "+JSON.stringify(data): "")+": no function ("+typeof this.impl[actionName]+")");//debug
		} else {
			if(logger.isVerbose()) logger.debug("could not perform '" + actionName + "' of '" + this.name + "'"+ ((typeof data !== 'undefined' && data !== null)? " with data: "+JSON.stringify(data): "")+": not implemented (undefined)");//debug
		}
	};


	/**
	 * This function performs a helper action of a controller by calling the appropriate method<br>
	 * {@link mmir.ctrl.Helper#perform} of the instance of the helper class associated with the controller.
	 *
	 * @function performHelper
	 * @param {String} actionName Name of the helper method to be executed
	 * @param {Object} data Data to pass to the helper method as argument
	 * @returns {Object} The return value of the executed method
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.performHelper = function(actionName, data){
		if(arguments.length > 2){
			return this.helper.perform(actionName, data, arguments[2]);
		}
		else {
			return this.helper.perform(actionName, data);
		}
	};


	/**
	 * Returns the helper of the controller instance.
	 *
	 * @function getHelper
	 * @returns {mmir.ctrl.Helper} The helper instance
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.getHelper = function(){
		return this.helper;
	};


	/**
	 * Stores all names of the views of the controller by iterating over the array of the views definition.<br>
	 * This function is called by the constructor of the {@link mmir.ctrl.Controller} class.
	 *
	 * @function parseViews
	 * @param {Array} viewDefs Array of the json-definition of the controllers views - containing name of the views and their corresponding path to the js-files
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.parseViews = function(viewDefs){

		for(var i=0, size = viewDefs.length; i < size; ++i){
			this.views.push(viewDefs[i].name);
		}

	};


	/**
	 * Stores all names of the partials of the controller by iterating over the array of the partials definition.<br>
	 * This function is called by the constructor of the {@link mmir.ctrl.Controller} class.
	 *
	 * @function parsePartials
	 * @param {Array} partialDefs Array of the json-definition of the controllers partials - containing name of the partials and their corresponding path to the js-files
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.parsePartials = function(partialDefs){

		for(var i=0, size = partialDefs.length; i < size; ++i){
			this.partials.push(partialDefs[i].name);
		}

	};


	/**
	 * Returns the view names for the controller instance.
	 *
	 * @function getViewNames
	 * @returns {Array<String>} An array of the names of the controller's views
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.getViewNames = function(){
		return this.views;
	};

	/**
	 * Returns the view names for the controller instance.
	 *
	 * TODO should this be private/hidden? -> provides "internal" JSON-details object (used in PresentationManager)
	 *
	 * Each info object has properties:
	 * {String} name
	 * {String} path
	 *
	 * @function getViews
	 * @returns {Array<Object>} An array of the controller's views
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.getViews = function(){
		return this.def.views;
	};

	/**
	 * Returns the partial names for the controller instance.
	 *
	 * @function getPartialNames
	 * @returns {Array<String>} An array of the names of the controller's partials
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.getPartialNames = function(){
		return this.partials;
	};

	/**
	 * Returns the partial info object for the controller instance.
	 *
	 * TODO should this be private/hidden? -> provides "internal" JSON-details object (used in PresentationManager)
	 *
	 * Each info object has properties:
	 * {String} name
	 * {String} path
	 *
	 * @function getPartials
	 * @returns {Array<Object>} An array of the controller's partials
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.getPartials = function(){
		return this.def.partials;
	};

	/**
	 * Returns the layout of the controller instance.
	 *
	 * If undefined, the default layout should be used.
	 *
	 * TODO should this be private/hidden? -> provides "internal" JSON-details object (used in PresentationManager)
	 *
	 * The info object has properties:
	 * {String} name
	 * {String} path
	 * {String} fileName
	 *
	 * @function getLayout
	 * @returns {Object} The controller's layout (may be undefined)
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.getLayout = function(){
		return this.layout;
	};

	/**
	 * Returns the layout name for the controller instance.
	 *
	 * This is equal to the controller name.
	 *
	 * If undefined, the default layout should be used.
	 *
	 * @function getLayoutName
	 * @returns {String} The controller's layout name (may be undefined)
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.getLayoutName = function(){
		return this.layout? this.layout.name : this.layout;
	};

	/**
	 * Returns the name of the controller instance.
	 *
	 * @function getName
	 * @returns {String} The name of the controller
	 * @public
	 * @memberOf mmir.ctrl.Controller#
	 */
	Controller.prototype.getName = function(){
		return this.name;
	};

	return Controller;

});