//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.eventcore.guimodel;

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

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

/**
 * <p>
 * This class provides the interfaces for GUI element trees.
 * </p>
 * <p>
 * The GUIElementTree represents the hierarchical structure of the GUI elements "as it is" currently during
 * a session. It may change during the session due to creation and destruction of GUI elements.
 * </p>
 * 
 * @author Fabian Glaser
 * @author Steffen Herbold
 * @version 1.0
 */
public class GUIElementTree {
	/* Note that in the current version the id of a GUI element is assumed to be a long. For future
	versions it might be more suitable to change to some generic id type. */
	
	 /**
     * <p>
     * Map of all GUI elements that are part of the tree for efficient searching. The keys of the
     * map are the ids of the GUI elements.
     * </p>
     */
    private Map<Long, IGUIElement> guiElements;

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

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

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

    /**
     * <p>
     * the internally created GUI model
     * </p>
     */
    private GUIModel guiModel;

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

 

    /**
     * <p>
     * Creates a new GUIElementTree.
     * </p>
     */
    public GUIElementTree() {
        guiElementSpecs = new HashMap<Long, IGUIElementSpec>();
        childRelations = new HashMap<Long, List<Long>>();
        parentRelations = new HashMap<Long, Long>();
        guiElements = new HashMap<Long, IGUIElement>();
        guiModel = new GUIModel();
    }
    
    /**
     * <p>
     * Creates a GUIElementTree with an already existing guiModel
     * @param guiModel
     * </p>
     */
    public GUIElementTree(GUIModel guiModel){
        guiElementSpecs = new HashMap<Long, IGUIElementSpec>();
        childRelations = new HashMap<Long, List<Long>>();
        parentRelations = new HashMap<Long, Long>();
        guiElements = new HashMap<Long, IGUIElement>();
        this.guiModel = guiModel;
    }

    /**
     * <p>
     * Adds a new GUI element to the tree.
     * </p>
     * 
     * @param guiElementID
     *            id of the GUI element to be created
     * @param parentID
     *            id of the parent GUI element 
     * @param guiElementSpec
     * 			  the GUI element specification
     */
    public void add(Long guiElementID,
    				Long parentID,
                    IGUIElementSpec guiElementSpec)
    {
        IGUIElement guiElement = guiElements.get(guiElementID);
        
        if (guiElement == null) {
        	IGUIElementSpec parent = guiElementSpecs.get(parentID);
            if (parent != null) {
                List<Long> otherChildren = childRelations.get(parentID);

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

                otherChildren.add(guiElementID);

                parentRelations.put(guiElementID, parentID);
            }
            guiElementSpecs.put(guiElementID, guiElementSpec);
            
            List<IGUIElementSpec> guiElementPath = new ArrayList<IGUIElementSpec>();

            Long currentElementID = guiElementID;
            while (guiElementSpec != null) {
                guiElementPath.add(0, guiElementSpec);
                currentElementID = parentRelations.get(currentElementID);
                guiElementSpec = guiElementSpecs.get(currentElementID);
            }

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

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

            IGUIElementSpec elementSpec = guiElementSpecs.get(id);

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

            Long currentElementID = id;
            while (elementSpec != null) {
                guiElementPath.add(0, elementSpec);
                currentElementID = parentRelations.get(currentElementID);
                elementSpec = guiElementSpecs.get(currentElementID);
            }

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

    /**
     * <p>
     * Removes a GUI element (defined by its id) from the tree. All children of the GUI element will be
     * removed recursively.
     * </p>
     * 
     * @param id
     *            id of the GUI element to be removed
     * @return number of GUI elements that were removed
     */
    public int remove(long id) {
        int removedCounter = 0;
        IGUIElementSpec node = guiElementSpecs.remove(id);

        if (node != null) {
        	removedCounter++;
            List<Long> nodesToBeRemoved = childRelations.remove(id);

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

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

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

            /* the node may be a child node of a parent. So search for it in the child relations
            of the parent and remove it */
            Long parent = parentRelations.remove(id);
            if (parent != null) {
                List<Long> children = childRelations.get(parent);

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

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

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

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

}
