package de.ugoe.cs.quest.plugin.mfc;

import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;

import de.ugoe.cs.quest.eventcore.Event;
import de.ugoe.cs.quest.plugin.mfc.eventcore.MFCEventTarget;
import de.ugoe.cs.quest.plugin.mfc.eventcore.MFCEventType;
import de.ugoe.cs.quest.plugin.mfc.eventcore.WindowTree;
import de.ugoe.cs.quest.plugin.mfc.eventcore.WindowTreeNode;
import de.ugoe.cs.quest.plugin.mfc.eventcore.WindowsMessage;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * Translates sequences of windows messages into {@link WindowsEvent}s that can
 * be used by the EventBench core libraries.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class EventGenerator {

	/**
	 * <p>
	 * Helper method that fetches the document node of an XML file.
	 * </p>
	 * 
	 * @param filename
	 *            name of the XML file
	 * @return the document node
	 */
	private static Document getDocument(String filename) {
		SAXBuilder builder = new SAXBuilder();
		Document doc = null;

		try {
			doc = builder.build(filename);
			rulesNamespace = Namespace.getNamespace("ul:rules");
		} catch (JDOMException e) {
			Console.printerrln("Invalid rules file.");
			e.printStackTrace();
		} catch (IOException e) {
			Console.printerrln("Invalid rules file.");
			e.printStackTrace();
		}

		return doc;
	}

	/**
	 * <p>
	 * Name and path of the XML files containing the rules.
	 * </p>
	 */
	private String rulesFile;

	/**
	 * <p>
	 * Iterator used for the current sequence.
	 * </p>
	 */
	private ListIterator<WindowsMessage> sequenceIterator;

	/**
	 * <p>
	 * Token that is currently being generated.
	 * </p>
	 */
	private Event currentToken;
	
	/**
	 * <p>
	 * Event type of the current token. Stored as a member to be able to update it during the parsing of the idinfo tag.
	 * </p>
	 */
	private MFCEventType currentType;

	/**
	 * <p>
	 * Reference to the ul:rules namespace.
	 * </p>
	 */
	private static Namespace rulesNamespace;

	/**
	 * <p>
	 * The name of the rule that is currently being evaluated.
	 * </p>
	 */
	private String currentRuleName;

	/**
	 * <p>
	 * Internal message storage. Used to implement the
	 * <code>{@literal <store>}</code> and <code>{@literal <storeSeq>}</code>
	 * tags.
	 * </p>
	 */
	private Map<String, Object> messageStorage;

	/**
	 * <p>
	 * Creates a new EventGenerator. Sets "data/rules.xml" as default file for
	 * the rules.
	 * </p>
	 */
	public EventGenerator() {
		rulesFile = "data/rules.xml";
	}

	/**
	 * <p>
	 * Tries to match the rules to the given sequence to generate an
	 * {@link WindowsEvent}.
	 * </p>
	 * <p>
	 * The rules are matched the order, in which they are defined in the XML
	 * file. Therefore, the order of the rules in the file defines priorities,
	 * when multiple rules could be matched to the same sequence.
	 * </p>
	 * 
	 * @param sequence
	 *            sequence of message for which an event will be generated
	 * @return event that matches the messages; null, if no rule can be matched
	 */
	@SuppressWarnings("unchecked")
	public Event generateEvent(List<WindowsMessage> sequence) {
		Document rulesDoc = getDocument(rulesFile);
		Element rulesRoot = rulesDoc.getRootElement();

		List<Element> ruleElements = rulesRoot.getChildren("rule",
				rulesNamespace);

		boolean isMatch = false;

		for (int ruleIndex = 0; ruleIndex < ruleElements.size() && !isMatch; ruleIndex++) {
			Element currentRule = ruleElements.get(ruleIndex);
			currentRuleName = currentRule.getAttributeValue("name");
			currentType = new MFCEventType(currentRuleName);
			currentToken = new Event(currentType);
			
			isMatch = true;
			messageStorage = new HashMap<String, Object>();
			sequenceIterator = sequence.listIterator();
			List<Element> ruleChildrenMsg = currentRule.getChildren("msg",
					rulesNamespace);

			int i = 0;
			while (isMatch && i < ruleChildrenMsg.size()) {
				Element messageElement = ruleChildrenMsg.get(i);
				if ("true".equals(messageElement.getAttributeValue("multiple"))) {
					Element nextMessageElement = null;
					if (i + 1 < ruleChildrenMsg.size()) {
						nextMessageElement = ruleChildrenMsg.get(i + 1);
					}
					try {
						isMatch = matchMultipleMessages(messageElement,
								nextMessageElement);
					} catch (InvalidParameterException e) {
						Console.printerrln(e.getMessage());
					}
				} else {
					try {
						isMatch = matchSingleMessage(messageElement);
					} catch (InvalidParameterException e) {
						Console.printerrln(e.getMessage());
					}
				}
				i++;
			}
			if (isMatch) {
				List<Element> ruleChildren = currentRule.getChildren();
				for (Element genMsgElement : ruleChildren) {
					if (genMsgElement.getName().equals("genMsg")) {
						try {
							generateReplayMessage(genMsgElement);
						} catch (InvalidParameterException e) {
							Console.printerrln(e.getMessage());
							// TODO currentToken.invalidateReplay();
						}
					} else if (genMsgElement.getName().equals("genMsgSeq")) {
						try {
							generateReplaySequence(genMsgElement);
							// TODO currentToken.invalidateReplay();
						} catch (InvalidParameterException e) {
							Console.printerrln(e.getMessage());
							// TODO currentToken.invalidateReplay();
						}
					}
				}
				Element idinfoElement = currentRule.getChild("idinfo",
						rulesNamespace);
				if (idinfoElement != null) {
					// cannot be empty if document is valid
					List<Element> valueElements = idinfoElement.getChildren();
					currentType.setInfo(getTermValue(null,
							valueElements.get(0)));
				}
				Console.traceln(currentToken.getType().toString() + " matched");
			} else {
				currentToken = null;
			}
		}
		if (!isMatch) {
			Console.traceln("no match found for sequence: "
					+ sequence.toString());
		}
		return currentToken;
	}

	// ////////////////////////////////////////////////////////////
	// Helper functions for matching of events, i.e., msg-nodes //
	// ////////////////////////////////////////////////////////////

	/**
	 * <p>
	 * Handles msg-nodes where multiple is not true, i.e., not a sequences.
	 * </p>
	 * 
	 * @param messageElement
	 *            {@link Element} representing the msg-node
	 * @return true, if a match is found; false otherwise
	 */
	private boolean matchSingleMessage(Element messageElement) {
		boolean isMatch = false;
		WindowsMessage currentMessage = null;

		int type = Integer.parseInt(messageElement.getAttributeValue("type"));

		while (!isMatch && sequenceIterator.hasNext()) {
			/*
			 * traverses the messages from the current position forward till a
			 * message with the correct type is found
			 */
			currentMessage = sequenceIterator.next();
			if (type == currentMessage.getType()) {
				// message with the correct type found
				// eval child nodes for further matching/storing
				isMatch = evalEqualRestrictions(currentMessage, messageElement);

				// in case the message is a match, eval storage children
				if (isMatch) {
					handleStorage(messageElement, currentMessage);
					currentToken.setTarget(new MFCEventTarget(currentMessage
							.getXmlWindowDescription()));
					// TODO currentToken.setTargetShort(currentMessage.getParentNames());
				}
			}
		}

		return isMatch;
	}

	/**
	 * <p>
	 * Handles msg-nodes where multiple is true, i.e., sequences. Requires
	 * knowledge about the next msg-node to determine the end of the sequence.
	 * </p>
	 * 
	 * @param messageElement
	 *            {@link Element} representing the msg-node
	 * @param nextMessageElement
	 *            {@link Element} representing the next msg-node; {@code null}
	 *            if the current node is the last one
	 * @return true, if a sequence is matched; false otherwise
	 */
	private boolean matchMultipleMessages(Element messageElement,
			Element nextMessageElement) {
		boolean isMatch = false;
		boolean isCurrentMatch = false;
		boolean nextMatchFound = false;
		WindowsMessage currentMessage = null;
		WindowsMessage nextMessage = null;

		int type = Integer.parseInt(messageElement.getAttributeValue("type"));

		int nextType = -1;
		if (nextMessageElement != null) {
			nextType = Integer.parseInt(nextMessageElement
					.getAttributeValue("type"));
		}

		while (!nextMatchFound && sequenceIterator.hasNext()) {
			currentMessage = sequenceIterator.next();
			if (type == currentMessage.getType()) {
				isCurrentMatch = evalEqualRestrictions(currentMessage,
						messageElement);
				isMatch = isMatch || isCurrentMatch;

				if (isCurrentMatch) {
					handleStorage(messageElement, currentMessage);
					currentToken.setTarget(new MFCEventTarget(currentMessage
							.getXmlWindowDescription()));
					// TODO currentToken.setTargetShort(currentMessage.getParentNames());
				}
			}
			if (nextMessageElement != null && isMatch) {
				// peek next message to check if the sequence ends and the next
				// match is found
				if (!sequenceIterator.hasNext()) {
					return false; // sequence is over, but not all messages are
									// found
				}
				nextMessage = sequenceIterator.next();
				sequenceIterator.previous();

				if (nextType == nextMessage.getType()) {
					nextMatchFound = evalEqualRestrictions(nextMessage,
							nextMessageElement);
				}

			}
		}

		return isMatch;
	}

	/**
	 * <p>
	 * Handles equals-nodes.
	 * </p>
	 * 
	 * @param currentMessage
	 *            {@link Element} representing the msg-node the equals-node
	 *            belongs to
	 * @param messageElement
	 *            {@link Element} representing the equals-node to be evaluated
	 * @return true, if constraint is fulfilled; false otherwise
	 */
	@SuppressWarnings("unchecked")
	private boolean evalEqualRestrictions(WindowsMessage currentMessage,
			Element messageElement) {
		boolean isMatch = true;
		for (Element childElement : (List<Element>) messageElement.getChildren(
				"equals", rulesNamespace)) {
			List<Element> termElements = childElement.getChildren();
			// the size 2 of termElements is guaranteed by the XML schema
			String value1 = getTermValue(currentMessage, termElements.get(0));
			String value2 = getTermValue(currentMessage, termElements.get(1));
			if (value1 == null || value2 == null) {
				isMatch = false;
			} else {
				isMatch = isMatch && value1.equals(value2);
			}
		}
		for (Element childElement : (List<Element>) messageElement.getChildren(
				"equalsSeq", rulesNamespace)) {
			List<Element> termElements = childElement.getChildren();
			List<String> values1 = getTermValueSeq(termElements.get(0));
			List<String> values2 = getTermValueSeq(termElements.get(0));
			if (values1 == null || values2 == null) {
				isMatch = false;
			} else {
				isMatch = isMatch && values1.equals(values2);
			}
		}
		return isMatch;
	}

	/**
	 * <p>
	 * Handles store-nodes and storeSeq-nodes.
	 * </p>
	 * 
	 * @param messageElement
	 *            {@link Element} representing the msg-node that is currently
	 *            being evaluated
	 * @param currentMessage
	 *            current message in the message sequence that is matched; this
	 *            is the message that is stored
	 */
	@SuppressWarnings("unchecked")
	private void handleStorage(Element messageElement,
			WindowsMessage currentMessage) {
		for (Element childElement : (List<Element>) messageElement.getChildren(
				"store", rulesNamespace)) {
			String identifier = childElement.getAttributeValue("var");
			messageStorage.put(identifier, currentMessage);
			resolveHwnd(currentMessage, childElement);
		}
		for (Element childElement : (List<Element>) messageElement.getChildren(
				"storeSeq", rulesNamespace)) {
			String identifier = childElement.getAttributeValue("varSeq");
			Object tmp = messageStorage.get(identifier);
			List<WindowsMessage> storedSequence;
			if (tmp == null || tmp instanceof WindowsMessage) {
				storedSequence = new LinkedList<WindowsMessage>();
				storedSequence.add(currentMessage);
				messageStorage.put(identifier, storedSequence);
			} else if (tmp instanceof List<?>) {
				storedSequence = (List<WindowsMessage>) tmp;
				storedSequence.add(currentMessage);
				messageStorage.put(identifier, storedSequence);
			}
			resolveHwnd(currentMessage, childElement);
		}
	}

	/**
	 * <p>
	 * Resolves a parameter that contains a HWND of a message to the target
	 * string of the HWND and stores it.
	 * </p>
	 * 
	 * @param currentMessage
	 *            message whose HWND is resolved
	 * @param childElement
	 *            child element of the store node that represents the resolve
	 */
	@SuppressWarnings("unchecked")
	private void resolveHwnd(WindowsMessage currentMessage, Element childElement) {
		List<Element> resolveElements = childElement.getChildren("resolveHwnd",
				rulesNamespace);
		for (Element resolveElement : resolveElements) {
			String param = resolveElement.getAttributeValue("param");
			String storeParam = resolveElement.getAttributeValue("storeParam");
			int paramHwnd = Integer
					.parseInt(currentMessage.getParameter(param));
			WindowTreeNode node = WindowTree.getInstance().find(paramHwnd);
			if (node != null) {
				currentMessage.addParameter(storeParam,
						node.xmlRepresentation());
			}
		}
	}

	// /////////////////////////////////////////////////////
	// Helper functions for generating the replay, i.e.,
	// parsing of genMsg und genMsgSeq-nodes
	// /////////////////////////////////////////////////////

	/**
	 * <p>
	 * Handles genMsg-nodes and adds the replay to the {@link Event} that is
	 * generated.
	 * </p>
	 * 
	 * @param genMsgElement
	 *            {@link Element} representing the genMsg-node
	 */
	@SuppressWarnings("unchecked")
	private void generateReplayMessage(Element genMsgElement) {
		List<Element> genMsgChildren = genMsgElement.getChildren();
		WindowsMessage generatedMessage = null;
		if (genMsgChildren.size() == 1) { // replay stored message without
											// change
			String obj = genMsgChildren.get(0).getAttributeValue("obj");
			generatedMessage = getStoredMessageVariable(null, obj);
		} else { // generate message according to the rule
			for (Element genMsgChild : genMsgChildren) {
				Element termElement = (Element) genMsgChild.getChildren()
						.get(0);
				if (genMsgChild.getName().equals("type")) {
					try {
						int msgType = Integer.parseInt(getTermValue(null,
								termElement));
						generatedMessage = new WindowsMessage(msgType);
					} catch (NumberFormatException e) {
						throw new InvalidParameterException(
								"Failure generating replay sequence for rule "
										+ currentRuleName
										+ ": Defined type is not an integer.");
					}
				} else if (genMsgChild.getName().equals("target")) {
					String targetString = getTermValue(null, termElement);
					generatedMessage.setXmlWindowDescription(targetString);
				} else if (genMsgChild.getName().equals("LPARAM")) {
					String paramValueStr = getTermValue(null, termElement);
					long paramValue = 0;
					Element loword = genMsgChild.getChild("LOWORD",
							rulesNamespace);
					if (loword != null) {
						paramValue = loHiWord(genMsgChild);
						generatedMessage.setLPARAM(paramValue);
					} else {
						try {
							paramValue = Integer.parseInt(paramValueStr);
							generatedMessage.setLPARAM(paramValue);
						} catch (NumberFormatException e) {
							generatedMessage
									.setLPARAMasWindowDesc(paramValueStr);
						}
					}
				} else if (genMsgChild.getName().equals("WPARAM")) {
					String paramValueStr = getTermValue(null, termElement);
					long paramValue = 0;
					Element loword = genMsgChild.getChild("LOWORD",
							rulesNamespace);
					if (loword != null) {
						paramValue = loHiWord(genMsgChild);
						generatedMessage.setWPARAM(paramValue);
					} else {
						try {
							paramValue = Integer.parseInt(paramValueStr);
							generatedMessage.setWPARAM(paramValue);
						} catch (NumberFormatException e) {
							generatedMessage
									.setWPARAMasWindowDesc(paramValueStr);
						}
					}
				}
			}
		}
		if (generatedMessage != null) {
			int delay = Integer.parseInt(genMsgElement
					.getAttributeValue("delay"));
			generatedMessage.setDelay(delay);
		} else {
			// TODO currentToken.invalidateReplay();
		}
		currentToken.addReplayable(generatedMessage);
	}

	/**
	 * Handles genMsgSeq-nodes and adds the replay to the {@link Event} that is
	 * generated.</p>
	 * 
	 * @param genMsgElement
	 *            {@link Element} representing the genMsgSeq-node.
	 */
	@SuppressWarnings("unchecked")
	private void generateReplaySequence(Element genMsgElement) {
		List<Element> genMsgSeqChildren = genMsgElement.getChildren();
		List<WindowsMessage> generatedMessageSeq = new LinkedList<WindowsMessage>();
		if (genMsgSeqChildren.size() == 1) {
			String obj = genMsgSeqChildren.get(0).getAttributeValue("seqObj");
			generatedMessageSeq = getStoredSeqVariable(obj);
		} else {
			boolean msgsGenerated = false;
			int constMsgType = 0;
			for (Element genMsgSeqChild : genMsgSeqChildren) {
				Element termElement = (Element) genMsgSeqChild.getChildren()
						.get(0);
				if (genMsgSeqChild.getName().equals("type")) {
					// note: cannot easily be extracted because of mulitple
					// return values
					if (termElement.getName().equals("seqValue")) {
						String obj = termElement.getAttributeValue("seqObj");
						List<WindowsMessage> seqVar = getStoredSeqVariable(obj);
						for (WindowsMessage msg : seqVar) {
							generatedMessageSeq.add(new WindowsMessage(msg
									.getType()));
						}
						msgsGenerated = true;
					} else { // constValue type
						constMsgType = Integer.parseInt(getTermValue(null,
								termElement));
					}
				} else if (genMsgSeqChild.getName().equals("target")) {
					msgsGenerated = createSequenceTarget(generatedMessageSeq,
							msgsGenerated, constMsgType, termElement);
				} else if (genMsgSeqChild.getName().equals("LPARAM")) {
					msgsGenerated = createSequenceLParam(generatedMessageSeq,
							msgsGenerated, constMsgType, termElement);
				} else if (genMsgSeqChild.getName().equals("WPARAM")) {
					msgsGenerated = createSequenceWParam(generatedMessageSeq,
							msgsGenerated, constMsgType, termElement);
				}
			}
		}
		currentToken.addReplayableSequence(generatedMessageSeq);
	}

	/**
	 * <p>
	 * Creates the targets for replay sequences generated with genMsgSeq-nodes.
	 * </p>
	 * 
	 * @param generatedMessageSeq
	 *            list of the messages that is being generated
	 * @param msgsGenerated
	 *            boolean stating if the list of messages is already generated
	 *            or if the generation has to be handles by this method
	 * @param constMsgType
	 *            a constant message type that is used for message generation,
	 *            in case the list of message is generated by this method
	 * @param termElement
	 *            {@link Element} representing the term-node describing the
	 *            target
	 * @return true, if the list of message is generated after calling this
	 *         method; false otherwise
	 * @throws NoSuchElementException
	 *             thrown if the seqVar referred to in the termElement contains
	 *             a different number of messages than is contained in
	 *             messageSeq
	 */
	private boolean createSequenceTarget(
			List<WindowsMessage> generatedMessageSeq, boolean msgsGenerated,
			int constMsgType, Element termElement)
			throws NoSuchElementException {
		Iterator<WindowsMessage> seqIterator = generatedMessageSeq.iterator();
		if (termElement.getName().equals("seqValue")) {
			String obj = termElement.getAttributeValue("seqObj");
			List<WindowsMessage> seqVar = getStoredSeqVariable(obj);
			if (msgsGenerated && seqVar.size() != generatedMessageSeq.size()) {
				throw new InvalidParameterException(
						"Failure generating replay sequence for rule "
								+ currentRuleName
								+ ": One or more of the sequence variables used to generate a sequence have different lenghts.");
			}
			for (WindowsMessage msg : seqVar) {
				WindowsMessage currentSeqMsg = getCurrentSeqMsg(
						generatedMessageSeq, msgsGenerated, constMsgType,
						seqIterator);
				String targetString = msg.getParameter(termElement
						.getAttributeValue("param"));
				currentSeqMsg.setXmlWindowDescription(targetString);
			}
			msgsGenerated = true;
		} else { // const value
			throw new AssertionError("target must be a sequence variable!");
			/*
			 * If target would not be a variable, the message-elements could not
			 * yet be created and the whole sequence might be broken. If this is
			 * to be changed, createSequenceLParam and createSequenceWParam need
			 * to be addepted, too.
			 */
		}
		return msgsGenerated;
	}

	/**
	 * <p>
	 * Creates the LPARAMs for replay sequences generated with genMsgSeq-nodes.
	 * </p>
	 * 
	 * @param generatedMessageSeq
	 *            list of the messages that is being generated
	 * @param msgsGenerated
	 *            boolean stating if the list of messages is already generated
	 *            or if the generation has to be handles by this method
	 * @param constMsgType
	 *            a constant message type that is used for message generation,
	 *            in case the list of message is generated by this method
	 * @param termElement
	 *            {@link Element} representing the term-node describing the
	 *            LPARAM
	 * @return true, if the list of message is generated after calling this
	 *         method; false otherwise
	 * @throws NoSuchElementException
	 *             thrown if the seqVar referred to in the termElement contains
	 *             a different number of messages than is contained in
	 *             messageSeq
	 */
	private boolean createSequenceLParam(
			List<WindowsMessage> generatedMessageSeq, boolean msgsGenerated,
			int constMsgType, Element termElement)
			throws NoSuchElementException {
		Iterator<WindowsMessage> seqIterator = generatedMessageSeq.iterator();
		if (termElement.getName().equals("seqValue")) {
			String obj = termElement.getAttributeValue("seqObj");
			List<WindowsMessage> seqVar = getStoredSeqVariable(obj);
			if (msgsGenerated && seqVar.size() != generatedMessageSeq.size()) {
				throw new InvalidParameterException(
						"Failure generating replay sequence for rule "
								+ currentRuleName
								+ ": One or more of the sequence variables used to generate a sequence have different lenghts.");
			}
			for (WindowsMessage msg : seqVar) {
				WindowsMessage currentSeqMsg = getCurrentSeqMsg(
						generatedMessageSeq, msgsGenerated, constMsgType,
						seqIterator);
				String paramValueStr = msg.getParameter(termElement
						.getAttributeValue("param"));
				int paramValue = 0;
				try {
					paramValue = Integer.parseInt(paramValueStr);
					currentSeqMsg.setLPARAM(paramValue);
				} catch (NumberFormatException e) {
					currentSeqMsg.setLPARAMasWindowDesc(paramValueStr);
				}
			}
			if (seqIterator.hasNext()) {
				// the first seq-var has a different number of elements than the
				// current one
				throw new NoSuchElementException();
			}
			msgsGenerated = true;
		} else { // const value
			int paramValue = Integer.parseInt(getTermValue(null, termElement));
			while (seqIterator.hasNext()) {
				seqIterator.next().setLPARAM(paramValue);
			}
		}
		return msgsGenerated;
	}

	/**
	 * <p>
	 * Creates the WPARAMs for replay sequences generated with genMsgSeq-nodes.
	 * </p>
	 * 
	 * @param generatedMessageSeq
	 *            list of the messages that is being generated
	 * @param msgsGenerated
	 *            boolean stating if the list of messages is already generated
	 *            or if the generation has to be handles by this method
	 * @param constMsgType
	 *            a constant message type that is used for message generation,
	 *            in case the list of message is generated by this method
	 * @param termElement
	 *            {@link Element} representing the term-node describing the
	 *            WPARAM
	 * @return true, if the list of message is generated after calling this
	 *         method; false otherwise
	 * @throws NoSuchElementException
	 *             thrown if the seqVar referred to in the termElement contains
	 *             a different number of messages than is contained in
	 *             messageSeq
	 */
	private boolean createSequenceWParam(
			List<WindowsMessage> generatedMessageSeq, boolean msgsGenerated,
			int constMsgType, Element termElement)
			throws NoSuchElementException {
		Iterator<WindowsMessage> seqIterator = generatedMessageSeq.iterator();
		if (termElement.getName().equals("seqValue")) {
			String obj = termElement.getAttributeValue("seqObj");
			List<WindowsMessage> seqVar = getStoredSeqVariable(obj);
			if (msgsGenerated && seqVar.size() != generatedMessageSeq.size()) {
				throw new InvalidParameterException(
						"Failure generating replay sequence for rule "
								+ currentRuleName
								+ ": One or more of the sequence variables used to generate a sequence have different lenghts.");
			}
			for (WindowsMessage msg : seqVar) {
				WindowsMessage currentSeqMsg = getCurrentSeqMsg(
						generatedMessageSeq, msgsGenerated, constMsgType,
						seqIterator);
				String paramValueStr = msg.getParameter(termElement
						.getAttributeValue("param"));
				int paramValue = 0;
				try {
					paramValue = Integer.parseInt(paramValueStr);
					currentSeqMsg.setWPARAM(paramValue);
				} catch (NumberFormatException e) {
					currentSeqMsg.setWPARAMasWindowDesc(paramValueStr);
				}
			}
			if (seqIterator.hasNext()) {
				// the first seq-var has a different number of elements than the
				// current one
				throw new NoSuchElementException();
			}
			msgsGenerated = true;
		} else { // const value
			int paramValue = Integer.parseInt(getTermValue(null, termElement));
			while (seqIterator.hasNext()) {
				seqIterator.next().setWPARAM(paramValue);
			}
		}
		return msgsGenerated;
	}

	/**
	 * <p>
	 * If a message sequence is already generated, i.e., msgsGenerated is true,
	 * the seqIterator is used to iterate through these messages and return the
	 * current one. If the message sequence is not yet generated, i.e.,
	 * msgsGenerated is false, the message sequence is generated on the fly
	 * during each call of this message and the newly generated messages are
	 * returned.
	 * </p>
	 * 
	 * @param generatedMessageSeq
	 *            message sequence
	 * @param msgsGenerated
	 *            indicates if generatedMessageSeq is already generated or has
	 *            to be generated on the fly by this method
	 * @param constMsgType
	 *            type of the message to be used for message generation
	 * @param seqIterator
	 *            iterates through an already generated message sequence; must
	 *            not be {@code null}, if msgsGenerated is true
	 * @return current message
	 */
	private WindowsMessage getCurrentSeqMsg(
			List<WindowsMessage> generatedMessageSeq, boolean msgsGenerated,
			int constMsgType, Iterator<WindowsMessage> seqIterator) {
		WindowsMessage currentSeqMsg = null;
		if (msgsGenerated) {
			currentSeqMsg = seqIterator.next();
		} else {
			currentSeqMsg = new WindowsMessage(constMsgType);
			generatedMessageSeq.add(currentSeqMsg);
		}
		return currentSeqMsg;
	}

	// ////////////////////////////
	// General helper functions //
	// ////////////////////////////

	/**
	 * <p>
	 * Retrieves a message from the storage for, e.g., comparison or replay.
	 * "this" is used to refer to the current message.
	 * </p>
	 * 
	 * @param currentMessage
	 *            current message during the parsing; passed to handle "this"
	 * @param obj
	 *            object identifier in the storage
	 * @return message retrieved from the storage
	 * @throws InvalidParameterException
	 *             thrown in case of invalid uses of "this" or if no message
	 *             with the identifier obj is found in the storage
	 */
	private WindowsMessage getStoredMessageVariable(
			WindowsMessage currentMessage, String obj)
			throws InvalidParameterException {
		WindowsMessage varMessage = null;
		if (obj.equals("this")) {
			if (currentMessage == null) {
				throw new InvalidParameterException(
						"Failure obtaining term value for rule "
								+ currentRuleName
								+ ": \"this\" is not a valid name for generating runtime messages.");
			}
			varMessage = currentMessage;
		} else {
			Object tmp = messageStorage.get(obj);
			if (tmp instanceof WindowsMessage) {
				varMessage = (WindowsMessage) tmp;
			} else {
				throw new InvalidParameterException(
						"Failure obtaining term value for rule "
								+ currentRuleName + ": No message \"" + obj
								+ "\" stored.");
			}
		}
		return varMessage;
	}

	/**
	 * <p>
	 * Retrieves a stored message sequence from the storage.
	 * </p>
	 * 
	 * @param obj
	 *            object identifier in the storage
	 * @return message sequence retrieved from the storage
	 * @throws InvalidParameterException
	 *             thrown if no message sequences with the identifier obj is
	 *             found in the storage
	 */
	@SuppressWarnings("unchecked")
	private List<WindowsMessage> getStoredSeqVariable(String obj)
			throws InvalidParameterException {
		List<WindowsMessage> varMsgSeq = null;
		Object tmp = messageStorage.get(obj);
		if (tmp instanceof List<?>) {
			varMsgSeq = (List<WindowsMessage>) tmp;
		} else {
			throw new InvalidParameterException(
					"Failure obtaining term value for rule " + currentRuleName
							+ ": No sequence \"" + obj + "\" store.");
		}
		return varMsgSeq;
	}

	/**
	 * <p>
	 * Handles term-nodes and returns the value of the described term.
	 * </p>
	 * 
	 * @param currentMessage
	 *            current message during the parsing; required to resolve
	 *            references to "this" in a term
	 * @param termElement
	 *            {@link Element} representing the term node
	 * @return value of the term or {@code null} of the term node could not be
	 *         evaluated
	 */
	private String getTermValue(WindowsMessage currentMessage,
			Element termElement) {
		String value = null;
		WindowsMessage varMessage = null;
		if (termElement.getName().equals("constValue")) {
			value = termElement.getAttributeValue("value");
		} else if (termElement.getName().equals("paramValue")) {
			String objectName = termElement.getAttributeValue("obj");
			varMessage = getStoredMessageVariable(currentMessage, objectName);
			if (varMessage != null) {
				String param = termElement.getAttributeValue("param");
				value = varMessage.getParameter(param);
			}
		} else if (termElement.getName().equals("winInfoValue")) {
			String objectName = termElement.getAttributeValue("obj");
			varMessage = getStoredMessageVariable(currentMessage, objectName);
			if (varMessage != null) {
				String paramString = termElement.getAttributeValue("winParam");
				if (paramString.equals("class")) {
					value = varMessage.getWindowClass();
				} else if (paramString.equals("resourceId")) {
					value = "" + varMessage.getWindowResourceId();
				} else if (paramString.equals("hwnd")) {
					value = "" + varMessage.getHwnd();
				} else if (paramString.equals("parentTarget")) {
					String target = varMessage.getXmlWindowDescription();
					int index = target.lastIndexOf("<");
					if (index == 0) {
						Console.traceln("Trying to adress parent of top-level window! Replay probably invalid!");
					}
					value = target.substring(0, index);
				} else if (paramString.equals("parentClass")) {
					value = varMessage.getParentClass();
				}
			}
		} else if (termElement.getName().equals("msgInfoValue")) {
			String objectName = termElement.getAttributeValue("obj");
			varMessage = getStoredMessageVariable(currentMessage, objectName);
			if (varMessage != null) {
				String paramString = termElement.getAttributeValue("msgParam");
				if (paramString.equals("type")) {
					value = "" + varMessage.getType();
				} else if (paramString.equals("target")) {
					value = varMessage.getXmlWindowDescription();
				}
			}
		}
		return value;
	}

	/**
	 * <p>
	 * Handles term-nodes contained by equalSeq nodes.
	 * </p>
	 * 
	 * @param termElement
	 *            {@link Element} representing the term-node
	 * @return list of values of the term
	 */
	private List<String> getTermValueSeq(Element termElement) {
		List<String> values = new LinkedList<String>();
		if (termElement.getName().equals("seqValue")) {
			String obj = termElement.getAttributeValue("seqObj");
			String param = termElement.getAttributeValue("param");
			List<WindowsMessage> seqVar = getStoredSeqVariable(obj);

			for (WindowsMessage msg : seqVar) {
				// msg.getParameter returns null, if parameter is not found,
				// therefore the List can contain null-values
				values.add(msg.getParameter(param));
			}
		}
		return values;
	}

	/**
	 * <p>
	 * Handles LOWORD and HIWORD child nodes of LPARAM and WPARAM nodes. The
	 * returned value is the LPARAM/WPARAM value based on the LOWORD and HIWORD.
	 * </p>
	 * 
	 * @param param
	 *            {@link Element} representing the LPARAM/WPARAM node
	 * @return value of the LPARAM/WPARAM
	 */
	private long loHiWord(Element param) {
		Element loword = param.getChild("LOWORD", rulesNamespace);
		Element hiword = param.getChild("HIWORD", rulesNamespace);
		String lowordStr = getTermValue(null, (Element) loword.getChildren()
				.get(0));
		String hiwordStr = getTermValue(null, (Element) hiword.getChildren()
				.get(0));
		return MAKEPARAM(Short.parseShort(lowordStr),
				Short.parseShort(hiwordStr));
	}

	/**
	 * <p>
	 * Takes to short integers and combines them into the high and low order
	 * bits of an integer.
	 * </p>
	 * 
	 * @param loword
	 *            low word
	 * @param hiword
	 *            high word
	 * @return combined integer
	 */
	private static int MAKEPARAM(short loword, short hiword) {
		return loword | ((int) hiword) << Short.SIZE;
	}

}
