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

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MenuAdapter;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

import de.ugoe.cs.autoquest.eventcore.IEventTarget;
import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTaskInstance;
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.tasktrees.treeifc.ITaskInfo;
import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
import de.ugoe.cs.autoquest.tasktrees.treeifc.TaskMetric;

/**
 * <p>
 * TODO comment
 * </p>
 * 
 * @author Patrick Harms
 */
class VisualizationUtils {
    
    /**
     *
     */
    static Tree createTaskDetailsTree(Composite  parent,
                                      String     firstColumnHeader,
                                      ITaskModel taskModel)
    {
        final Tree tree = new Tree(parent, SWT.BORDER | SWT.SINGLE | SWT.VIRTUAL);
        tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        tree.setHeaderVisible(true);
        
        TreeColumn taskColumn = new TreeColumn(tree, SWT.NONE);
        taskColumn.setText(firstColumnHeader);
        taskColumn.setAlignment(SWT.LEFT);
       
        TaskMetric[] metrics = taskModel.getAllMetrics();
        
        for (TaskMetric metric : metrics) {
            TreeColumn metricColumn = new TreeColumn(tree, SWT.WRAP);
            metricColumn.setText(metric.getName().replace(' ', '\n'));
            metricColumn.setToolTipText(metric.getDescription());
            metricColumn.setAlignment(SWT.CENTER);
        }

        // update the columns widths on expansion and collapse
        if (tree.getListeners(SWT.Expand).length == 0) {
            tree.addListener(SWT.Expand, new Listener() {
               public void handleEvent(final Event event) {
                   ((TreeItem) event.item).setExpanded(true);
                   updateColumnWidths(tree);
               }
           });
        }

        if (tree.getListeners(SWT.Collapse).length == 0) {
            tree.addListener(SWT.Collapse, new Listener() {
               public void handleEvent(final Event event) {
                   ((TreeItem) event.item).setExpanded(false);
                   updateColumnWidths(tree);
               }
           });
        }

        return tree;
    }

    /**
     * convenience method to create a tree item for a task
     */
    static void createTreeItemFor(ITask      task,
                                  Widget     parent,
                                  ITaskModel taskModel,
                                  boolean    createChildren)
    {
        ITaskInfo taskInfo = taskModel.getTaskInfo(task);
        
        TaskMetric[] metrics = taskModel.getAllMetrics();
        String[] values = new String[metrics.length + 2];
        
        values[0] = task.toString();
        
        for (int i = 0; i < metrics.length; i++) {
            int contextValue = Integer.MIN_VALUE;
            
            if (parent.getData() instanceof ITask) {
                contextValue = taskInfo.getMeasureValue(metrics[i]);
            }
            
            if (contextValue > Integer.MIN_VALUE) {
                values[i + 1] = metrics[i].formatValue(contextValue) + "(" +
                    metrics[i].formatValue(taskInfo.getMeasureValue(metrics[i])) + ")";
            }
            else {
                values[i + 1] = metrics[i].formatValue(taskInfo.getMeasureValue(metrics[i]));
            }
        }
        
        values[values.length - 1] = Integer.toString(task.getId());
        
        TreeItem item;
        if (parent instanceof TreeItem) {
            item = new TreeItem((TreeItem) parent, SWT.NULL);
        }
        else {
            item = new TreeItem((Tree) parent, SWT.NULL);
        }
        item.setText(values);
        item.setData(task);
        
        if (createChildren) {
            // simulate a child
            if ((task instanceof IStructuringTemporalRelationship) ||
                (task instanceof IMarkingTemporalRelationship))
            {
                new TreeItem(item, SWT.NULL);
            }
        }
    }

    /**
     *
     */
    static Tree createTargetsTree(Composite  parent, String name) {
        Tree tree = new Tree(parent, SWT.BORDER | SWT.MULTI);
        tree.setHeaderVisible(true);
        
        TreeColumn guiElementColumn = new TreeColumn(tree, SWT.NONE);
        guiElementColumn.setText(name);
        guiElementColumn.setAlignment(SWT.LEFT);
        
        return tree;
    }

    /**
     *
     */
    static void updateColumnWidths(Tree tree) {
        TreeColumn firstColumn = tree.getColumn(0);
        firstColumn.pack();
        
        if (tree.getColumnCount() > 1) {
            int availableWidth = tree.getBounds().width - firstColumn.getWidth();
            int effectiveWidth = 0;
            for (int i = 1; i < tree.getColumnCount(); i++) {
                TreeColumn column = tree.getColumn(i);
                column.pack();
                effectiveWidth += column.getWidth();
            }
            
            if (effectiveWidth < availableWidth) {
                firstColumn.setWidth(firstColumn.getWidth() + availableWidth - effectiveWidth - 20);
            }
        }
    }

    /**
     *
     */
    static void addItemSpecificContextMenu(final Tree             tree,
                                           final Class<?>         selectedItemDataType,
                                           final String           menuItemText,
                                           final SelectionAdapter menuItemSelectionAdapter)
    {
        Menu menu = tree.getMenu();
        
        if (menu == null) {
            menu = new Menu(tree);
            tree.setMenu(menu);
        }
        
        final Menu finalMenu = menu;
        
        menu.addMenuListener(new MenuAdapter() {
            public void menuShown(MenuEvent e) {
                MenuItem[] items = finalMenu.getItems();
                for (int i = 0; i < items.length; i++) {
                    if (menuItemText.equals(items[i].getText())) {
                        items[i].dispose();
                    }
                }
                
                if (selectedItemDataType.isInstance(tree.getSelection()[0].getData())) {
                    MenuItem newItem = new MenuItem(finalMenu, SWT.NONE);
                    newItem.setText(menuItemText);
                    newItem.addSelectionListener(menuItemSelectionAdapter);
                }
            }
        });
    }
    
    /**
     *
     */
    static void addInvolvedTargets(Tree guiElementsTree, ITask task) {
        List<IEventTarget> targets = new ArrayList<IEventTarget>();
        getInvolvedTargets(task, targets);
        addInvolvedTargets(guiElementsTree, targets);
    }
    
    /**
     *
     */
    static void getInvolvedTargets(ITask task, List<IEventTarget> involvedTargets) {
        if (task instanceof IStructuringTemporalRelationship) {
            for (ITask child : ((IStructuringTemporalRelationship) task).getChildren()) {
                getInvolvedTargets(child, involvedTargets);
            }
        }
        else if (task instanceof IMarkingTemporalRelationship) {
            getInvolvedTargets
                (((IMarkingTemporalRelationship) task).getMarkedTask(), involvedTargets);
        }
        else {
            involvedTargets.add
                (((IEventTaskInstance) task.getInstances().iterator().next()).getEvent().getTarget());
        }
    }

    /**
     *
     */
    static void addInvolvedTargets(Tree guiElementsTree, List<IEventTarget> targets) {
        for (IEventTarget target : targets) {
            List<IEventTarget> pathToIntegrate = new LinkedList<IEventTarget>();

            while (target != null) {
                pathToIntegrate.add(0, target);

                if (target instanceof IGUIElement) {
                    target = ((IGUIElement) target).getParent();
                }
                else {
                    target = null;
                }
            }

            TreeItem parent = null;

            while (pathToIntegrate.size() > 0) {
                TreeItem[] children;

                if (parent == null) {
                    children = guiElementsTree.getItems();
                }
                else {
                    children = parent.getItems();
                }

                TreeItem childItem = null;

                if (children != null) {
                    for (TreeItem child : children) {
                        if (pathToIntegrate.get(0).equals(child.getData())) {
                            childItem = child;
                        }
                    }
                }

                if (childItem == null) {
                    if (parent == null) {
                        childItem = new TreeItem(guiElementsTree, SWT.NONE);
                    }
                    else {
                        childItem = new TreeItem(parent, SWT.NONE);
                    }

                    childItem.setText(pathToIntegrate.get(0).toString());
                    childItem.setData(pathToIntegrate.get(0));
                }

                pathToIntegrate.remove(0);
                parent = childItem;
            }
        }
        
        expandAll(guiElementsTree, true);
        updateColumnWidths(guiElementsTree);
    }

    /**
     *
     */
    static void addInvolvedTargetsHighlighting(final Tree tasks, final Tree involvedTargetsTree) {
        tasks.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TreeItem[] selectedItems = tasks.getSelection();
                if ((selectedItems.length == 1) &&
                    (selectedItems[0].getData() instanceof ITask))
                {
                    highlightInvolvedTargets
                        ((ITask) selectedItems[0].getData(), involvedTargetsTree);
                }
            }
        });

    }

    /**
     *
     */
    static void highlightInvolvedTargets(ITask task, Tree involvedTargetsTree) {
        List<IEventTarget> targets = new ArrayList<IEventTarget>();
        getInvolvedTargets(task, targets);
        
        List<TreeItem> itemsToSelect = new LinkedList<TreeItem>();
        List<TreeItem> candidateItems = new LinkedList<TreeItem>();
        
        for (TreeItem item : involvedTargetsTree.getItems()) {
            candidateItems.add(item);
        }
        
        while (candidateItems.size() > 0) {
            TreeItem candidate = candidateItems.remove(0);
            
            if (targets.contains(candidate.getData())) {
                itemsToSelect.add(candidate);
            }
            
            for (TreeItem child : candidate.getItems()) {
                candidateItems.add(child);
            }
        }
        
        involvedTargetsTree.setSelection
            (itemsToSelect.toArray(new TreeItem[itemsToSelect.size()]));
    }

    /**
     *
     */
    static void addExpansionListener(Tree tree, Listener listener) {
        Listener[] listeners = tree.getListeners(SWT.Expand);
        
        for (Listener existingListener : listeners) {
            if ((existingListener == listener) || (existingListener.equals(listener))) {
                return;
            }
        }
        
        tree.addListener(SWT.Expand, listener);
    }
    
    /**
     * expands all nodes in a tree
     */
    static void expandAll(Tree tree, boolean expanded) {
        for (TreeItem item : tree.getItems()) {
            expandAll(item, expanded, Integer.MAX_VALUE);
        }
    }

    /**
     * expands all nodes in a tree up to a specific number of children
     */
    static void expandAll(TreeItem item, boolean expanded, int maxChildrenToExpand) {
        if (item.getItems().length < maxChildrenToExpand) {
            Listener[] listeners = item.getParent().getListeners(SWT.Expand);
            if (listeners != null) {
                for (Listener listener : listeners) {
                    Event event = new Event();
                    event.type = SWT.Expand;
                    event.item = item;
                    listener.handleEvent(event);
                }
            }
            
            item.setExpanded(expanded);

            for (TreeItem childItem : item.getItems()) {
                expandAll(childItem, expanded, maxChildrenToExpand);
            }
        }
    }

    /**
     * 
     */
    private VisualizationUtils() {
        // prevent instantiation
    }
}
