/*******************************************************************************
 * Copyright (c) 2009 Red Hat, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * 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.jface.action.IAction;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.viewers.DoubleClickEvent;
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.profiling.ui.ProfileUIUtils;
import org.eclipse.linuxtools.valgrind.ui.IValgrindToolView;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
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.PartInitException;
import org.eclipse.ui.part.ViewPart;

public class CachegrindViewPart extends ViewPart implements IValgrindToolView {

	protected CachegrindOutput[] outputs;
	protected TreeViewer viewer;

	protected static final int COLUMN_SIZE = 75;
	protected CachegrindLabelProvider labelProvider;
	protected CachegrindTreeContentProvider contentProvider;
	protected IDoubleClickListener doubleClickListener;
	protected ExpandAction expandAction;
	protected CollapseAction collapseAction;
	
	// Events - Cache
	protected static final String IR = "Ir"; //$NON-NLS-1$
	protected static final String I1MR = "I1mr"; //$NON-NLS-1$
	protected static final String I2MR = "I2mr"; //$NON-NLS-1$
	protected static final String DR = "Dr"; //$NON-NLS-1$
	protected static final String D1MR = "D1mr"; //$NON-NLS-1$
	protected static final String D2MR = "D2mr"; //$NON-NLS-1$
	protected static final String DW = "Dw"; //$NON-NLS-1$
	protected static final String D1MW = "D1mw"; //$NON-NLS-1$
	protected static final String D2MW = "D2mw"; //$NON-NLS-1$
	
	// Events - Branch
	protected static final String BC = "Bc"; //$NON-NLS-1$
	protected static final String BCM = "Bcm"; //$NON-NLS-1$
	protected static final String BI = "Bi"; //$NON-NLS-1$
	protected 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();

		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 = new IDoubleClickListener() {
			public void doubleClick(DoubleClickEvent 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 e) {
							e.printStackTrace();
						}						
					}
				}
				if (path != null) {
					try {
						ProfileUIUtils.openEditorAndSelect(path, line);
					} catch (PartInitException e) {
						e.printStackTrace();
					} catch (BadLocationException e) {
						e.printStackTrace();
					}
				}
			}			
		};
		viewer.addDoubleClickListener(doubleClickListener);
		
		expandAction = new ExpandAction(viewer);
		collapseAction = new CollapseAction(viewer);
		
		MenuManager manager = new MenuManager();
		manager.addMenuListener(new IMenuListener() {
			public void menuAboutToShow(IMenuManager manager) {
				ITreeSelection selection = (ITreeSelection) viewer.getSelection();
				ICachegrindElement element = (ICachegrindElement) selection.getFirstElement();
				if (contentProvider.hasChildren(element)) {
					manager.add(expandAction);
					manager.add(collapseAction);
				}
			}			
		});
		
		manager.setRemoveAllWhenShown(true);	
		Menu contextMenu = manager.createContextMenu(viewer.getTree());
		viewer.getControl().setMenu(contextMenu);
	}

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

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

	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 new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent 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(I2MR)) {
			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(D2MR)) {
			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(D2MW)) {
			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 class CachegrindTreeContentProvider implements ITreeContentProvider {

		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;
		}

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

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

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

		public void dispose() {
		}

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

	}

	

}
