/*******************************************************************************
 * Copyright (c) 2000, 2017 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.ui.synchronize;

import java.lang.reflect.InvocationTargetException;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareNavigator;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.ICompareNavigator;
import org.eclipse.compare.INavigatable;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.synchronize.SyncInfo;
import org.eclipse.team.internal.ui.Policy;
import org.eclipse.team.internal.ui.TeamUIMessages;
import org.eclipse.team.internal.ui.Utils;
import org.eclipse.team.internal.ui.synchronize.SyncInfoModelElement;
import org.eclipse.team.internal.ui.synchronize.SynchronizePageConfiguration;
import org.eclipse.ui.progress.UIJob;

/**
 * A {@link SyncInfo} editor input used as input to a two-way or three-way
 * compare viewer. It defines methods for accessing the three sides for the
 * compare, and a name and image which is used when displaying the three way input
 * in an editor. This input can alternately be used to show compare results in
 * a dialog by calling {@link CompareUI#openCompareDialog(org.eclipse.compare.CompareEditorInput)}.
 * <p>
 * The editor will not update when the elements in the sync info are changed.
 * </p>
 * <p>
 * Supports saving the local resource that is changed in the editor and will be updated
 * when the local resources is changed.
 * </p>
 * @see SyncInfo
 * @since 3.0
 */
public final class SyncInfoCompareInput extends SaveableCompareEditorInput implements IResourceChangeListener {

	private final MyDiffNode node;
	private final String description;
	private final IResource resource;
	private ISynchronizeParticipant participant;
	private ISynchronizePageConfiguration synchronizeConfiguration;

	/*
	 * This class exists so that we can force the text merge viewers to update by
	 * calling #fireChange when we save the compare input to disk. The side
	 * effect is that the compare viewers will be updated to reflect the new changes
	 * that have been made. Compare doesn't do this by default.
	 */
	private static class MyDiffNode extends SyncInfoModelElement {
		public MyDiffNode(IDiffContainer parent, SyncInfo info) {
			super(parent, info);
		}
		@Override
		public void fireChange() {
			super.fireChange();
		}
	}

	/**
	 * Creates a compare editor input based on an existing <code>SyncInfo</code>.
	 *
	 * @param description a description of the context of this sync info. This
	 * is displayed to the user.
	 * @param sync the <code>SyncInfo</code> used as the base for the compare input.
	 */
	public SyncInfoCompareInput(String description, SyncInfo sync) {
		super(getDefaultCompareConfiguration(), null);
		Assert.isNotNull(sync);
		Assert.isNotNull(description);
		this.description = description;
		this.resource = sync.getLocal();
		this.node = new MyDiffNode(null, sync);
		setTitle(NLS.bind(TeamUIMessages.SyncInfoCompareInput_title, new String[] { sync.getLocal().getName() }));
	}

	/**
	 * Creates a compare editor input based on an existing <code>SyncInfo</code>
	 * from the given participant.
	 *
	 * @param participant the participant from which the sync info was obtained. The
	 * name of the participant is used as the description which is displayed to the user.
	 * @param sync the <code>SyncInfo</code> used as the base for the compare input.
	 *
	 * @since 3.1
	 */
	public SyncInfoCompareInput(ISynchronizeParticipant participant, SyncInfo sync) {
		this(participant.getName(), sync);
		this.participant = participant;
	}

	public SyncInfoCompareInput(ISynchronizePageConfiguration configuration,
			SyncInfo info) {
		this(configuration.getParticipant(), info);
		this.synchronizeConfiguration = configuration;
	}

	@Override
	protected void handleDispose() {
		super.handleDispose();
		if (synchronizeConfiguration != null) {
			ICompareNavigator navigator = (ICompareNavigator)synchronizeConfiguration.getProperty(SynchronizePageConfiguration.P_INPUT_NAVIGATOR);
			if (navigator != null && navigator == super.getNavigator()) {
				synchronizeConfiguration.setProperty(SynchronizePageConfiguration.P_INPUT_NAVIGATOR, new CompareNavigator() {
					@Override
					protected INavigatable[] getNavigatables() {
						return new INavigatable[0];
					}
				});
			}
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> adapter) {
		if (IFile.class.equals(adapter) && resource.getType() == IResource.FILE) {
			return (T) resource;
		}
		return super.getAdapter(adapter);
	}

	private static CompareConfiguration getDefaultCompareConfiguration() {
		CompareConfiguration cc = new CompareConfiguration();
		//cc.setProperty(CompareConfiguration.USE_OUTLINE_VIEW, true);
		return cc;
	}

	/**
	 * Note that until the compare editor inputs can be part of the compare editors lifecycle we
	 * can't register as a listener because there is no dispose() method to remove the listener.
	 */
	@Override
	public void resourceChanged(IResourceChangeEvent event) {
		IResourceDelta delta = event.getDelta();
		if (delta != null) {
			IResourceDelta resourceDelta = delta.findMember(resource.getFullPath());
			if (resourceDelta != null) {
				UIJob job = new UIJob("") { //$NON-NLS-1$
					@Override
					public IStatus runInUIThread(IProgressMonitor monitor) {
						if (!isSaveNeeded()) {
							//updateNode();
						}
						return Status.OK_STATUS;
					}
				};
				job.setSystem(true);
				job.schedule();
			}
		}
	}

	@Override
	protected ICompareInput prepareCompareInput(IProgressMonitor monitor)
			throws InvocationTargetException, InterruptedException {
		// update the title now that the remote revision number as been fetched
		// from the server
		setTitle(getTitle());
		monitor.beginTask(TeamUIMessages.SyncInfoCompareInput_3, 100);
		monitor.setTaskName(TeamUIMessages.SyncInfoCompareInput_3);
		try {
			if (participant != null) {
				participant.prepareCompareInput(node, getCompareConfiguration(), Policy.subMonitorFor(monitor, 100));
			} else {
				Utils.updateLabels(node.getSyncInfo(), getCompareConfiguration(), monitor);
				node.cacheContents(Policy.subMonitorFor(monitor, 100));
			}
		} catch (TeamException e) {
			throw new InvocationTargetException(e);
		} finally {
			monitor.done();
		}
		return node;
	}

	@Override
	public String getToolTipText() {
		return NLS.bind(TeamUIMessages.SyncInfoCompareInput_tooltip, new String[] { Utils.shortenText(30, description), node.getResource().getFullPath().toString() });
	}

	@Override
	public boolean equals(Object other) {
		if (other == this)
			return true;
		if (other instanceof SyncInfoCompareInput) {
			SyncInfo otherSyncInfo = ((SyncInfoCompareInput) other).getSyncInfo();
			SyncInfo thisSyncInfo = getSyncInfo();
			// Consider the inputs equal if the sync info are equal and the
			// left nodes are equal (i.e they have the same timestamp)
			return thisSyncInfo.equals(otherSyncInfo)
				&& node.getLeft().equals(((SyncInfoCompareInput) other).node.getLeft());
		}
		return false;
	}

	@Override
	public int hashCode() {
		return getSyncInfo().hashCode();
	}

	public SyncInfo getSyncInfo() {
		return node.getSyncInfo();
	}

	@Override
	public boolean canRunAsJob() {
		return true;
	}

	@Override
	public synchronized ICompareNavigator getNavigator() {
		if (synchronizeConfiguration != null && isSelectedInSynchronizeView()) {
			ICompareNavigator nav = (ICompareNavigator)synchronizeConfiguration.getProperty(SynchronizePageConfiguration.P_NAVIGATOR);
			synchronizeConfiguration.setProperty(SynchronizePageConfiguration.P_INPUT_NAVIGATOR, super.getNavigator());
			return nav;
		}
		return super.getNavigator();
	}

	private boolean isSelectedInSynchronizeView() {
		if (synchronizeConfiguration != null) {
			ISelection s = synchronizeConfiguration.getSite().getSelectionProvider().getSelection();
			if (s instanceof IStructuredSelection) {
				IStructuredSelection ss = (IStructuredSelection) s;
				Object element = ss.getFirstElement();
				if (element instanceof SyncInfoModelElement) {
					SyncInfoModelElement sime = (SyncInfoModelElement) element;
					return sime.getSyncInfo().getLocal().equals(resource);
				}
			}
		}
		return false;
	}

	@Override
	protected void fireInputChange() {
		node.fireChange();
	}
}
