/***********************************************************************/
/*                                                                     */
/*                      ADOBE CONFIDENTIAL                             */
/*                   _ _ _ _ _ _ _ _ _ _ _ _ _                         */
/*                                                                     */
/*  Copyright 2016 Adobe Systems Incorporated                          */
/*  All Rights Reserved.                                               */
/*                                                                     */
/* NOTICE:  All information contained herein is, and remains           */
/* the property of Adobe Systems Incorporated and its suppliers,       */
/* if any.  The intellectual and technical concepts contained          */
/* herein are proprietary to Adobe Systems Incorporated and its        */
/* suppliers and are protected by all applicable intellectual property */
/* laws, including trade secret and copyright laws.                    */
/* Dissemination of this information or reproduction of this material  */
/* is strictly forbidden unless prior written permission is obtained   */
/* from Adobe Systems Incorporated.                                    */
/*                                                                     */
/***********************************************************************/

function WorkflowProcessorState(/*[Number]*/ inMaxSteps)
{
	this.step		= NaN;
	this.stepRetry	= NaN;
	this.isSuccess	= false;
	this.isFailure	= false;
	
	var maxStep = inMaxSteps;
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Reset
	//
	this.reset = function(/*[Boolean]*/ inInit)
	{
		if (inInit)
		{
			this.step		= NaN;
			this.stepRetry	= NaN;
			this.isSuccess	= false;
			this.isFailure	= false;
		}
		else
		{
			this.step		= 0;
			this.stepRetry	= 0;
			this.isSuccess	= false;
			this.isFailure	= false;
		}
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Increase step counter, call callback when exceeding max count
	//
	this.incStep = function(/*[Function]*/ inCallback)
	{
		if (isNaN(this.step))
		{
			this.step = 0;
		}
		else
		{
			this.step++;
		}
		
		var ret = (this.step < maxStep);
		
		if (ret)
		{
			this.stepRetry = 0;
		}
		else if (isValidProperty(inCallback))
		{
			inCallback();
		}
		
		return ret;
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Decrease step counter, call callback when fall below 0
	//
	this.decStep = function(/*[Function]*/ inCallback)
	{
		if (isNaN(this.step))
		{
			this.step = 0;
		}
		else
		{
			this.step--;
		}
		
		var ret = (this.step >= 0);
		
		if (ret)
		{
			this.stepRetry = 0;
		}
		else if (isValidProperty(inCallback))
		{
			inCallback();
		}
		
		return ret;
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Increase retry count
	//
	this.incRetry = function()
	{
		if (isNaN(this.stepRetry))
		{
			this.stepRetry = 0;
		}
		else
		{
			this.stepRetry++;
		}
		
		this.isFailure = false;
	}

	//////////////////////////////////////////////////////////////////////////////
	//
	// Set success flag
	//
	this.setSuccess = function()
	{
		this.isSuccess = true;
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Set failure flag
	//
	this.setFailure = function()
	{
		this.isFailure = true;
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Clear failure flag
	//
	this.clearFailure = function()
	{
		this.isFailure = false;
	}
}

function WorkflowProcessor(/*[Workflow]*/ inWorkflow, /*[PanelSurface]*/ inSurface)
{
	throwInvalid(inWorkflow);
	throwInvalid(inSurface);
	
	var workflow = inWorkflow;
	var display = new WorkflowDisplayAdapter(inSurface, workflow);
	var maxSteps = inWorkflow.getStepLength();
	var state = new WorkflowProcessorState(maxSteps);
	var timerID = NaN;
	var timeMeasure = {};
	
	this.triggerHandler = null;

	// unique workflow identifier for this processing
	workflow.session = generateUUID();
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Initialize Processor
	//
	this.initialize = function()
	{
		state.reset(true);
		display.showInitial();

		if (!isNaN(timerID))
		{
			clearTimeout(timerID);
		}

		dbglogInfo('WorkflowProcessor INIT - Workflow: "' + workflow.name + '" [' + workflow.id + ']');
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Return is started
	//
	this.isStarted = function()
	{
		return !isNaN(state.step);
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Return is in failure
	//
	this.isFailure = function()
	{
		return state.isFailure;
	}

	//////////////////////////////////////////////////////////////////////////////
	//
	// Return true if interactive step succeeded
	//
	this.isSuccess = function()
	{
		return state.isSuccess;
	}

	//////////////////////////////////////////////////////////////////////////////
	//
	// Return current step number
	//
	this.getCurrentStep = function()
	{
		return state.step;
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Return measured time
	//
	this.getMeasurements = function()
	{
		return timeMeasure;
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Return how often the current step was retried
	//
	this.getRetry = function()
	{
		return state.stepRetry;
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Start workflow processing
	//
	this.start = function()
	{
		this.uninstallTrigger(state.step);
		state.reset(false);

		if (!isNaN(timerID))
		{
			clearTimeout(timerID);
		}
		try
		{
			display.showStep(state.step);
		}
		catch(exc)
		{
			exclog(exc);
		}
		
		this.installTrigger(state.step);
		gBroadcaster.postMessage(WorkflowProcessor.STARTED);

		startMeasure('workflow');
		startMeasure('step_total' + state.step);
		dbglogInfo('WorkflowProcessor START - Workflow: "' + workflow.name + '" [' + workflow.id + ']');
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Switch to next step 
	// (if there's no more steps then finish processing)
	//
	this.nextStep = function()
	{
		endMeasure('step_total' + state.step);
		this.uninstallTrigger(state.step);

		if (!isNaN(timerID))
		{
			clearTimeout(timerID);
		}

		if (!isNaN(state.step))
		{
			// switch from one step to the next one
			gBroadcaster.postMessage(WorkflowProcessor.SWITCH_STEP_NEXT);
		}

		if (state.incStep(function()
			{
				finish(WorkflowProcessor.FINISHED_SUCCESS);
			}))
		{
			try
			{
				display.showStep(state.step);
			}
			catch(exc)
			{
				exclog(exc);
			}
			
			this.installTrigger(state.step);
			startMeasure('step_total' + state.step);
			dbglogInfo('WorkflowProcessor NEXT - Workflow: "' + workflow.name + '" [' + workflow.id + ']');
		}
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Switch to previous step
	// (if there's no previous step then stay with the current)
	//
	this.prevStep = function()
	{
		endMeasure('step_total' + this.step);
		this.uninstallTrigger(this.step);

		if (!isNaN(timerID))
		{
			clearTimeout(timerID);
		}

		if (!isNaN(state.step))
		{
			// switch from one step to the previous one
			gBroadcaster.postMessage(WorkflowProcessor.SWITCH_STEP_PREV);
		}

		if (state.decStep(function() { gBroadcaster.sendMessage(WorkflowProcessor.RESTART); }))
		{
			try
			{
				display.showStep(state.step);
			}
			catch(exc)
			{
				exclog(exc);
			}
		
			this.installTrigger(state.step);
			startMeasure('step_total' + state.step);
			dbglogInfo('WorkflowProcessor PREV - Workflow: "' + workflow.name + '" [' + workflow.id + ']');
		}
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Refresh current step
	//
	this.refreshStep = function()
	{
		this.uninstallTrigger(state.step);
		
		state.incRetry();

		if (!isNaN(timerID))
		{
			clearTimeout(timerID);
		}

		try
		{
			display.showStep(state.step);
		}
		catch(exc)
		{
			exclog(exc);
		}
		
		this.installTrigger(state.step);
		dbglogInfo('WorkflowProcessor REFRESH - Workflow: "' + workflow.name + '" [' + workflow.id + ']');
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// User did something unexpected for the current step
	//
	this.failure = function(/*[Any]*/ inData)
	{
		endMeasure('step_total' + state.step);
		dbglogInfo('WorkflowProcessor FAILURE - Workflow: "' + workflow.name + '" [' + workflow.id + ']');
		state.setFailure();
		finish(WorkflowProcessor.FINISHED_FAILURE, inData)
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// User canceled the workflow processing
	//
	this.cancel = function()
	{
		endMeasure('step_total' + state.step);
		this.uninstallTrigger(state.step);

		try
		{
			display.showCancel(this.step);
		}
		catch(exc)
		{
			exclog(exc);
		}
		
		dbglogInfo('WorkflowProcessor CANCEL - Workflow: "' + workflow.name + '" [' + workflow.id + ']');
		finish(WorkflowProcessor.FINISHED_CANCELED);
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Install trigger for current step
	//
	this.installTrigger = function(/*[Number]*/ inStepNumber)
	{
		if (!isNaN(inStepNumber))
		{
			var wfStep = workflow.getStep(inStepNumber);
			
			if (isValidProperty(wfStep))
			{
				var triggers = wfStep.getTriggers();

				if (isValidProperty(triggers) && triggers.length > 0)
				{
					try
					{
						var processor = this;
						this.triggerHandler = new WorkflowTriggerHandler(triggers, function(/*[String]*/inReason, /*WorkflowTriggerValidationStatus*/ inStatus)
						{
							processor.onTrigger(inReason, inStatus);
						});
						this.triggerHandler.installHandler();
					}
					catch(exc)
					{
						exclog(exc);
						this.triggerHandler = null;
					}
				}
			}
		}
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Uninstall trigger for current step
	//
	this.uninstallTrigger = function(/*[Number]*/ inStepNumber)
	{
		// clear event objects
		var job = new ScriptJob('clearEvents();');
		job.wait(null, function(/*[String]*/ inError)
		{
			dbglogError(inError);
		});

		if (isValidProperty(this.triggerHandler))
		{
			this.triggerHandler.uninstallHandler();
			this.triggerHandler = null;
		}
	}
	
	// private ///////////////////////////////////////////////////////////////////
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Finalize workflow processing
	//
	function finish(/*[String]*/ inWhy, /*[Any]*/ inData)
	{
		dbglogInfo('WorkflowProcessor FINISH (Reason:"' + inWhy + '") - Workflow: "' + workflow.name + '" [' + workflow.id + ']');
		
		if (!isNaN(timerID))
		{
			clearTimeout(timerID);
		}

		gBroadcaster.sendMessage(inWhy, inData);
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Trigger handler callback function
	//
	this.onTrigger = function(/*[String]*/ inReason, /*WorkflowTriggerValidationStatus*/ inStatus)
	{
		dbglogInfo('WorkflowProcessor TRIGGER (Reason:"' + inReason + '") - Workflow: "' + workflow.name + '" [' + workflow.id + ']');
		
		switch (inReason)
		{
			case WorkflowProcessor.FINISHED_STEP_SUCCESS:
			{
				endMeasure('step_total' + state.step);
				this.uninstallTrigger(state.step);
				state.clearFailure();
				state.setSuccess();

				gBroadcaster.sendMessage(WorkflowProcessor.FINISHED_STEP_SUCCESS);

				switch(workflow.stepswitch)
				{
					case Workflow.STEPSWITCH_AUTOMATIC:
					{
						// start next step with a short delay
						var processor = this;
						timerID = setTimeout(function()
						{
							timerID = NaN;
							gBroadcaster.sendMessage(WorkflowProcessor.FINISHED_NEXT_STEP);
						}, TIMEOUT_SWITCH_NEXT_STEP);
					}
					break;
				}
			}
			break;
			
			case WorkflowProcessor.FINISHED_FAILURE:
			{
				this.uninstallTrigger(state.step);
				
				var data = null;
				
				if (isValidProperty(inStatus) &&
				    isValidProperty(inStatus.trigger) &&
				    isValidProperty(inStatus.trigger.content))
				{
					data = inStatus.trigger.content;
				}
				
				var processor = this;
				processor.failure(data);
			}
			break;
		}
	}
	
	function startMeasure(/*[String]*/ inName)
	{
		timeMeasure[inName] = new TimeMeasure();
	}
	
	function endMeasure(/*[String]*/ inName)
	{
		if (isValidProperty(timeMeasure[inName]))
		{
			timeMeasure[inName].end();
		}
	}
}

WorkflowProcessor.STARTED 				= 'started';
WorkflowProcessor.RESTART				= 'restart';
WorkflowProcessor.FINISHED_SUCCESS 		= 'success';
WorkflowProcessor.FINISHED_FAILURE 		= 'failure';
WorkflowProcessor.FINISHED_CANCELED 	= 'canceled';
WorkflowProcessor.FINISHED_STEP_SUCCESS = 'step_success';
WorkflowProcessor.FINISHED_NEXT_STEP 	= 'next_step';
WorkflowProcessor.SWITCH_STEP_NEXT 		= 'switch_next';
WorkflowProcessor.SWITCH_STEP_PREV 		= 'switch_prev';