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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import de.ugoe.cs.quest.eventcore.guimodel.GUIElementFactory;
import de.ugoe.cs.quest.eventcore.guimodel.GUIModel;
import de.ugoe.cs.quest.eventcore.guimodel.GUIModelException;
import de.ugoe.cs.quest.eventcore.guimodel.IGUIElementFactory;

/**
 * <p>
 * This class provides an the interfaces for window trees.
 * </p>
 * <p>
 * The window tree represents the hierarchical structure of the windows "as it is" currently during
 * a session. It may change during the session due to creation and destruction of windows.
 * </p>
 * 
 * @author Steffen Herbold
 * @version 1.0
 */
public class WindowTree {

    /**
     * <p>
     * Maintains a set of all the targets of all widgets that were at some point part of the window
     * tree.
     * </p>
     */
    private Set<MFCGUIElementSpec> targets;

    /**
     * <p>
     * Map of all GUI element specifications that are part of the tree for efficient searching. The
     * keys of the map are the hwnd's of the GUI elements.
     * </p>
     */
    private Map<Long, MFCGUIElementSpec> guiElementSpecs;

    /**
     * <p>
     * Map of all children of GUI elements that are part of the tree. The keys of the map are the
     * hwnd's of the parent GUI elements.
     * </p>
     */
    private Map<Long, List<MFCGUIElementSpec>> childRelations;

    /**
     * <p>
     * Map of all parents of GUI elements that are part of the tree. The keys of the map are the
     * hwnd's of the child GUI elements.
     * </p>
     */
    private Map<Long, MFCGUIElementSpec> parentRelations;

    /**
     * <p>
     * the internally created GUI model
     * </p>
     */
    private GUIModel guiModel = new GUIModel();

    /**
     * <p>
     * the GUI element factory used in the model
     * </p>
     */
    private IGUIElementFactory guiElementFactory = GUIElementFactory.getInstance();

    /**
     * <p>
     * Map of all GUI elements that are part of the tree for efficient searching. The keys of the
     * map are the hwnd's of the GUI elements.
     * </p>
     */
    private Map<Long, MFCGUIElement> guiElements;

    /**
     * <p>
     * Creates a new WindowTree.
     * </p>
     * <p>
     * Private, as the class is a singleton.
     * </p>
     */
    public WindowTree() {
        guiElementSpecs = new HashMap<Long, MFCGUIElementSpec>();
        targets = new HashSet<MFCGUIElementSpec>();
        childRelations = new HashMap<Long, List<MFCGUIElementSpec>>();
        parentRelations = new HashMap<Long, MFCGUIElementSpec>();
        guiElements = new HashMap<Long, MFCGUIElement>();
    }

    /**
     * <p>
     * Adds a new window to the tree.
     * </p>
     * 
     * @param parentHwnd
     *            hwnd of the parent window
     * @param childHwnd
     *            hwnd of the window to be created
     * @param childWindowName
     *            resource id of the window to be created
     * @param resourceId
     *            resource id of the window to be created
     * @param className
     *            class name of the window to be created
     */
    public void add(long parentHwnd,
                    long childHwnd,
                    String childWindowName,
                    int resourceId,
                    String className,
                    boolean isModal)
    {
        MFCGUIElementSpec parent = guiElementSpecs.get(parentHwnd);
        MFCGUIElementSpec child = guiElementSpecs.get(childHwnd);
        if (child == null) {
            child =
                new MFCGUIElementSpec(childHwnd, childWindowName, resourceId, className, isModal);
            if (parent != null) {
                List<MFCGUIElementSpec> otherChildren = childRelations.get(parentHwnd);

                if (otherChildren == null) {
                    otherChildren = new ArrayList<MFCGUIElementSpec>();
                    childRelations.put(parentHwnd, otherChildren);
                }

                otherChildren.add(child);

                parentRelations.put(childHwnd, parent);
            }
            guiElementSpecs.put(childHwnd, child);
            targets.add(child);
        }
    }

    /**
     * <p>
     * Searches the tree for a window with the specified hwnd and returns its
     * {@link MFCGUIElementSpec} .
     * </p>
     * 
     * @param hwnd
     *            hwnd that is looked for
     * @return {@link MFCGUIElementSpec} of the window with the given hwnd if found, null otherwise
     */
    public MFCGUIElement find(long hwnd) {
        MFCGUIElement guiElement = guiElements.get(hwnd);
        if (guiElement == null) {
            List<MFCGUIElementSpec> guiElementPath = new ArrayList<MFCGUIElementSpec>();

            MFCGUIElementSpec child = guiElementSpecs.get(hwnd);

            if (child == null) {
                throw new RuntimeException("no GUI element found with id " + hwnd);
            }

            while (child != null) {
                guiElementPath.add(0, child);
                child = parentRelations.get(child.getHwnd());
            }

            try {
                guiElement =
                    (MFCGUIElement) guiModel.integratePath(guiElementPath, guiElementFactory);
            }
            catch (GUIModelException e) {
                throw new RuntimeException("could not instantiate GUI element with id " + hwnd, e);
            }
            guiElements.put(hwnd, guiElement);
        }
        return guiElement;
    }

    /**
     * <p>
     * Sets the name of a GUI element given its HWND.
     * </p>
     * 
     * @param hwnd
     *            HWND of the GUI element
     * @param windowName
     *            new name of the GUI element
     */
    public void setName(long hwnd, String windowName) {
        MFCGUIElementSpec child = guiElementSpecs.get(hwnd);
        if (child != null) {
            child.setName(windowName);

            MFCGUIElement guiElement = guiElements.remove(hwnd);
            if (guiElement == null) {
                // we need to update the GUI model as well
                find(hwnd);
            }
        }
    }

    /**
     * <p>
     * Removes a window (defined by its hwnd) from the tree. All children of the window will be
     * removed recursively.
     * </p>
     * 
     * @param hwnd
     *            hwnd of the window to be removed
     * @return number of windows that were removed
     */
    public int remove(long hwnd) {
        MFCGUIElementSpec node = guiElementSpecs.remove(hwnd);
        int removedCounter = 1;

        if (node != null) {
            List<MFCGUIElementSpec> nodesToBeRemoved = childRelations.remove(hwnd);

            // remove all children and sub-children, if any
            if (nodesToBeRemoved != null) {
                for (int i = 0; i < nodesToBeRemoved.size(); i++) {
                    MFCGUIElementSpec nodeToBeRemoved = nodesToBeRemoved.get(i);
                    List<MFCGUIElementSpec> children =
                        childRelations.remove(nodeToBeRemoved.getHwnd());

                    if (children != null) {
                        nodesToBeRemoved.addAll(children);
                    }

                    guiElementSpecs.remove(nodeToBeRemoved.getHwnd());
                    parentRelations.remove(nodeToBeRemoved.getHwnd());
                    removedCounter++;
                }
            }

            // the node may be a child node of a parent. So search for it and remove it
            MFCGUIElementSpec parent = parentRelations.remove(hwnd);
            if (parent != null) {
                List<MFCGUIElementSpec> children = childRelations.get(parent.getHwnd());

                if (children != null) {
                    for (int i = 0; i < children.size(); i++) {
                        if (children.get(i).getHwnd() == hwnd) {
                            children.remove(i);
                            break;
                        }
                    }

                    if (children.size() <= 0) {
                        childRelations.remove(parent.getHwnd());
                    }
                }
            }
        }
        return removedCounter;
    }

    /**
     * @return the guiModel
     */
    public GUIModel getGUIModel() {
        return guiModel;
    }

    /**
     * <p>
     * Returns the number of nodes contained in the WindowTree.
     * </p>
     * 
     * @return number of nodes
     */
    public int size() {
        return guiElementSpecs.size();
    }

    /**
     * <p>
     * Returns a sorted set of all targets that existed any time in the window tree.
     * </p>
     * 
     * @return set of targets
     */
    public Set<MFCGUIElementSpec> getTargets() {
        return targets;
    }

}
