//   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.ui.swt;

import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;

import de.ugoe.cs.autoquest.eventcore.IEventTarget;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IStructuringTemporalRelationship;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.usability.UsabilitySmell;
import de.ugoe.cs.autoquest.usability.UsabilitySmellIntensity;
import de.ugoe.cs.autoquest.usability.UsabilityEvaluationResult;

/**
 * <p>
 * a dialog to inspect the results of a usability evaluation
 * TODO update comments
 * </p>
 * 
 * @author Patrick Harms
 */
public class ShowUsabilityEvaluationResultDialog extends Dialog {

    /** the main shell */
    protected Shell shell;

    /** the table containing all smells */
    private Tree smellList;

    /** the description label of a selected smell */
    private StyledText description;
    
    /** the tree of involved GUI elements of a specific task on the right bottom */
    private Tree involvedTargetsTree;

    /** the table containing the parents tasks of a displayed task */
    private Tree involvedTasks;

    /** the displayed usability evaluation result */
    private UsabilityEvaluationResult usabilityEvalResult;
    
    /** task tree dialog to show task details */
    private ShowTaskTreeDialog showTaskTreeDialog;

    /**
     * creates the dialog
     */
    public ShowUsabilityEvaluationResultDialog(Shell                     parent,
                                               int                       style,
                                               UsabilityEvaluationResult usabilityEvalResult,
                                               String                    dataSetName)
    {
        super(parent, style);
        setText("Usability Evaluation Result " + dataSetName);
        this.usabilityEvalResult = usabilityEvalResult;
    }

    /**
     * displays the dialog
     */
    public void open() {
        showTaskTreeDialog = new ShowTaskTreeDialog
            (super.getParent(), SWT.NONE, usabilityEvalResult.getTaskModel(),
             "task details of usability smells");

        createContents();
        shell.open();
        shell.layout();
        
        shell.addShellListener(new ShellAdapter() {
            @Override
            public void shellClosed(ShellEvent e) {
                showTaskTreeDialog.dispose();
            }
        });
        
        Display display = getParent().getDisplay();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    /**
     * creates the two views, one on the task instances on the left, on on the task models on the
     * right. Also adds a selection adapter to the task instances so that for a selected task
     * instance always the respective model is presented.
     */
    private void createContents() {
        shell = new Shell(getParent(), SWT.SHELL_TRIM | SWT.BORDER);
        GraphicsDevice gd =
            GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();

        shell.setSize(gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight());
        shell.setText(getText());

        shell.setLayout(new GridLayout(1, false));

        SashForm mainSashForm = new SashForm(shell, SWT.HORIZONTAL);
        mainSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        
        smellList = new Tree(mainSashForm, SWT.BORDER | SWT.SINGLE | SWT.VIRTUAL);
        smellList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        smellList.setHeaderVisible(true);
        smellList.setLinesVisible(true);

        TreeColumn treeColumn = new TreeColumn(smellList, SWT.NONE);
        treeColumn.setWidth(200);
        treeColumn.setText("smells");

        buildSmellTree();
        
        smellList.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TreeItem[] selectedItems = smellList.getSelection();
                if ((selectedItems.length == 1) &&
                    (selectedItems[0].getData() instanceof UsabilitySmell))
                {
                    displaySmellDetails((UsabilitySmell) selectedItems[0].getData());
                }
                else {
                    clearSmellDetails();
                }
            }
        });

        SashForm detailsSashForm = new SashForm(mainSashForm, SWT.VERTICAL);
        detailsSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        
        description = new StyledText(detailsSashForm, SWT.READ_ONLY | SWT.BORDER | SWT.WRAP);
        description.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));

        SashForm detailsBottomSashForm = new SashForm(detailsSashForm, SWT.HORIZONTAL);
        detailsBottomSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        
        involvedTasks = VisualizationUtils.createTaskDetailsTree
            (detailsBottomSashForm, "involved tasks", usabilityEvalResult.getTaskModel());
        
        VisualizationUtils.addItemSpecificContextMenu
            (involvedTasks, ITask.class, "show details", new SelectionAdapter()
        {
            @Override
            public void widgetSelected(SelectionEvent e) {
                showTaskTreeDialog.open((ITask) involvedTasks.getSelection()[0].getData());
            }
        });
        
        VisualizationUtils.addExpansionListener(involvedTasks, new Listener() {
            public void handleEvent(final Event event) {
                ensureChildren((TreeItem) event.item);
                ((TreeItem) event.item).setExpanded(true);
            }
        });
        
        involvedTargetsTree =
            VisualizationUtils.createTargetsTree(detailsBottomSashForm, "involved GUI elements");
        
        VisualizationUtils.addInvolvedTargetsHighlighting(involvedTasks, involvedTargetsTree);
        
        detailsBottomSashForm.setWeights(new int[] { 1, 1 });
        detailsSashForm.setWeights(new int[] { 1, 3 });
        mainSashForm.setWeights(new int[] { 1, 3 });

        //indexColumn.pack();
        //severityColumn.pack();
        //descriptionColumn.pack();
        //smellList.pack();
    }

    /**
     * convenience method for creating the display of the instances
     */
    private void buildSmellTree() {
        List<UsabilitySmell> smells = usabilityEvalResult.getAllSmells();
        
        int[] eventCoverageQuantileGroups = { 990, 975, 950, 0, -1 };
        int[] minEventCoverages = new int[eventCoverageQuantileGroups.length];
        int[] maxEventCoverages = new int[eventCoverageQuantileGroups.length];

        final List<Map<String, List<UsabilitySmell>>> sortedSmells =
            new LinkedList<Map<String, List<UsabilitySmell>>>();
        
        for (int i = 0; i < eventCoverageQuantileGroups.length; i++) {
            sortedSmells.add(new HashMap<String, List<UsabilitySmell>>());
        }
        
        for (UsabilitySmell smell : smells) {
            int eventCoverageQuantile = smell.getIntensity().getEventCoverageQuantile();
            
            for (int i = 0; i < eventCoverageQuantileGroups.length; i++) {
                if (eventCoverageQuantile >= eventCoverageQuantileGroups[i]) {
                    Map<String, List<UsabilitySmell>> smellMap = sortedSmells.get(i);
                    
                    List<UsabilitySmell> smellList = smellMap.get(smell.getBriefDescription());
                    
                    if (smellList == null) {
                        smellList = new ArrayList<UsabilitySmell>();
                        smellMap.put(smell.getBriefDescription(), smellList);
                    }
                    
                    int ratio = smell.getIntensity().getRatio();
                    int eventCoverage = smell.getIntensity().getEventCoverage();
                    int index = 0;
                    for (index = 0; index < smellList.size(); index++) {
                        UsabilitySmellIntensity candidate = smellList.get(index).getIntensity();
                        if ((ratio == candidate.getRatio()) &&
                            (eventCoverage > candidate.getEventCoverage()))
                        {
                            break;
                        }
                        else if (ratio > candidate.getRatio()) {
                            break;
                        }
                    }
                    
                    smellList.add(index, smell);
                    
                    if (minEventCoverages[i] == 0) {
                        minEventCoverages[i] = smell.getIntensity().getEventCoverage();
                        maxEventCoverages[i] = smell.getIntensity().getEventCoverage();
                    }
                    else {
                        minEventCoverages[i] = Math.min
                            (minEventCoverages[i], smell.getIntensity().getEventCoverage());
                        maxEventCoverages[i] = Math.max
                            (maxEventCoverages[i], smell.getIntensity().getEventCoverage());
                    }
                    
                    break;
                }
            }
        }
        
        if (smellList.getListeners(SWT.Expand).length == 0) {
            smellList.addListener(SWT.Expand, new Listener() {
               public void handleEvent(final Event event) {
                   ensureChildren((TreeItem) event.item);
                   ((TreeItem) event.item).setExpanded(true);
               }
           });
        }

        double taskPercentages = 0;
        double taskPercentagesCoveredByPreceedingGroups = 0;
        
        for (int i = 0; i < eventCoverageQuantileGroups.length; i++) {
            taskPercentages = ((1000 - eventCoverageQuantileGroups[i]) / 10.0) -
                taskPercentagesCoveredByPreceedingGroups;
            
            if (eventCoverageQuantileGroups[i] > -1) {
                createRootItem("smells for " + taskPercentages + "% of tasks covering " +
                               minEventCoverages[i] + " to " + maxEventCoverages[i] +
                               " recorded events", sortedSmells.get(i));
            }
            else {
                createRootItem("other smells not related to specific tasks", sortedSmells.get(i));

            }
            
            taskPercentagesCoveredByPreceedingGroups += taskPercentages;
        }
        
    }

    /**
     * 
     */
    private void createRootItem(String name, Map<String, List<UsabilitySmell>> smells) {
        TreeItem smellItem = new TreeItem(smellList, SWT.NULL);
        
        int count = 0;
        for (Map.Entry<String, List<UsabilitySmell>> entry : smells.entrySet()) {
            count += entry.getValue().size();
        }

        smellItem.setText(name + " (" + count + " smells)");
        smellItem.setData(smells);
        
        if (count > 0) {
            // simulate a child
            new TreeItem(smellItem, SWT.NULL);
        }
    }

    /**
     *
     */
    private void clearSmellDetails() {
        description.setText("");
        involvedTargetsTree.removeAll();
        involvedTasks.removeAll();
    }

    /**
     *
     */
    private void displaySmellDetails(UsabilitySmell smell) {
        clearSmellDetails();
        
        FontData data = description.getFont().getFontData()[0];
        int height = (int) (data.getHeight() * 1.5);
        Font defaultFont = new Font
            (description.getDisplay(), data.getName(), height, data.getStyle());
        
        Font boldFont = new Font
            (description.getDisplay(), data.getName(), height, data.getStyle() | SWT.BOLD);
        
        for (Object fragment : smell.getDescriptionFragments()) {
            int color;
            Font font;
            
            if (fragment instanceof String) {
                color = SWT.COLOR_BLACK;
                font = defaultFont;
            }
            else {
                color = SWT.COLOR_DARK_GREEN;
                font = boldFont;
            }
            
            int initialLength = description.getText().length();
            
            if (fragment instanceof Collection<?>) {
                int counter = 1;
                for (Object elem : ((Collection<?>) fragment)) {
                    description.append("\n");
                    description.append(Integer.toString(counter++));
                    description.append(".: ");
                    description.append(elem.toString());
                }
            }
            else {
                description.append(fragment.toString());
            }
            
            StyleRange styleRange = new StyleRange
                (initialLength, description.getText().length() - initialLength,
                 description.getDisplay().getSystemColor(color), null);

            styleRange.font = font;
                
            description.setStyleRange(styleRange);
            description.append(" ");
        }
        description.setLeftMargin(50);
        description.setRightMargin(50);
        description.setTopMargin(50);
        description.setBottomMargin(50);

        
        StyleRange styleRange = new StyleRange();
        styleRange.font = new Font(description.getDisplay(), "Courier", 12, SWT.NORMAL);
        description.setStyleRange(styleRange);
        
        List<ITask> involvedTaskList = getInvolvedTasks(smell);
        for (ITask involvedTask : involvedTaskList) {
            VisualizationUtils.createTreeItemFor
                (involvedTask, involvedTasks, usabilityEvalResult.getTaskModel(), true);
        }
        
        List<IEventTarget> involvedTargets = getInvolvedTargets(smell);
        if (involvedTargets.size() <= 0) {
            for (ITask involvedTask : involvedTaskList) {
                VisualizationUtils.getInvolvedTargets(involvedTask, involvedTargets);
            }
        }
        
        VisualizationUtils.addInvolvedTargets(involvedTargetsTree, involvedTargets);
        
        int weightLeft = involvedTaskList.size() == 0 ? 1 :
            Math.min(4, Math.max(3, involvedTaskList.size()));
        int weightRight = involvedTargets.size() == 0 ? 1 :
            Math.min(3, Math.max(1, involvedTargets.size()));
        ((SashForm) involvedTasks.getParent()).setWeights(new int[] { weightLeft, weightRight });
        
        VisualizationUtils.expandAll(involvedTasks, true);
        VisualizationUtils.updateColumnWidths(involvedTasks);
    }
    
    /**
     *
     */
    private void ensureChildren(TreeItem parent) {
        if ((parent.getItemCount() == 0) || (parent.getItems()[0].getData() != null)) {
            return;
        }
        
        for (int i = 0; i < parent.getItems().length; i++) {
            parent.getItems()[i].dispose();
        }
        
        if (parent.getData() instanceof Map<?, ?>) {
            @SuppressWarnings("unchecked")
            Map<String, List<UsabilitySmell>> map =
                (Map<String, List<UsabilitySmell>>) parent.getData();
            
            for (Map.Entry<String, List<UsabilitySmell>> entry : map.entrySet()) {
                TreeItem child = new TreeItem(parent, SWT.NULL);        
                child.setText(entry.getKey() + " (" + entry.getValue().size() + " smells)");
                child.setData(entry);
                
                if (entry.getValue().size() > 0) {
                    // simulate child
                    new TreeItem(child, SWT.NULL);
                }
            }
        }
        else if (parent.getData() instanceof Map.Entry<?, ?>) {
            @SuppressWarnings("unchecked")
            Map.Entry<String, List<UsabilitySmell>> entry =
                (Map.Entry<String, List<UsabilitySmell>>) parent.getData();
            
            int count = 0;
            for (UsabilitySmell smell : entry.getValue()) {
                TreeItem child = new TreeItem(parent, SWT.NULL);        
                child.setData(smell);
                child.setText(++count + ": ratio = " + smell.getIntensity().getRatio() +
                              ", covered events = " + smell.getIntensity().getEventCoverage());
            }
        }
        else if (parent.getData() instanceof ITask) {
            ITask task = (ITask) parent.getData();

            if (task instanceof IStructuringTemporalRelationship) {
                for (ITask subTask : ((IStructuringTemporalRelationship) task).getChildren()) {
                    VisualizationUtils.createTreeItemFor
                        (subTask, parent, usabilityEvalResult.getTaskModel(), true);
                }
            }
            else if (task instanceof IMarkingTemporalRelationship) {
                VisualizationUtils.createTreeItemFor
                    (((IMarkingTemporalRelationship) task).getMarkedTask(), parent,
                     usabilityEvalResult.getTaskModel(), true);
            }
        }
    }
    
    /**
     *
     */
    private List<ITask> getInvolvedTasks(UsabilitySmell smell) {
        List<Object> fragments = smell.getDescriptionFragments();
        List<ITask> involvedTasks = new ArrayList<ITask>();
        
        for (Object fragment : fragments) {
            if (fragment instanceof ITask) {
                involvedTasks.add((ITask) fragment);
            }
        }
        
        return involvedTasks;
    }

    /**
     *
     */
    private List<IEventTarget> getInvolvedTargets(UsabilitySmell smell) {
        List<Object> fragments = smell.getDescriptionFragments();
        List<IEventTarget> involvedTargets = new ArrayList<IEventTarget>();
        
        for (Object fragment : fragments) {
            if (fragment instanceof IEventTarget) {
                involvedTargets.add((IEventTarget) fragment);
            }
            else if (fragment instanceof Collection<?>) {
                for (Object elem : (Collection<?>) fragment) {
                    if (elem instanceof IEventTarget) {
                        involvedTargets.add((IEventTarget) elem);
                    }
                }
            }
        }
        
        return involvedTargets;
    }
}
