//   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;

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

import de.ugoe.cs.autoquest.eventcore.EventTargetModelException;
import de.ugoe.cs.autoquest.eventcore.IEventTargetFactory;

/**
 * <p>
 * This class provides the interfaces for hierarchical event target trees.
 * </p>
 * <p>
 * The HierarchicalEventTargetTree represents the hierarchical structure of the event targets
 * "as it is" currently during a session. It may change during the session due to creation and
 * destruction of event targets. The parameter ID represents the id type of the event targets
 * that are handled internally. The other type parameters specify the concrete target type.
 * </p>
 * 
 * @author Fabian Glaser
 * @author Steffen Herbold
 * @version 1.0
 */
public class HierarchicalEventTargetTree<ID,
                                         TARGET_TYPE extends IHierarchicalEventTarget,
                                         TARGET_SPEC_TYPE extends IEventTargetSpec>
{
    /**
     * <p>
     * Map of all event targets that are part of the tree for efficient searching. The keys of the
     * map are the ids of the event targets.
     * </p>
     */
    private Map<ID, TARGET_TYPE> eventTargets;

    /**
     * <p>
     * Map of all event target specifications that are part of the tree for efficient searching. The
     * keys of the map are the ids of the event targets.
     * </p>
     */
    private Map<ID, TARGET_SPEC_TYPE> eventTargetSpecs;

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

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

    /**
     * <p>
     * the internally created GUI model
     * </p>
     */
    private HierarchicalEventTargetModel<TARGET_TYPE> eventTargetModel;

    /**
     * <p>
     * the event target factory used in the model
     * </p>
     */
    private IEventTargetFactory eventTargetFactory;

    /**
     * <p>
     * Creates a new GUIElementTree.
     * </p>
     */
    public HierarchicalEventTargetTree(HierarchicalEventTargetModel<TARGET_TYPE> eventTargetModel,
                                       IEventTargetFactory                       eventTargetFactory)
    {
        this.eventTargetSpecs = new HashMap<ID, TARGET_SPEC_TYPE>();
        this.childRelations = new HashMap<ID, List<ID>>();
        this.parentRelations = new HashMap<ID, ID>();
        this.eventTargets = new HashMap<ID, TARGET_TYPE>();
        this.eventTargetModel = eventTargetModel;
        this.eventTargetFactory = eventTargetFactory;
    }

    /**
     * <p>
     * Adds a new event target to the tree.
     * </p>
     * 
     * @param eventTargetID
     *            id of the event target to be created
     * @param parentID
     *            id of the parent event target 
     * @param eventTargetSpec
     *            the event target specification
     *            
     * @throws EventTargetModelException if the event target can not be added to the underlying GUI model
     */
    public void add(ID               eventTargetID,
                    ID               parentID,
                    TARGET_SPEC_TYPE eventTargetSpec)
        throws EventTargetModelException
    {
        TARGET_TYPE eventTarget = eventTargets.get(eventTargetID);
        
        if (eventTarget == null) {
            TARGET_SPEC_TYPE parent = eventTargetSpecs.get(parentID);
            if (parent != null) {
                List<ID> otherChildren = childRelations.get(parentID);

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

                otherChildren.add(eventTargetID);

                parentRelations.put(eventTargetID, parentID);
            }
            eventTargetSpecs.put(eventTargetID, eventTargetSpec);
            
            List<TARGET_SPEC_TYPE> eventTargetPath = new ArrayList<TARGET_SPEC_TYPE>();

            ID currentElementID = eventTargetID;
            while (eventTargetSpec != null) {
                eventTargetPath.add(0, eventTargetSpec);
                currentElementID = parentRelations.get(currentElementID);
                eventTargetSpec = eventTargetSpecs.get(currentElementID);
            }

            eventTarget = (TARGET_TYPE)
                  eventTargetModel.integratePath(eventTargetPath, eventTargetFactory);
            
            eventTargets.put(eventTargetID, eventTarget);
        }
    }

    /**
     * <p>
     * Searches the tree for a event target with the specified id and returns its
     * {@link IGUIElement} .
     * </p>
     * 
     * @param id
     *            id that is looked for
     * @return {@link IEventTargetSpec} of the event target with the given id if found, null otherwise
     */
    public TARGET_TYPE find(ID id) {
        return eventTargets.get(id);
    }

    /**
     * <p>
     * Returns the id of the provided {@link IHierachicalEventTarget}. The comparison is performed using the
     * equals method of the event target.
     * </p>
     * 
     * @param eventTarget
     *            guiElement that is looked for
     * @return the id of the event target, null if the event target can not be found
     */
    public ID find(TARGET_TYPE eventTarget) {
        for (Map.Entry<ID, TARGET_TYPE> entry : eventTargets.entrySet()) {
            if (eventTarget.equals(entry.getValue())) {
                return entry.getKey();
            }
        }
        
        return null;
    }

    /**
     * <p>
     * Removes a event target (defined by its id) from the tree. All children of the event target will be
     * removed recursively.
     * </p>
     * 
     * @param id
     *            id of the event target to be removed
     * @return number of event targets that were removed
     */
    public int remove(ID id) {
        int removedCounter = 0;
        TARGET_SPEC_TYPE node = eventTargetSpecs.remove(id);

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

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

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

                    eventTargetSpecs.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 */
            ID parent = parentRelations.remove(id);
            if (parent != null) {
                List<ID> 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 HierarchicalEventTargetModel<TARGET_TYPE> getHierarchicalEventTargetModel() {
        return eventTargetModel;
    }

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

}
