package de.ugoe.cs.eventbench.jfcmonitor;

import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.event.AWTEventListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import de.ugoe.cs.util.StringTools;

/**
 * <p>
 * This class implements monitoring of AWT and Swing mouse and keyboard events.
 * Each of the events is written to an output stream.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class JFCListener implements AWTEventListener {

	/**
	 * <p>
	 * Convenience variable.
	 * </p>
	 */
	final static private String ENDLINE = System.getProperty("line.separator");

	/**
	 * <p>
	 * Writer for logging events.
	 * </p>
	 */
	final private OutputStreamWriter outputWriter;

	public JFCListener(OutputStreamWriter outputWriter) {
		this.outputWriter = outputWriter;
		try {
			outputWriter.write("<?xml version=\"1.0\" encoding=\"UTF-16\"?>"
					+ ENDLINE);
			outputWriter.write("<sessions>" + ENDLINE);
		} catch (IOException e) {
			System.err.println("JFCMONITOR -- Failure writing to log: "
					+ e.getMessage());
		}
	}

	/**
	 * <p>
	 * Writes all received {@link MouseEvent}s and {@link KeyEvent}s to the
	 * {@link #outputWriter}.
	 * </p>
	 * 
	 * @see java.awt.event.AWTEventListener#eventDispatched(java.awt.AWTEvent)
	 */
	@Override
	public void eventDispatched(AWTEvent event) {
		StringBuilder builder = new StringBuilder();

		if (event instanceof MouseEvent) {
			MouseEvent mouseEvent = (MouseEvent) event;
			if (!isMouseMovement(event.getID())) {
				builder.append("<event id=\"" + event.getID() + "\">" + ENDLINE);
				builder.append(" <param name=\"X\" value=\""
						+ mouseEvent.getX() + "\" />" + ENDLINE);
				builder.append(" <param name=\"Y\" value=\""
						+ mouseEvent.getY() + "\" />" + ENDLINE);
				builder.append(" <param name=\"Button\" value=\""
						+ mouseEvent.getButton() + "\" />" + ENDLINE);
				builder.append(" <param name=\"Modifiers\" value=\""
						+ mouseEvent.getModifiers() + "\" />" + ENDLINE);
				addSourceInfo(builder, event);
				builder.append("</event>" + ENDLINE);
			}
		}
		if (event instanceof KeyEvent) {
			KeyEvent keyEvent = (KeyEvent) event;
			if (keyEvent.getID() == KeyEvent.KEY_TYPED) {
				builder.append("<event id=\"" + event.getID() + "\">" + ENDLINE);
				builder.append(" <param name=\"KeyCode\" value=\""
						+ keyEvent.getKeyCode() + "\" />" + ENDLINE);
				builder.append(" <param name=\"Modifiers\" value=\""
						+ keyEvent.getModifiers() + "\" />" + ENDLINE);
				addSourceInfo(builder, event);
				builder.append("</event>" + ENDLINE);
			}
		}
		if (builder.length() > 0 && outputWriter != null) {
			try {
				outputWriter.write(builder.toString());
				outputWriter.flush();
			} catch (IOException e) {
				System.err.println("JFCMONITOR -- Failure writing to log: "
						+ e.getMessage());
			}
		}
	}

	/**
	 * <p>
	 * Appends information about the event to a {@link StringBuilder}.
	 * </p>
	 * 
	 * @param builder
	 *            {@link StringBuilder} where the information is appended
	 * @param event
	 *            event whose information is appended
	 */
	private void addSourceInfo(StringBuilder builder, AWTEvent event) {
		builder.append(" <source>" + ENDLINE);
		builder.append("  <param name=\"toString\" value=\""
				+ StringTools
						.xmlEntityReplacement(event.getSource().toString())
				+ "\" />" + ENDLINE);
		if (event.getSource() instanceof Component) {
			Component source = (Component) event.getSource();
			addComponentInfo(builder, source);
			builder.append(" <parent>" + ENDLINE);
			addComponentInfo(builder, source.getParent());
			builder.append(" </parent>" + ENDLINE);
		}
		builder.append(" </source>" + ENDLINE);
	}

	/**
	 * <p>
	 * Appends information about the component to the a {@link StringBuilder}.
	 * The prefix can be used to give further information about the component.
	 * </p>
	 * 
	 * @param builder
	 *            {@link StringBuilder} where the information is appended
	 * @param component
	 *            component whose information is appended
	 */
	private void addComponentInfo(StringBuilder builder, Component component) {
		builder.append("  <param name=\"getName\" value=\""
				+ StringTools.xmlEntityReplacement(component.getName())
				+ "\" />" + ENDLINE);
		for (Method method : component.getClass().getMethods()) {
			try {
				if (method.getName() == "getText") {
					String text = (String) method.invoke(component,
							new Object[] {});
					if (text != null) {
						builder.append("  <param name=\"getText\" value=\""
								+ StringTools.xmlEntityReplacement(text)
								+ "\" />" + ENDLINE);
					}
				}
				if (method.getName() == "getTitle") {
					String title = (String) method.invoke(component,
							new Object[] {});
					if (title != null) {
						builder.append("  <param name=\"getTitle\" value=\""
								+ StringTools.xmlEntityReplacement(title)
								+ "\" />" + ENDLINE);
					}
				}
			} catch (IllegalArgumentException e) {
			} catch (IllegalAccessException e) {
			} catch (InvocationTargetException e) {
				System.err.println("Found method with name " + method.getName()
						+ " but could not access it.");
			}
		}
	}

	/**
	 * <p>
	 * Checks if the Id of an {@link AWTEvent} is a mouse movement Id.
	 * </p>
	 * 
	 * @param eventId
	 *            id of the {@link AWTEvent}
	 * @return true, if the event is a mouse movement event; false otherwise
	 */
	private boolean isMouseMovement(int eventId) {
		return eventId == MouseEvent.MOUSE_MOVED
				|| eventId == MouseEvent.MOUSE_DRAGGED
				|| eventId == MouseEvent.MOUSE_ENTERED
				|| eventId == MouseEvent.MOUSE_EXITED;
	}

}
