//   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.plugin.html.commands;

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

import de.ugoe.cs.autoquest.CommandHelpers;
import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement;
import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServer;
import de.ugoe.cs.util.console.Command;
import de.ugoe.cs.util.console.Console;
import de.ugoe.cs.util.console.GlobalDataContainer;

/**
 * <p>
 * TODO document
 * </p>
 * 
 * @author Patrick Harms
 * @version 1.0
 */
public class CMDcondenseHTMLListStructures implements Command {

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
     */
    @Override
    public void run(List<Object> parameters) {
        String sequencesName;

        try {
            sequencesName = (String) parameters.get(0);
        }
        catch (Exception e) {
            throw new IllegalArgumentException("illegal parameters provided: " + e);
        }

        Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName + "_targets");
        if (dataObject == null) {
            CommandHelpers.objectNotFoundMessage(sequencesName + "_targets");
            return;
        }
        if (!(dataObject instanceof GUIModel)) {
            CommandHelpers.objectNotType(sequencesName, "GUIModel");
            return;
        }

        GUIModel model = (GUIModel) dataObject;
        
        for (IGUIElement root : model.getRootElements()) {
            if (root instanceof HTMLServer) {
                try {
                    condenseListStructures((HTMLServer) root, model);
                }
                catch (Exception e) {
                    Console.printerrln
                        ("problems while condensing list structures on pages of server " + root);
                    Console.logException(e);
                    break;
                }
            }
        }
    }

    /**
     * <p>
     * 
     * </p>
     *
     * @param server the server of which all documents shall be condensed
     * @param model  the GUI model in which the server is referenced
     */
    private void condenseListStructures(HTMLServer server, GUIModel model) {
        Console.traceln(Level.INFO, "condensing list structures in documents of " + server);
        
        for (IGUIElement document : model.getChildren(server)) {
            if (document instanceof HTMLDocument) {
                condenseListStructures((HTMLDocument) document, model, "");
            }
            else {
                Console.traceln(Level.WARNING, "child " + document + " of server " + server +
                                " is no document");
            }
        }
        
        Console.traceln(Level.INFO, "condensed list structures in documents of " + server);
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param document
     * @param model
     * @param string
     */
    private void condenseListStructures(HTMLDocument document, GUIModel model, String indent) {
        for (IGUIElement pageElement : model.getChildren(document)) {
            if (pageElement instanceof HTMLPageElement) {
                condenseListStructures((HTMLPageElement) pageElement, model, indent + "  ");
            }
            else {
                Console.traceln(Level.WARNING, "child " + pageElement + " of document " + document +
                                " is no page element");
            }
        }
    }

    /**
     * <p>
     * TODO: comment
     * </p>
     *
     * @param pageElement
     * @param model
     * @param string
     */
    private List<List<HTMLPageElement>> condenseListStructures(HTMLPageElement pageElement,
                                                               GUIModel        model,
                                                               String          indent)
    {
        List<IGUIElement> children = model.getChildren(pageElement);
        
        if ((children == null) || (children.size() == 0)) {
            // no children, return the element itself as the single entry of a list
            List<List<HTMLPageElement>> result = new ArrayList<>();
            result.add(new ArrayList<HTMLPageElement>());
            result.get(0).add(pageElement);
            return result;
        }
        
        // traverse the children for structure information
        @SuppressWarnings("unchecked")
        List<List<HTMLPageElement>>[] childPaths = new List[children.size()];
        int index = 0;
        for (IGUIElement childElement : children) {
            if (childElement instanceof HTMLPageElement) {
                childPaths[index++] =
                    condenseListStructures((HTMLPageElement) childElement, model, indent + "  ");
            }
            else {
                Console.traceln(Level.WARNING, "child " + childElement + " of " + pageElement +
                                " is no page element");
            }
        }
        
        // check for equally structured direct sibling children and group them. For this, their
        // child paths need to be equal ignoring the HTML ids. So check for this.
        Map<Integer, List<Integer>> equallyStructured = new HashMap<>();
        int indexToCompareTo = 0;
        equallyStructured.put(indexToCompareTo, new ArrayList<Integer>());
        
        SEARCH:
        for (int i = 1; i < childPaths.length; i++) {
            // compare any with the previous that needs to be compared with.
            if (childPaths[indexToCompareTo].size() != childPaths[i].size()) {
                // no equally structured children, try next.
                indexToCompareTo = i;
                equallyStructured.put(indexToCompareTo, new ArrayList<Integer>());
                continue SEARCH;
            }
            
            // compare individual child paths, first for their length
            for (int j = 0; j < childPaths[indexToCompareTo].size(); j++) {
                List<HTMLPageElement> firstPath = childPaths[indexToCompareTo].get(j);
                List<HTMLPageElement> secondPath = childPaths[i].get(j);
                if (firstPath.size() != secondPath.size()) {
                    // no equally structured children, try next.
                    indexToCompareTo = i;
                    equallyStructured.put(indexToCompareTo, new ArrayList<Integer>());
                    continue SEARCH;
                }
            
                // compare individual path elements
                for (int k = 0; k < firstPath.size(); k++) {
                    if (!firstPath.get(k).getTagName().equals(secondPath.get(k).getTagName())) {
                        // no equally structured children, try next.
                        indexToCompareTo = i;
                        equallyStructured.put(indexToCompareTo, new ArrayList<Integer>());
                        continue SEARCH;
                    }
                }
            }
                
            // found a match
            equallyStructured.get(indexToCompareTo).add(i);
        }
        
        for (Map.Entry<Integer, List<Integer>> entry : equallyStructured.entrySet()) {
            if ((entry.getValue().size() > 1)) {
                entry.getValue().add(0, entry.getKey());
                
                // check if the equal one have a minimum depth of 3
                boolean hasMinDepth = false;
                for (Integer indexesOfEqualChildren : entry.getValue()) {
                    for (List<HTMLPageElement> path : childPaths[indexesOfEqualChildren]) {
                        if (path.size() >= 3) {
                            hasMinDepth = true;
                            break;
                        }
                    }
                }
                
                if (!hasMinDepth) {
                    continue;
                }
                
                // found equally structured children --> dump them
                System.out.print("tata: " + pageElement.getView() + "  ");
                String log = "";
                
                IGUIElement elem = pageElement;
                while ((elem != null) && (elem instanceof HTMLPageElement)) {
                    log = elem + "/" + log;
                    elem = elem.getParent();
                }
                
                System.out.println(log);
                
                for (int i = 0; i < entry.getValue().size(); i++) {
                    System.out.println("  child " + (i + 1));
                    for (int j = 0; j < childPaths[entry.getValue().get(i)].size(); j++) {
                        System.out.print("    ");
                        List<HTMLPageElement> path = childPaths[entry.getValue().get(i)].get(j);
                        for (int k = 0; k < path.size(); k++) {
                            System.out.print(path.get(k));
                            System.out.print('/');
                        }
                        System.out.println();
                    }
                }
            }
        }
        
        List<List<HTMLPageElement>> result = new ArrayList<>();
        for (List<List<HTMLPageElement>> childStructure : childPaths) {
            for (List<HTMLPageElement> childPath : childStructure) {
                childPath.add(0, pageElement);
                result.add(childPath);
            }
        }
        
        return result;
    }
        

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.util.console.Command#help()
     */
    @Override
    public String help() {
        return "condenseHTMLListStructures <sequence>";
    }
}
