/**
 * CompositeKeyMatcher.java
 * Created Mar 16, 2007
 * Copyright (C) Tandberg Television 2007
 */
package com.tandbergtv.workflow.driver.command.locate;

import static com.tandbergtv.workflow.core.ProcessStatus.BUSY;
import static com.tandbergtv.workflow.core.ProcessStatus.QUEUED;
import static com.tandbergtv.workflow.core.ProcessStatus.RUNNING;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import org.jbpm.graph.def.Node;
import org.jbpm.graph.exe.Token;

import com.tandbergtv.workflow.core.AutomaticTaskNode;
import com.tandbergtv.workflow.core.CustomToken;
import com.tandbergtv.workflow.core.WorkflowProcess;
import com.tandbergtv.workflow.core.service.ServiceRegistry;
import com.tandbergtv.workflow.core.service.cache.ICacheService;
import com.tandbergtv.workflow.message.WorkflowMessage;

/**
 * Looks up the token that corresponds to a message by matching the set of pre-defined composite keys
 * 
 * @author Sahil Verma
 */
public class CompositeKeyMatcher extends AbstractTokenLocator {

	private static final Logger logger = Logger.getLogger(CompositeKeyMatcher.class);
	
	/* (non-Javadoc)
	 * @see com.tandbergtv.workflow.messageprocessor.ITokenLocatorStrategy#locate(com.tandbergtv.workflow.message.WorkflowMessage)
	 */
	@SuppressWarnings("unchecked")
	public Token locate(WorkflowMessage message) throws TokenLocatorException {
		ICacheService<WorkflowProcess> cache = 
			(ICacheService<WorkflowProcess>)ServiceRegistry.getDefault().lookup("Process Cache");
		
		for (Serializable id : cache.getKeys()) {
			WorkflowProcess process = cache.get(id);
			
			for (CustomToken token : process.findAllTokens()) {
				logger.debug(token + ", trying to match message " + message.getMessageUID());
				boolean match = correlateToken(message, token);

				if (match)
					return token;
			}
		}
		
		return null;
	}
	
	/**
	 * Performs the token to message correlation
	 * 
	 * @param message
	 * @param token
	 * @return true if the message is intended for the specified token
	 * @throws TokenLocatorException
	 */
	protected boolean correlateToken(WorkflowMessage message, CustomToken token) throws TokenLocatorException {
		/* Be sure to check the status */
		if (token.getStatus() != RUNNING && token.getStatus() != BUSY && token.getStatus() != QUEUED)
			return false;

		Node node = getNode(token);

		/* Not expecting a message, bail */
		if (!needsMessage(node, message))
			return false;

		Map<String, String> keys = getCorrelationKeys(node);
		
		if (keys.isEmpty())
			return false;
		
		boolean found = true;

		/* OK, we want the message but do the variables match? */
		for (String key : keys.keySet()) {
			String name = keys.get(key);
			String value = token.getProcessInstance().getContextInstance().getVariable(key).toString();

			logger.debug("Variable " + key + " value " + value + " message parameter "
				+ name + " value " + message.getValue(name));

			found = found && value.equals(message.getValue(name));
		}

		return found;
	}
	
	/**
	 * Returns the set of composite keys for the specified process (to be precise, its template).
	 * Also returns the corresponding message parameter name for each key
	 * 
	 * @param node
	 * @return
	 * @throws TokenLocatorException
	 */
	private Map<String, String> getCorrelationKeys(Node node) throws TokenLocatorException {
		Map<String, String> keys = new HashMap<String, String>();
		
		if (!(node instanceof AutomaticTaskNode))
			return keys;
		
		try {
			keys = AutomaticTaskNode.class.cast(node).getCorrelationKeys();
		} catch (RuntimeException e) {
			throw new TokenLocatorException(e);
		}
		
		return keys;
	}
}
