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

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

import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.operation.IRunnableWithProgress;
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.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.mylyn.builds.core.IBuild;
import org.eclipse.mylyn.builds.core.IChange;
import org.eclipse.mylyn.builds.core.IChangeArtifact;
import org.eclipse.mylyn.builds.core.IChangeSet;
import org.eclipse.mylyn.builds.internal.core.Change;
import org.eclipse.mylyn.builds.internal.core.ChangeArtifact;
import org.eclipse.mylyn.internal.builds.ui.BuildsUiPlugin;
import org.eclipse.mylyn.internal.team.ui.actions.TaskFinder;
import org.eclipse.mylyn.versions.core.ScmArtifact;
import org.eclipse.mylyn.versions.core.ScmCore;
import org.eclipse.mylyn.versions.core.spi.ScmConnector;
import org.eclipse.mylyn.versions.ui.ScmUi;
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.Menu;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.statushandlers.StatusManager;

/**
 * @author Steffen Pingel
 */
public class ChangesPart extends AbstractBuildEditorPart {

	static class ChangesContentProvider implements ITreeContentProvider {

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

		private IChangeSet input;

		public void dispose() {
			input = null;
		}

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

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

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

		public Object getParent(Object element) {
			if (element instanceof EObject) {
				return ((EObject) element).eContainer();
			}
			return null;
		}

		public boolean hasChildren(Object element) {
			if (element instanceof IChangeSet) {
				return !((IChangeSet) element).getChanges().isEmpty();
			}
			if (element instanceof IChange) {
				return !((IChange) element).getArtifacts().isEmpty();
			}
			return false;
		}

	}

	private TreeViewer viewer;

	private MenuManager menuManager;

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

	public ChangesPart() {
		super(ExpandableComposite.TITLE_BAR | ExpandableComposite.EXPANDED);
		setPartName("Changes");
		setExpandVertically(true);
		this.span = 2;
	}

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

		IChangeSet changeSet = getInput(IBuild.class).getChangeSet();

//		if (changeSet == null || changeSet.getChanges().isEmpty()) {
//			createLabel(composite, toolkit, "No changes.");
//		}

		viewer = new TreeViewer(toolkit.createTree(composite, SWT.H_SCROLL));
		GridDataFactory.fillDefaults().hint(500, 100).grab(true, true).applyTo(viewer.getControl());
		viewer.setContentProvider(new ChangesContentProvider());
		viewer.setLabelProvider(new DecoratingStyledCellLabelProvider(new ChangesLabelProvider(), 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 selection = ((IStructuredSelection) event.getSelection()).getFirstElement();
				if (selection instanceof Change) {
					ChangesPart.this.open((Change) selection);
				}
				if (selection instanceof ChangeArtifact) {
					try {
						ChangesPart.this.open((ChangeArtifact) selection);
					} catch (CoreException e) {
						e.printStackTrace();
					}
				}
			}

		});

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

		if (changeSet == null || changeSet.getChanges().isEmpty()) {
			viewer.setInput("No changes.");
		} else {
			viewer.setInput(changeSet);
		}

		toolkit.paintBordersFor(composite);
		return composite;
	}

	private void open(IChangeArtifact changeArtifact) throws CoreException {
		final IResource resource = ScmCore.findResource(changeArtifact.getFile());
		if (resource == null) {
			getMessageManager().addMessage(ChangesPart.class.getName(),
					"The selected file is not available in the workspace", null, IMessageProvider.WARNING);
			return;
		}
		final ScmConnector connector = ScmCore.getConnector(resource);
		if (connector == null) {
			getMessageManager().addMessage(ChangesPart.class.getName(),
					"No extension available to open the selected file", null, IMessageProvider.WARNING);
			return;
		}

		final String prevRevision = changeArtifact.getPrevRevision();
		final String revision = (changeArtifact.getRevision() != null)
				? changeArtifact.getRevision()
				: ((IChange) ((ChangeArtifact) changeArtifact).eContainer()).getRevision();
		if (revision == null) {
			getMessageManager().addMessage(ChangesPart.class.getName(),
					"Could not determine change revisions for the selected file", null, IMessageProvider.WARNING);
		}

		try {
			final AtomicReference<IFileRevision> left = new AtomicReference<IFileRevision>();
			final AtomicReference<IFileRevision> right = new AtomicReference<IFileRevision>();
			PlatformUI.getWorkbench().getProgressService().busyCursorWhile(new IRunnableWithProgress() {
				public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
					try {
						ScmArtifact rightArtifact = connector.getArtifact(resource, revision);
						right.set(rightArtifact.getFileRevision(monitor));

						if (prevRevision != null) {
							ScmArtifact leftArtifact = connector.getArtifact(resource, prevRevision);
							left.set(leftArtifact.getFileRevision(monitor));
						}
						if (left.get() == null) {
							try {
								IFileRevision[] contributors = rightArtifact.getContributors(monitor);
								if (contributors != null && contributors.length > 0) {
									left.set(contributors[0]);
								}
							} catch (UnsupportedOperationException e) {
								// ignore
							}
						}
					} catch (CoreException e) {
						throw new InvocationTargetException(e);
					}
				}
			});
			if (right.get() != null) {
				getMessageManager().removeMessage(ChangesPart.class.getName());

				ScmUi.openCompareEditor(getPage().getSite().getPage(), left.get(), right.get());
			} else {
				getMessageManager().addMessage(ChangesPart.class.getName(),
						"Could not determine change revisions for the selected file", null, IMessageProvider.WARNING);
			}
		} catch (InvocationTargetException e) {
			StatusManager.getManager().handle(
					new Status(IStatus.ERROR, BuildsUiPlugin.ID_PLUGIN, "Unexpected error", e),
					StatusManager.SHOW | StatusManager.LOG);
		} catch (InterruptedException e) {
			// ignore
		}

	}

	private void open(IChange selection) {
		TaskReference reference = new TaskReference();
		reference.setText(selection.getMessage());
		TaskFinder finder = new TaskFinder(reference);
		finder.open();
	}

}
