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

import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.Map;

import de.ugoe.cs.quest.eventcore.IReplayable;
import de.ugoe.cs.util.StringTools;

/**
 * <p>
 * Contains all informations about a windows message, i.e., all parameters that
 * are read when a windows message is parsed as well as its target, hwnd, etc.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 * 
 */
public class WindowsMessage implements IReplayable {

	/**
	 * <p>
	 * Id for object serialization.
	 * </p>
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * <p>
	 * Type of the message.
	 * </p>
	 */
	final int type;

	/**
	 * <p>
	 * Window class of the message target. Default: ""
	 * </p>
	 */
	private String windowClass = "";

	/**
	 * <p>
	 * Resource Id of the message target. Default: 0
	 * </p>
	 */
	private int resourceId = 0;

	/**
	 * <p>
	 * XML representation of the message target.
	 * </p>
	 */
	private String xmlWindowDescription = "";

	/**
	 * <p>
	 * String that contains the names of all parent widgets and itself, separated by dots,
	 * e.g., "GrandParent.Parent.self".
	 * </p>
	 */
	private String parentNames = null;

	/**
	 * <p>
	 * String that contains the window class of the parent widget.
	 * </p>
	 */
	private String parentClass = null;

	/**
	 * <p>
	 * LPARAM of the message. Default: 0
	 * </p>
	 */
	private long LPARAM = 0;

	/**
	 * <p>
	 * WPARAM of the message. Default: 0
	 * </p>
	 */
	private long WPARAM = 0;

	/**
	 * <p>
	 * If the LPARAM contains a HWND, this string stores the target of the HWND.
	 * </p>
	 */
	private String LPARAMasWindowDesc = null;

	/**
	 * <p>
	 * If the WPARAM contains a HWND, this string stores the target of the HWND.
	 * </p>
	 */
	private String WPARAMasWindowDesc = null;

	/**
	 * <p>
	 * Delay after sending the messages during a replay. Default: 0
	 * </p>
	 */
	private int delay = 0;

	/**
	 * <p>
	 * A map of all parameters, associated with the message, created during the
	 * parsing of messages from the logs {@code param}-nodes.
	 * </p>
	 */
	private Map<String, String> params = new HashMap<String, String>();

	/**
	 * <p>
	 * Constructor. Creates a new message with a given message type.
	 * </p>
	 * 
	 * @param type
	 *            type of the message
	 */
	public WindowsMessage(int type) {
		this.type = type;
	}

	/**
	 * <p>
	 * Adds a parameter to the message.
	 * </p>
	 * 
	 * @param type
	 *            type descriptor of the parameter
	 * @param value
	 *            value of the parameter
	 */
	public void addParameter(String type, String value) {
		params.put(type, value);
		if (type.equals("LPARAM")) {
			LPARAM = Long.parseLong(value);
		} else if (type.equals("WPARAM")) {
			WPARAM = Long.parseLong(value);
		}
	}

	/**
	 * <p>
	 * Returns the type of the message.
	 * </p>
	 * 
	 * @return type of the message
	 */
	public int getType() {
		return type;
	}

	/**
	 * <p>
	 * Returns the value of a parameter, given its type. If the parameter is not
	 * found, {@code null} is returned.
	 * </p>
	 * 
	 * @param type
	 *            type of the parameter
	 * @return value of the parameter
	 */
	public String getParameter(String type) {
		return params.get(type);
	}

	/**
	 * <p>
	 * Returns the window class of the message target.
	 * </p>
	 * 
	 * @return window class of the message target
	 */
	public String getWindowClass() {
		return windowClass;
	}

	/**
	 * <p>
	 * Returns the HWND the message is addressed to.
	 * </p>
	 * 
	 * @return HWND the message is addressed to
	 */
	public int getHwnd() {
		int hwnd = -1;
		String hwndString = getParameter("window.hwnd"); // possible, as
															// "window.hwnd" is
															// mandatory
		if (hwndString != null) {
			hwnd = Integer.parseInt(hwndString);
		}
		return hwnd;
	}

	/**
	 * <p>
	 * Returns the resource Id of the message target.
	 * </p>
	 * 
	 * @return resource Id of the message target
	 */
	public int getWindowResourceId() {
		return resourceId;
	}

	/**
	 * <p>
	 * Two {@link WindowsMessage} are equal, if their {@link #type},
	 * {@link #xmlWindowDescription}, and {@link #params} are equal.
	 * </p>
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object other) {
		if (other == this) {
			return true;
		}
		boolean isEqual = false;
		if (other instanceof WindowsMessage) {
			isEqual = ((WindowsMessage) other).type == this.type
					&& ((WindowsMessage) other).xmlWindowDescription
							.equals(this.xmlWindowDescription)
					&& ((WindowsMessage) other).params.equals(this.params);
		}
		return isEqual;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		int multiplier = 17;
		int hash = 42;

		hash = multiplier * hash + type;
		hash = multiplier * hash + xmlWindowDescription.hashCode();
		hash = multiplier * hash + params.hashCode();

		return hash;
	}

	/**
	 * <p>
	 * Returns a string representation of the message of the form
	 * "msg[target=HWND;type=TYPE]".
	 * </p>
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "msg[target=" + getParameter("window.hwnd") + ";type=" + type
				+ "]";
	}

	/**
	 * <p>
	 * Retrieves the target string of a message from a given {@link WindowTree}
	 * through looking up the HWND the message is addressed to in the window
	 * tree.
	 * </p>
	 * 
	 * @param windowTree
	 *            {@link WindowTree} from which the target is extracted
	 * @throws InvalidParameterException
	 *             thrown if HWND is not contained in windowTree
	 */
	public void setTarget(WindowTree windowTree)
			throws InvalidParameterException {
		int hwnd = Integer.parseInt(getParameter("window.hwnd"));
		WindowTreeNode node = windowTree.find(hwnd);
		if (node == null) {
			throw new InvalidParameterException("No window with HWND " + hwnd
					+ " found in window tree!");
		} else {
			windowClass = node.getClassName();
			resourceId = node.getResourceId();
			xmlWindowDescription = node.xmlRepresentation();
			parentNames = node.getParentNames();
			WindowTreeNode parent = node.getParent();
			if (parent == null) {
				parentClass = "";
			} else {
				parentClass = parent.getClassName();
			}
		}
	}

	/**
	 * <p>
	 * Sets the LPARAM of a message.
	 * </p>
	 * 
	 * @param paramValue
	 *            value of the LPARAM
	 */
	public void setLPARAM(long paramValue) {
		LPARAM = paramValue;
	}

	/**
	 * <p>
	 * Sets the WPARAM of a message.
	 * </p>
	 * 
	 * @param paramValue
	 *            value of the WPARAM
	 */
	public void setWPARAM(long paramValue) {
		WPARAM = paramValue;
	}

	/**
	 * <p>
	 * Returns the LPARAM of a message.
	 * </p>
	 * 
	 * @return LPARAM of the message
	 */
	public long getLPARAM() {
		return LPARAM;
	}

	/**
	 * <p>
	 * Returns the WPARAM of a message.
	 * </p>
	 * 
	 * @return WPARAM of the message
	 */
	public long getWPARAM() {
		return WPARAM;
	}

	/**
	 * <p>
	 * If the LPARAM contains a HWND, this function can be used to set a target
	 * string to identify the HWND at run-time.
	 * </p>
	 * 
	 * @param windowDesc
	 *            target string
	 */
	public void setLPARAMasWindowDesc(String windowDesc) {
		LPARAMasWindowDesc = windowDesc;
	}

	/**
	 * <p>
	 * If the WPARAM contains a HWND, this function can be used to set a target
	 * string to identify the HWND at run-time.
	 * </p>
	 * 
	 * @param windowDesc
	 *            target string
	 */
	public void setWPARAMasWindowDesc(String windowDesc) {
		WPARAMasWindowDesc = windowDesc;
	}

	/**
	 * <p>
	 * If the LPARAM contains a HWND and the target string for the HWND is set,
	 * this function returns the target string. Otherwise, {@code null} is
	 * returned.
	 * </p>
	 * 
	 * @return target string if available; {@code null} otherwise
	 */
	public String getLPARAMasWindowDesc() {
		return LPARAMasWindowDesc;
	}

	/**
	 * <p>
	 * If the WPARAM contains a HWND and the target string for the HWND is set,
	 * this function returns the target string. Otherwise, {@code null} is
	 * returned.
	 * </p>
	 * 
	 * @return target string if available; {@code null} otherwise
	 */
	public String getWPARAMasWindowDesc() {
		return WPARAMasWindowDesc;
	}

	/**
	 * <p>
	 * Returns the target string of the message.
	 * </p>
	 * 
	 * @return target string of the message
	 */
	public String getXmlWindowDescription() {
		return xmlWindowDescription;
	}

	/**
	 * <p>
	 * Sets the target string manually.
	 * </p>
	 * 
	 * @param xmlWindowDescription
	 *            target string
	 */
	public void setXmlWindowDescription(String xmlWindowDescription) {
		this.xmlWindowDescription = xmlWindowDescription;
	}

	/**
	 * <p>
	 * Returns the delay after this message during replays.
	 * </p>
	 * 
	 * @return delay after this message
	 */
	public int getDelay() {
		return delay;
	}

	/**
	 * <p>
	 * Sets the delay after this message during replays.
	 * </p>
	 * 
	 * @param delay
	 *            delay after this message
	 */
	public void setDelay(int delay) {
		this.delay = delay;
	}

	/**
	 * <p>
	 * Returns the parent names separated by dots, e.g., "GrandParent.Parent".
	 * </p>
	 * 
	 * @return names of the parents
	 */
	public String getParentNames() {
		return parentNames;
	}

	/**
	 * <p>
	 * Returns the window class of the parent.
	 * </p>
	 * 
	 * @return window classes of the parents
	 */
	public String getParentClass() {
		return parentClass;
	}

	/**
	 * <p>
	 * Returns the number of parameters stored together with this message.
	 * </p>
	 * 
	 * @return number of parameters stored with this message
	 */
	public int getNumParams() {
		return params.size();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.ugoe.cs.quest.eventcore.IReplayable#getReplay()
	 */
	@Override
	public String getReplay() {
		StringBuilder currentMsgStr = new StringBuilder(400);
		currentMsgStr.append("  <msg type=\"" + type + "\" ");
		currentMsgStr.append("LPARAM=\"" + LPARAM + "\" ");
		currentMsgStr.append("WPARAM=\"" + WPARAM + "\" ");
		currentMsgStr.append("delay=\"" + delay + "\">");
		if (LPARAMasWindowDesc != null) {
			currentMsgStr.append(StringTools.ENDLINE);
			currentMsgStr.append("   <LPARAM>");
			currentMsgStr.append(StringTools.ENDLINE);
			currentMsgStr.append(LPARAMasWindowDesc);
			currentMsgStr.append(StringTools.ENDLINE);
			currentMsgStr.append("</LPARAM>");
		}
		if (WPARAMasWindowDesc != null) {
			currentMsgStr.append(StringTools.ENDLINE);
			currentMsgStr.append("   <WPARAM>");
			currentMsgStr.append(StringTools.ENDLINE);
			currentMsgStr.append(WPARAMasWindowDesc);
			currentMsgStr.append(StringTools.ENDLINE);
			currentMsgStr.append("   </WPARAM>");
		}
		currentMsgStr.append(StringTools.ENDLINE);
		currentMsgStr.append(xmlWindowDescription);
		currentMsgStr.append(StringTools.ENDLINE);
		currentMsgStr.append("  </msg>");
		currentMsgStr.append(StringTools.ENDLINE);
		return currentMsgStr.toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.ugoe.cs.quest.eventcore.IReplayable#getTarget()
	 */
	@Override
	public String getTarget() {
		return xmlWindowDescription;
	}
}
