/*******************************************************************************
 * Copyright (c) 2000, 2009 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.compare.structuremergeviewer;

import java.lang.reflect.InvocationTargetException;

import org.eclipse.compare.*;
import org.eclipse.compare.contentmergeviewer.IDocumentRange;
import org.eclipse.compare.internal.*;
import org.eclipse.core.runtime.*;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.services.IDisposable;


/**
 * A diff tree viewer that can be configured with a <code>IStructureCreator</code>
 * to retrieve a hierarchical structure from the input object (an <code>ICompareInput</code>)
 * and perform a two-way or three-way compare on it.
 * <p>
 * This class may be instantiated; it is not intended to be subclassed outside
 * this package.
 * </p>
 *
 * @see IStructureCreator
 * @see ICompareInput
 * @noextend This class is not intended to be subclassed by clients.
 */
		
public class StructureDiffViewer extends DiffTreeViewer {
	private Differencer fDifferencer;
	private boolean fThreeWay= false;
	
	private StructureInfo fAncestorStructure = new StructureInfo();
	private StructureInfo fLeftStructure = new StructureInfo();
	private StructureInfo fRightStructure = new StructureInfo();
	
	private IStructureCreator fStructureCreator;
	private IDiffContainer fRoot;
	private IContentChangeListener fContentChangedListener;
	private CompareViewerSwitchingPane fParent;
	private ICompareInputChangeListener fCompareInputChangeListener;
	
	/*
	 * A set of background tasks for updating the structure
	 */
	private IRunnableWithProgress diffTask = new IRunnableWithProgress() {
		public void run(IProgressMonitor monitor) throws InvocationTargetException,
				InterruptedException {
			monitor.beginTask(CompareMessages.StructureDiffViewer_0, 100);
			diff(new SubProgressMonitor(monitor, 100));
			monitor.done();
		}
	};
	
	private IRunnableWithProgress inputChangedTask = new IRunnableWithProgress() {
		public void run(IProgressMonitor monitor) throws InvocationTargetException,
				InterruptedException {
			monitor.beginTask(CompareMessages.StructureDiffViewer_1, 100);
			// TODO: Should we always force
			compareInputChanged((ICompareInput)getInput(), true, new SubProgressMonitor(monitor, 100));
			monitor.done();
		}
	};
	
	/*
	 * A helper class for holding the input and generated structure
	 * for the ancestor, left and right inputs.
	 */
	private class StructureInfo {
		private ITypedElement fInput;
		private IStructureComparator fStructureComparator;
		private IRunnableWithProgress refreshTask = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor) throws InvocationTargetException,
					InterruptedException {
				refresh(monitor);
			}
		};
		
		public boolean setInput(ITypedElement newInput, boolean force, IProgressMonitor monitor) {
			boolean changed = false;
			if (force || newInput != fInput) {
				removeDocumentRangeUpdaters();
				if (fInput instanceof IContentChangeNotifier && fContentChangedListener != null)
					((IContentChangeNotifier)fInput).removeContentChangeListener(fContentChangedListener);
				fInput= newInput;
				if (fInput == null) {
					dispose(); // destroy fStructureComparator
					fStructureComparator= null;
				} else {
					refresh(monitor);
					changed= true;
				}
				if (fInput instanceof IContentChangeNotifier && fContentChangedListener != null)
					((IContentChangeNotifier)fInput).addContentChangeListener(fContentChangedListener);
			}
			return changed;
		}

		/**
		 * Remove any document range updaters that were registered against the document.
		 */
		private void removeDocumentRangeUpdaters() {
			if (fStructureComparator instanceof IDocumentRange) {
				IDocument doc = ((IDocumentRange) fStructureComparator).getDocument();
				try {
					doc.removePositionCategory(IDocumentRange.RANGE_CATEGORY);
				} catch (BadPositionCategoryException ex) {
					// Ignore
				}
			}
		}
		
		public IStructureComparator getStructureComparator() {
			return fStructureComparator;
		}

		public void refresh(IProgressMonitor monitor) {
			IStructureComparator oldComparator = fStructureComparator;
			fStructureComparator= createStructure(monitor);
			// Dispose of the old one after in case they are using a shared document
			// (i.e. disposing it after will hold on to a reference to the document 
			// so it doesn't get freed and reloaded)
			if (oldComparator instanceof IDisposable) {
				IDisposable disposable = (IDisposable) oldComparator;
				disposable.dispose();
			}
		}

		public Object getInput() {
			return fInput;
		}
		
		private IStructureComparator createStructure(IProgressMonitor monitor) {
			// Defend against concurrent disposal
			Object input = fInput;
			if (input == null)
				return null;
			if (fStructureCreator instanceof IStructureCreator2) {
				IStructureCreator2 sc2 = (IStructureCreator2) fStructureCreator;
				try {
					return sc2.createStructure(input, monitor);
				} catch (CoreException e) {
					CompareUIPlugin.log(e);
				}
			}
			return fStructureCreator.getStructure(input);
		}

		public void dispose() {
			if (fStructureComparator != null && fStructureCreator instanceof IStructureCreator2) {
				IStructureCreator2 sc2 = (IStructureCreator2) fStructureCreator;
				sc2.destroy(fStructureComparator);
			}
		}

		public IRunnableWithProgress getRefreshTask() {
			return refreshTask;
		}
	}
	
	/**
	 * Creates a new viewer for the given SWT tree control with the specified configuration.
	 *
	 * @param tree the tree control
	 * @param configuration the configuration for this viewer
	 */
	public StructureDiffViewer(Tree tree, CompareConfiguration configuration) {
		super(tree, configuration);
		Composite c= tree.getParent();
		if (c instanceof CompareViewerSwitchingPane)
			fParent= (CompareViewerSwitchingPane) c;
		initialize();
	}
	
	/**
	 * Creates a new viewer under the given SWT parent with the specified configuration.
	 *
	 * @param parent the SWT control under which to create the viewer
	 * @param configuration the configuration for this viewer
	 */
	public StructureDiffViewer(Composite parent, CompareConfiguration configuration) {
		super(parent, configuration);
		if (parent instanceof CompareViewerSwitchingPane)
			fParent= (CompareViewerSwitchingPane) parent;
		initialize();
	}
	
	private void initialize() {
		
		setAutoExpandLevel(3);
		
		fContentChangedListener= new IContentChangeListener() {
			public void contentChanged(IContentChangeNotifier changed) {
				StructureDiffViewer.this.contentChanged(changed);
			}
		};
		fCompareInputChangeListener = new ICompareInputChangeListener() {
			public void compareInputChanged(ICompareInput input) {
				StructureDiffViewer.this.compareInputChanged(input, true);
			}
		};
	}
	
	/**
	 * Configures the <code>StructureDiffViewer</code> with a structure creator.
	 * The structure creator is used to create a hierarchical structure
	 * for each side of the viewer's input element of type <code>ICompareInput</code>.
	 *
	 * @param structureCreator the new structure creator
	 */
	public void setStructureCreator(IStructureCreator structureCreator) {
		if (fStructureCreator != structureCreator) {
			fStructureCreator= structureCreator;
			Control tree= getControl();
			if (tree != null && !tree.isDisposed())
				tree.setData(CompareUI.COMPARE_VIEWER_TITLE, getTitle());
		}
	}
	
	/**
	 * Returns the structure creator or <code>null</code> if no
	 * structure creator has been set with <code>setStructureCreator</code>.
	 *
	 * @return the structure creator or <code>null</code>
	 */
	public IStructureCreator getStructureCreator() {
		return fStructureCreator;
	}
	
	/**
	 * Reimplemented to get the descriptive title for this viewer from the <code>IStructureCreator</code>.
	 * @return the viewer's name
	 */
	public String getTitle() {
		if (fStructureCreator != null)
			return fStructureCreator.getName();
		return super.getTitle();
	}
	
	/**
	 * Overridden because the input of this viewer is not identical to the root of the tree.
	 * The tree's root is a IDiffContainer that was returned from the method <code>diff</code>.
	 * 
	 * @return the root of the diff tree produced by method <code>diff</code>
	 */
	protected Object getRoot() {
		return fRoot;
	}
	
    /*
     * (non-Javadoc) Method declared on StructuredViewer.
     * Overridden to create the comparable structures from the input object
	 * and to feed them through the differencing engine. Note: for this viewer
	 * the value from <code>getInput</code> is not identical to <code>getRoot</code>.
	 */
	protected void inputChanged(Object input, Object oldInput) {
		if (oldInput instanceof ICompareInput) {
			ICompareInput old = (ICompareInput) oldInput;
			old.removeCompareInputChangeListener(fCompareInputChangeListener);
		}
		if (input instanceof ICompareInput) {
			ICompareInput ci = (ICompareInput) input;
			ci.addCompareInputChangeListener(fCompareInputChangeListener);
			compareInputChanged(ci);
			if (input != oldInput)
				initialSelection();
		}
	}
	
	protected void initialSelection() {
		expandToLevel(2);
	}

	/* (non Javadoc)
	 * Overridden to unregister all listeners.
	 */
	protected void handleDispose(DisposeEvent event) {
		Object input = getInput();
		if (input instanceof ICompareInput) {
			ICompareInput ci = (ICompareInput) input;
			ci.removeCompareInputChangeListener(fCompareInputChangeListener);
		}
		compareInputChanged(null);
		fContentChangedListener= null;
		super.handleDispose(event);
	}
	
	/**
	 * Recreates the comparable structures for the input sides.
	 * @param input this viewer's new input
	 */
	protected void compareInputChanged(ICompareInput input) {
		compareInputChanged(input, false);
	}
		
	/* package */ void compareInputChanged(final ICompareInput input, final boolean force) {
		if (input == null) {
			// When closing, we don't need a progress monitor to handle the input change
			compareInputChanged(input, force, null);
			return;
		}
		CompareConfiguration cc = getCompareConfiguration();
		// The compare configuration is nulled when the viewer is disposed
		if (cc != null) {
			BusyIndicator.showWhile(Display.getDefault(), new Runnable() {
				public void run() {
					try {
						inputChangedTask.run(new NullProgressMonitor());
					} catch (InvocationTargetException e) {
						CompareUIPlugin.log(e.getTargetException());
					} catch (InterruptedException e) {
						// Ignore
					}
				}
			});
		}
	}

	/* package */ void compareInputChanged(ICompareInput input, boolean force, IProgressMonitor monitor) {
		ITypedElement t= null;
		boolean changed= false;
		
		if (input != null)
			t= input.getAncestor();
		fThreeWay= (t != null);
		beginWork(monitor, 400);
		try {
			if (fAncestorStructure.setInput(t, force, subMonitor(monitor, 100)))
				changed = true;
			
			if (input != null)
				t= input.getLeft();
			if (fLeftStructure.setInput(t, force, subMonitor(monitor, 100)))
				changed = true;
			
			if (input != null)
				t= input.getRight();
			if (fRightStructure.setInput(t, force, subMonitor(monitor, 100)))
				changed = true;
			
			// The compare configuration is nulled when the viewer is disposed
			CompareConfiguration cc = getCompareConfiguration();
			if (changed && cc != null)
				cc.getContainer().runAsynchronously(diffTask);
		} finally {
			endWork(monitor);
		}
	}
	
	private void endWork(IProgressMonitor monitor) {
		if (monitor != null)
			monitor.done();
	}

	private IProgressMonitor subMonitor(IProgressMonitor monitor, int work) {
		if (monitor != null) {
			if (monitor.isCanceled() || getControl().isDisposed())
				throw new OperationCanceledException();
			return new SubProgressMonitor(monitor, work);
		}
		return null;
	}

	private void beginWork(IProgressMonitor monitor, int totalWork) {
		if (monitor != null)
			monitor.beginTask(null, totalWork);
	}

	/**
	 * Calls <code>diff</code> whenever the byte contents changes.
	 * @param changed the object that sent out the notification
	 */
	protected void contentChanged(final IContentChangeNotifier changed) {
		
		if (fStructureCreator == null)
			return;
		
		if (changed == null) {
			getCompareConfiguration().getContainer().runAsynchronously(fAncestorStructure.getRefreshTask());
			getCompareConfiguration().getContainer().runAsynchronously(fLeftStructure.getRefreshTask());
			getCompareConfiguration().getContainer().runAsynchronously(fRightStructure.getRefreshTask());
		} else if (changed == fAncestorStructure.getInput()) {
			getCompareConfiguration().getContainer().runAsynchronously(fAncestorStructure.getRefreshTask());
		} else if (changed == fLeftStructure.getInput()) {
			getCompareConfiguration().getContainer().runAsynchronously(fLeftStructure.getRefreshTask());
		} else if (changed == fRightStructure.getInput()) {
			getCompareConfiguration().getContainer().runAsynchronously(fRightStructure.getRefreshTask());
		} else {
			return;
		}
		getCompareConfiguration().getContainer().runAsynchronously(diffTask);
	}

	/**
	 * This method is called from within <code>diff()</code> before the
	 * difference tree is being built. Clients may override this method to
	 * perform their own pre-processing. This default implementation does
	 * nothing.
	 * 
	 * @param ancestor the ancestor input to the differencing operation
	 * @param left the left input to the differencing operation
	 * @param right the right input to the differencing operation
	 * @since 2.0
	 * @deprecated Clients should override
	 *             {@link #preDiffHook(IStructureComparator, IStructureComparator, IStructureComparator, IProgressMonitor)}
	 */
	protected void preDiffHook(IStructureComparator ancestor, IStructureComparator left, IStructureComparator right) {
		// we do nothing here
	}
	
	/**
	 * This method is called from within {@link #diff(IProgressMonitor)} before
	 * the difference tree is being built. This method may be called from a
	 * background (non-UI) thread).
	 * <p>
	 * For backwards compatibility, this default implementation calls
	 * {@link #preDiffHook(IStructureComparator, IStructureComparator, IStructureComparator)}
	 * from the UI thread. Clients should override this method even if they
	 * don't perform pre-processing to avoid the call to the UI thread.
	 * 
	 * @param ancestor the ancestor input to the differencing operation
	 * @param left the left input to the differencing operation
	 * @param right the right input to the differencing operation
	 * @param monitor a progress monitor or null if progress is not required
	 * @since 3.3
	 */
	protected void preDiffHook(final IStructureComparator ancestor, final IStructureComparator left, final IStructureComparator right, IProgressMonitor monitor) {
		syncExec(new Runnable() {
			public void run() {
				preDiffHook(ancestor, left, right);
			}
		});
	}

	/**
	 * Runs the difference engine and refreshes the tree. This method may be called
	 * from a background (non-UI) thread).
	 * @param monitor a progress monitor or <code>null</code> if progress in not required
	 */
	protected void diff(IProgressMonitor monitor) {
		try {
			beginWork(monitor, 150);
			
			IStructureComparator ancestorComparator = fAncestorStructure.getStructureComparator();
			IStructureComparator leftComparator = fLeftStructure.getStructureComparator();
			IStructureComparator rightComparator = fRightStructure.getStructureComparator();
			
			preDiffHook(ancestorComparator, 
					leftComparator, 
					rightComparator,
					subMonitor(monitor, 25));
								
			String message= null;
			
			if ((fThreeWay && ancestorComparator == null) || leftComparator == null || rightComparator == null) {
				// could not get structure of one (or more) of the legs
				fRoot= null;
				message= CompareMessages.StructureDiffViewer_StructureError;	
				
			} else {	// calculate difference of the two (or three) structures
	
				if (fDifferencer == null)
					fDifferencer= new Differencer() {
						protected boolean contentsEqual(Object o1, Object o2) {
							return StructureDiffViewer.this.contentsEqual(o1, o2);
						}
						protected Object visit(Object data, int result, Object ancestor, Object left, Object right) {
							Object o= super.visit(data, result, ancestor, left, right);
							if (fLeftIsLocal && o instanceof DiffNode)
								((DiffNode)o).swapSides(fLeftIsLocal);
							return o;
						}
					};
				
				fRoot= (IDiffContainer) fDifferencer.findDifferences(fThreeWay, subMonitor(monitor, 100), null,
						ancestorComparator, leftComparator, rightComparator);
						
				if (fRoot == null || fRoot.getChildren().length == 0) {
					message= CompareMessages.StructureDiffViewer_NoStructuralDifferences;	
				} else {
					postDiffHook(fDifferencer, fRoot, subMonitor(monitor, 25));
				}
			}
			
			if (Display.getCurrent() != null)
				refreshAfterDiff(message);
			else {
				final String theMessage = message;
				Display.getDefault().asyncExec(new Runnable() {
					public void run() {
						refreshAfterDiff(theMessage);
					}
				});
			}
		} finally {
			endWork(monitor);
		}
	}

	private void refreshAfterDiff(String message) {
		if (getControl().isDisposed())
			return;
		if (fParent != null)
			fParent.setTitleArgument(message);
		
		refresh(getRoot());
		// Setting the auto-expand level doesn't do anything for refreshes
		expandToLevel(3);
	}
	
	/**
	 * Runs the difference engine and refreshes the tree.
	 */
	protected void diff() {
		try {
			CompareConfiguration compareConfiguration = getCompareConfiguration();
			// A null compare configuration indicates that the viewer was disposed
			if (compareConfiguration != null) {
				compareConfiguration.getContainer().run(true, true, new IRunnableWithProgress() {
					public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
						monitor.beginTask(CompareMessages.StructureDiffViewer_2, 100);
						diffTask.run(new SubProgressMonitor(monitor, 100));
						monitor.done();
					}
				});
			}
		} catch (InvocationTargetException e) {
			// Shouldn't happen since the run doesn't throw
			CompareUIPlugin.log(e.getTargetException());
			handleFailedRefresh(e.getTargetException().getMessage());
		} catch (InterruptedException e) {
			// Canceled by user
			handleFailedRefresh(CompareMessages.StructureDiffViewer_3);
		}
	}
	
	private void handleFailedRefresh(final String message) {
		Runnable runnable = new Runnable() {
			public void run() {
				if (getControl().isDisposed())
					return;
				refreshAfterDiff(message);
			}
		};
		if (Display.getCurrent() != null)
			runnable.run();
		else
			Display.getDefault().asyncExec(runnable);
	}

	/**
	 * This method is called from within <code>diff()</code> after the
	 * difference tree has been built. Clients may override this method to
	 * perform their own post-processing. This default implementation does
	 * nothing.
	 * 
	 * @param differencer the differencer used to perform the differencing
	 * @param root the non-<code>null</code> root node of the difference tree
	 * @since 2.0
	 * @deprecated Subclasses should override
	 *             {@link #postDiffHook(Differencer, IDiffContainer, IProgressMonitor)}
	 *             instead
	 */
	protected void postDiffHook(Differencer differencer, IDiffContainer root) {
		// we do nothing here
	}
	
	/**
	 * This method is called from within {@link #diff(IProgressMonitor)} after
	 * the difference tree has been built. This method may be called from a
	 * background (non-UI) thread).
	 * <p>
	 * For backwards compatibility, this default implementation calls
	 * {@link #postDiffHook(Differencer, IDiffContainer)} from the UI thread.
	 * Clients should override this method even if they don't perform post
	 * processing to avoid the call to the UI thread.
	 * 
	 * @param differencer the differencer used to perform the differencing
	 * @param root the non-<code>null</code> root node of the difference tree
	 * @param monitor a progress monitor or <code>null</code> if progress is
	 *            not required
	 * @since 3.3
	 */
	protected void postDiffHook(final Differencer differencer, final IDiffContainer root, IProgressMonitor monitor) {
		syncExec(new Runnable() {
			public void run() {
				postDiffHook(differencer, root);
			}
		});
	}
	
	/*
	 * Performs a byte compare on the given objects.
	 * Called from the difference engine.
	 * Returns <code>null</code> if no structure creator has been set.
	 */
	private boolean contentsEqual(Object o1, Object o2) {
		if (fStructureCreator != null) {
			boolean ignoreWhiteSpace= Utilities.getBoolean(getCompareConfiguration(), CompareConfiguration.IGNORE_WHITESPACE, false);		
			String s1= fStructureCreator.getContents(o1, ignoreWhiteSpace);
			String s2= fStructureCreator.getContents(o2, ignoreWhiteSpace);
			if (s1 == null || s2 == null)
				return false;
			return s1.equals(s2);
		}
		return false;
	}
	
	/**
	 * Tracks property changes of the configuration object.
	 * Clients may override to track their own property changes.
	 * In this case they must call the inherited method.
	 * @param event the property changed event that triggered the call to this method
	 */
	protected void propertyChange(PropertyChangeEvent event) {
		String key= event.getProperty();
		if (key.equals(CompareConfiguration.IGNORE_WHITESPACE)) {
			diff();
		} else if (key.equals("ANCESTOR_STRUCTURE_REFRESH")) { //$NON-NLS-1$
			fAncestorStructure.refresh(new NullProgressMonitor());
			diff();
		} else if (key.equals("LEFT_STRUCTURE_REFRESH")) { //$NON-NLS-1$
			fLeftStructure.refresh(new NullProgressMonitor());
			diff();
		} else if (key.equals("RIGHT_STRUCTURE_REFRESH")) { //$NON-NLS-1$
			fRightStructure.refresh(new NullProgressMonitor());
			diff();
		} else if (key.equals("ALL_STRUCTURE_REFRESH")) { //$NON-NLS-1$
			fAncestorStructure.refresh(new NullProgressMonitor());
			fLeftStructure.refresh(new NullProgressMonitor());
			fRightStructure.refresh(new NullProgressMonitor());
			diff();
		} else {
			super.propertyChange(event);
		}
	}
		
	/**
	 * Overridden to call the <code>save</code> method on the structure creator after
	 * nodes have been copied from one side to the other side of an input object.
	 *
	 * @param leftToRight if <code>true</code> the left side is copied to the right side.
	 * If <code>false</code> the right side is copied to the left side
	 */
	protected void copySelected(boolean leftToRight) {
		super.copySelected(leftToRight);
		
		if (fStructureCreator != null)
			fStructureCreator.save(
							leftToRight ? fRightStructure.getStructureComparator() : fLeftStructure.getStructureComparator(),
							leftToRight ? fRightStructure.getInput() : fLeftStructure.getInput());
	}
	
	private void syncExec(final Runnable runnable) {
		if (getControl().isDisposed())
			return;
		if (Display.getCurrent() != null)
			runnable.run();
		else
			getControl().getDisplay().syncExec(new Runnable() {
				public void run() {
					if (!getControl().isDisposed())
						runnable.run();
				}
			});
	}
}

