/*******************************************************************************
 *  Copyright (c) 2000, 2017 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.internal.ui.synchronize.actions;

import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;

import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.util.OpenStrategy;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.team.core.synchronize.SyncInfo;
import org.eclipse.team.internal.ui.Utils;
import org.eclipse.team.internal.ui.mapping.ModelCompareEditorInput;
import org.eclipse.team.internal.ui.synchronize.SyncInfoModelElement;
import org.eclipse.team.internal.ui.synchronize.patch.ApplyPatchModelCompareEditorInput;
import org.eclipse.team.internal.ui.synchronize.patch.ApplyPatchSubscriberMergeContext;
import org.eclipse.team.ui.mapping.ISynchronizationCompareInput;
import org.eclipse.team.ui.mapping.ITeamContentProviderManager;
import org.eclipse.team.ui.synchronize.*;
import org.eclipse.ui.*;

/**
 * Action to open a compare editor from a SyncInfo object.
 *
 * @see SyncInfoCompareInput
 * @since 3.0
 */
public class OpenInCompareAction extends Action {

	private final ISynchronizePageConfiguration configuration;

	public OpenInCompareAction(ISynchronizePageConfiguration configuration) {
		this.configuration = configuration;
		Utils.initAction(this, "action.openInCompareEditor."); //$NON-NLS-1$
	}

	@Override
	public void run() {
		ISelection selection = configuration.getSite().getSelectionProvider().getSelection();
		if(selection instanceof IStructuredSelection) {
			if (!isOkToRun(selection))
				return;

			boolean reuseEditorIfPossible = ((IStructuredSelection) selection).size()==1;
			for (Iterator iterator = ((IStructuredSelection) selection).iterator(); iterator.hasNext();) {
				Object obj = iterator.next();
				if (obj instanceof SyncInfoModelElement) {
					SyncInfo info = ((SyncInfoModelElement) obj).getSyncInfo();
					if (info != null) {
						// Use the open strategy to decide if the editor or the sync view should have focus
						openCompareEditorOnSyncInfo(configuration, info, !OpenStrategy.activateOnOpen(), reuseEditorIfPossible);
					}
				} else if (obj != null){
					openCompareEditor(configuration, obj, !OpenStrategy.activateOnOpen(), reuseEditorIfPossible);
				}
			}
		}
	}

	private boolean isOkToRun(ISelection selection) {
		// do not open Compare Editor unless all elements have input
		Object[] elements = ((IStructuredSelection) selection).toArray();
		ISynchronizeParticipant participant = configuration
				.getParticipant();
		// model synchronize
		if (participant instanceof ModelSynchronizeParticipant) {
			ModelSynchronizeParticipant msp = (ModelSynchronizeParticipant) participant;
			for (int i = 0; i < elements.length; i++) {
				// TODO: This is inefficient
				if (!msp.hasCompareInputFor(elements[i])) {
					return false;
				}
			}
		} else {
			// all files
			IResource resources[] = Utils.getResources(elements);
			for (int i = 0; i < resources.length; i++) {
	            if (resources[i].getType() != IResource.FILE) {
	                // Only supported if all the items are files.
	                return false;
	            }
	        }
		}
		return true;
	}

	public static IEditorInput openCompareEditor(ISynchronizePageConfiguration configuration, Object object, boolean keepFocus, boolean reuseEditorIfPossible) {
		Assert.isNotNull(object);
		Assert.isNotNull(configuration);
		ISynchronizeParticipant participant = configuration.getParticipant();
		ISynchronizePageSite site = configuration.getSite();
		if (object instanceof SyncInfoModelElement) {
			SyncInfo info = ((SyncInfoModelElement) object).getSyncInfo();
			if (info != null)
				return openCompareEditorOnSyncInfo(configuration, info, keepFocus, reuseEditorIfPossible);
		}
		if (participant instanceof ModelSynchronizeParticipant) {
			ModelSynchronizeParticipant msp = (ModelSynchronizeParticipant) participant;
			ICompareInput input = msp.asCompareInput(object);
			IWorkbenchPage workbenchPage = getWorkbenchPage(site);
			if (input != null && workbenchPage != null && isOkToOpen(site, participant, input)) {
				if (isApplyPatchModelPresent(configuration))
					return openCompareEditor(workbenchPage, new ApplyPatchModelCompareEditorInput(msp, input, workbenchPage, configuration), keepFocus, site, reuseEditorIfPossible);
				else
					return openCompareEditor(workbenchPage, new ModelCompareEditorInput(msp, input, workbenchPage, configuration), keepFocus, site, reuseEditorIfPossible);
			}
		}
		return null;
	}

	private static boolean isApplyPatchModelPresent(
			ISynchronizePageConfiguration configuration) {
		Object object = configuration.getProperty(ITeamContentProviderManager.P_SYNCHRONIZATION_CONTEXT);
		return object instanceof ApplyPatchSubscriberMergeContext;
	}

	private static boolean isOkToOpen(final ISynchronizePageSite site, final ISynchronizeParticipant participant, final ICompareInput input) {
		if (participant instanceof ModelSynchronizeParticipant && input instanceof ISynchronizationCompareInput) {
			final ModelSynchronizeParticipant msp = (ModelSynchronizeParticipant) participant;
			final boolean[] result = new boolean[] { false };
			try {
				PlatformUI.getWorkbench().getProgressService().run(true, true, monitor -> {
					try {
						result[0] = msp.checkForBufferChange(site.getShell(), (ISynchronizationCompareInput) input,
								true, monitor);
					} catch (CoreException e) {
						throw new InvocationTargetException(e);
					}
				});
			} catch (InvocationTargetException e) {
				Utils.handleError(site.getShell(), e, null, null);
			} catch (InterruptedException e) {
				return false;
			}
			return result[0];
		}
		return true;
	}

	public static CompareEditorInput openCompareEditorOnSyncInfo(ISynchronizePageConfiguration configuration, SyncInfo info, boolean keepFocus, boolean reuseEditorIfPossible) {
		Assert.isNotNull(info);
		Assert.isNotNull(configuration);
		if(info.getLocal().getType() != IResource.FILE) return null;
		SyncInfoCompareInput input = new SyncInfoCompareInput(configuration, info);
		return openCompareEditor(getWorkbenchPage(configuration.getSite()), input, keepFocus, configuration.getSite(), reuseEditorIfPossible);
	}

	public static CompareEditorInput openCompareEditor(ISynchronizeParticipant participant, SyncInfo info, ISynchronizePageSite site) {
		Assert.isNotNull(info);
		Assert.isNotNull(participant);
		if(info.getLocal().getType() != IResource.FILE) return null;
		SyncInfoCompareInput input = new SyncInfoCompareInput(participant, info);
		return openCompareEditor(getWorkbenchPage(site), input, false, site, false);
	}

	private static CompareEditorInput openCompareEditor(
			IWorkbenchPage page,
			CompareEditorInput input,
			boolean keepFocus,
			ISynchronizePageSite site,
			boolean reuseEditorIfPossible) {
		if (page == null)
			return null;

		openCompareEditor(input, page, reuseEditorIfPossible);
		if(site != null && keepFocus) {
			site.setFocus();
		}
		return input;
	}

	private static IWorkbenchPage getWorkbenchPage(ISynchronizePageSite site) {
		IWorkbenchPage page = null;
		if(site == null || site.getWorkbenchSite() == null) {
			IWorkbenchWindow window= PlatformUI.getWorkbench().getActiveWorkbenchWindow();
			if (window != null)
				page = window.getActivePage();
		} else {
			page = site.getWorkbenchSite().getPage();
		}
		return page;
	}

    public static void openCompareEditor(CompareEditorInput input, IWorkbenchPage page) {
    	// try to reuse editors, if possible
		openCompareEditor(input, page, true);
	}

    public static void openCompareEditor(CompareEditorInput input, IWorkbenchPage page, boolean reuseEditorIfPossible) {
        if (page == null || input == null)
            return;
		IEditorPart editor = Utils.findReusableCompareEditor(input, page,
				new Class[] { SyncInfoCompareInput.class,
						ModelCompareEditorInput.class });
        // reuse editor only for single selection
        if(editor != null && reuseEditorIfPossible) {
        	IEditorInput otherInput = editor.getEditorInput();
        	if(otherInput.equals(input)) {
        		// simply provide focus to editor
        		page.activate(editor);
        	} else {
        		// if editor is currently not open on that input either re-use existing
        		CompareUI.reuseCompareEditor(input, (IReusableEditor)editor);
        		page.activate(editor);
        	}
        } else {
        	CompareUI.openCompareEditorOnPage(input, page);
        }
    }

	/**
	 * Returns an editor handle if a SyncInfoCompareInput compare editor is opened on
	 * the given IResource.
	 *
	 * @param site the view site in which to search for editors
	 * @param resource the resource to use to find the compare editor
	 * @return an editor handle if found and <code>null</code> otherwise
	 */
	public static IEditorPart findOpenCompareEditor(IWorkbenchPartSite site, IResource resource) {
		IWorkbenchPage page = site.getPage();
		IEditorReference[] editorRefs = page.getEditorReferences();
		for (int i = 0; i < editorRefs.length; i++) {
			final IEditorPart part = editorRefs[i].getEditor(false /* don't restore editor */);
			if(part != null) {
				IEditorInput input = part.getEditorInput();
				if(part != null && input instanceof SyncInfoCompareInput) {
					SyncInfo inputInfo = ((SyncInfoCompareInput)input).getSyncInfo();
					if(inputInfo.getLocal().equals(resource)) {
						return part;
					}
				}
			}
		}
		return null;
	}

	/**
	 * Returns an editor handle if a compare editor is opened on
	 * the given object.
	 * @param site the view site in which to search for editors
	 * @param object the object to use to find the compare editor
	 * @param participant
	 * @return an editor handle if found and <code>null</code> otherwise
	 */
	public static IEditorPart findOpenCompareEditor(IWorkbenchPartSite site, Object object, ISynchronizeParticipant participant) {
		if (object instanceof SyncInfoModelElement) {
			SyncInfoModelElement element = (SyncInfoModelElement) object;
			SyncInfo info = element.getSyncInfo();
			return findOpenCompareEditor(site, info.getLocal());
		}
		IWorkbenchPage page = site.getPage();
		IEditorReference[] editorRefs = page.getEditorReferences();
		for (int i = 0; i < editorRefs.length; i++) {
			final IEditorPart part = editorRefs[i].getEditor(false /* don't restore editor */);
			if(part != null) {
				IEditorInput input = part.getEditorInput();
				if(input instanceof ModelCompareEditorInput) {
					if(((ModelCompareEditorInput)input).matches(object, participant)) {
						return part;
					}
				}
			}
		}
		return null;
	}
}
