/***********************************************************************/
/*                                                                     */
/*                      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 WorkflowTriggerValidationStatus(/*[WorkflowTriggerValidationStatus]*/ inStatus)
{
	this.state = WorkflowTriggerValidator.STATUS_UNDEFINED;
	this.trigger = null;
	
	if (isValidProperty(inStatus))
	{
		this.state = inStatus.state;
		this.trigger = inStatus.trigger;
	}
}

function WorkflowTriggerValidator(/*[WorkflowTrigger]*/ inTrigger)
{
	throwInvalid(inTrigger, 'WorkflowTrigger');

	var trigger = inTrigger;
	var status = new WorkflowTriggerValidationStatus();
	var resultHandler = null;
	
	this.link = function(/*[Function]*/ inResultHandler)
	{
		resultHandler = inResultHandler;
	}
	
	this.validate = function(/*[CSEvent]*/ inEventObj, /*[WorkflowTriggerValidationStatus]*/ inStatus)
	{
		throwInvalid(resultHandler);

		dbglogInfo('WorkflowTriggerValidator ("' + trigger.eventType + '") VALIDATE "' + inEventObj.type + '"');
		
		if (isValidProperty(inStatus))
		{
			status = new WorkflowTriggerValidationStatus(inStatus);
		}
		
		if (((status.state & WorkflowTriggerValidator.STATUS_TRUE) &&
			 !(status.state & WorkflowTriggerValidator.STATUS_IGNORE)) ||
			(status.state & WorkflowTriggerValidator.STATUS_FALSE))
		{
			// there was a complete match already, no need to validate
			resultHandler(inEventObj, status);
		}
		else
		{
			trigger.validate(inEventObj, onValidateResult);
		}
	}
	
	function onValidateResult(/*[WorkflowTrigger]*/ inTrigger, /*[CSEvent]*/ inEventObj, /*[Boolean]*/ inMatch)
	{
		dbglogInfo('WorkflowTriggerValidator ("' + inTrigger.eventType + '") RESULT "' + inEventObj.type + '" match:' + inMatch);
		
		var callResultHdl = true;
		
		if (inMatch)
		{
			if (inTrigger.isPositiveMatch())
			{
				// trigger match to the event
				status.state |= WorkflowTriggerValidator.STATUS_TRUE;
			
				if (inTrigger.ignore)
				{
					// trigger should be ignored
					status.state |= WorkflowTriggerValidator.STATUS_IGNORE;
				}
				else
				{
					// complete match, completely without doubt
					status.state &= WorkflowTriggerValidator.STATUS_TRUE;
					status.trigger = inTrigger;
				}
			}
			else
			{
				status.state |= WorkflowTriggerValidator.STATUS_FALSE;
				status.trigger = inTrigger;
			}
		}
		else if(trigger.timer)
		{
			// trigger timer needs to be restarted
			status.state |= WorkflowTriggerValidator.STATUS_TIMER;
		}
		else if (inTrigger.isPositiveMatch() && inTrigger.hasContent())
		{
			// If a positive match trigger didn't matched then check
			// if only the condition failed. If that's the case and there's
			// content defined for this case then let this step fail
			// in order to show the content
			//
			callResultHdl = false;
			
			inTrigger.validate(inEventObj, function(/*[WorkflowTrigger]*/ inTrigger, /*[CSEvent]*/ inEventObj, /*[Boolean]*/ inMatch)
			{
				if (inMatch)
				{
					status.state |= WorkflowTriggerValidator.STATUS_FALSE;
					status.trigger = inTrigger;
				}
				
				resultHandler(inEventObj, status);
				
			}, true);
		}
		
		if (callResultHdl)
		{
			resultHandler(inEventObj, status);
		}
	}
}

WorkflowTriggerValidator.STATUS_UNDEFINED	= 0x0;
WorkflowTriggerValidator.STATUS_FALSE 		= 0x1;
WorkflowTriggerValidator.STATUS_TRUE 		= 0x2;
WorkflowTriggerValidator.STATUS_IGNORE 		= 0x4;
WorkflowTriggerValidator.STATUS_TIMER 		= 0x8;

//////////////////////////////////////////////////////////////////////////////
 
function WorkflowTriggerHandler(/*[Array]*/ inTriggers, /*[Function]*/ inCallback)
{
	throwInvalid(inTriggers, 'Array');
	throwInvalid(inCallback, 'Function');
	
	var triggers = inTriggers;
	var callback = inCallback;		// function([String], [WorkflowTriggerValidationStatus])
	var timer = false;
	var timerID = NaN;
	var processingBlocked = false;
	var pendingEvents = [];
	var eventHandlerFct = null;
	var validatorChain = null;
	var installNegative = true;
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Install handler for triggers
	//
	this.installHandler = function()
	{
		var events = getTriggerEventTypes(triggers, true);
		registerEventHandler(this, events);
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Remove handler for triggers
	//
	this.uninstallHandler = function()
	{
		pendingEvents = [];
		blockProcessing(false);
		
		var events = getTriggerEventTypes(triggers, true);
		unregisterEventHandler(eventHandlerFct, events);
		eventHandlerFct = null;
	}
	
	// private ///////////////////////////////////////////////////////////////////

	//////////////////////////////////////////////////////////////////////////////
	//
	// Register event handler
	//
	function registerEventHandler(/*[WorkflowTriggerHandler]*/ inTargetObj, /*[Array]*/ inEventTypes)
	{
		eventHandlerFct = function(inEventObj)
		{
			processEvent(inEventObj);
		}
		
		var registered = {};
		var eventHdl = false;
		
		forEach(inEventTypes, function(type)
		{
			if (!isValidProperty(registered[type]))
			{
				if (type == WorkflowTrigger.EVENTTYPE_TIMER)
				{
					timer = true;
				}
				else
				{
					addEventListener(type, eventHandlerFct);
					eventHdl = true;
				}
				
				registered[type] = true;
			}
		});
		
		if (!eventHdl)
		{
			eventHandlerFct = null;
		}
		
		if (timer)
		{
			// start timer
			startTriggerTimer(inTargetObj);
		}
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Unregister event handler
	//
	function unregisterEventHandler(/*[Function]*/ inHandler, /*[Array]*/ inEventTypes)
	{
		var timer = false;
		
		forEach(inEventTypes, function(type)
		{
			if (type != WorkflowTrigger.EVENTTYPE_TIMER)
			{
				removeEventListener(type, inHandler);
			}
			else if (!isNaN(timerID))
			{
				clearTimeout(timerID);
				timerID = NaN;
			}
		});
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Start timer for timer based WorkflowTrigger
	//
	function startTriggerTimer()
	{
		timerID = setTimeout(function()
		{
			processEvent(new CSEvent(WorkflowTrigger.EVENTTYPE_TIMER));
		}, TIMEOUT_STEP_TIMER);	
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Get event type names for current step
	//
	function getTriggerEventTypes(/*[Boolean]*/ inNegative)
	{
		var timer = false;
		var events = [];

		forEach(triggers, function(trigger)
		{
			if (isValidProperty(trigger.eventType) &&
				trigger.eventType.length)
			{
				events.push(trigger.eventType);
			}
			else if (isValidProperty(trigger.timer) &&
					 isValidProperty(trigger.condition) &&
					 trigger.condition.length)
			{
				if (trigger.timer && !timer)
				{
					events.push(WorkflowTrigger.EVENTTYPE_TIMER);
				}
			
				timer |= trigger.timer;
			}
		});
		
		if (isValidProperty(inNegative) && inNegative && installNegative)
		{
			var negTriggers = getNegativeTriggers();
			
			forEach(negTriggers, function(trigger)
			{
				if (isValidProperty(trigger.eventType) && events.indexOf(trigger.eventType) < 0)
				{
					events.push(trigger.eventType);
				}
			});
		}
		
		return events;
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Get negative event types for current step
	//
	function getNegativeTriggers()
	{
		if (!isValidProperty(WorkflowTriggerHandler.negativeTriggers))
		{
			var cs = new CSInterface();
			var path = cs.getSystemPath(SystemPath.EXTENSION) + '/' + NEGATIVE_TRIGGER_FILENAME;
			var res = cep.fs.readFile(path);
			
			if (isValidProperty(res) && res.err == cep.fs.NO_ERROR)
			{
				try
				{
					var obj = JSON.parse(res.data);
					throwInvalid(obj, 'Array');
					
					WorkflowTriggerHandler.negativeTriggers = [];
					
					forEach(obj, function(triggerObj)
					{
						var trigger = new WorkflowTrigger();
						trigger.initializeJSON(triggerObj);
						WorkflowTriggerHandler.negativeTriggers.push(trigger);
					});
				}
				catch(exc)
				{
					exclog(exc);
					WorkflowTriggerHandler.negativeTriggers = [];
				}
			}		
		}
		
		return WorkflowTriggerHandler.negativeTriggers;
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Event handling function
	// Start evaluation of triggers (pos./neg.) for the incoming event
	//
	function processEvent(/*[CSEvent]*/ inEventObj)
	{
		if (processingBlocked)
		{
			if (pendingEvents.length < WorkflowTriggerHandler.MAX_PENDING_EVENTS)
			{
				dbglogInfo('WorkflowTriggerHandler SHIFT "' + inEventObj.type + '"');
				pendingEvents.push(inEventObj);
			}
			else
			{
				dbglogInfo('WorkflowTriggerHandler DISCARD "' + inEventObj.type + '"');
			}
		}
		else if (isValidProperty(validatorChain))
		{
			blockProcessing(true);
			dbglogInfo('WorkflowTriggerHandler START "' + inEventObj.type + '"');
			validatorChain.validate(inEventObj);
		}
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Result from validation chain
	//
	function onValidationResult(/*[CSEvent]*/ inEventObj, /*[WorkflowTriggerValidationStatus]*/ inStatus)
	{
		if (inStatus.state & WorkflowTriggerValidator.STATUS_TRUE)
		{
			if (inStatus.state & WorkflowTriggerValidator.STATUS_IGNORE)
			{
				if (inStatus.state & WorkflowTriggerValidator.STATUS_TIMER)
				{
					startTriggerTimer();
				}
				
				blockProcessing(false);
			}
			else
			{
				// signal trigger matched
				callCallback(WorkflowProcessor.FINISHED_STEP_SUCCESS, inStatus);
			}
		}
		else if (inStatus.state & WorkflowTriggerValidator.STATUS_FALSE)
		{
			callCallback(WorkflowProcessor.FINISHED_FAILURE, inStatus);
		}
		else if (inStatus.state & WorkflowTriggerValidator.STATUS_TIMER)
		{
			blockProcessing(false);
			startTriggerTimer();
		}
		else if (installNegative)
		{
			callCallback(WorkflowProcessor.FINISHED_FAILURE, inStatus);
		}
		else
		{
			blockProcessing(false);
		}
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// Un/block event processing
	//
	function blockProcessing(/*[Boolean]*/ inBlock)
	{
		if (inBlock != processingBlocked)
		{
			processingBlocked = inBlock;
			
			if (!processingBlocked && pendingEvents.length > 0)
			{
				var evObj = pendingEvents.shift();
				processEvent(evObj);
			}
		}
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// call the callback
	//
	function callCallback(/*[String]*/ inReason, /*[WorkflowTriggerValidationStatus]*/ inStatus)
	{
		dbglogInfo('WorkflowTriggerHandler CALL "' + inReason + '" : 0x' + inStatus.state.toString(16));
		callback(inReason, inStatus);
	}
	
	//////////////////////////////////////////////////////////////////////////////
	//
	// initialize
	//
	(function()
	{
		dbglogInfo('WorkflowTriggerHandler INIT');
		var validators = [];
		
		forEach(triggers, function(trigger)
		{
			var validator = new WorkflowTriggerValidator(trigger);
			validators.push(validator);
			
			if (!trigger.isPositiveMatch())
			{
				installNegative = false;
			}
		});

		if (validators.length > 0)
		{
			validatorChain = validators.shift();
			var tail = validatorChain;

			forEach(validators, function (validator) {
				tail.link(validator.validate);
				tail = validator;
			});

			tail.link(onValidationResult);
		}
	})();
}

WorkflowTriggerHandler.negativeTriggers = null;
WorkflowTriggerHandler.MAX_PENDING_EVENTS = 20;