Source: skillVC.js

/**
 * @author Sloan Seaman 
 * @copyright 2016 and on
 * @version .1
 * @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
 */

/** @private */
var FilterChainExecutor = require('./filter/filterChainPromiseExecutor.js');
var SessionHandlerExecutor = require('./sessionHandler/sessionHandlerExecutor.js');
var IntentHandlerFilter = require('./intentHandler/intentHandlerFilter.js');
var ContextWrapperFilter = require('./context/contextWrapperFilter.js');
var log = require('winston-simple').getLogger('SkillVC');

/**
 * Creates an instance of SkillVC.
 *
 * Most uses of SkillVC are better instantiated by using one of the methods in {@link SkillVCFactory}.
 * However, if none of those functions provide the functionality required, or if more customization of SkillVC
 * is required, this constructor should be used.
 *
 * SkillVC, by default, requires the objects defined in {@link SVContext#appConfig} be set.  However, if
 * any of the SkillVC functions have been overridden, the functionality and required configuration would change.
 * 
 * @constructor
 * @param {SVContext.appConfig} config The configuration to initialized SkillVC with.  As this configuration
 * is made available throughout the execution of SkillVC it can also be used as a means to inject objects into
 * SkillVC to be used by objects downstream.
 */
function SkillVC(config) {
	// main context object for the whole system
	this._skillVCContext = {};
	this._skillVCContext.appConfig = config;

	this._initialized = false;
}

/**
 * Initializes SkillVC.  This is only called once (unless SkillVC is dumped from memory and recreated).
 *
 * The execution series of SkillVC is as follows:
 *
 * * Clear the appSession
 * * Place the lambda information (event, context) into the {@link SVContext}
 * * Register the @{link ResponseManager}
 * * Register the @{link SessionHandlerManager}
 * * Register the @{link FilterManager}
 * * Register the @{link IntentHandlerManager}
 * * Record that init completed (so it doesn't fire ever again)
 * * Call the initCallback.success function
 *
 * @function
 * @param  {Object} event        The Lambda event
 * @param  {Object} context      The Lambda context
 * @param  {SkillVC~callback} initCallback The callback used when initialization completes
 * @return {SkillVC} This instance of SkillVC for use in builder style calling
 */
SkillVC.prototype.init = function(event, context, initCallback) {
	if (this._initialized) {
		initCallback.success();  // nothing to do, pop out
		return;
	}

	log.info("Initializing...");

	// reset the context session
	this._skillVCContext.appSession = {};

	// make things available to anyone
	this._skillVCContext.lambda = { 
		'context' : context,
		'event' : event
	};
	this._skillVCContext.appConfig.filterChainExecutor = null;
	this._skillVCContext.appConfig.sessionHandlerExecutor = null;

	log.info("Registering ResponseManager");
	this._skillVCContext.appConfig.responseManager = this.registerResponseManager(this._skillVCContext);

	log.info("Registering SessionHandlerManager");
	this._skillVCContext.appConfig.sessionHandlerManager = this.registerSessionHandlerManager(this._skillVCContext);

	var sv = this;
	// might be a way to do this with promises, but I'm not going to put the time into it yet
	log.info("Registering Filters");
	sv.registerFilterManager(sv._skillVCContext, {
		success : function(filterManager) {

			log.info("Registering Intent Handlers");
			sv.registerIntentHandlerManager(sv._skillVCContext, {
				success : function(iHandlerManager) {
					
					// need to do this to get the intents to be handled as part of the chain
					log.debug("Injecting IntentHandlerFilter as pre filter");
					var preFilters = filterManager.getPreFilters();
					preFilters.push(new IntentHandlerFilter(iHandlerManager));

					// need to do this to convert the intent results to something lambda understands
					log.debug("Adding ContextWrapperFilter as last post filter");
					var postFilters = filterManager.getPostFilters();
					postFilters.push(new ContextWrapperFilter());

					sv._skillVCContext.appConfig.filterChainExecutor = new FilterChainExecutor(
						preFilters, 
						postFilters
						);

					sv._skillVCContext.appConfig.sessionHandlerExecutor = new SessionHandlerExecutor(
						sv._skillVCContext.appConfig.sessionHandlerManager.getStartSessionHandlers(),
						sv._skillVCContext.appConfig.sessionHandlerManager.getEndSessionHandlers()
						);

					sv._initialized = true;
					log.info("Initialization complete");
					initCallback.success(); // registration completed
				},
				failure : function(err) {
					initCallback.failure(err);
				}
			});
		},
		failure : function(err) {
			initCallback.failure(err);
		}
	});

	return this;
};

/**
 * The handler() function should be invoked by the index.js of the skill to transfer control of the
 * skill execution into SkillVC.
 *
 * Upon execution the {@link SVContext.session} and {@link SVContext.callback} will be reset to provide a
 * clean execution state.  The configured {@link FilterChainExecutor~execute} will be invoked 
 * 
 * @function
 * @param  {Object} event        The Lambda event
 * @param  {Object} context      The Lambda context
 */
SkillVC.prototype.handler = function (event, context)  {
	log.info("Handler invoked");

    try {
		if (this._skillVCContext.appId && (event.session.application.applicationId !== this._skillVCContext.appId)) {
			throw new Error("Invalid Application ID");
		}

        var sv = this;
		sv.init(event, context, {
			success : function() {
				// re-setting just in case
				sv._skillVCContext.lambda.context = context;
				sv._skillVCContext.lambda.event = event;

				// give a session for people to use
				sv._skillVCContext.session = {};

				var handleRequest = function(eventType, svContext) {
					if (eventType === "IntentRequest" || eventType === "LaunchRequest") {
						svContext.appConfig.filterChainExecutor.execute(svContext);
					} 
					else if (eventType === "SessionEndedRequest" 
						&& svContext.appConfig.sessionHandlerExecutor) 
					{
						svContext.appConfig.sessionHandlerExecutor.executeEnd(svContext);
					}
					else {
						context.fail("Nothing to do");
					}
				};

				if (event.session && event.session.new && sv._skillVCContext.appConfig.sessionHandlerExecutor) {
					// since session starts can be async, we have to handle the promise
					// have to use bind because you don't want to call the function now, you want the call to `then` to do it
					sv._skillVCContext.appConfig.sessionHandlerExecutor.executeStart(sv._skillVCContext)
						.then(handleRequest.bind(this, event.request.type, sv._skillVCContext));
				}
				else {
					// if we don't have to wait for session start, just run like normal
					handleRequest(event.request.type, sv._skillVCContext);
				}
			},
			failure : function(err) {
				log.error("Error Initializing. "+err);
				throw new Error("Error initializing. "+err);
			}
		});
    } catch (e) {
		log.error("Exception handling skill request:"+e.stack);
		context.fail("Exception handling skill request: " + e);
	}
};

/**
 * Called when SkillVC is looking to register/load the ResponseManager.  By default this method uses the
 * ResponseManager specified by svContext.appConfig.responseManager.
 * 
 * Extending SkillVC and overriding this method will allow for a more customized approach
 * 
 * @function
 * @param  {SVContext} svContext   	The svContext. As this is called during initialization, not all objects 
 *                                  may be available in the context
 * @return {ResponseManager}         	The ResponseManager to use
 */
SkillVC.prototype.registerResponseManager = function(svContext) {
	return svContext.appConfig.responseManager;
};

/**
 * Called when SkillVC is looking to register/load the SessionHandlerManager.  By default this method uses the
 * SessionHandlerManager specified by svContext.appConfig.sessionHanlerManager.
 * 
 * Extending SkillVC and overriding this method will allow for a more customized approach
 * 
 * @function
 * @param  {SVContext} svContext   	The svContext. As this is called during initialization, not all objects 
 *                                  may be available in the context
 * @return {SessionHandlerManager}  The SessionHandlerManager to use
 */
SkillVC.prototype.registerSessionHandlerManager = function(svContext) {
	return svContext.appConfig.sessionHandlerManager;
};

/**
 * Called when SkillVC is looking to register/load the IntentHandlerManager. By default this method uses
 * the {@link IntentHandlerManager} specified by svContext.appConfig.intentHandlerManager.  To have the 
 * IntentHandlerManager by part of the execution cycle the passed in IntentHandlerManager is wrapped
 * by a {@link IntentHandlerFilter}.
 *
 * When creating an IntentHandler, if you specifiying an intent name of 'launch' 
 * it will cause the IntentHandler to be invoked on a @link{http://tinyurl.com/jpdl5cc|LaunchRequest}
 * 
 * Extending SkillVC and overriding this method will allow for a more customized approach but will need
 * to take into consideration that SkillVC requires a {@link IntentHandlerFilter} to actually execute
 * 
 * @function
 * @param  {SVContext} svContext   	The svContext. As this is called during initialization, not all objects 
 *                                  may be available in the context
 * @param {SkillVC~callback} callback	The callback to use when registration has completed
 */
SkillVC.prototype.registerIntentHandlerManager = function(svContext, callback) {
	callback.success(svContext.appConfig.intentHandlerManager);
};

/**
 * Called when SkillVC is looking to register/load the FilterManager.  By default this method uses the
 * {@link Filter|FilterManager} specified by svContext.appConfig.filterManager
 * 
 * Extending SkillVC and overriding this method will allow for a more customized approach
 * 
 * @function
 * @param {SVContext} svContext   	The svContext. As this is called during initialization, not all objects 
 *                                  may be available in the context
 * @param {SkillVC~callback} callback	The callback to use when registration has completed
 */
SkillVC.prototype.registerFilterManager = function(svContext, callback) {
	callback.success(svContext.appConfig.filterManager);
};

/**
 * Returns the current SkillVC context instance
 * 
 * @fuction
 * @return {SVContext} The SVContext of this instance of SkillVC
 */
SkillVC.prototype.getContext = function() {
	return this._skillVCContext;
};

/**
 * @typedef {Object<string,function>} SkillVC~callback
 * @property {function} success 
 * @property {JSON} 	success.result The results of the successful registration
 * @property {function} failure
 * @property {JSON}   	failure.error The result of an unsuccessful registration
 */


module.exports = SkillVC;