//   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.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.UsabilityDefect;
import de.ugoe.cs.autoquest.usability.UsabilityDefectSeverity;
import de.ugoe.cs.autoquest.usability.UsabilityEvaluationResult;

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

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

    /** the table containing all defects */
    private Tree defectList;

    /** the description label of a selected defect */
    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 task model */
    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 defects");

        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));
        
        defectList = new Tree(mainSashForm, SWT.BORDER | SWT.SINGLE | SWT.VIRTUAL);
        defectList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        defectList.setHeaderVisible(true);
        defectList.setLinesVisible(true);

        TreeColumn treeColumn = new TreeColumn(defectList, SWT.NONE);
        treeColumn.setWidth(200);
        treeColumn.setText("defects");

        buildDefectTree();
        
        defectList.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TreeItem[] selectedItems = defectList.getSelection();
                if ((selectedItems.length == 1) &&
                    (selectedItems[0].getData() instanceof UsabilityDefect))
                {
                    displayDefectDetails((UsabilityDefect) selectedItems[0].getData());
                }
                else {
                    clearDefectDetails();
                }
            }
        });

        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();
        //defectList.pack();
    }

    /**
     * convenience method for creating the display of the instances
     */
    private void buildDefectTree() {
        List<UsabilityDefect> defects = usabilityEvalResult.getAllDefects();

        final Map<UsabilityDefectSeverity, Map<String, List<UsabilityDefect>>> sortedDefects =
            new HashMap<UsabilityDefectSeverity, Map<String, List<UsabilityDefect>>>();
        sortedDefects.put(UsabilityDefectSeverity.HIGH,
                          new HashMap<String, List<UsabilityDefect>>());
        sortedDefects.put(UsabilityDefectSeverity.MEDIUM,
                          new HashMap<String, List<UsabilityDefect>>());
        sortedDefects.put(UsabilityDefectSeverity.LOW,
                          new HashMap<String, List<UsabilityDefect>>());
        sortedDefects.put(UsabilityDefectSeverity.INFO,
                          new HashMap<String, List<UsabilityDefect>>());
        
        for (UsabilityDefect defect : defects) {
            Map<String, List<UsabilityDefect>> defectMap = sortedDefects.get(defect.getSeverity());
            
            List<UsabilityDefect> defectList = defectMap.get(defect.getBriefDescription());
            
            if (defectList == null) {
                defectList = new ArrayList<UsabilityDefect>();
                defectMap.put(defect.getBriefDescription(), defectList);
            }
            
            defectList.add(defect);
        }
        
        if (defectList.getListeners(SWT.Expand).length == 0) {
            defectList.addListener(SWT.Expand, new Listener() {
               public void handleEvent(final Event event) {
                   ensureChildren((TreeItem) event.item);
                   ((TreeItem) event.item).setExpanded(true);
               }
           });
        }

        createRootItem("high", sortedDefects.get(UsabilityDefectSeverity.HIGH));
        createRootItem("medium", sortedDefects.get(UsabilityDefectSeverity.MEDIUM));
        createRootItem("low", sortedDefects.get(UsabilityDefectSeverity.LOW));
        createRootItem("info", sortedDefects.get(UsabilityDefectSeverity.INFO));
    }

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

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

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

    /**
     *
     */
    private void displayDefectDetails(UsabilityDefect defect) {
        clearDefectDetails();
        
        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 : defect.getDescriptionFragments()) {
            int color;
            Font font;
            
            if (fragment instanceof String) {
                color = SWT.COLOR_BLACK;
                font = defaultFont;
            }
            else {
                color = SWT.COLOR_DARK_GREEN;
                font = boldFont;
            }
            
            StyleRange styleRange = new StyleRange
                (description.getText().length(), fragment.toString().length(),
                 description.getDisplay().getSystemColor(color), null);

            styleRange.font = font;
            
            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());
            }
            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(defect);
        for (ITask involvedTask : involvedTaskList) {
            VisualizationUtils.createTreeItemFor
                (involvedTask, involvedTasks, usabilityEvalResult.getTaskModel(), true);
        }
        
        List<IEventTarget> involvedTargets = getInvolvedTargets(defect);
        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<UsabilityDefect>> map =
                (Map<String, List<UsabilityDefect>>) parent.getData();
            
            for (Map.Entry<String, List<UsabilityDefect>> entry : map.entrySet()) {
                TreeItem child = new TreeItem(parent, SWT.NULL);        
                child.setText(entry.getKey() + " (" + entry.getValue().size() + " defects)");
                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<UsabilityDefect>> entry =
                (Map.Entry<String, List<UsabilityDefect>>) parent.getData();
            
            int count = 0;
            for (UsabilityDefect defect : entry.getValue()) {
                TreeItem child = new TreeItem(parent, SWT.NULL);        
                child.setData(defect);
                child.setText("defect " + ++count);
            }
        }
        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(UsabilityDefect defect) {
        List<Object> fragments = defect.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(UsabilityDefect defect) {
        List<Object> fragments = defect.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;
    }
}
