// Module    : $RCSfile: GUIModel.java,v $
// Version   : $Revision: 0.0 $  $Author: pharms $  $Date: 14.08.2012 $
// Project   : quest-core-events
// Creation  : 2012 by pharms
// Copyright : Patrick Harms, 2012

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

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * The goal of a GUI model is to correctly l
 * </p>
 * 
 * @version $Revision: $ $Date: 14.08.2012$
 * @author 2012, last modified by $Author: pharms$
 */
public class GUIModel {
    
    /**
     * 
     */
    private TreeNode root = new TreeNode();
    
    /**
     * 
     */
    private List<TreeNode> allNodes = new ArrayList<TreeNode>();

    /**
     * TODO: comment
     *
     * @param currentGUIElementPath
     * @return
     * @throws GUIModelException 
     */
    public IGUIElement integratePath(List<? extends IGUIElementSpec> guiElementPath,
                                     IGUIElementFactory              guiElementFactory)
        throws GUIModelException
    {
        List<IGUIElementSpec> remainingPath = new ArrayList<IGUIElementSpec>();
        
        for (IGUIElementSpec spec : guiElementPath)
        {
            remainingPath.add(spec);
        }
        
        return integratePath(root, remainingPath, guiElementFactory);
    }

    /**
     * TODO: comment
     *
     * @param root
     * @return
     */
    public List<IGUIElement> getChildren(IGUIElement guiElement) {
        for (TreeNode node : allNodes) {
            if (node.guiElement.equals(guiElement)) {
                List<IGUIElement> result = new ArrayList<IGUIElement>();
                
                if (node.children != null) {
                    for (TreeNode child : node.children) {
                      result.add(child.guiElement);
                    }
                }
                
                return result;
            }
        }
        
        return null;
    }

    /**
     * TODO: comment
     *
     * @return
     */
    public List<IGUIElement> getRootElements() {
        List<IGUIElement> roots = new ArrayList<IGUIElement>();
        for (TreeNode rootChild : root.children) {
            roots.add(rootChild.guiElement);
        }
        return roots;
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param root2
     * @param guiElementPath
     * @param guiElementFactory
     * @return
     * @throws GUIModelException 
     */
    private IGUIElement integratePath(TreeNode                        parentNode,
                                      List<? extends IGUIElementSpec> remainingPath,
                                      IGUIElementFactory              guiElementFactory)
        throws GUIModelException
    {
        IGUIElementSpec specToIntegrateElementFor = remainingPath.remove(0);
        
        List<TreeNode> matchingChildren = new ArrayList<TreeNode>();
        int maximumSimilarity = 0;
        
        if (parentNode.children != null) {
            for (TreeNode child : parentNode.children) {
                int similarityLevel = getSimilarityLevel
                    (specToIntegrateElementFor, child.guiElement.getSpecification());
                
                if (similarityLevel >= maximumSimilarity) {
                    if (maximumSimilarity == 100) {
                        throw new GUIModelException
                          ("several children of gui element " + parentNode.guiElement +
                           " pretend to fully match specification " + specToIntegrateElementFor);
                    }
                    
                    if (maximumSimilarity != similarityLevel) {
                        matchingChildren.clear();
                        maximumSimilarity = similarityLevel;
                    }
                    
                    matchingChildren.add(child);
                }
            }
        }
        
        // we do not see something as matching if the similarity level is below 80%
        if (maximumSimilarity < 80) {
            matchingChildren.clear();
        }
        
        // if we get here, the corresponding path does not exist yet. So create it
        if (matchingChildren.size() == 0) {
            matchingChildren.add
                (parentNode.addChild
                     (guiElementFactory.instantiateGUIElement(specToIntegrateElementFor)));
        }
        else if (matchingChildren.size() > 1) {
            throw new GUIModelException
              ("several children of gui element " + parentNode.guiElement +
               " match the specification " + specToIntegrateElementFor + " at the same level. " +
               "Can not decide which is the right one.");
        }
        
        if (remainingPath.size() > 0) {
            return integratePath(matchingChildren.get(0), remainingPath, guiElementFactory);
        }
        else {
            return matchingChildren.get(0).guiElement;
        }
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param spec1
     * @param spec2
     * @return
     * @throws GUIModelException 
     */
    private int getSimilarityLevel(IGUIElementSpec spec1, IGUIElementSpec spec2)
        throws GUIModelException
    {
        if (spec1 == spec2) {
            return 100;
        }
        else if (spec1 != null) {
            int level = spec1.getSimilarity(spec2);
            
            if ((level < 0) || (100 < level)) {
               throw new GUIModelException("invalid tree node similarity provided (" + level +
                                           "). Must be between 0 and 100."); 
            }
            
            return level;
        }
        else {
            return 0;
        }
    }

    /**
     * <p>
     * TODO comment
     * </p>
     * 
     * @version $Revision: $ $Date: 17.08.2012$
     * @author 2012, last modified by $Author: pharms$
     */
    private class TreeNode
    {
        /** */
        private IGUIElement guiElement;
        
        /** */
        private List<TreeNode> children;
        
        /** */
        //private TreeNode parent;
        
        /**
         * <p>
         * TODO: comment
         * </p>
         *
         * @param guiElement
         * @return
         */
        private TreeNode addChild(IGUIElement guiElement)
        {
            if (children == null)
            {
                children = new ArrayList<TreeNode>();
            }
            
            TreeNode child = new TreeNode();
            child.guiElement = guiElement;
            //child.parent = this;
            children.add(child);
            
            allNodes.add(child);
            
            return child;
        }
    }
}
