//   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.LinkedList;
import java.util.List;
import java.util.Map;

import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.eventcore.gui.MouseClick;
import de.ugoe.cs.autoquest.eventcore.guimodel.IButton;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IIterationInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;

/**
 * TODO comment
 * 
 * @version $Revision: $ $Date: 16.07.2012$
 * @author 2012, last modified by $Author: pharms$
 */
public class MissingFeedbackRule implements UsabilityEvaluationRule {

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.usability.UsabilityEvaluationRule#evaluate(TaskTree)
     */
    @Override
    public UsabilityEvaluationResult evaluate(ITaskModel taskModel) {
        UsabilityEvaluationResult results = new UsabilityEvaluationResult(taskModel);

        Map<ITask, Integer> smellingTasks = getTasksShowingMissingFeedback(taskModel.getTasks());
        analyzeTasksShowingMissingFeedback(smellingTasks, results, taskModel);

        return results;
    }

    /**
     *
     */
    private void analyzeTasksShowingMissingFeedback(Map<ITask, Integer>       smellingTasks,
                                                    UsabilityEvaluationResult results,
                                                    ITaskModel                taskModel)
    {

        for (Map.Entry<ITask, Integer> entry : smellingTasks.entrySet()) {
            // impatience ratio is the sum of the times between two clicks of a user on the
            // identical (not semantically equal) button. If the user is highly impatient and
            // clicks the identical button more than two times, the time difference between the
            // first click and the last click is multiplied with the number of additional clicks
            // to give such behavior a higher weight. The average impatience ratio is the
            // cumulative impatience of all task instances divided by the number of task
            // instances. I.e. if all users show impatience, i.e., all instances have an impatience
            // ratio, the average impatience of a task is relatively high. Else, it is rather low.
            // If, e.g., all users clicked the identical button again in between 3 seconds, the
            // average impatience of the task would be 3000, which should be a usability smell.
            // If the users click even more often on the identical button, the ratio is even higher.
            // If only one of 50 users clicked twice, than the ratio is only 60, which should not
            // be considered as usability smell.
            
            UsabilitySmellIntensity intensity =
                UsabilitySmellIntensity.getIntensity(entry.getValue(), entry.getKey(), taskModel);

            if (intensity != null) {
                Map<String, Object> parameters = new HashMap<String, Object>();

                int allClickCount = 0;
                int multipleClickCount = 0;
                long cummulatedWaitingTime = 0;
                int numberOfAdditionalClicks = 0;
                
                Event exampleEvent = null;
                for (ITaskInstance instance : entry.getKey().getInstances()) {
                    List<List<Event>> clicksOnIdenticalButton =
                        getSubsequentClicksOnIdenticalButton((IIterationInstance) instance);
                    
                    if (clicksOnIdenticalButton != null) {
                        multipleClickCount += clicksOnIdenticalButton.size();
                        
                        for (List<Event> subsequence : clicksOnIdenticalButton) {
                            exampleEvent = subsequence.get(0);
                            
                            Event endEvent = subsequence.get(subsequence.size() - 1);
                            long timeDiff = endEvent.getTimestamp() - exampleEvent.getTimestamp();
                            
                            cummulatedWaitingTime += timeDiff;
                            numberOfAdditionalClicks += subsequence.size() - 1;
                        }
                    }
                    
                    allClickCount += ((IIterationInstance) instance).size();
                }
                
                parameters.put("multipleClickCount", multipleClickCount);
                parameters.put("allClickCount", allClickCount);
                parameters.put("averageWaitingTime",
                               (cummulatedWaitingTime / (numberOfAdditionalClicks * 1000)));
                
                parameters.put("button", exampleEvent.getTarget());
                
                parameters.put("task", entry.getKey());
                
                results.addSmell(entry.getKey(), intensity,
                                 UsabilitySmellDescription.MISSING_FEEDBACK, parameters);
            }
        }
    }

    /**
     * 
     */
    private Map<ITask, Integer> getTasksShowingMissingFeedback(Collection<ITask> tasks) {
        Map<ITask, Integer> impatienceRatios = new HashMap<ITask, Integer>();
        
        for (ITask task : tasks) {
            if (isSubsequentClickOnButton(task))  {
                int ratio = getAverageRatioOfUserImpatience((IIteration) task);
                
                if (ratio > 0) {
                    impatienceRatios.put(task, ratio);
                }
            }
        }
        
        return impatienceRatios;
    }

    /**
     *
     */
    private boolean isSubsequentClickOnButton(ITask task) {
        if (!(task instanceof IIteration)) {
            return false;
        }
        
        if (!(((IIteration) task).getMarkedTask() instanceof IEventTask)) {
            return false;
        }
        
        IEventTask childTask = (IEventTask) ((IIteration) task).getMarkedTask();
        
        if ((childTask.getInstances() != null) && (childTask.getInstances().size() > 0)) {
            Event event =
                ((IEventTaskInstance) childTask.getInstances().iterator().next()).getEvent();
            
            return
                ((event.getType() instanceof MouseClick) && (event.getTarget() instanceof IButton));
        }
        else {
            return false;
        }
    }

    /**
     *
     */
    private int getAverageRatioOfUserImpatience(IIteration task) {
        if (task.getInstances().size() > 0) {
            int cummulativeImpatienceRatio = 0;
            for (ITaskInstance instance : task.getInstances()) {
                cummulativeImpatienceRatio += getImpatienceRatio((IIterationInstance) instance);
            }

            return cummulativeImpatienceRatio / task.getInstances().size();
        }
        else {
            return 0;
        }
    }

    /**
     *
     */
    private long getImpatienceRatio(IIterationInstance instance) {
        List<List<Event>> clicksOnIdenticalButton = getSubsequentClicksOnIdenticalButton(instance);
        
        if (clicksOnIdenticalButton != null) {
            if (clicksOnIdenticalButton.size() > 1) {
                //throw new IllegalStateException("not described in dissertation");
            }
            
            long cummulativeImpatience = 0;
            
            for (List<Event> subsequence : clicksOnIdenticalButton) {
                int startIndex = 0;
                int endIndex = 1;
                
                while (endIndex < subsequence.size()) {
                    Event startEvent = subsequence.get(startIndex);

                    boolean includeNext = false;
                    
                    if ((endIndex + 1) < subsequence.size()) {
                        Event nextEvent = subsequence.get(endIndex + 1);
                        long extendedTimeDiff =
                            nextEvent.getTimestamp() - startEvent.getTimestamp();
                        includeNext = extendedTimeDiff < 15000;
                    }
                    
                    if (!includeNext && ((endIndex - startIndex) >= 1)) {
                        Event endEvent = subsequence.get(endIndex);
                        long timeDiff = endEvent.getTimestamp() - startEvent.getTimestamp();
                        
                        if ((((endIndex - startIndex) > 1) || (timeDiff > 1000)) &&
                            (timeDiff < 15000))
                        {
                            // the user clicked on the same link several times. In case of only two
                            // clicks, this was not occasionally, as the time differences between
                            // the clicks is above one second. And it is also due to impatience, as
                            // it is below 15 seconds (everything above 15 seconds is considered a
                            // new action e.g. clicking again on a download link to download a
                            // file a second time
                            cummulativeImpatience += timeDiff * (endIndex - startIndex);
                        }
                        
                        startIndex = endIndex;
                    }
                    endIndex++;
                }
            }
            
            return cummulativeImpatience;
        }

        return 0;
    }


    /**
     *
     */
    private List<List<Event>> getSubsequentClicksOnIdenticalButton(IIterationInstance instance) {
        if (instance.size() >= 2) {
            List<List<Event>> result = new LinkedList<List<Event>>();
            List<Event> currentList = new LinkedList<Event>();
            for (int i = 0; i < instance.size(); i++) {
                Event event = ((IEventTaskInstance) instance.get(i)).getEvent();
                
                if (currentList.size() == 0) {
                    // initially fill the current list with first event
                    currentList.add(event);
                }
                else if (currentList.get(currentList.size() - 1).getTarget() == event.getTarget()) {
                    // check if the targets are really identical. A check for equal targets would
                    // also reveal a re-click on a semantically equal target in a distinct view.
                    
                    // a further event with an identical target has been detected. Add it to the
                    // current list, as well.
                    currentList.add(event);
                }
                else {
                    // the current target is not identical to the previous one
                    if (currentList.size() > 1) {
                        // there were several preceding events with identical targets. Memorize
                        // this.
                        result.add(currentList);
                        currentList = new LinkedList<Event>();
                    }
                    else {
                        currentList.clear();
                    }
                    
                    // a new list of events with identical targets may start. Add the current
                    // event as the first one to the list.
                    currentList.add(event);
                }
            }

            if (currentList.size() > 1) {
                result.add(currentList);
            }
            
            if (result.size() > 0) {
                return result;
            }
        }

        return null;
    }
}
