package de.ugoe.cs.eventbench.jfcmonitor;

import java.awt.Component;
import java.awt.Container;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.accessibility.AccessibleContext;

import de.ugoe.cs.util.StringTools;

/**
 * <p>
 * This class manages information about the current GUI. It always contains the
 * current GUI hierarchy.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class JFCComponent {

	/**
	 * <p>
	 * Map of all known GUI components.
	 * </p>
	 */
	private static Map<Component, JFCComponent> knownComponents = new HashMap<Component, JFCComponent>();

	/**
	 * <p>
	 * Adds a AWT component to the GUI hierarchy. If the component already
	 * exists in the hierarchy, it is not added a second time.
	 * </p>
	 * 
	 * @param component
	 *            component that is added
	 */
	public static void add(Component component) {
		add(component, find(component.getParent()));
	}

	/**
	 * <p>
	 * Adds a AWT component to the GUI hierarchy. If the component already
	 * exists in the hierarchy, it is not added a second time.
	 * </p>
	 * 
	 * @param component
	 *            component that is added
	 * @param parent
	 *            parent of the component
	 */
	public static void add(Component component, JFCComponent parent) {
		if (!knownComponents.containsKey(component)) {
			knownComponents.put(component, new JFCComponent(component, parent));
		}
	}

	/**
	 * <p>
	 * Finds a component in the GUI hierarchy and returns the corresponding
	 * JFComponent instance. Returns null if the component is not found.
	 * </p>
	 * 
	 * @param component
	 *            component that is searched for
	 * @return corresponding JFComponent instance; null if the compenent is not
	 *         found
	 */
	public static JFCComponent find(Component component) {
		return knownComponents.get(component);
	}

	/**
	 * <p>
	 * Removes a component from the GUI hierarchy. In case the component is not
	 * part of the known hierachy, nothing happens.
	 * </p>
	 * 
	 * @param component
	 *            component to be removed
	 */
	public static void remove(Component component) {
		JFCComponent jfcComponent = knownComponents.remove(component);
		if (jfcComponent != null) {
			jfcComponent.removeFromParent();
			jfcComponent.removeChildren();
		}
	}

	/**
	 * <p>
	 * Parent of the GUI component. null means, that the component has no
	 * parent.
	 * </p>
	 */
	private JFCComponent parent = null;

	/**
	 * <p>
	 * Child components of the component.
	 * </p>
	 */
	private List<JFCComponent> children = new LinkedList<JFCComponent>();

	/**
	 * <p>
	 * Reference to the actual GUI component.
	 * </p>
	 */
	private Component component;

	/**
	 * <p>
	 * Helper attribute that contains the title of the component. Set by
	 * {@link #setTitle()}.
	 * </p>
	 */
	private String title = null;

	/**
	 * <p>
	 * Helper attribute that contains the class of the component. Set by
	 * {@link #setClass()}.
	 * </p>
	 */
	private String componentClass = null;

	/**
	 * <p>
	 * Helper attribute that contains the icon of the component. Set by
	 * {@link #setIcon()}.
	 * </p>
	 */
	private String icon = null;

	/**
	 * <p>
	 * Helper attribute that contains the icon of the component. Set by
	 * {@link #setIndex()}.
	 * </p>
	 */
	private int index = -1;

	/**
	 * <p>
	 * Constructor. Creates a new JFCComponent. Only used internally by
	 * {@link #add(Component, JFCComponent)}.
	 * </p>
	 * 
	 * @param component
	 *            component associated with the JFCComponent
	 * @param parent
	 *            parent of the component; null if there is no parent
	 */
	private JFCComponent(Component component, JFCComponent parent) {
		if (component == null) {
			throw new InvalidParameterException(
					"parameter component must not be null");
		}
		this.component = component;
		this.parent = parent;
		if (parent != null) {
			parent.addChild(this);
		}

		if (component instanceof Container) {
			for (Component childComponent : ((Container) component)
					.getComponents()) {
				add(childComponent, this);
			}
		}
	}

	/**
	 * <p>
	 * Adds a child component to the current component.
	 * </p>
	 * 
	 * @param child
	 *            child component to be added
	 */
	private void addChild(JFCComponent child) {
		children.add(child);
	}

	/**
	 * <p>
	 * Returns an XML representation of the component.
	 * </p>
	 * 
	 * @return XLM snippet
	 */
	public String getXML() {
		setClass();
		setIcon();
		setIndex();
		setTitle();
		StringBuilder builder = new StringBuilder();
		if (parent != null) {
			builder.append(parent.getXML());
		}
		builder.append("  <component>" + StringTools.ENDLINE);
		builder.append("   <param name=\"title\" value=\"" + title + "\" />"
				+ StringTools.ENDLINE);
		builder.append("   <param name=\"class\" value=\"" + componentClass
				+ "\" />" + StringTools.ENDLINE);
		builder.append("   <param name=\"icon\" value=\"" + icon + "\" />"
				+ StringTools.ENDLINE);
		builder.append("   <param name=\"index\" value=\"" + index + "\" />"
				+ StringTools.ENDLINE);
		builder.append("   <param name=\"hash\" value=\""
				+ Integer.toHexString(component.hashCode()) + "\" />"
				+ StringTools.ENDLINE);
		builder.append("  </component>" + StringTools.ENDLINE);
		return builder.toString();
	}

	/**
	 * <p>
	 * Removes a child component from the current component.
	 * </p>
	 * 
	 * @param child
	 *            child component to be removed
	 */
	private void removeChild(JFCComponent child) {
		children.remove(child);
	}

	/**
	 * <p>
	 * Removes the component from the list of children of its parent.
	 * </p>
	 */
	private void removeFromParent() {
		if (parent != null) {
			parent.removeChild(this);
		}
	}

	/**
	 * <p>
	 * Triggers the removals of all child components from the GUI hierarchy,
	 * i.e., calls {@link #remove(Component)} for all child components.
	 * </p>
	 */
	private void removeChildren() {
		for (JFCComponent child : children) {
			remove(child.component);
		}
	}

	/**
	 * <p>
	 * Sets the {@link #title} of the component. The title is defined as follows
	 * (first in the list, that is not null):
	 * <ul>
	 * <li>accessible name of the component if available</li>
	 * <li>{@link #icon} of the component</li>
	 * <li>name of the component</li>
	 * <li>coordinates of the component</li>
	 * </ul>
	 * </p>
	 */
	private void setTitle() {
		title = null; // reset title

		AccessibleContext accessibleContext = component.getAccessibleContext();
		if (accessibleContext != null) {
			title = accessibleContext.getAccessibleName();
		}
		if (title == null) {
			title = icon;
		}
		if (title == null) {
			title = component.getName();
		}
		if (title == null) {
			// use coordinates as last resort
			title = "Pos(" + component.getX() + "," + component.getY() + ")";
		}
	}

	/**
	 * <p>
	 * Sets the {@link #componentClass} of the component.
	 * </p>
	 */
	private void setClass() {
		componentClass = component.getClass().getName();
	}

	/**
	 * <p>
	 * Sets the {@link #icon} of the component.
	 * </p>
	 */
	private void setIcon() {
		icon = null; // reset icon

		Method getIconMethod;
		try {
			getIconMethod = component.getClass().getMethod("getIcon",
					new Class[0]);
			if (getIconMethod != null) {
				Object iconObject = getIconMethod.invoke(component,
						new Object[] {});
				if (iconObject != null) {
					String iconPath = iconObject.toString();
					if (!iconPath.contains("@")) {
						System.out.println("iconPath");
						String[] splitResult = iconPath
								.split(File.separatorChar == '\\' ? "\\\\"
										: File.separator);
						icon = splitResult[splitResult.length - 1];
					}
				}
			}
		} catch (SecurityException e) {
		} catch (NoSuchMethodException e) {
		} catch (IllegalArgumentException e) {
		} catch (IllegalAccessException e) {
		} catch (InvocationTargetException e) {
			System.err.println("Found method with name " + "getIcon"
					+ " but could not access it.");
		}
	}

	/**
	 * <p>
	 * Sets the {@link #index} of the component as the index in the parent, if
	 * it is accessible.
	 * </p>
	 */
	private void setIndex() {
		index = -1; // reset index

		AccessibleContext accessibleContext = component.getAccessibleContext();
		if (accessibleContext != null) {
			index = accessibleContext.getAccessibleIndexInParent();
		}
	}

}
