//   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.IEventType;
import de.ugoe.cs.autoquest.eventcore.gui.KeyInteraction;
import de.ugoe.cs.autoquest.eventcore.gui.MouseInteraction;
import de.ugoe.cs.autoquest.eventcore.gui.Scroll;
import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
import de.ugoe.cs.autoquest.eventcore.gui.TextSelection;
import de.ugoe.cs.autoquest.eventcore.gui.ValueSelection;
import de.ugoe.cs.autoquest.tasktrees.treeifc.DefaultTaskInstanceTraversingVisitor;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
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 DataEntryMethodChangeRule 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 = getDataEntryMethodChangeRatios(taskModel.getTasks());
        analyzeDataEntryMethodChangeRatios(smellingTasks, results, taskModel);

        return results;
    }

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

        for (Map.Entry<ITask, Integer> entry : smellingTasks.entrySet()) {
            // data entry method change ratio of an instance is the sum of data entry method
            // changes between subsequent events divided by the number of events minus one. The
            // data entry method change ratio of a tasks is the average of the ratios of the
            // instances. If this ratio is 0 no changes are done at all. If it is higher, it should
            // be informed.
            
            UsabilitySmellIntensity intensity =
                UsabilitySmellIntensity.getIntensity(entry.getValue(), entry.getKey(), taskModel);

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

                parameters.put("task", entry.getKey());
                parameters.put("ratio", (float) entry.getValue() / 10);
                
                results.addSmell(entry.getKey(), intensity,
                                 UsabilitySmellDescription.DATA_ENTRY_METHOD_CHANGE, parameters);
            }
        }
    }

    /**
     * 
     */
    private Map<ITask, Integer> getDataEntryMethodChangeRatios(Collection<ITask> tasks) {
        Map<ITask, Integer> methodChangeRatios = new HashMap<ITask, Integer>();
        
        for (ITask task : tasks) {
            if (task instanceof ISequence)  {
                int ratio = getAverageDataEntryMethodChangeRatio((ISequence) task);
                
                if (ratio > 0) {
                    methodChangeRatios.put(task, ratio);
                }
            }
        }
        
        return methodChangeRatios;
    }

    /**
     *
     */
    private int getAverageDataEntryMethodChangeRatio(ISequence task) {
        if (task.getInstances().size() > 0) {
            int cummulativeDataEntryMethodChangeRatio = 0;
            for (ITaskInstance instance : task.getInstances()) {
                cummulativeDataEntryMethodChangeRatio += getDataEntryMethodChangeRatio(instance);
            }

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

    /**
     *
     */
    private long getDataEntryMethodChangeRatio(ITaskInstance instance) {
        final List<IEventTaskInstance> terminalNodes = new LinkedList<>();
        
        instance.accept(new DefaultTaskInstanceTraversingVisitor() {
            @Override
            public void visit(IEventTaskInstance eventTaskInstance) {
                terminalNodes.add(eventTaskInstance);
            }
        });
        
        if (terminalNodes.size() > 1) {
            IEventTaskInstance previous = null;
            int changeCount = 0;

            for (IEventTaskInstance current : terminalNodes) {
                if ((previous != null) && (dataEntryMethodChanges(previous, current))) {
                    changeCount++;
                }
                
                previous = current;
            }

            return changeCount * 1000 / (terminalNodes.size() - 1);
        }
        else {
            return 0;
        }
    }


    /**
     *
     */
    private boolean dataEntryMethodChanges(IEventTaskInstance first, IEventTaskInstance second) {
        IEventType firstEventType = first.getEvent().getType();
        IEventType secondEventType = second.getEvent().getType();
        
        boolean hasKeyboardInteraction =
            isKeyboardInteraction(firstEventType) || isKeyboardInteraction(secondEventType);
        
        boolean hasMouseInteraction =
            isMouseInteraction(firstEventType) || isMouseInteraction(secondEventType);
        
        return hasKeyboardInteraction && hasMouseInteraction;
    }

    /**
     *
     */
    private boolean isKeyboardInteraction(IEventType eventType) {
        return (eventType instanceof KeyInteraction) || (eventType instanceof TextInput);
    }

    /**
     *
     */
    private boolean isMouseInteraction(IEventType eventType) {
        return (eventType instanceof MouseInteraction) || (eventType instanceof ValueSelection) ||
            (eventType instanceof TextSelection) || (eventType instanceof Scroll);
    }
}
