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

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import com.google.common.collect.Sets;

import de.ugoe.cs.autoquest.CommandHelpers;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
import de.ugoe.cs.autoquest.tasktrees.treeifc.TaskTreeUtils;
import de.ugoe.cs.autoquest.usability.UsabilityEvaluationResult;
import de.ugoe.cs.autoquest.usability.UsabilitySmell;
import de.ugoe.cs.autoquest.usability.UsabilitySmell.ManualLabel;
import de.ugoe.cs.util.console.Command;
import de.ugoe.cs.util.console.GlobalDataContainer;

/**
 * <p>
 * TODO comment
 * </p>
 * 
 * @author Patrick Harms
 * @version 1.0
 */
public class CMDusabilityStatistics implements Command {
    
    private static int COUNT = 0;
    private static int DUPLICATES = 1;
    private static int INTENSITY_LVL = 2;
    private static int TRUE_POSITIVE = 3;
    private static int TRUE_POSITIVE_DUPLICATES = 4;
    private static int TRUE_POSITIVE_INTENSITY_LVL = 5;
    private static int UNASSESSED = 6;
    private static int MP_COUNT = 7;
    private static int MP_DUPLICATES = 8;
    private static int MP_INTENSITY_LVL = 9;
    private static int MP_TRUE_POSITIVE = 10;
    private static int MP_TRUE_POSITIVE_DUPLICATES = 11;
    private static int MP_TRUE_POSITIVE_INTENSITY_LVL = 12;
    private static int MP_UNASSESSED = 13;

    /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
     */
    @Override
    public void run(List<Object> parameters) {
        List<String> usabilityResultNames = new ArrayList<>(parameters.size());
        try {
            for (Object parameter : parameters) {
                usabilityResultNames.add((String) parameter);
            }
        }
        catch (Exception e) {
            throw new IllegalArgumentException();
        }

        List<UsabilityEvaluationResult> usabilityResults = new ArrayList<>(usabilityResultNames.size());
        
        for (String usabilityResultName : usabilityResultNames) {
            Object dataObject = GlobalDataContainer.getInstance().getData(usabilityResultName);
            if (dataObject == null) {
                CommandHelpers.objectNotFoundMessage(usabilityResultName);
                return;
            }
            if (!(dataObject instanceof UsabilityEvaluationResult)) {
                CommandHelpers.objectNotType(usabilityResultName, "UsabilityEvaluationResult");
                return;
            }
            
            usabilityResults.add((UsabilityEvaluationResult) dataObject);
        }
        
        // analyse all smells
        Map<String, List<UsabilitySmell>> allSmells = new TreeMap<>();
        Map<String, Set<ISequence>> mostProminentSequences = new TreeMap<>();
        Set<String> smellTypes = new TreeSet<>();
        
        for (int i = 0; i < usabilityResults.size(); i++) {
            String usabilityResultName = usabilityResultNames.get(i);
            allSmells.put(usabilityResultName, usabilityResults.get(i).getAllSmells());
            
            for (UsabilitySmell smell : usabilityResults.get(i).getAllSmells()) {
                smellTypes.add(smell.getBriefDescription());
            }
            
            if (!mostProminentSequences.containsKey(usabilityResultName)) {
                mostProminentSequences.put(usabilityResultName,
                                           TaskTreeUtils.getMostProminentTasks
                                               (usabilityResults.get(i).getTaskModel()));
            }
        }
        
        analyseAndDump("all smells", allSmells, mostProminentSequences);
        
        for (String smellType : smellTypes) {
            Map<String, List<UsabilitySmell>> relevantSmells = new TreeMap<>();
            for (int i = 0; i < usabilityResults.size(); i++) {
                List<UsabilitySmell> smellList = new LinkedList<>();
                
                for (UsabilitySmell smell : usabilityResults.get(i).getAllSmells()) {
                    if (smellType.equals(smell.getBriefDescription())) {
                        smellList.add(smell);
                    }
                }
                
                relevantSmells.put(usabilityResultNames.get(i), smellList);
            }
                
            analyseAndDump(smellType, relevantSmells, mostProminentSequences);
        }
    }

    /**
     *
     */
    private void analyseAndDump(String                            setName,
                                Map<String, List<UsabilitySmell>> smells,
                                Map<String, Set<ISequence>>       mostProminentSequences)
    {
        System.out.println("\n\n###################################################################");
        System.out.println("usability statistics for " + setName + "\n");
        
        // determine statistics
        int[][] basicResultData = new int[14][];

        for (int i = 0; i < basicResultData.length; i++) {
            basicResultData[i] = new int[smells.size()];
        }
        
        Map<String, Map<String, List<UsabilitySmell>>> tagCounters = new TreeMap<>();
        List<String> columnNames = new LinkedList<>();

        
        int index = 0;
        for (Map.Entry<String, List<UsabilitySmell>> smellGroup : smells.entrySet()) {
            String usabilityResultName = smellGroup.getKey();
            columnNames.add(usabilityResultName);
            
            basicResultData[COUNT][index] = smellGroup.getValue().size();
            basicResultData[DUPLICATES][index] = getDuplicates(smellGroup.getValue());
            basicResultData[INTENSITY_LVL][index] = getIntensityLevel(smellGroup.getValue());
            
            List<UsabilitySmell> truePositives = new LinkedList<>();
            List<UsabilitySmell> unassessed = new LinkedList<>();
            
            List<UsabilitySmell> mps = new LinkedList<>();
            List<UsabilitySmell> mpTruePositives = new LinkedList<>();
            List<UsabilitySmell> mpUnassessed = new LinkedList<>();
            
            for (UsabilitySmell smell : smellGroup.getValue()) {
                if (smell.getManualLabel() == ManualLabel.TRUE_POSITIVE) {
                    truePositives.add(smell);
                }
                else if (smell.getManualLabel() == ManualLabel.UNCHECKED) {
                    unassessed.add(smell);
                }
                
                if (mostProminentSequences.get(smellGroup.getKey()).contains(smell.getSmellingTask())) {
                    mps.add(smell);
                    
                    if (smell.getManualLabel() == ManualLabel.TRUE_POSITIVE) {
                        mpTruePositives.add(smell);
                    }
                    else if (smell.getManualLabel() == ManualLabel.UNCHECKED) {
                        mpUnassessed.add(smell);
                    }
                }
                
                Set<String> tagList = new HashSet<>(smell.getTags());
                
                Set<Set<String>> powerSetTags = Sets.powerSet(tagList);
                
                for (Set<String> tagSet : powerSetTags) {
                    List<String> tags = new LinkedList<>(tagSet);
                    //{List<String> tags = new LinkedList<>(tagList);
                    Collections.sort(tags);
                    String tagCombinationKey = tags.toString();
                    
                    Map<String, List<UsabilitySmell>> counterMap = tagCounters.get(tagCombinationKey);
                    
                    if (counterMap == null) {
                        counterMap = new HashMap<>();
                        tagCounters.put(tagCombinationKey, counterMap);
                    }
                    
                    List<UsabilitySmell> smellWithSameTagCombination =
                        counterMap.get(usabilityResultName);
                    
                    if (smellWithSameTagCombination == null) {
                        smellWithSameTagCombination = new LinkedList<>();
                        counterMap.put(usabilityResultName, smellWithSameTagCombination);
                    }
                    
                    smellWithSameTagCombination.add(smell);
                }
            }
            
            basicResultData[TRUE_POSITIVE][index] = truePositives.size();
            basicResultData[TRUE_POSITIVE_DUPLICATES][index] = getDuplicates(truePositives);
            basicResultData[TRUE_POSITIVE_INTENSITY_LVL][index] = getIntensityLevel(truePositives);
            basicResultData[UNASSESSED][index] = unassessed.size();
            
            basicResultData[MP_COUNT][index] = mps.size();
            basicResultData[MP_DUPLICATES][index] = getDuplicates(mps);
            basicResultData[MP_INTENSITY_LVL][index] = getIntensityLevel(mps);
            basicResultData[MP_TRUE_POSITIVE][index] = mpTruePositives.size();
            basicResultData[MP_TRUE_POSITIVE_DUPLICATES][index] = getDuplicates(mpTruePositives);
            basicResultData[MP_TRUE_POSITIVE_INTENSITY_LVL][index] = getIntensityLevel(mpTruePositives);
            basicResultData[MP_UNASSESSED][index] = mpUnassessed.size();
            
            index++;
        }
        
        int maxTagNameLength = 0;
        for (String tagCombination : tagCounters.keySet()) {
            maxTagNameLength = Math.max(maxTagNameLength, tagCombination.length());
        }

        maxTagNameLength = Math.max(maxTagNameLength, "    intensity level".length());

        List<StringBuffer> lines = new LinkedList<>();
        
        lines.add(createBorderLine(maxTagNameLength, columnNames));
        lines.add(new StringBuffer("overall"));
        lines.add(createDataLine("  count", maxTagNameLength, basicResultData[COUNT], columnNames));
        lines.add(createDataLine("    duplicates", maxTagNameLength, basicResultData[DUPLICATES], columnNames));
        lines.add(createDataLine("    intensity level", maxTagNameLength, basicResultData[INTENSITY_LVL], columnNames));
        lines.add(createDataLine("  true positives", maxTagNameLength, basicResultData[TRUE_POSITIVE], columnNames));
        lines.add(createDataLine("    duplicates", maxTagNameLength, basicResultData[TRUE_POSITIVE_DUPLICATES], columnNames));
        lines.add(createDataLine("    intensity level", maxTagNameLength, basicResultData[TRUE_POSITIVE_INTENSITY_LVL], columnNames));
        lines.add(createDataLine("  unassessed", maxTagNameLength, basicResultData[UNASSESSED], columnNames));
        lines.add(createBorderLine(maxTagNameLength, columnNames));
        lines.add(new StringBuffer("most prominent"));
        lines.add(createDataLine("  count", maxTagNameLength, basicResultData[MP_COUNT], columnNames));
        lines.add(createDataLine("    duplicates", maxTagNameLength, basicResultData[MP_DUPLICATES], columnNames));
        lines.add(createDataLine("    intensity level", maxTagNameLength, basicResultData[MP_INTENSITY_LVL], columnNames));
        lines.add(createDataLine("  true positives", maxTagNameLength, basicResultData[MP_TRUE_POSITIVE], columnNames));
        lines.add(createDataLine("    duplicates", maxTagNameLength, basicResultData[MP_TRUE_POSITIVE_DUPLICATES], columnNames));
        lines.add(createDataLine("    intensity level", maxTagNameLength, basicResultData[MP_TRUE_POSITIVE_INTENSITY_LVL], columnNames));
        lines.add(createDataLine("  unassessed", maxTagNameLength, basicResultData[MP_UNASSESSED], columnNames));
        lines.add(createBorderLine(maxTagNameLength, columnNames));
        
        for (Map.Entry<String, Map<String, List<UsabilitySmell>>> tagStats : tagCounters.entrySet()) {
            StringBuffer line = new StringBuffer();
            line.append(tagStats.getKey());
            
            for (int i = tagStats.getKey().length(); i < maxTagNameLength; i++) {
                line.append(' ');
            }
            
            for (String columnName : columnNames) {
                String numberStr = "";
                
                if (tagStats.getValue().get(columnName) != null) {
                    numberStr += tagStats.getValue().get(columnName).size();
                    
                    while (numberStr.length() < 5) {
                        numberStr += ' ';
                    }
                    
                    numberStr += "(" + getIntensityLevel(tagStats.getValue().get(columnName)) + ")";
                }
                
                line.append(" | ");
                line.append(numberStr);
                
                for (int i = numberStr.length(); i < columnName.length(); i++) {
                    line.append(' ');
                }
            }
            
            lines.add(line);
        }
        
        for (int i = 0; i < maxTagNameLength; i++) {
            System.out.print(' ');
        }
        
        for (String columnName : columnNames) {
            System.out.print(" | " + columnName);
        }
        
        System.out.println();
        
        for (StringBuffer line : lines) {
            System.out.println(line);
        }
    }

    /**
     *
     */
    private StringBuffer createBorderLine(int firstColumnWith, List<String> columnNames) {
        StringBuffer line = new StringBuffer();
        
        for (int i = 0; i < firstColumnWith; i++) {
            line.append('-');
        }
        
        for (String columnName : columnNames) {
            line.append("-|-");
            for (int j = 0; j < columnName.length(); j++) {
                line.append('-');
            }
        }
        
        return line;
    }

    /**
     *
     */
    private StringBuffer createDataLine(String       lineName,
                                        int          firstColumnWith,
                                        int[]        values,
                                        List<String> columnNames)
    {
        StringBuffer line = new StringBuffer();
        
        line.append(lineName);
        
        for (int i = lineName.length(); i < firstColumnWith; i++) {
            line.append(' ');
        }
        
        for (int i = 0; i < values.length; i++) {
            String numberStr = "" + values[i];

            line.append(" | ");
            line.append(numberStr);
            
            for (int j = numberStr.length(); j < columnNames.get(i).length(); j++) {
                line.append(' ');
            }
        }
        
        return line;
    }

    /**
     *
     */
    private int getDuplicates(List<UsabilitySmell> allSmells) {
        int duplicateCount = 0;
        
        for (UsabilitySmell smell1 : allSmells) {
            if (smell1.getSmellingTask() != null) {
                for (UsabilitySmell smell2 : allSmells) {
                    if ((smell2.getSmellingTask() != null) &&
                        (smell1.getSmellingTask() != smell2.getSmellingTask()) &&
                        (TaskTreeUtils.isChild(smell1.getSmellingTask(), smell2.getSmellingTask())))
                    {
                        duplicateCount++;
                        break;
                    }
                }
            }
        }
        
        return duplicateCount;
    }

    /**
     *
     */
    private int getIntensityLevel(List<UsabilitySmell> smellList) {
        if (smellList.size() <= 0) {
            return -1;
        }
        
        LinkedList<UsabilitySmell> smellsToConsider = new LinkedList<>();
        
        // determine the smells with the highest intensity
        for (UsabilitySmell smell : smellList) {
            boolean added = false;
            ListIterator<UsabilitySmell> it = smellsToConsider.listIterator();
            
            while (it.hasNext()) {
                if (it.next().getIntensity().getRatio() < smell.getIntensity().getRatio()) {
                    it.previous();
                    it.add(smell);
                    added = true;
                    break;
                }
            }
            
            if (!added) {
                smellsToConsider.add(smell);
            }
            
            while (smellsToConsider.size() > 5) {
                smellsToConsider.removeLast();
            }
        }
        
        // calculate the average intensity of the smells with the highest intensity
        int cummulativeIntensity = 0;
        for (UsabilitySmell smell : smellsToConsider) {
            cummulativeIntensity += smell.getIntensity().getRatio();
        }
        
        return cummulativeIntensity / smellsToConsider.size();
    }
   /*
     * (non-Javadoc)
     * 
     * @see de.ugoe.cs.util.console.Command#help()
     */
    @Override
    public String help() {
        return "usabilityStatistics [<usabilityEvaluationResultName>]*";
    }

}
