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

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import de.ugoe.cs.autoquest.CommandHelpers;
import de.ugoe.cs.autoquest.SequenceInstanceOf;
import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.eventcore.IHierarchicalEventTarget;
import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonDown;
import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonInteraction;
import de.ugoe.cs.autoquest.eventcore.gui.MouseClick;
import de.ugoe.cs.autoquest.eventcore.gui.MouseDoubleClick;
import de.ugoe.cs.autoquest.eventcore.gui.ValueSelection;
import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
import de.ugoe.cs.util.console.Command;
import de.ugoe.cs.util.console.Console;
import de.ugoe.cs.util.console.GlobalDataContainer;

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

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

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
     */
    @SuppressWarnings("unchecked")
    @Override
    public void run(List<Object> parameters) {
        String sequencesName;
        String newSequencesName;
        try {
            sequencesName = (String) parameters.get(0);
            if (parameters.size() > 1) {
                newSequencesName = (String) parameters.get(1);
            }
            else {
                newSequencesName = sequencesName;
            }
        }
        catch (Exception e) {
            throw new IllegalArgumentException("must provide a sequences name");
        }

        Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName);
        if (dataObject == null) {
            CommandHelpers.objectNotFoundMessage(sequencesName);
            return;
        }
        if (!SequenceInstanceOf.isCollectionOfSequences(dataObject)) {
            CommandHelpers.objectNotType(sequencesName, "Collection<List<Event<?>>>");
            return;
        }

        Collection<List<Event>> newSequences =
            removeEventDuplicates((Collection<List<Event>>) dataObject);

        if (GlobalDataContainer.getInstance().addData(newSequencesName, newSequences)) {
            CommandHelpers.dataOverwritten(newSequencesName);
        }
        
    }

    /**
     *
     */
    private Collection<List<Event>> removeEventDuplicates(Collection<List<Event>> sequences) {
        Collection<List<Event>> newSequences = new LinkedList<List<Event>>();
        
        //long[] diffCounter = new long[1000];
        //Map<String, long[]> diffCounterForPairs = new HashMap<>();
        
        int eventsRemoved = 0;
        
        for (List<Event> sequence : sequences) {
            Event predecessor = null;
            List<Event> result = new ArrayList<Event>();
            
            System.out.println("sequence");
            
            for (Event event : sequence) {
                long diff = (predecessor != null) ?
                    (event.getTimestamp() - predecessor.getTimestamp()) : Long.MAX_VALUE;
                
                System.out.println("  " + event.getType() + "\t" + ((IHierarchicalEventTarget) event.getTarget()).getSpecification().toString() + "\t" + event.getTimestamp() + "\t" + diff);
                /*if (diff < diffCounter.length) {
                    diffCounter[(int) diff]++;
                    
                    long[] specCounter = diffCounterForPairs.get(getKey(predecessor, event));
                    
                    if (specCounter == null) {
                        specCounter = new long[diffCounter.length];
                        diffCounterForPairs.put(getKey(predecessor, event), specCounter);
                    }
                    
                    specCounter[(int) diff]++;
                }*/
                
                // this border of 30 milliseconds was determined by plotting the time differences
                // and seeing that there is a real drop of time differences above 30 milliseconds.
                // And 30 milliseconds is still small enough to expect, that this is not triggered
                // by a human.
                if (diff < 30) {
                    Event toPrefer = getEventToPrefer(predecessor, event);

                    if (toPrefer != null) {
                        result.set(result.size() - 1, toPrefer);
                        eventsRemoved++;
                    }
                    else {
                        // do not prefer any of them
                        result.add(event);
                    }
                }
                else {
                    result.add(event);
                }
                
                predecessor = result.get(result.size() - 1);
            }
            
            newSequences.add(result);
        }
        
        /*for (int i = 0; i < diffCounter.length; i++) {
            System.out.print((i + 1) + "ms :\t ");
            for (int j = 0; j < diffCounter[i]; j = j + 10) {
                System.out.print('|');
            }
            
            System.out.println();
        }
        
        for (Map.Entry<String, long[]> entry : diffCounterForPairs.entrySet()) {
            System.out.println("\n\n" + entry.getKey());
            for (int i = 0; i < entry.getValue().length; i++) {
                if (entry.getValue()[i] > 0) {
                    System.out.print((i + 1) + "ms :\t ");
                    for (int j = 0; j < entry.getValue()[i]; j = j + 10) {
                        System.out.print('|');
                    }
                    
                    System.out.println();
                }
            }
        }*/
        
        Console.println("removed " + eventsRemoved + " events");
        
        return newSequences;
    }

    /**
     *
     */
    /*private String getKey(Event event1, Event event2) {
        return event1.getType().getClass().getSimpleName() + " on " + event1.getTarget().getClass()
            + "  -->  " + event2.getType().getClass().getSimpleName() + " on " +
            event2.getTarget().getClass();
    }*/

    /**
     *
     */
    private Event getEventToPrefer(Event event1, Event event2) {
        Event result = null;
        
        if (!isSameEventType(event1, event2)) {
            if (isSameEventTarget(event1, event2) ||
                oneIsParentTargetOfOtherTarget(event1, event2))
            {
                // in a combination of click and value selection on the same element or an element
                // and its parent, prefer the value selection as it is triggered by the click
                if (isLeftMouseButtonInteraction(event1) && isValueSelection(event2)) {
                    result = event2;
                }
                else if (isLeftMouseButtonInteraction(event2) && isValueSelection(event1)) {
                    result = event1;
                }
            }
        }

        return result;
    }

    /**
     *
     */
    private boolean isSameEventType(Event event1, Event event2) {
        return event1.getType().getName().equals(event2.getType().getName());
    }

    /**
     * 
     */
    private boolean isSameEventTarget(Event event1, Event event2) {
        return event1.getTarget().equals(event2.getTarget());
    }

    /**
     *
     */
    private boolean oneIsParentTargetOfOtherTarget(Event event1, Event event2) {
        if ((event1.getTarget() instanceof IGUIElement) &&
            (event2.getTarget() instanceof IGUIElement))
        {
            IGUIElement elem1 = (IGUIElement) event1.getTarget();
            IGUIElement elem2 = (IGUIElement) event2.getTarget();
            
            return isParent(elem1, elem2) || isParent(elem2, elem1);
        }
        
        return false;
    }

    /**
     *
     */
    private boolean isParent(IGUIElement elem1, IGUIElement elem2) {
        IGUIElement parent = elem2.getParent();
        
        while (parent != null) {
            if (parent.equals(elem1)) {
                return true;
            }
            
            parent = parent.getParent();
        }
        
        return false;
    }

    /**
     * 
     */
    private boolean isLeftMouseButtonInteraction(Event event) {
        if (((event.getType() instanceof MouseClick) ||
             (event.getType() instanceof MouseDoubleClick) ||
             (event.getType() instanceof MouseButtonDown)) &&
            (((MouseButtonInteraction) event.getType()).getButton() ==
                MouseButtonInteraction.Button.LEFT))
        {
            return true;
        }
        else {
            return false;
        }
    }

    /**
     * 
     */
    private boolean isValueSelection(Event event) {
        return event.getType() instanceof ValueSelection;
    }
}
