/*******************************************************************************
 * Copyright (c) 2009, 2018 Red Hat, Inc.
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Elliott Baron <ebaron@redhat.com> - initial API and implementation
 *******************************************************************************/
package org.eclipse.linuxtools.internal.valgrind.cachegrind;

import java.util.Arrays;

import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.ISourceRange;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.CachegrindFile;
import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.CachegrindFunction;
import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.CachegrindLine;
import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.CachegrindOutput;
import org.eclipse.linuxtools.internal.valgrind.cachegrind.model.ICachegrindElement;
import org.eclipse.linuxtools.internal.valgrind.ui.ValgrindUIPlugin;
import org.eclipse.linuxtools.profiling.ui.ProfileUIUtils;
import org.eclipse.linuxtools.valgrind.ui.CollapseAction;
import org.eclipse.linuxtools.valgrind.ui.ExpandAction;
import org.eclipse.linuxtools.valgrind.ui.IValgrindToolView;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.part.ViewPart;

public class CachegrindViewPart extends ViewPart implements IValgrindToolView {

    private CachegrindOutput[] outputs;
    private TreeViewer viewer;

    private static final int COLUMN_SIZE = 75;
    private CachegrindLabelProvider labelProvider;
    private CachegrindTreeContentProvider contentProvider;
    private IDoubleClickListener doubleClickListener;
    private ExpandAction expandAction;
    private CollapseAction collapseAction;

    // Events - Cache
    private static final String IR = "Ir"; //$NON-NLS-1$
    private static final String I1MR = "I1mr"; //$NON-NLS-1$
    private static final String ILMR = "ILmr"; //$NON-NLS-1$
    private static final String DR = "Dr"; //$NON-NLS-1$
    private static final String D1MR = "D1mr"; //$NON-NLS-1$
    private static final String DLMR = "DLmr"; //$NON-NLS-1$
    private static final String DW = "Dw"; //$NON-NLS-1$
    private static final String D1MW = "D1mw"; //$NON-NLS-1$
    private static final String DLMW = "DLmw"; //$NON-NLS-1$

    // Events - Branch
    private static final String BC = "Bc"; //$NON-NLS-1$
    private static final String BCM = "Bcm"; //$NON-NLS-1$
    private static final String BI = "Bi"; //$NON-NLS-1$
    private static final String BIM = "Bim"; //$NON-NLS-1$

    @Override
    public void createPartControl(Composite parent) {
        Composite top = new Composite(parent, SWT.NONE);
        GridLayout topLayout = new GridLayout();
        topLayout.marginHeight = topLayout.marginWidth = 0;
        top.setLayout(topLayout);
        top.setLayoutData(new GridData(GridData.FILL_BOTH));

        viewer = new TreeViewer(top, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL
                | SWT.FULL_SELECTION);

        labelProvider = new CachegrindLabelProvider();
        ColumnViewerToolTipSupport.enableFor(viewer);

        Tree tree = viewer.getTree();
        tree.setHeaderVisible(true);
        tree.setLinesVisible(true);
        tree.setLayoutData(new GridData(GridData.FILL_BOTH));

        TreeViewerColumn column = new TreeViewerColumn(viewer, SWT.NONE);
        column.getColumn().setText(Messages.getString("CachegrindViewPart.Location")); //$NON-NLS-1$
        column.getColumn().setWidth(COLUMN_SIZE * 4);
        column.getColumn().setResizable(true);
        column.getColumn().addSelectionListener(getHeaderListener());
        column.setLabelProvider(labelProvider);

        contentProvider = new CachegrindTreeContentProvider();
        viewer.setContentProvider(contentProvider);
        viewer.setLabelProvider(labelProvider);
        viewer.setAutoExpandLevel(2);
        doubleClickListener = event -> {
		    Object selection = ((StructuredSelection) event.getSelection()).getFirstElement();
		    String path = null;
		    int line = 0;
		    if (selection instanceof CachegrindFile) {
		        path = ((CachegrindFile) selection).getPath();
		    } else if (selection instanceof CachegrindLine) {
		        CachegrindLine element = (CachegrindLine) selection;
		        CachegrindFile file = (CachegrindFile) element.getParent().getParent();
		        path = file.getPath();
		        line = element.getLine();
		    } else if (selection instanceof CachegrindFunction) {
		        CachegrindFunction function = (CachegrindFunction) selection;
		        path = ((CachegrindFile) function.getParent()).getPath();
		        if (function.getModel() instanceof ISourceReference) {
		            ISourceReference model = (ISourceReference) function.getModel();
		            try {
		                ISourceRange sr = model.getSourceRange();
		                if (sr != null) {
		                    line = sr.getStartLine();
		                }
		            } catch (CModelException e1) {
		                e1.printStackTrace();
		            }
		        }
		    }
		    if (path != null) {
		        try {
		        	ProfileUIUtils.openEditorAndSelect(path, line, ValgrindUIPlugin.getDefault().getProfiledProject());
		        } catch (BadLocationException | CoreException e2) {
		            e2.printStackTrace();
		        }
		    }
		};
        viewer.addDoubleClickListener(doubleClickListener);

        expandAction = new ExpandAction(viewer);
        collapseAction = new CollapseAction(viewer);

        MenuManager manager = new MenuManager();
        manager.addMenuListener(manager1 -> {
		    ITreeSelection selection = viewer.getStructuredSelection();
		    ICachegrindElement element = (ICachegrindElement) selection.getFirstElement();
		    if (contentProvider.hasChildren(element)) {
		        manager1.add(expandAction);
		        manager1.add(collapseAction);
		    }
		});

        manager.setRemoveAllWhenShown(true);
        Menu contextMenu = manager.createContextMenu(viewer.getTree());
        viewer.getControl().setMenu(contextMenu);
    }

    @Override
    public void setFocus() {
        viewer.getTree().setFocus();
    }

    @Override
    public IAction[] getToolbarActions() {
        return null;
    }

    @Override
    public void refreshView() {
        if (outputs != null && outputs.length > 0) {
            String[] events = outputs[0].getEvents();
            for (int i = 0; i < events.length; i++) {
                TreeViewerColumn column = new TreeViewerColumn(viewer, SWT.NONE);
                column.getColumn().setText(events[i]);
                column.getColumn().setWidth(COLUMN_SIZE);
                column.getColumn().setToolTipText(getFullEventName(events[i]));
                column.getColumn().setResizable(true);
                column.getColumn().addSelectionListener(getHeaderListener());
                column.setLabelProvider(labelProvider);
            }
            viewer.setInput(outputs);
            viewer.getTree().layout(true);
        }
    }

    public void setOutputs(CachegrindOutput[] outputs) {
        this.outputs = outputs;
    }

    public CachegrindOutput[] getOutputs() {
        return outputs;
    }

    public TreeViewer getViewer() {
        return viewer;
    }

    public IDoubleClickListener getDoubleClickListener() {
        return doubleClickListener;
    }

	private SelectionListener getHeaderListener() {
		return SelectionListener.widgetSelectedAdapter(e -> {
			TreeColumn column = (TreeColumn) e.widget;
			Tree tree = viewer.getTree();
			if (column.equals(tree.getSortColumn())) {
				int direction = tree.getSortDirection() == SWT.UP ? SWT.DOWN : SWT.UP;
				tree.setSortDirection(direction);
			} else {
				tree.setSortDirection(SWT.UP);
			}
			tree.setSortColumn(column);
			viewer.setComparator(new ViewerComparator() {
				@Override
				public int compare(Viewer viewer, Object e1, Object e2) {
					Tree tree = ((TreeViewer) viewer).getTree();
					int direction = tree.getSortDirection();
					ICachegrindElement o1 = (ICachegrindElement) e1;
					ICachegrindElement o2 = (ICachegrindElement) e2;
					long result = 0;

					int sortIndex = Arrays.asList(tree.getColumns()).indexOf(tree.getSortColumn());
					if (sortIndex == 0) { // use compareTo
						result = o1.compareTo(o2);
					} else {
						long[] v1 = null;
						long[] v2 = null;
						if (o1 instanceof CachegrindFunction && o2 instanceof CachegrindFunction) {
							v1 = ((CachegrindFunction) o1).getTotals();
							v2 = ((CachegrindFunction) o2).getTotals();
						} else if (o1 instanceof CachegrindLine && o2 instanceof CachegrindLine) {
							v1 = ((CachegrindLine) o1).getValues();
							v2 = ((CachegrindLine) o2).getValues();
						} else if (o1 instanceof CachegrindOutput && o2 instanceof CachegrindOutput) {
							v1 = ((CachegrindOutput) o1).getSummary();
							v2 = ((CachegrindOutput) o2).getSummary();
						}

						if (v1 != null && v2 != null) {
							result = v1[sortIndex - 1] - v2[sortIndex - 1];
						}
					}

					// ascending or descending
					result = direction == SWT.UP ? result : -result;

					// overflow check
					if (result > Integer.MAX_VALUE) {
						result = Integer.MAX_VALUE;
					} else if (result < Integer.MIN_VALUE) {
						result = Integer.MIN_VALUE;
					}

					return (int) result;
				}
			});
		});
	}

    private String getFullEventName(String event) {
        String result = event;
        if (event.equals(IR)) {
            result = Messages.getString("CachegrindViewPart.Ir_long"); //$NON-NLS-1$
        } else if (event.equals(I1MR)) {
            result = Messages.getString("CachegrindViewPart.I1mr_long"); //$NON-NLS-1$
        } else if (event.equals(ILMR)) {
            result = Messages.getString("CachegrindViewPart.I2mr_long"); //$NON-NLS-1$
        } else if (event.equals(DR)) {
            result = Messages.getString("CachegrindViewPart.Dr_long"); //$NON-NLS-1$
        } else if (event.equals(D1MR)) {
            result = Messages.getString("CachegrindViewPart.D1mr_long"); //$NON-NLS-1$
        } else if (event.equals(DLMR)) {
            result = Messages.getString("CachegrindViewPart.D2mr_long"); //$NON-NLS-1$
        } else if (event.equals(DW)) {
            result = Messages.getString("CachegrindViewPart.Dw_long"); //$NON-NLS-1$
        } else if (event.equals(D1MW)) {
            result = Messages.getString("CachegrindViewPart.D1mw_long"); //$NON-NLS-1$
        } else if (event.equals(DLMW)) {
            result = Messages.getString("CachegrindViewPart.D2mw_long"); //$NON-NLS-1$
        } else if (event.equals(BC)) {
            result = Messages.getString("CachegrindViewPart.Bc_long"); //$NON-NLS-1$
        } else if (event.equals(BCM)) {
            result = Messages.getString("CachegrindViewPart.Bcm_long"); //$NON-NLS-1$
        } else if (event.equals(BI)) {
            result = Messages.getString("CachegrindViewPart.Bi_long"); //$NON-NLS-1$
        } else if (event.equals(BIM)) {
            result = Messages.getString("CachegrindViewPart.Bim_long"); //$NON-NLS-1$
        }
        return result;
    }

    protected static class CachegrindTreeContentProvider implements ITreeContentProvider {

        @Override
        public Object[] getChildren(Object parentElement) {
            Object[] result = null;
            if (parentElement instanceof CachegrindOutput[]) {
                result = (CachegrindOutput[]) parentElement;
            }
            else if (parentElement instanceof ICachegrindElement) {
                result = ((ICachegrindElement) parentElement).getChildren();
            }
            return result;
        }

        @Override
        public Object getParent(Object element) {
            return ((ICachegrindElement) element).getParent();
        }

        @Override
        public boolean hasChildren(Object element) {
            ICachegrindElement[] children = (ICachegrindElement[]) getChildren(element);
            return children != null && children.length > 0;
        }

        @Override
        public Object[] getElements(Object inputElement) {
            return getChildren(inputElement);
        }

        @Override
        public void dispose() {
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }

    }
}
