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

import java.util.LinkedList;
import java.util.List;

import de.ugoe.cs.quest.eventcore.Event;
import de.ugoe.cs.quest.plugin.mfc.eventcore.WindowsMessage;
import de.ugoe.cs.util.console.Console;

/**
 * <p>
 * Responsible to split sequences into subsequences, such that each subsequences
 * contains exactly one event.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class SequenceSplitter {

	/**
	 * <p>
	 * Contains the current subsequence.
	 * </p>
	 */
	private List<WindowsMessage> currentSequence;

	/**
	 * <p>
	 * Number of messages in the current sequences, that signal that a key or
	 * mouse button has been pressed down to which not yet a message has been
	 * found, that signals that the button has been released.
	 * </p>
	 */
	private int openDowns;

	/**
	 * <p>
	 * Internal flag that signals if {@link #currentSequence} needs to be
	 * initialized.
	 * </p>
	 */
	private boolean initMessages;

	/**
	 * <p>
	 * The {@link EventGenerator} used to convert the subsequences into
	 * {@link Event}s
	 * </p>
	 */
	private EventGenerator tokenGenerator;

	/**
	 * <p>
	 * The event sequence generated.
	 * </p>
	 */
	private List<Event> actionSequence;

	/**
	 * <p>
	 * Type of the previous message.
	 * </p>
	 */
	private int prevMsg = 0;

	/**
	 * <p>
	 * Constructor. Creates a new SequenceSplitter.
	 * </p>
	 */
	public SequenceSplitter() {
		currentSequence = new LinkedList<WindowsMessage>();
		openDowns = 0;
		initMessages = true;
		tokenGenerator = new EventGenerator();
		actionSequence = new LinkedList<Event>();
		prevMsg = 0;
	}

	/**
	 * <p>
	 * Called by the {@link MFCLogParser} every time a message is parsed.
	 * </p>
	 * 
	 * @param msg
	 *            message to be added
	 */
	public void addMessage(WindowsMessage msg) {
		if (startOfSequence(msg)) {
			if (!initMessages) {
				Event currentAction = tokenGenerator
						.generateEvent(currentSequence);
				if (currentAction != null) {
					actionSequence.add(currentAction);
				}
				if (isKeyMessage(msg.getType()) && openDowns > 0) {
					Console.traceln("Key message found with open down mouse messages - will probabably result in a faulty sequence.");
				}
			} else {
				initMessages = false;
			}
			currentSequence = new LinkedList<WindowsMessage>();
		}
		if (isUpMessage(msg.getType())) {
			if (openDowns > 0) {
				openDowns--;
			}
		}

		// this fix checks if there are two consecutive mouse-down messages.
		// This sometimes occurs due to incorrect filtering in the monitoring
		// dll.
		if (!(prevMsg == MessageDefs.WM_LBUTTONDOWN && prevMsg == msg.getType())) {
			currentSequence.add(msg);
		} else {
			openDowns--;
		}
		prevMsg = msg.getType();
	}

	/**
	 * <p>
	 * Returns the event sequence generated from the message that have been
	 * added.
	 * </p>
	 * 
	 * @return generated event sequence
	 */
	public List<Event> getSequence() {
		return actionSequence;
	}

	/**
	 * <p>
	 * Called when a session in the log file is finished, i.e., a closing
	 * session-node is found.
	 * </p>
	 */
	public void endSession() {
		Event currentAction = tokenGenerator
				.generateEvent(currentSequence);
		if (currentAction != null) {
			actionSequence.add(currentAction);
		}
	}

	/**
	 * <p>
	 * Checks if the message starts a new subsequence and returns the result.
	 * </p>
	 * 
	 * @param msg
	 *            message that is checked
	 * @return true, if a new subsequence begins
	 */
	private boolean startOfSequence(WindowsMessage msg) {
		boolean isStart = false;
		int msgType = msg.getType();
		if (isKeyMessage(msgType)) {
			isStart = true;
		}
		if (isDownMessage(msgType)) {
			openDowns++;
			if (openDowns == 1) {
				isStart = true;
			}
		}
		if (isDblclkMessage(msgType)) {
			openDowns++;
		}
		return isStart;
	}

	/**
	 * <p>
	 * Checks if the type of a message is generated is a keyboard interaction.
	 * </p>
	 * 
	 * @param msgType
	 *            type of the message
	 * @return true if it is a keyboard interaction; false otherwise
	 */
	private boolean isKeyMessage(int msgType) {
		boolean isKeyMsg = false;
		switch (msgType) {
		case MessageDefs.WM_KEYDOWN:
		case MessageDefs.WM_KEYUP:
		case MessageDefs.WM_SYSKEYDOWN:
		case MessageDefs.WM_SYSKEYUP:
			isKeyMsg = true;
			break;
		default:
			break;
		}
		return isKeyMsg;
	}

	/**
	 * <p>
	 * Checks if the type of a message indicates that the mouse has been pressed
	 * down.
	 * </p>
	 * 
	 * @param msgType
	 *            type of the message
	 * @return true if it is mouse-down message; false otherwise
	 */
	private boolean isDownMessage(int msgType) {
		boolean isDownMsg = false;
		switch (msgType) {
		case MessageDefs.WM_LBUTTONDOWN:
		case MessageDefs.WM_RBUTTONDOWN:
		case MessageDefs.WM_MBUTTONDOWN:
		case MessageDefs.WM_XBUTTONDOWN:
		case MessageDefs.WM_NCLBUTTONDOWN:
		case MessageDefs.WM_NCRBUTTONDOWN:
		case MessageDefs.WM_NCMBUTTONDOWN:
		case MessageDefs.WM_NCXBUTTONDOWN:
			isDownMsg = true;
			break;
		default:
			break;
		}
		return isDownMsg;
	}

	/**
	 * <p>
	 * Checks if the type of a message indicates that a double click has been
	 * performed.
	 * </p>
	 * 
	 * @param msgType
	 *            type of the message
	 * @return true if it is a double click message; false otherwise
	 */
	private boolean isDblclkMessage(int msgType) {
		boolean isDblclkMsg = false;
		switch (msgType) {
		case MessageDefs.WM_LBUTTONDBLCLK:
		case MessageDefs.WM_RBUTTONDBLCLK:
		case MessageDefs.WM_MBUTTONDBLCLK:
		case MessageDefs.WM_XBUTTONDBLCLK:
		case MessageDefs.WM_NCLBUTTONDBLCLK:
		case MessageDefs.WM_NCRBUTTONDBLCLK:
		case MessageDefs.WM_NCMBUTTONDBLCLK:
		case MessageDefs.WM_NCXBUTTONDBLCLK:
			isDblclkMsg = true;
			break;
		default:
			break;
		}
		return isDblclkMsg;
	}

	/**
	 * <p>
	 * Checks if the type of a message indicates that the mouse has been
	 * released.
	 * </p>
	 * 
	 * @param msgType
	 *            type of the message
	 * @return true if it is mouse-up message; false otherwise
	 */
	private boolean isUpMessage(int msgType) {
		boolean isUpMsg = false;
		switch (msgType) {
		case MessageDefs.WM_LBUTTONUP:
		case MessageDefs.WM_RBUTTONUP:
		case MessageDefs.WM_MBUTTONUP:
		case MessageDefs.WM_XBUTTONUP:
		case MessageDefs.WM_NCLBUTTONUP:
		case MessageDefs.WM_NCRBUTTONUP:
		case MessageDefs.WM_NCMBUTTONUP:
		case MessageDefs.WM_NCXBUTTONUP:
			isUpMsg = true;
			break;
		default:
			break;
		}
		return isUpMsg;
	}

}
