/*******************************************************************************
 * Copyright (c) 2010 Tasktop Technologies and others.
 * 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:
 *     Tasktop Technologies - initial API and implementation
 *     GitHub, Inc. - fixes for bug 350334
 *******************************************************************************/

package org.eclipse.mylyn.internal.builds.ui.editor;

import org.eclipse.emf.databinding.FeaturePath;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.viewers.DecoratingStyledCellLabelProvider;
import org.eclipse.jface.viewers.IOpenListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.OpenEvent;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.mylyn.builds.core.IBuild;
import org.eclipse.mylyn.builds.core.ITestCase;
import org.eclipse.mylyn.builds.core.ITestResult;
import org.eclipse.mylyn.builds.core.ITestSuite;
import org.eclipse.mylyn.builds.core.TestCaseResult;
import org.eclipse.mylyn.builds.internal.core.BuildPackage.Literals;
import org.eclipse.mylyn.builds.internal.core.TestResult;
import org.eclipse.mylyn.commons.workbench.WorkbenchUtil;
import org.eclipse.mylyn.internal.builds.ui.BuildImages;
import org.eclipse.mylyn.internal.builds.ui.BuildsUiInternal;
import org.eclipse.mylyn.internal.builds.ui.BuildsUiPlugin;
import org.eclipse.mylyn.internal.builds.ui.actions.ShowTestResultsAction;
import org.eclipse.mylyn.internal.builds.ui.util.TestResultManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;

/**
 * @author Steffen Pingel
 * @author Kevin Sawicki
 */
public class TestResultPart extends AbstractBuildEditorPart {

	public class FilterTestFailuresAction extends Action {

		private final TestFailureFilter filter;

		public FilterTestFailuresAction() {
			super("Show Failures Only", IAction.AS_CHECK_BOX);
			filter = new TestFailureFilter();
			setToolTipText("Show Failures Only");
			setImageDescriptor(BuildImages.FILTER_FAILURES);
			setChecked(BuildsUiPlugin.getDefault()
					.getPreferenceStore()
					.getBoolean(BuildsUiInternal.PREF_SHOW_TEST_FAILURES_ONLY));
		}

		public void initialize() {
			if (isChecked()) {
				addFilter();
			}
		}

		private void addFilter() {
			viewer.addFilter(filter);
			viewer.expandAll();
		}

		private void removeFilter() {
			viewer.removeFilter(filter);
		}

		@Override
		public void run() {
			boolean checked = isChecked();
			if (checked) {
				addFilter();
			} else {
				removeFilter();
			}
			BuildsUiPlugin.getDefault()
					.getPreferenceStore()
					.setValue(BuildsUiInternal.PREF_SHOW_TEST_FAILURES_ONLY, checked);
		}
	}

	private static final String ID_POPUP_MENU = "org.eclipse.mylyn.builds.ui.editor.menu.TestResult"; //$NON-NLS-1$

	static class TestResultContentProvider implements ITreeContentProvider {

		private static final Object[] NO_ELEMENTS = new Object[0];

		private TestResult input;

		public void dispose() {
			input = null;
		}

		public Object[] getChildren(Object parentElement) {
			if (parentElement instanceof ITestSuite) {
				return ((ITestSuite) parentElement).getCases().toArray();
			}
			return NO_ELEMENTS;
		}

		public Object[] getElements(Object inputElement) {
			if (inputElement == input) {
				return input.getSuites().toArray();
			}
			if (inputElement instanceof String) {
				return new Object[] { inputElement };
			}
			return NO_ELEMENTS;
		}

		public Object getParent(Object element) {
			return null;
		}

		public boolean hasChildren(Object element) {
			if (element instanceof ITestSuite) {
				return !((ITestSuite) element).getCases().isEmpty();
			}
			return false;
		}

		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			if (newInput instanceof TestResult) {
				input = (TestResult) newInput;
			} else {
				input = null;
			}
		}

	}

	/**
	 * Selects failed tests only.
	 */
	private class TestFailureFilter extends ViewerFilter {

		@Override
		public boolean select(Viewer viewer, Object parentElement, Object element) {
			if (element instanceof ITestCase) {
				return isFailure(element);
			} else if (element instanceof ITestSuite) {
				for (ITestCase testCase : ((ITestSuite) element).getCases()) {
					if (isFailure(testCase)) {
						return true;
					}
				}
			}
			return false;
		}

		boolean isFailure(Object element) {
			TestCaseResult status = ((ITestCase) element).getStatus();
			return status == TestCaseResult.FAILED || status == TestCaseResult.REGRESSION;
		}

	}

	private MenuManager menuManager;

	private ShowTestResultsAction showTestResultsAction;

	private FilterTestFailuresAction filterTestFailuresAction;

	private TreeViewer viewer;

	public TestResultPart() {
		super(ExpandableComposite.TITLE_BAR | ExpandableComposite.EXPANDED);
		setPartName("Test Results");
	}

	@Override
	protected Control createContent(Composite parent, FormToolkit toolkit) {
		Composite composite = toolkit.createComposite(parent);
		composite.setLayout(new GridLayout(6, false));

		ITestResult testResult = getInput(IBuild.class).getTestResult();
		if (testResult != null) {
			Label label;
			Text text;

			label = createLabel(composite, toolkit, "Passed:");
			GridDataFactory.defaultsFor(label).indent(0, 0).applyTo(label);
			text = createTextReadOnly(composite, toolkit, "");
			bind(text, IBuild.class,
					FeaturePath.fromList(Literals.BUILD__TEST_RESULT, Literals.TEST_RESULT__PASS_COUNT));

			label = createLabel(composite, toolkit, "Failed:");
			GridDataFactory.defaultsFor(label).indent(0, 0).applyTo(label);
			text = createTextReadOnly(composite, toolkit, "");
			bind(text, IBuild.class,
					FeaturePath.fromList(Literals.BUILD__TEST_RESULT, Literals.TEST_RESULT__FAIL_COUNT));

			label = createLabel(composite, toolkit, "Ignored:");
			GridDataFactory.defaultsFor(label).indent(0, 0).applyTo(label);
			text = createTextReadOnly(composite, toolkit, "");
			bind(text, IBuild.class,
					FeaturePath.fromList(Literals.BUILD__TEST_RESULT, Literals.TEST_RESULT__IGNORED_COUNT));
		}

		viewer = new TreeViewer(toolkit.createTree(composite, SWT.MULTI));
		GridDataFactory.fillDefaults().hint(300, 100).span(6, 1).grab(true, true).applyTo(viewer.getControl());
		viewer.setContentProvider(new TestResultContentProvider());
		viewer.setLabelProvider(new DecoratingStyledCellLabelProvider(new TestResultLabelProvider(), null, null));
		viewer.addSelectionChangedListener(new ISelectionChangedListener() {
			public void selectionChanged(SelectionChangedEvent event) {
				getPage().getSite().getSelectionProvider().setSelection(event.getSelection());
			}
		});
		viewer.addOpenListener(new IOpenListener() {
			public void open(OpenEvent event) {
				Object item = ((IStructuredSelection) event.getSelection()).getFirstElement();
				if (item instanceof ITestSuite) {
					TestResultManager.openInEditor((ITestSuite) item);
				} else if (item instanceof ITestCase) {
					TestResultManager.openInEditor((ITestCase) item);
				}
			}
		});

		menuManager = new MenuManager();
		WorkbenchUtil.addDefaultGroups(menuManager);
		getPage().getEditorSite().registerContextMenu(ID_POPUP_MENU, menuManager, viewer, true);
		Menu menu = menuManager.createContextMenu(viewer.getControl());
		viewer.getControl().setMenu(menu);

		refresh(testResult);
		filterTestFailuresAction.initialize();

		toolkit.paintBordersFor(composite);
		return composite;
	}

	void refresh(ITestResult testResult) {
		if (testResult != null) {
			viewer.setInput(testResult);
			boolean hasFailures = testResult.getFailCount() > 0;
			filterTestFailuresAction.setEnabled(hasFailures);
			if (!hasFailures) {
				filterTestFailuresAction.setChecked(false);
			}
			showTestResultsAction.setEnabled(true);
		} else {
			viewer.setInput("No test results generated.");
			filterTestFailuresAction.setEnabled(false);
			filterTestFailuresAction.setChecked(false);
			showTestResultsAction.setEnabled(false);
		}
	}

	@Override
	public void initialize(BuildEditorPage page) {
		super.initialize(page);

		showTestResultsAction = new ShowTestResultsAction();
		showTestResultsAction.selectionChanged(new StructuredSelection(getInput(IBuild.class)));

		filterTestFailuresAction = new FilterTestFailuresAction();
		showTestResultsAction.selectionChanged(new StructuredSelection(getInput(IBuild.class)));
	}

	@Override
	protected void fillToolBar(ToolBarManager toolBarManager) {
		super.fillToolBar(toolBarManager);

		toolBarManager.add(filterTestFailuresAction);
		toolBarManager.add(showTestResultsAction);
	}

}
