/*******************************************************************************
 * Copyright (c) 2011 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.team.ui.history;

import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.mylyn.internal.provisional.commons.ui.AbstractColumnViewerSupport;
import org.eclipse.mylyn.internal.tasks.core.sync.GetTaskHistoryJob;
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
import org.eclipse.mylyn.internal.tasks.ui.compare.TaskDataDiffNode;
import org.eclipse.mylyn.internal.tasks.ui.util.TasksUiInternal;
import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector;
import org.eclipse.mylyn.tasks.core.ITask;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskHistory;
import org.eclipse.mylyn.tasks.core.data.TaskRevision;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.team.ui.history.HistoryPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;

/**
 * @author Steffen Pingel
 */
public class TaskHistoryPage extends HistoryPage {

	private class AddedLabelProvider extends ChangeLabelProvider {

		@Override
		public String getText(TaskRevision.Change change) {
			return change.getAdded();
		}

	}

	private static class AuthorLabelProvider extends ColumnLabelProvider {

		@Override
		public String getText(Object element) {
			if (element instanceof TaskRevision) {
				TaskRevision entry = (TaskRevision) element;
				return entry.getAuthor().toString();
			} else if (element instanceof TaskRevision.Change) {
				return null;
			}
			return super.getText(element);
		}

	}

	private static abstract class ChangeLabelProvider extends ColumnLabelProvider {

		@Override
		public String getText(Object element) {
			if (element instanceof TaskRevision) {
				TaskRevision entry = (TaskRevision) element;
				if (entry.getChanges().size() == 1) {
					return getText(entry.getChanges().get(0));
				}
				return null;
			} else if (element instanceof TaskRevision.Change) {
				return getText((TaskRevision.Change) element);
			}
			return super.getText(element);
		}

		public abstract String getText(TaskRevision.Change change);

	}

	private class FieldLabelProvider extends ChangeLabelProvider {

		@Override
		public String getText(TaskRevision.Change change) {
			return change.getField();
		}

	}

	private class RemovedLabelProvider extends ChangeLabelProvider {

		@Override
		public String getText(TaskRevision.Change change) {
			return change.getRemoved();
		}

	}

	private static class TimeLabelProvider extends ColumnLabelProvider {

		private final DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);

		@Override
		public String getText(Object element) {
			if (element instanceof TaskRevision) {
				TaskRevision entry = (TaskRevision) element;
				return (entry.getDate() != null) ? formatter.format(entry.getDate()) : null;
			} else if (element instanceof TaskRevision.Change) {
				return null;
			}
			return super.getText(element);
		}

	}

	public static boolean canShowHistoryFor(Object object) {
		ITask task = getTask(object);
		if (task != null) {
			AbstractRepositoryConnector connector = TasksUiPlugin.getRepositoryManager().getRepositoryConnector(
					task.getConnectorKind());
			TaskRepository repository = TasksUiPlugin.getRepositoryManager().getRepository(task.getConnectorKind(),
					task.getRepositoryUrl());
			return connector.canGetTaskHistory(repository, task);
		}
		return false;
	}

	public static ITask getTask(Object object) {
		ITask task = null;
		if (object instanceof ITask) {
			task = (ITask) object;
		} else if (object instanceof IAdaptable) {
			task = (ITask) ((IAdaptable) object).getAdapter(ITask.class);
		}
		return task;
	}

	private TaskHistoryContentProvider contentProvider;

	private GetTaskHistoryJob refreshOperation;

	private TreeViewer viewer;

	public TaskHistoryPage() {
	}

	@Override
	public void createControl(Composite parent) {
		viewer = new TreeViewer(parent, SWT.FULL_SELECTION);
		Tree tree = viewer.getTree();
		tree.setHeaderVisible(true);

		TreeViewerColumn authorViewerColumn = new TreeViewerColumn(viewer, SWT.LEFT);
		authorViewerColumn.setLabelProvider(new AuthorLabelProvider());
		TreeColumn authorColumn = authorViewerColumn.getColumn();
		authorColumn.setText("Author");
		authorColumn.setWidth(120);
		authorColumn.setData(AbstractColumnViewerSupport.KEY_COLUMN_CAN_HIDE, false);

		TreeViewerColumn timeViewerColumn = new TreeViewerColumn(viewer, SWT.LEFT);
		timeViewerColumn.setLabelProvider(new TimeLabelProvider());
		TreeColumn timeColumn = timeViewerColumn.getColumn();
		timeColumn.setText("Time");
		timeColumn.setWidth(140);

		TreeViewerColumn fieldViewerColumn = new TreeViewerColumn(viewer, SWT.LEFT);
		fieldViewerColumn.setLabelProvider(new FieldLabelProvider());
		TreeColumn fieldColumn = fieldViewerColumn.getColumn();
		fieldColumn.setText("Field");
		fieldColumn.setWidth(120);

		TreeViewerColumn removedViewerColumn = new TreeViewerColumn(viewer, SWT.LEFT);
		removedViewerColumn.setLabelProvider(new RemovedLabelProvider());
		TreeColumn remmovedColumn = removedViewerColumn.getColumn();
		remmovedColumn.setText("Removed");
		remmovedColumn.setWidth(120);

		TreeViewerColumn addedViewerColumn = new TreeViewerColumn(viewer, SWT.LEFT);
		addedViewerColumn.setLabelProvider(new AddedLabelProvider());
		TreeColumn addedColumn = addedViewerColumn.getColumn();
		addedColumn.setText("Added");
		addedColumn.setWidth(120);

		contentProvider = new TaskHistoryContentProvider();
		viewer.setContentProvider(contentProvider);

		viewer.addDoubleClickListener(new IDoubleClickListener() {
			public void doubleClick(DoubleClickEvent event) {
				Object item = ((IStructuredSelection) event.getSelection()).getFirstElement();
				if (item instanceof TaskRevision) {
					final TaskRevision revision = (TaskRevision) item;
					final TaskHistory history = (TaskHistory) viewer.getInput();

					CompareConfiguration configuration = new CompareConfiguration();
					configuration.setProperty(CompareConfiguration.IGNORE_WHITESPACE, Boolean.valueOf(true));
					configuration.setLeftEditable(false);
					configuration.setLeftLabel("Old Value");
					configuration.setRightEditable(false);
					configuration.setRightLabel("New Value");
					CompareEditorInput editorInput = new CompareEditorInput(configuration) {
						@Override
						protected Object prepareInput(IProgressMonitor monitor) throws InvocationTargetException,
								InterruptedException {
							try {
								AbstractRepositoryConnector connector = TasksUiPlugin.getRepositoryManager()
										.getRepositoryConnector(history.getTask().getConnectorKind());
								TaskData newData = connector.getTaskData(history.getRepository(), history.getTask()
										.getTaskId(), monitor);
								TaskData oldData = TasksUiInternal.computeTaskData(newData, history, revision.getId(),
										monitor);
								return new TaskDataDiffNode(Differencer.CHANGE, oldData, newData);
							} catch (CoreException e) {
								throw new InvocationTargetException(e);
							}
						}
					};
					CompareUI.openCompareEditor(editorInput);
				}
			}
		});
	}

	@Override
	public void dispose() {
		cancelRefresh();
		super.dispose();
	}

	public Object getAdapter(@SuppressWarnings("rawtypes")
	Class adapter) {
		return null;
	}

	@Override
	public Control getControl() {
		return viewer.getControl();
	}

	public String getDescription() {
		return NLS.bind("Task history for {0}", getName());
	}

	public String getName() {
		ITask task = getTask();
		return (task != null) ? task.getSummary() : null;
	}

	public ITask getTask() {
		return getTask(getInput());
	}

	@Override
	public boolean inputSet() {
		cancelRefresh();

		if (viewer == null) {
			return false;
		}

		final ITask task = getTask();
		if (task != null) {
			AbstractRepositoryConnector connector = TasksUiPlugin.getRepositoryManager().getRepositoryConnector(
					task.getConnectorKind());
			TaskRepository repository = TasksUiPlugin.getRepositoryManager().getRepository(task.getConnectorKind(),
					task.getRepositoryUrl());
			refreshOperation = new GetTaskHistoryJob(connector, repository, task);
			refreshOperation.addJobChangeListener(new JobChangeAdapter() {
				@Override
				public void done(final IJobChangeEvent event) {
					if (!event.getResult().isOK()) {
						return;
					}
					if (Display.getDefault().isDisposed()) {
						return;
					}
					Display.getDefault().asyncExec(new Runnable() {
						public void run() {
							if (viewer.getControl() != null && !viewer.getControl().isDisposed()) {
								TaskHistory history = ((GetTaskHistoryJob) event.getJob()).getHistory();
								viewer.setInput(history);
							}
						}
					});
				}
			});
			schedule(refreshOperation);
			return true;
		}
		return false;
	}

	public boolean isValidInput(Object object) {
		return canShowHistoryFor(object);
	}

	public void refresh() {
		inputSet();
	}

	@Override
	public void setFocus() {
		if (viewer != null) {
			viewer.getControl().setFocus();
		}
	}

	private void cancelRefresh() {
		if (refreshOperation != null) {
			refreshOperation.cancel();
			refreshOperation = null;
		}
	}

	private IWorkbenchPartSite getWorkbenchSite() {
		final IWorkbenchPart part = getHistoryPageSite().getPart();
		return part != null ? part.getSite() : null;
	}

	private void schedule(final Job job) {
		final IWorkbenchPartSite site = getWorkbenchSite();
		if (site != null) {
			IWorkbenchSiteProgressService progress = (IWorkbenchSiteProgressService) site.getAdapter(IWorkbenchSiteProgressService.class);
			if (progress != null) {
				progress.schedule(job, 0, true);
				return;
			}
		}
		// fall-back
		job.schedule();
	}

}
