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

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
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.events.ShellListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;

import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptional;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptionalInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelectionInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IStructuringTemporalRelationship;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstanceList;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;

/**
 * <p>
 * a dialog to inspect the tasks and task instances of a task model
 * </p>
 * 
 * @author Patrick Harms
 */
public class ShowTaskTreeDialog extends Dialog {

    /** the main shell */
    private Shell shell;
    
    /** the listener for the main shell to prevent disposing required */
    private ShellListener shellListener;
    
    /** the tab folder containing the instance tree as well as the model tree */
    private TabFolder tabFolder;

    /** the tree of task instances on the left */
    private Tree instanceTree;
    
    /** the tree of tasks on the left */
    private Tree modelTree;
    
    /** the tree of a specific task on the right */
    private Tree taskDetailsTree;

    /** the tree of execution variants of a specific task on the right */
    private Tree executionVariantsTree;

    /** 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 parentTasks;

    /** the displayed task model */
    private ITaskModel taskModel;
    
    /** default expansion listener */
    private Listener defaultExpansionListener = new Listener() {
        public void handleEvent(final Event event) {
            ensureChildren((TreeItem) event.item);
            ((TreeItem) event.item).setExpanded(true);
        }
    };

    /**
     * creates the dialog
     */
    public ShowTaskTreeDialog(Shell parent, int style, ITaskModel taskModel, String taskTreeName) {
        super(parent, style);
        setText("Task Model " + taskTreeName);
        this.taskModel = taskModel;
    }

    /**
     * displays the dialog
     * @param task 
     */
    public void open() {
        open(null);
    }

    /**
     * displays the dialog with a specific task opened
     * @param task 
     */
    public void open(ITask task) {
        if (shell == null) {
            createContents();
            shell.open();
            shell.layout();

            VisualizationUtils.updateColumnWidths(taskDetailsTree);
        }
        else {
            shell.setVisible(true);
        }
        
        if (task != null) {
            shellListener = new ShellAdapter() {
                @Override
                public void shellClosed(ShellEvent e) {
                    e.doit = false;
                    shell.setVisible(false);
                }
            };
            
            shell.addShellListener(shellListener);
        }

        if (task != null) {
            navigateTo(task);
        }
        
        Display display = getParent().getDisplay();
        while (!shell.isDisposed() && shell.isVisible()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        
        if (task == null) {
            dispose();
        }
    }

    /**
     * disposes the dialog if it is not used anymore
     */
    public void dispose() {
        if ((shell != null) && (!shell.isDisposed())) {
            if (shellListener != null) {
                shell.removeShellListener(shellListener);
            }
            
            shell.dispose();
        }
    }

    /**
     * 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(4, false));
        
        SashForm mainSashForm = new SashForm(shell, SWT.HORIZONTAL);
        mainSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1));
        
        createSessionsAndTasksTabFolder(mainSashForm);
        createTaskDetailsView(mainSashForm);

        mainSashForm.setWeights(new int[] { 1, 3 });

        Button btnExpandAll = new Button(shell, SWT.NONE);
        btnExpandAll.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (tabFolder.getSelectionIndex() == 0) {
                    VisualizationUtils.expandAll(instanceTree, true);
                }
                else {
                    VisualizationUtils.expandAll(modelTree, true);
                }
            }
        });
        btnExpandAll.setText("Expand all");

        Button btnCollapseAll = new Button(shell, SWT.NONE);
        btnCollapseAll.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (tabFolder.getSelectionIndex() == 0) {
                    VisualizationUtils.expandAll(instanceTree, false);
                }
                else {
                    VisualizationUtils.expandAll(modelTree, false);
                }
            }
        });
        btnCollapseAll.setText("Collapse all");
        
        new Label(shell, SWT.NONE);
        new Label(shell, SWT.NONE);
    }

    /**
     *
     */
    private void createSessionsAndTasksTabFolder(SashForm mainSashForm) {
        tabFolder = new TabFolder(mainSashForm, SWT.NONE);
        tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        tabFolder.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                VisualizationUtils.updateColumnWidths(modelTree);
            }
        });
        
        TabItem instanceTreeTab = new TabItem(tabFolder, SWT.NONE);
        instanceTreeTab.setText("Task Instances");

        instanceTree = new Tree(tabFolder, SWT.BORDER | SWT.SINGLE | SWT.VIRTUAL);
        instanceTree.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TreeItem[] selectedItems = instanceTree.getSelection();
                if ((selectedItems.length == 1) &&
                    (selectedItems[0].getData() instanceof ITaskInstance))
                {
                    displayTaskDetails(((ITaskInstance) selectedItems[0].getData()).getTask());
                }
                else {
                    clearTaskDetails();
                }
            }
        });

        VisualizationUtils.addItemSpecificContextMenu
            (instanceTree, ITaskInstance.class, "show in task list", new SelectionAdapter()
        {
            @Override
            public void widgetSelected(SelectionEvent e) {
                navigateTo(((ITaskInstance) instanceTree.getSelection()[0].getData()).getTask());
            }
        });

        buildInstanceTree();
        instanceTreeTab.setControl(instanceTree);

        TabItem modelTreeTab = new TabItem(tabFolder, SWT.NONE);
        modelTreeTab.setText("Tasks");
        
        modelTree =
            VisualizationUtils.createTaskDetailsTree(tabFolder, "tasks in model", taskModel);
       
        // show task details if requested
        modelTree.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TreeItem[] selectedItems = modelTree.getSelection();
                if ((selectedItems.length == 1) && (selectedItems[0].getData() instanceof ITask)) {
                    displayTaskDetails((ITask) selectedItems[0].getData());
                }
                else {
                    clearTaskDetails();
                }
            }
        });
        
        buildModelTree(taskModel);
        
        modelTreeTab.setControl(modelTree);
    }

    /**
     *
     */
    private void createTaskDetailsView(SashForm mainSashForm) {
        Composite detailsComposite = new Composite(mainSashForm, SWT.NO_BACKGROUND);
        detailsComposite.setLayout(new GridLayout(1, false));
        detailsComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        
        new Label(detailsComposite, SWT.NONE).setText("Task Details:");
        
        SashForm detailsSashForm = new SashForm(detailsComposite, SWT.VERTICAL);
        detailsSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        
        SashForm detailsTopSashForm = new SashForm(detailsSashForm, SWT.HORIZONTAL);
        detailsTopSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        
        taskDetailsTree =
            VisualizationUtils.createTaskDetailsTree(detailsTopSashForm, "task details", taskModel);
        
        VisualizationUtils.addExpansionListener(taskDetailsTree, defaultExpansionListener);
        
        VisualizationUtils.addItemSpecificContextMenu(taskDetailsTree, ITask.class,
                                                      "show in task list", new SelectionAdapter()
        {
            @Override
            public void widgetSelected(SelectionEvent e) {
                navigateTo((ITask) taskDetailsTree.getSelection()[0].getData());
            }
        });
        
        executionVariantsTree = new Tree(detailsTopSashForm, SWT.BORDER | SWT.SINGLE | SWT.WRAP);
        executionVariantsTree.setHeaderVisible(true);
        
        TreeColumn taskColumn = new TreeColumn(executionVariantsTree, SWT.NONE);
        taskColumn.setText("instance variants");
        taskColumn.setAlignment(SWT.LEFT);

        // load the correct children on expansion
        VisualizationUtils.addExpansionListener(executionVariantsTree, defaultExpansionListener);
        
        detailsTopSashForm.setWeights(new int[] { 3, 2 });

        SashForm detailsBottomSashForm = new SashForm(detailsSashForm, SWT.HORIZONTAL);
        detailsBottomSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        
        parentTasks = VisualizationUtils.createTaskDetailsTree
            (detailsBottomSashForm, "parent tasks", taskModel);
        
        VisualizationUtils.addItemSpecificContextMenu(parentTasks, ITask.class,
                                                      "show in task list", new SelectionAdapter()
        {
            @Override
            public void widgetSelected(SelectionEvent e) {
                navigateTo((ITask) parentTasks.getSelection()[0].getData());
            }
        });
        
        involvedTargetsTree =
            VisualizationUtils.createTargetsTree(detailsBottomSashForm, "involved GUI elements");
        
        detailsBottomSashForm.setWeights(new int[] { 2, 3 });
        detailsSashForm.setWeights(new int[] { 1, 1 });
    }

    /**
     * convenience method for creating the display of the instances
     */
    private void buildInstanceTree() {
        List<IUserSession> sessions = taskModel.getUserSessions();

        // load the correct children on expansion
        VisualizationUtils.addExpansionListener(instanceTree, defaultExpansionListener);

        TreeItem root = new TreeItem(instanceTree, SWT.NULL);
        root.setText(sessions.size() + " sessions");
        root.setData(taskModel);
        
        for (IUserSession session : sessions) {
            buildInstanceTree(root, session);
        }
        
        root.setExpanded(true);
    }

    /**
     * convenience method for creating the display of the instances
     */
    private void buildInstanceTree(TreeItem currentParent, IUserSession session) {
        TreeItem item = new TreeItem(currentParent, SWT.NULL);
        item.setText(session.toString());
        item.setData(session);
        
        // simulate a child
        new TreeItem(item, SWT.NULL);
    }

    /**
     * convenience method for creating the display of the task model
     */
    private void buildModelTree(ITaskModel taskModel) {
        Collection<ITask> allTasks = taskModel.getTasks();
        
        List<ITask> sequences = new LinkedList<ITask>();
        List<ITask> iterations = new LinkedList<ITask>();
        List<ITask> selections = new LinkedList<ITask>();
        List<ITask> optionals = new LinkedList<ITask>();
        List<ITask> others = new LinkedList<ITask>();
        
        TreeItem root = new TreeItem(modelTree, SWT.NULL);
        root.setText(allTasks.size() + " tasks in model");
        root.setData(taskModel);

        createSortedTaskLists(allTasks, sequences, iterations, selections, optionals, others);
        
        //allTasks = createSortedTaskList(allTasks);
        
        createModelTreeItemFor(sequences, sequences.size() + " Sequences", root);
        createModelTreeItemFor(iterations, iterations.size() + " Iterations", root);
        createModelTreeItemFor(selections, selections.size() + " Selections", root);
        createModelTreeItemFor(optionals, optionals.size() + " Optionals", root);
        createModelTreeItemFor(others, others.size() + " other Tasks", root);
        
        root.setExpanded(true);
    }

    /**
     *
     */
    private void createSortedTaskLists(Collection<ITask> allTasks,
                                       List<ITask>       sequences,
                                       List<ITask>       iterations,
                                       List<ITask>       selections,
                                       List<ITask>       optionals,
                                       List<ITask>       others)
    {
        List<ITask> toAdd;
        
        for (ITask task : allTasks) {
            if (task instanceof ISequence) {
                toAdd = sequences;
            }
            else if (task instanceof IIteration) {
                toAdd = iterations;
            }
            else if (task instanceof ISelection) {
                toAdd = selections;
            }
            else if (task instanceof IOptional) {
                toAdd = optionals;
            }
            else {
                toAdd = others;
            }
            
            int taskCount = task.getInstances().size();
            int start = 0;
            int end = toAdd.size();
            int center = 0;
            int centerCount;
            
            while (start != end) {
                center = start + ((end - start) / 2);
                
                if ((center != start) || (center != end)) {
                    centerCount = toAdd.get(center).getInstances().size();
                
                    if (centerCount > taskCount) {
                        start = Math.max(center, start + 1);
                    }
                    else if (centerCount < taskCount) {
                        end = Math.min(center, end - 1);
                    }
                    else {
                        // add the event directly where the center is, as the count of the center
                        // and the new task are equal
                        end = center;
                        start = end;
                        break;
                    }
                }
                else {
                    // add the event to the position denoted by the add index
                    break;
                }
            }
            
            toAdd.add(start, task);
        }
    }

    /**
     *
     */
    private void clearTaskDetails() {
        taskDetailsTree.removeAll();
        executionVariantsTree.removeAll();
        involvedTargetsTree.removeAll();
        parentTasks.removeAll();
    }

    /**
     *
     */
    private void displayTaskDetails(ITask task) {
        clearTaskDetails();
        
        VisualizationUtils.createTreeItemFor(task, taskDetailsTree, taskModel, true);
        VisualizationUtils.expandAll(taskDetailsTree, true);
        
        // do it twice. Otherwise, it doesn't work
        VisualizationUtils.updateColumnWidths(taskDetailsTree);
        
        Collection<Collection<ITaskInstance>> executionVariants = task.getExecutionVariants();
        List<Collection<ITaskInstance>> sortedExecutionVariants =
            new LinkedList<Collection<ITaskInstance>>(executionVariants);
        
        Collections.sort(sortedExecutionVariants, new Comparator<Collection<ITaskInstance>>() {
            @Override
            public int compare(Collection<ITaskInstance> list1, Collection<ITaskInstance> list2) {
                return list2.size() - list1.size();
            }
        });
        
        int counter = 1;
        for (Collection<ITaskInstance> variant : sortedExecutionVariants) {
            TreeItem item = new TreeItem(executionVariantsTree, SWT.NULL);
            if (variant.size() > 1) {
                item.setText("variant " + counter++ + " (executed " + variant.size() + " times)");
            }
            else {
                item.setText("variant " + counter++ + " (executed once)");
            }
            item.setData(variant);
            
            createTreeItemFor(variant.iterator().next(), item);
        }

        VisualizationUtils.expandAll(executionVariantsTree, true);
        VisualizationUtils.updateColumnWidths(executionVariantsTree);
        
        addParentTasks(task);
        VisualizationUtils.updateColumnWidths(parentTasks);
        
        VisualizationUtils.addInvolvedTargets(involvedTargetsTree, task);
    }

    /**
     *
     */
    private void addParentTasks(ITask task) {
        for (ITask candidate : taskModel.getTasks()) {
            if (((candidate instanceof IStructuringTemporalRelationship) &&
                 (((IStructuringTemporalRelationship) candidate).getChildren().contains(task))) ||
                ((candidate instanceof IMarkingTemporalRelationship) &&
                 (((IMarkingTemporalRelationship) candidate).getMarkedTask().equals(task))))
            {
                VisualizationUtils.createTreeItemFor(candidate, parentTasks, taskModel, false);
            }
        }
    }

    /**
     * 
     */
    private void navigateTo(ITask task) {
        tabFolder.setSelection(1);
        
        OUTER:
        for (TreeItem sublist : modelTree.getItem(0).getItems()) {
            for (TreeItem taskItem : sublist.getItems()) {
                if (task.equals(taskItem.getData())) {
                    modelTree.setSelection(taskItem);
                    sublist.setExpanded(true);
                    VisualizationUtils.updateColumnWidths(modelTree);
                    break OUTER;
                }
            }
        }
        
        displayTaskDetails(task);
    }
    
    /**
     * ensures, that the children of a specific node are loaded
     */
    private void ensureChildren(TreeItem parent) {
        TreeItem[] items = parent.getItems();
        if ((items == null) || (items.length == 0) || (items[0].getData() == null)) {
            if (items != null) {
                for (int i = 0; i < items.length; i++) {
                    items[i].dispose();
                }
            }

            if (parent.getData() instanceof ITask) {
                ITask task = (ITask) parent.getData();

                if (task instanceof IStructuringTemporalRelationship) {
                    for (ITask subTask : ((IStructuringTemporalRelationship) task).getChildren()) {
                        VisualizationUtils.createTreeItemFor(subTask, parent, taskModel, true);
                    }
                }
                else if (task instanceof IMarkingTemporalRelationship) {
                    VisualizationUtils.createTreeItemFor
                        (((IMarkingTemporalRelationship) task).getMarkedTask(), parent,
                         taskModel, true);
                }
            }
            else if (parent.getData() instanceof List<?>) {
                @SuppressWarnings("unchecked")
                List<ITask> tasks = (List<ITask>) parent.getData();
                for (ITask task : tasks) {
                    VisualizationUtils.createTreeItemFor(task, parent, taskModel, false);
                }
            }
            else if (parent.getData() instanceof ITaskInstanceList) {
                for (ITaskInstance subInstance : (ITaskInstanceList) parent.getData()) {
                    createTreeItemFor(subInstance, parent);
                }
            }
            else if (parent.getData() instanceof ITaskInstance) {
                ITaskInstance instance = (ITaskInstance) parent.getData();

                if (instance instanceof ISelectionInstance) {
                    if (((ISelectionInstance) instance).getChild() != null) {
                        createTreeItemFor(((ISelectionInstance) instance).getChild(), parent);
                    }
                }
                else if (instance instanceof IOptionalInstance) {
                    if (((IOptionalInstance) instance).getChild() != null) {
                        createTreeItemFor(((IOptionalInstance) instance).getChild(), parent);
                    }
                }
            }
        }
    }

    /**
     *
     */
    private void createModelTreeItemFor(List<ITask> taskList, String name, TreeItem parent) {
        TreeItem item = new TreeItem(parent, SWT.NULL);
        item.setText(name);
        item.setData(taskList);
        
        // simulate a child
        if (taskList.size() > 0) {
            for (ITask task : taskList) {
                VisualizationUtils.createTreeItemFor(task, item, taskModel, false);
            }
        }
    }

    /**
     * convenience method to create a tree item for a task
     */
    private void createTreeItemFor(ITaskInstance taskInstance,
                                   TreeItem      parent)
    {
        TreeItem item = new TreeItem(parent, SWT.NULL);
        item.setText(taskInstance.toString());
        item.setData(taskInstance);
        
        // simulate a child
        if ((taskInstance instanceof ITaskInstanceList) ||
            (taskInstance instanceof ISelectionInstance) ||
            (taskInstance instanceof IOptionalInstance))
        {
            new TreeItem(item, SWT.NULL);
        }
    }
}
