
package de.ugoe.cs.quest.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();
        }
    }

}
