package com.tandbergtv.workflow.executionhandler.handlers;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.jbpm.graph.def.Node;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.graph.exe.Token;

import com.tandbergtv.workflow.core.WorkflowProcess;
import com.tandbergtv.workflow.core.event.DefaultMediator;
import com.tandbergtv.workflow.exe.ActionException;
import com.tandbergtv.workflow.core.graph.exe.IExecutable;
import com.tandbergtv.workflow.message.event.WorkflowMessageLogEvent;
import com.tandbergtv.workflow.message.WPCLCommand;
import com.tandbergtv.workflow.message.WorkflowMessage;
import com.tandbergtv.workflow.message.WorkflowMessageFactory;
import com.tandbergtv.workflow.message.WorkflowPayload;
import com.tandbergtv.workflow.message.WorkflowMessage.MessageType;
import com.tandbergtv.workflow.message.util.WFSMessageConstants;
import com.tandbergtv.workflow.messageprocessor.CommandManager;

/**
 * @author Raj Prakash
 *
 * Prepares a WPCL message with 'create' command with variables
 * selected and added based on the supplied configuration
 * and calls the Command Manager to execute the message.
 */
public class WorkOrderCreatorAction implements IExecutable {
	private static final long serialVersionUID = 1L;
	
	private static final String CONSTANT_UID = "uid";
	private static final String CONSTANT_SELECTOR = "selector";
	
	/*
	 * Set in the template using <configFile> tag as a nested tag to <action>.
	 * 
	 * Refers to the config file that will be available in the 
	 * WatchPoint application lib directory
	 * (usually, C:\Program Files\TandbergTV\WatchPoint Workflow System\lib).
	 * 
	 * Holds just the file name.
	 */
	private String configFile;
	
	/* (non-Javadoc)
	 * @see com.tandbergtv.workflow.exe.IExecutable#abort()
	 */
	public void abort() {}

	/* (non-Javadoc)
	 * @see org.jbpm.graph.def.ActionHandler#execute(org.jbpm.graph.exe.ExecutionContext)
	 */
	public void execute(ExecutionContext ec) throws Exception {
		WorkflowMessage requestMessage = createMessage(ec);
		storeMessage(ec.getToken(), ec.getNode(), requestMessage, false);
		WorkflowMessage responseMessage = CommandManager.createAndExecute(requestMessage);
		storeMessage(ec.getToken(), ec.getNode(), responseMessage, true);
		validateResponse(responseMessage);
	}
	
	/**
	 * Creates the WPCL Message to be used to call the Command Manager to create
	 * a workorder.
	 * 
	 * @param ec	JBPM Execution Context
	 * @return		Workflow Message
	 */
	/*
	 * Reads the config file.
	 * Constants 'uid' and 'selector' are used as appropriate message attributes.
	 * Other constants (name-value pairs) are added as message parameters.
	 * For the specified variables, corresponding value is obtained from the
	 *  execution context, and used as message parameters. If there are mapped names
	 *  specified for the variables in the configuration, they are used as message
	 *  parameter names instead of the variable names.
	 * Command Parameter 'priority' is set with this workorder's priority. 
	 */
	private WorkflowMessage createMessage(ExecutionContext ec) {
		Map<String, String> parameters = new HashMap<String, String>();
		
		Map<String, String> constants = null;
		//map of variable name to their mapped name
		Map<String, String> variables = null;
		try {
			constants = WorkOrderCreatorConfigReader.getInstance().getConstants(configFile);
			variables = WorkOrderCreatorConfigReader.getInstance().getVariables(configFile);
		} catch(Exception e) {
			throw new ActionException("Exception while reading the config file for this ActionHandler.", e);
		}
		
		String uid = constants.remove(CONSTANT_UID);
		String selector = constants.remove(CONSTANT_SELECTOR);
		parameters.putAll(constants);
		
		//map of variable name to value
		Map<String, String> variableNameValueMap = getVariableValuesFromContext(ec, variables.keySet());
		//map of variable mapped name (if available) or variabble name to value
		Map<String, String> variablesMappedNameValueMap = changeNameToMappedName(variables, variableNameValueMap); 
		parameters.putAll(variablesMappedNameValueMap);
		
		WorkflowMessage message = WorkflowMessageFactory.createControlMessage(uid);
		
		WorkflowPayload payload = new WorkflowPayload();
		message.setPayload(payload);
		for(Map.Entry<String, String> parameter : parameters.entrySet()) {
			payload.putValue(parameter.getKey(), parameter.getValue());
		}
		
		String priorityOfThisWorkOrder = ((WorkflowProcess) ec.getProcessInstance()).getPriority().name();
		message.setCommand(createCommand(selector, priorityOfThisWorkOrder));
		
		return message;
	}
	
	/**
	 * For the given collection of variable names, gets their string values from
	 * the given execution context and return a map of variable name to value.
	 * If a variable is not found in the execution context, null value is assigned.
	 * 
	 * @param ec JBPM Execution Context
	 * @param variableNames	Collection of variable names
	 * @return map of variable name to value
	 */
	private Map<String, String> getVariableValuesFromContext(ExecutionContext ec, Collection<String> variableNames) {
		Map<String, String> variableNameValueMap = new HashMap<String, String>();
		for(String variableName : variableNames) {
			Object objValue = ec.getVariable(variableName);
			String strValue = (objValue != null) ? objValue.toString() : null;
			variableNameValueMap.put(variableName, strValue);
		}
		return variableNameValueMap;
	}
	
	/**
	 * From the given variable name to value map and name to mappedName map,
	 * returns a map of mappedName(if available)/name to value map .
	 * 
	 * @param variables	map of variable name to mapped name 
	 * @param variableNameValueMap	map of variable name to value
	 * @return map of variable mapped name (if available) or name to value
	 */
	private Map<String, String> changeNameToMappedName(Map<String, String> variables, Map<String, String> variableNameValueMap) {
		Map<String, String> result = new HashMap<String, String>();
		for(Map.Entry<String, String> variable : variables.entrySet()) {
			String resultVariableName = (variable.getValue() != null && variable.getValue().trim().length() > 0)
											? variable.getValue()
											: variable.getKey();  
			result.put(resultVariableName, variableNameValueMap.get(variable.getKey()));
		}
		return result;
	}
	
	/**
	 * Creates a WPCLCommand object with the given selector and priority
	 * as command parameters.
	 * 
	 * @param selector	selector class name
	 * @param priority	work order priority
	 * @return	a Create Command object 
	 */
	private WPCLCommand createCommand(String selector, String priority) {
		WPCLCommand command = new WPCLCommand("create");
		command.addParameter(WFSMessageConstants.PARAM_TEMPLATE_SELECTOR_CLASS, selector);
		command.addParameter(WFSMessageConstants.PARAM_PRIORITY, priority);
		return command;
	}
	
	/**
	 * If the response message is of type nack,
	 * throws an ActionException with error-message from the response message.
	 * 
	 * @param responseMessage the response message
	 */
	private void validateResponse(WorkflowMessage responseMessage) {
		if (responseMessage.getType() == MessageType.nack)
			throw new ActionException(responseMessage.getValue(WFSMessageConstants.ERROR_MESSAGE));
	}
	
	/**
	 * Sends a Workflow Message Log Event using the Mediator to store the message.
	 */
	private void storeMessage(Token token, Node node, WorkflowMessage message, boolean incoming)
	{
		WorkflowMessageLogEvent logEvent = new WorkflowMessageLogEvent(token, node, incoming, message);
		DefaultMediator.getInstance().sendAsync(logEvent);
	}
}
