/*******************************************************************************
 * Copyright (C) 2010, 2013 Jens Baumgart <jens.baumgart@sap.com> 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
 *******************************************************************************/
package org.eclipse.egit.ui.internal.merge;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.egit.ui.Activator;
import org.eclipse.egit.ui.UIUtils;
import org.eclipse.egit.ui.internal.UIText;
import org.eclipse.egit.ui.internal.commit.CommitEditor;
import org.eclipse.egit.ui.internal.commit.RepositoryCommit;
import org.eclipse.egit.ui.internal.dialogs.CheckoutConflictDialog;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.util.OpenStrategy;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
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.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.OpenAndLinkWithEditorHelper;
import org.eclipse.ui.model.WorkbenchLabelProvider;

/**
 * Dialog for displaying a MergeResult
 *
 */
public class MergeResultDialog extends Dialog {

	private static final String SPACE = " "; //$NON-NLS-1$

	private final MergeResult mergeResult;

	private final Repository repository;

	private ObjectReader objectReader;

	/**
	 * @param parentShell
	 * @param repository
	 * @param mergeResult
	 * @return the created dialog
	 */
	public static Dialog getDialog(Shell parentShell, Repository repository, MergeResult mergeResult) {
		if(mergeResult.getMergeStatus() == MergeStatus.CHECKOUT_CONFLICT)
			return new CheckoutConflictDialog(parentShell, repository, mergeResult.getCheckoutConflicts());
		else
			return new MergeResultDialog(parentShell, repository, mergeResult);
	}

	/**
	 * @param parentShell
	 * @param repository
	 * @param mergeResult
	 */
	public MergeResultDialog(Shell parentShell, Repository repository,
			MergeResult mergeResult) {
		super(parentShell);
		setShellStyle(getShellStyle() | SWT.RESIZE);
		this.repository = repository;
		this.mergeResult = mergeResult;
		objectReader = repository.newObjectReader();
	}

	@Override
	protected void createButtonsForButtonBar(final Composite parent) {
		createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL,
				true);
	}

	@Override
	public Control createDialogArea(final Composite parent) {
		final Composite composite = (Composite) super.createDialogArea(parent);
		GridLayout gridLayout = new GridLayout();
		gridLayout.numColumns = 2;
		composite.setLayout(gridLayout);
		// result
		Label resultLabel = new Label(composite, SWT.NONE);
		resultLabel.setText(UIText.MergeResultDialog_result);
		resultLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false,
				false));
		Text resultText = new Text(composite, SWT.READ_ONLY);
		MergeStatus status = mergeResult.getMergeStatus();
		resultText.setText(status.toString());
		resultText.setSelection(resultText.getCaretPosition());
		resultText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		if (status == MergeStatus.FAILED) {
			resultText.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_RED));

			StringBuilder paths = new StringBuilder();
			Label pathsLabel = new Label(composite, SWT.NONE);
			pathsLabel.setText(UIText.MergeResultDialog_failed);
			pathsLabel.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false));
			Text pathsText = new Text(composite, SWT.READ_ONLY);
			pathsText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
			Set<Entry<String, MergeFailureReason>> failedPaths = mergeResult.getFailingPaths().entrySet();
			int n = 0;
			for (Map.Entry<String, MergeFailureReason> e : failedPaths) {
				if (n > 0)
					paths.append(Text.DELIMITER);
				paths.append(e.getValue());
				paths.append("\t"); //$NON-NLS-1$
				paths.append(e.getKey());
				n++;
				if (n > 10 && failedPaths.size() > 15)
					break;
			}
			if (n < failedPaths.size()) {
				paths.append(Text.DELIMITER);
				paths.append(MessageFormat.format(UIText.MergeResultDialog_nMore, Integer.valueOf(n - failedPaths.size())));
			}
			pathsText.setText(paths.toString());
		}

		if (status == MergeStatus.FAST_FORWARD
				|| status == MergeStatus.FAST_FORWARD_SQUASHED
				|| status == MergeStatus.MERGED) {
			// new head
			Label newHeadLabel = new Label(composite, SWT.NONE);
			newHeadLabel.setText(UIText.MergeResultDialog_newHead);
			newHeadLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false,
					false));
			Text newHeadText = new Text(composite, SWT.READ_ONLY);
			ObjectId newHead = mergeResult.getNewHead();
			if (newHead != null)
				newHeadText.setText(getCommitMessage(newHead) + SPACE
						+ abbreviate(mergeResult.getNewHead(), true));
			newHeadText
					.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		}

		// Merge Input
		Label mergeInputLabel = new Label(composite, SWT.NONE);
		mergeInputLabel.setText(UIText.MergeResultDialog_mergeInput);
		GridDataFactory.fillDefaults().align(SWT.LEAD, SWT.CENTER).span(2, 1)
				.applyTo(mergeInputLabel);
		TableViewer viewer = new TableViewer(composite);
		viewer.setContentProvider(new IStructuredContentProvider() {

			public void dispose() {
				// empty
			}

			public void inputChanged(Viewer theViewer, Object oldInput,
					Object newInput) {
				// empty
			}

			public Object[] getElements(Object inputElement) {
				return getCommits(mergeResult.getMergedCommits());
			}
		});
		final IStyledLabelProvider styleProvider = new IStyledLabelProvider() {

			private final WorkbenchLabelProvider wrapped = new WorkbenchLabelProvider();

			public void removeListener(ILabelProviderListener listener) {
				// Empty
			}

			public boolean isLabelProperty(Object element, String property) {
				return false;
			}

			public void dispose() {
				wrapped.dispose();
			}

			public void addListener(ILabelProviderListener listener) {
				// Empty
			}

			public StyledString getStyledText(Object element) {
				// TODO Replace with use of IWorkbenchAdapter3 when is no longer
				// supported
				if (element instanceof RepositoryCommit)
					return ((RepositoryCommit) element).getStyledText(element);

				return new StyledString(wrapped.getText(element));
			}

			public Image getImage(Object element) {
				return wrapped.getImage(element);
			}
		};
		viewer.setLabelProvider(new DelegatingStyledCellLabelProvider(
				styleProvider));
		applyDialogFont(composite);
		GridDataFactory.fillDefaults().grab(true, true)
				.align(SWT.FILL, SWT.FILL).span(2, 1)
				.applyTo(viewer.getControl());
		viewer.setInput(mergeResult);

		new OpenAndLinkWithEditorHelper(viewer) {
			@Override
			protected void linkToEditor(ISelection selection) {
				// Not supported

			}
			@Override
			protected void open(ISelection selection, boolean activate) {
				handleOpen(selection, OpenStrategy.activateOnOpen());
			}
			@Override
			protected void activate(ISelection selection) {
				handleOpen(selection, true);
			}
			private void handleOpen(ISelection selection, boolean activateOnOpen) {
				if (selection instanceof IStructuredSelection)
					for (Object element : ((IStructuredSelection) selection)
							.toArray())
						if (element instanceof RepositoryCommit)
							CommitEditor.openQuiet((RepositoryCommit) element, activateOnOpen);
			}
		};

		return composite;
	}

	/**
	 * @param mergeStatus
	 * @return text describing merge status in short form
	 */
	public static String getStatusText(MergeStatus mergeStatus) {
		switch (mergeStatus) {
		case FAST_FORWARD:
			return UIText.MergeResultDialog_StatusFastForward;
		case FAST_FORWARD_SQUASHED:
			return UIText.MergeResultDialog_StatusFastForwardSquashed;
		case ALREADY_UP_TO_DATE:
			return UIText.MergeResultDialog_StatusAlreadyUpToDate;
		case FAILED:
			return UIText.MergeResultDialog_StatusFailed;
		case MERGED:
			return UIText.MergeResultDialog_StatusMerged;
		case MERGED_SQUASHED:
			return UIText.MergeResultDialog_StatusMergedSquashed;
		case MERGED_SQUASHED_NOT_COMMITTED:
			return UIText.MergeResultDialog_StatusMergedSquashedNotCommitted;
		case CONFLICTING:
			return UIText.MergeResultDialog_StatusConflicting;
		case ABORTED:
			return UIText.MergeResultDialog_StatusAborted;
		case MERGED_NOT_COMMITTED:
			return UIText.MergeResultDialog_StatusMergedNotCommitted;
		case NOT_SUPPORTED:
			return UIText.MergeResultDialog_StatusNotSupported;
		case CHECKOUT_CONFLICT:
			return UIText.MergeResultDialog_StatusCheckoutConflict;
		}
		return mergeStatus.toString();
	}

	private RepositoryCommit[] getCommits(final ObjectId[] merges) {
		final List<RepositoryCommit> commits = new ArrayList<RepositoryCommit>();
		final RevWalk walk = new RevWalk(objectReader);
		walk.setRetainBody(true);
		for (ObjectId merge : merges)
			try {
				commits.add(new RepositoryCommit(repository, walk
						.parseCommit(merge)));
			} catch (IOException e) {
				Activator.logError(MessageFormat.format(
						UIText.MergeResultDialog_couldNotFindCommit,
						merge.name()), e);
			}
		return commits.toArray(new RepositoryCommit[commits.size()]);
	}

	private String getCommitMessage(ObjectId id) {
		RevCommit commit;
		try {
			commit = new RevWalk(objectReader).parseCommit(id);
		} catch (IOException e) {
			Activator.logError(UIText.MergeResultDialog_couldNotFindCommit, e);
			return UIText.MergeResultDialog_couldNotFindCommit;
		}
		return commit.getShortMessage();
	}

	private String abbreviate(ObjectId id, boolean addBrackets) {
		StringBuilder result = new StringBuilder();
		if (addBrackets)
			result.append("["); //$NON-NLS-1$
		try {
			result.append(objectReader.abbreviate(id).name());
		} catch (IOException e) {
			result.append(id.name());
		}
		if (addBrackets)
			result.append("]"); //$NON-NLS-1$
		return result.toString();
	}

	@Override
	protected void configureShell(Shell newShell) {
		super.configureShell(newShell);
		newShell.setText(UIText.MergeAction_MergeResultTitle);
		newShell.addDisposeListener(new DisposeListener() {
			public void widgetDisposed(DisposeEvent e) {
				if (objectReader != null)
					objectReader.release();
			}
		});
	}

	protected IDialogSettings getDialogBoundsSettings() {
		return UIUtils.getDialogBoundSettings(getClass());
	}
}
