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

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementGroup;
import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;

/**
 * <p>
 * TODO comment
 * </p>
 * 
 * @author Patrick Harms
 */
class RuleUtils {

    /**
     *
     */
    static Map<IGUIElement, List<IGUIElement>> getGroups(Collection<IGUIElement> guiElements,
                                                         int                     maxDistToCommonParent)
    {
        Map<IGUIElement, List<IGUIElement>> groups = new HashMap<>();
        List<IGUIElement> guiElementsToGroup = new LinkedList<>(guiElements);
        IGUIElement parentToGroup;
        
        do {
            List<LinkedList<IGUIElement>> sortedPaths = new LinkedList<>();
            List<IGUIElement> commonParents = new LinkedList<>();
            
            createSortedPaths
                (guiElementsToGroup, sortedPaths, commonParents, maxDistToCommonParent);

            // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION
            // Iterator<LinkedList<IGUIElement>> sortedPathsIt1 = sortedPaths.iterator();
            // Iterator<IGUIElement> commonParentIt1 = commonParents.iterator();
            //  
            // while (commonParentIt1.hasNext()) {
            //     IGUIElement currentParent = commonParentIt1.next();
            //     IGUIElement sortedPath = sortedPathsIt1.next().getLast();
            //     
            //     System.out.println(toPathString(sortedPath));
            //     System.out.println(toPathString(currentParent) + "###########");
            // }
            //  
            // System.out.println();
            // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION
            
            parentToGroup = getParentToGroup(commonParents);
        
            if (parentToGroup != null) {
                Iterator<LinkedList<IGUIElement>> sortedPathsIt = sortedPaths.iterator();
                ListIterator<IGUIElement> commonParentIt = commonParents.listIterator();
        
                while (commonParentIt.hasNext()) {
                    if (!parentToGroup.equals(commonParentIt.next())) {
                        sortedPathsIt.next();
                    }
                    else {
                        break;
                    }
                }
                
                List<IGUIElement> groupedGUIElements = new LinkedList<>();
                
                // go one backward to ensure, that next will return the first occurrence
                // of the parent to create the group for.
                commonParentIt.previous();
                
                do {
                    IGUIElement guiElementToGroup = sortedPathsIt.next().getLast();
                    groupedGUIElements.add(guiElementToGroup);
                    guiElementsToGroup.remove(guiElementToGroup);
                    
                }
                while (parentToGroup.equals(commonParentIt.next()));
                
                groups.put(parentToGroup, groupedGUIElements);
                
                // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> TEST IMPLEMENTATION
                // System.out.println("group for");
                // System.out.println(toPathString(parentToGroup));
                //  
                // for (IGUIElement element : groupedGUIElements) {
                //     System.out.println(toPathString(element));
                // }
                //  
                // System.out.println();
                // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TEST IMPLEMENTATION
            }
        }
        while (parentToGroup != null);
        
        return groups;
    }

    /**
     *
     */
    static String toPathString(IGUIElement element) {
        IGUIElement parent = element;
        String result = "";
        
        while (parent != null) {
            if (!(parent instanceof GUIElementGroup)) {
                result = parent.toString() + "/" + result;
            }
            else {
                result = parent.toString().hashCode() + "/" + result;
            }
            
            parent = parent.getParent();
        }
        
        return result.toString();
    }

    /**
     *
     */
    private static void createSortedPaths(List<IGUIElement>             guiElements,
                                          List<LinkedList<IGUIElement>> sortedPaths,
                                          List<IGUIElement>             commonParents,
                                          int                           maxDistToCommonParent)
    {
        for (IGUIElement guiElement : guiElements) {
            // create the path
            LinkedList<IGUIElement> path = new LinkedList<>();
            IGUIElement parent = guiElement;
            while (parent != null) {
                if (!(parent instanceof GUIElementGroup)) {
                    path.addFirst(parent);
                }
                
                parent = parent.getParent();
            }
            
            // sort it into the list of paths
            int maxEquality = 0;
            int maxEqualityPos = 0;
            int pos = 0;
            
            for (List<IGUIElement> candidate : sortedPaths) {
                int equality = 0;
                while ((equality < candidate.size()) && (equality < path.size()) &&
                       (candidate.get(equality).equals(path.get(equality))))
                {
                    equality++;
                }
                
                if (equality > maxEquality) {
                    maxEquality = equality;
                    maxEqualityPos = pos;
                }
                
                pos++;
            }
            
            sortedPaths.add(maxEqualityPos, path);
            
            if ((maxEquality > 0) && ((path.size() - maxEquality) < maxDistToCommonParent)) {
                commonParents.add(maxEqualityPos, path.get(maxEquality - 1));
            }
            else {
                commonParents.add(maxEqualityPos, null);
            }
        }
    }

    /**
     *
     */
    private static IGUIElement getParentToGroup(List<IGUIElement> commonParents) {
        Map<IGUIElement, Integer> occurrenceCounts = new HashMap<>();
        Map<IGUIElement, Integer> depths = new HashMap<>();
        
        // get the required information about path lengths and occurrence counts
        for (IGUIElement commonParent : commonParents) {
            Integer occurrenceCount = occurrenceCounts.get(commonParent);
            
            if (occurrenceCount != null) {
                occurrenceCounts.put(commonParent, occurrenceCount + 1);
            }
            else {
                occurrenceCounts.put(commonParent, 1);
            }
            
            if (!depths.containsKey(commonParent)) {
                int depth = 0;
                IGUIElement parent = commonParent;
                
                while (parent != null) {
                    depth++;
                    parent = parent.getParent();
                }
                
                depths.put(commonParent, depth);
            }
        }
        
        IGUIElement elementToGroup = null;
        
        // get the GUI element being the parent most often 
        for (IGUIElement commonParent : commonParents) {
            if (elementToGroup == null) {
                elementToGroup = commonParent;
            }
            else if ((commonParent != null) &&
                     (!elementToGroup.equals(commonParent)))
            {
                int occurrenceCountCandidate = occurrenceCounts.get(commonParent);
                int depthCandidate = depths.get(commonParent);
            
                int occurrenceCountElement = occurrenceCounts.get(elementToGroup);
                int depthElement = depths.get(elementToGroup);
                
                if ((depthCandidate > depthElement) ||
                    ((depthCandidate == depthElement) &&
                     (occurrenceCountCandidate > occurrenceCountElement)))
                {
                    elementToGroup = commonParent;
                }
                else if ((occurrenceCountCandidate == occurrenceCountElement) &&
                         (depthCandidate == depthElement))
                {
                    // in this situation, the order is irrelevant. The GUI elements for which
                    // both paths were identified as parents are completely different.
                    // Otherwise, they would have the same parent. But as they are that
                    // different, they will not occur subsequently in the ordered list
                    // of GUI elements to group. Hence, we just reuse the one the is currently
                    // identified as the one to group next.
                }
            }
        }
        
        return elementToGroup;
    }

    /**
     * 
     */
    private RuleUtils() {
        // prevent instantiation
    }
}
