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

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.compare.IStreamContentAccessor;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.internal.MergeViewerContentProvider;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;

import com.ibm.icu.text.MessageFormat;

/**
 * A generic two-way or three-way differencing engine.
 * <p>
 * The engine is used by calling one of the <code>findDifferences</code> methods and passing
 * in the objects to compare.
 * The engine calls the following methods on the input objects to perform the compare:
 * <UL>
 * <LI><code>getChildren</code>: for enumerating the children of an object (if any),
 * <LI><code>contentsEqual</code>: for comparing the content of leaf objects, that is, objects without children,
 * <LI><code>visit</code>: for every pair of compared object the compare result is passed in.
 * </UL>
 * Clients may use as is, or subclass to provide a custom implementation for the three hooks.
 * However the default implementation already deals with the typical case:
 * <UL>
 * <LI><code>getChildren</code>: tries to apply the <code>IStructureComparator</code>
 * 	interface to enumerate the children,
 * <LI><code>contentsEqual</code>: tries to apply the <code>IStreamContentAccessor</code> interface
 *	to perform a byte-wise content comparison,
 * <LI><code>visit</code>: creates a <code>DiffNode</code> for any detected difference between the compared objects and
 *	links it under a parent node effectively creating a tree of differences.
 * </UL>
 * The different kind of changes detected by the engine are decoded as follows:
 * In the two-way case only NO_CHANGE, ADDITION, DELETION, and CHANGE are used.
 * In the three-way case these constants are bitwise ORed with one of directional constants
 * LEFT, RIGHT, and CONFLICTING.
 */
public class Differencer {
	// The kind of differences.
	/**
	 * Difference constant (value 0) indicating no difference.
	 */
	public static final int NO_CHANGE= 0;
	/**
	 * Difference constant (value 1) indicating one side was added.
	 */
	public static final int ADDITION= 1;
	/**
	 * Difference constant (value 2) indicating one side was removed.
	 */
	public static final int DELETION= 2;
	/**
	 * Difference constant (value 3) indicating side changed.
	 */
	public static final int CHANGE= 3;

	/**
	 * Bit mask (value 3) for extracting the kind of difference.
	 */
	public static final int CHANGE_TYPE_MASK= 3;

	// The direction of a three-way change.
	/**
	 * Three-way change constant (value 4) indicating a change on left side.
	 */
	public static final int LEFT= 4;

	/**
	 * Three-way change constant (value 8) indicating a change on right side.
	 */
	public static final int RIGHT= 8;

	/**
	 * Three-way change constant (value 12) indicating a change on left and
	 * right sides.
	 */
	public static final int CONFLICTING= 12;

	/**
	 * Bit mask (value 12) for extracting the direction of a three-way change.
	 */
	public static final int DIRECTION_MASK= 12;

	/**
	 * Constant (value 16) indicating a change on left and
	 * right side (with respect to ancestor) but left and right are identical.
	 */
	public static final int PSEUDO_CONFLICT= 16;


	static class Node {
		List<Node> fChildren;
		int fCode;
		Object fAncestor;
		Object fLeft;
		Object fRight;

		Node() {
			// nothing to do
		}

		Node(Node parent, Object ancestor, Object left, Object right) {
			parent.add(this);
			fAncestor= ancestor;
			fLeft= left;
			fRight= right;
		}

		void add(Node child) {
			if (fChildren == null)
				fChildren= new ArrayList<>();
			fChildren.add(child);
		}

		Object visit(Differencer d, Object parent, int level) {
			if (fCode == NO_CHANGE)
				return null;
			//dump(level);
			Object data= d.visit(parent, fCode, fAncestor, fLeft, fRight);
			if (fChildren != null) {
				for (Node n : fChildren) {
					n.visit(d, data, level + 1);
				}
			}
			return data;
		}

//		private void dump(int level) {
//			String name= null;
//			if (fAncestor instanceof ITypedElement)
//				name= ((ITypedElement)fAncestor).getName();
//			if (name == null && fLeft instanceof ITypedElement)
//				name= ((ITypedElement)fLeft).getName();
//			if (name == null && fRight instanceof ITypedElement)
//				name= ((ITypedElement)fRight).getName();
//			if (name == null)
//				name= "???"; //$NON-NLS-1$
//
//			for (int i= 0; i < level; i++)
//				System.out.print("  "); //$NON-NLS-1$
//
//			System.out.println(getDiffType(fCode) + name);
//		}

//		private String getDiffType(int code) {
//			String dir= " "; //$NON-NLS-1$
//			switch (code & DIRECTION_MASK) {
//			case LEFT:
//				dir= ">"; //$NON-NLS-1$
//				break;
//			case RIGHT:
//				dir= "<"; //$NON-NLS-1$
//				break;
//			case CONFLICTING:
//				dir= "!"; //$NON-NLS-1$
//				break;
//			}
//			String change= "="; //$NON-NLS-1$
//			switch (code & CHANGE_TYPE_MASK) {
//			case ADDITION:
//				change= "+"; //$NON-NLS-1$
//				break;
//			case DELETION:
//				change= "-"; //$NON-NLS-1$
//				break;
//			case CHANGE:
//				change= "#"; //$NON-NLS-1$
//				break;
//			}
//			return dir + change + " "; //$NON-NLS-1$
//		}
	}

	/**
	 * Creates a new differencing engine.
	 */
	public Differencer() {
		// nothing to do
	}

	/**
	 * Starts the differencing engine on the three input objects. If threeWay is <code>true</code> a
	 * three-way comparison is performed, otherwise a two-way compare (in the latter case the ancestor argument is ignored).
	 * The progress monitor is passed to the method <code>updateProgress</code> which is called for every node or
	 * leaf compare. The method returns the object that was returned from the top-most call to method <code>visit</code>.
	 * At most two of the ancestor, left, and right parameters are allowed to be <code>null</code>.
	 *
	 * @param threeWay if <code>true</code> a three-way comparison is performed, otherwise a two-way compare
	 * @param pm a progress monitor which is passed to method <code>updateProgress</code>
	 * @param data a client data that is passed to the top-level call to <code>visit</code>
	 * @param ancestor the ancestor object of the compare (may be <code>null</code>)
	 * @param left the left object of the compare
	 * @param right the right object of the compare
	 * @return the object returned from the top most call to method <code>visit</code>,
	 *   possibly <code>null</code>
	 */
	public Object findDifferences(boolean threeWay, IProgressMonitor pm, Object data, Object ancestor, Object left, Object right) {
		Node root= new Node();

		int code= traverse(threeWay, root, pm, threeWay ? ancestor : null, left, right);

		if (code != NO_CHANGE) {
			List<Node> l= root.fChildren;
			if (!l.isEmpty()) {
				Node first= l.get(0);
				return first.visit(this, data, 0);
			}
		}
		return null;
	}

	/*
	 * Traverse tree in postorder.
	 */
	private int traverse(boolean threeWay, Node parent, IProgressMonitor pm,
			Object ancestor, Object left, Object right) {
		Object[] ancestorChildren= getChildren(ancestor);
		Object[] rightChildren= getChildren(right);
		Object[] leftChildren= getChildren(left);

		int code= NO_CHANGE;

		Node node= new Node(parent, ancestor, left, right);

		boolean content= true;	// we reset this if we have at least one child

		if (((threeWay && ancestorChildren != null) || !threeWay)
					 && rightChildren != null && leftChildren != null) {
			// we only recurse down if no leg is null
			// a node

			Set<Object> allSet= new HashSet<>(20);
			Map<Object, Object> ancestorSet= null;
			Map<Object, Object> rightSet= null;
			Map<Object, Object> leftSet= null;

			if (ancestorChildren != null) {
				ancestorSet= new HashMap<>(10);
				for (int i= 0; i < ancestorChildren.length; i++) {
					Object ancestorChild= ancestorChildren[i];
					ancestorSet.put(ancestorChild, ancestorChild);
					allSet.add(ancestorChild);
				}
			}

			if (rightChildren != null) {
				rightSet= new HashMap<>(10);
				for (int i= 0; i < rightChildren.length; i++) {
					Object rightChild= rightChildren[i];
					rightSet.put(rightChild, rightChild);
					allSet.add(rightChild);
				}
			}

			if (leftChildren != null) {
				leftSet= new HashMap<>(10);
				for (int i= 0; i < leftChildren.length; i++) {
					Object leftChild= leftChildren[i];
					leftSet.put(leftChild, leftChild);
					allSet.add(leftChild);
				}
			}

			for (Object keyChild : allSet) {
				if (pm != null) {
					if (pm.isCanceled())
						throw new OperationCanceledException();

					updateProgress(pm, keyChild);
				}

				Object ancestorChild= ancestorSet != null ? ancestorSet.get(keyChild) : null;
				Object leftChild= leftSet != null ? leftSet.get(keyChild) : null;
				Object rightChild= rightSet != null ? rightSet.get(keyChild) : null;

				int c= traverse(threeWay, node, pm, ancestorChild, leftChild, rightChild);

				if ((c & CHANGE_TYPE_MASK) != NO_CHANGE) {
					code|= CHANGE;	// deletions and additions of child result in a change of the container
					code|= (c & DIRECTION_MASK);	// incoming & outgoing are just ored
					content= false;
				}
			}
		}

		if (content)			// a leaf
			code= compare(threeWay, ancestor, left, right);

		node.fCode= code;

		return code;
	}

	/**
	 * Called for every node or leaf comparison.
	 * The differencing engine passes in the input objects of the compare and the result of the compare.
	 * The data object is the value returned from a call to the <code>visit</code> method on the parent input.
	 * It can be considered the "parent" reference and is useful when building a tree.
	 * <p>
	 * The <code>Differencer</code> implementation returns a new
	 * <code>DiffNode</code> which is initialized with the corresponding values.
	 * Subclasses may override.
	 *
	 * @param data object returned from parent call to <code>visit</code>,
	 *   possibly <code>null</code>
	 * @param result the result of the compare operation performed on the three inputs
	 * @param ancestor the compare ancestor of the left and right inputs
	 * @param left the left input to the compare
	 * @param right the right input to the compare
	 * @return the result, possibly <code>null</code>
	 */
	protected Object visit(Object data, int result, Object ancestor, Object left, Object right) {
		return new DiffNode((IDiffContainer) data, result, (ITypedElement)ancestor, (ITypedElement)left, (ITypedElement)right);
	}

	/*
	 * Performs a 2-way or 3-way compare of the given leaf elements and returns an integer
	 * describing the kind of difference.
	 */
	private int compare(boolean threeway, Object ancestor, Object left, Object right) {

		int description= NO_CHANGE;

		if (threeway) {
			if (ancestor == null) {
				if (left == null) {
					if (right == null) {
						Assert.isTrue(false);
						// shouldn't happen
					} else {
						description= RIGHT | ADDITION;
					}
				} else {
					if (right == null) {
						description= LEFT | ADDITION;
					} else {
						description= CONFLICTING | ADDITION;
						if (contentsEqual(left, MergeViewerContentProvider.LEFT_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR))
							description|= PSEUDO_CONFLICT;
					}
				}
			} else {
				if (left == null) {
					if (right == null) {
						description= CONFLICTING | DELETION | PSEUDO_CONFLICT;
					} else {
						if (contentsEqual(ancestor,	MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR))
							description= LEFT | DELETION;
						else
							description= CONFLICTING | CHANGE;
					}
				} else {
					if (right == null) {
						if (contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, left, MergeViewerContentProvider.LEFT_CONTRIBUTOR))
							description= RIGHT | DELETION;
						else
							description= CONFLICTING | CHANGE;
					} else {
						boolean ay= contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, left, MergeViewerContentProvider.LEFT_CONTRIBUTOR);
						boolean am= contentsEqual(ancestor, MergeViewerContentProvider.ANCESTOR_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR);

						if (ay && am) {
							// empty
						} else if (ay && !am) {
							description= RIGHT | CHANGE;
						} else if (!ay && am) {
							description= LEFT | CHANGE;
						} else {
							description= CONFLICTING | CHANGE;
							if (contentsEqual(left, MergeViewerContentProvider.LEFT_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR))
								description|= PSEUDO_CONFLICT;
						}
					}
				}
			}
		} else {	// two way compare ignores ancestor
			if (left == null) {
				if (right == null) {
					Assert.isTrue(false);
					// shouldn't happen
				} else {
					description= ADDITION;
				}
			} else {
				if (right == null) {
					description= DELETION;
				} else {
					if (! contentsEqual(left, MergeViewerContentProvider.LEFT_CONTRIBUTOR, right, MergeViewerContentProvider.RIGHT_CONTRIBUTOR))
						description= CHANGE;
				}
			}
		}

		return description;
	}

	/**
	 * Performs a content compare on the two given inputs.
	 * <p>
	 * The <code>Differencer</code> implementation returns
	 * <code>contentsEqual(Object input1, Object input2)</code>. Subclasses may
	 * override to implement a different content compare on the given inputs.
	 * </p>
	 *
	 * @param input1
	 *            first input to contents compare
	 * @param contributor1
	 *            the contributor of input1
	 * @param input2
	 *            second input to contents compare
	 * @param contributor2
	 *            the contributor of input2
	 * @return <code>true</code> if content is equal
	 * @noreference This method is not intended to be referenced by clients.
	 * @since 3.6
	 */
	protected boolean contentsEqual(Object input1, char contributor1,
			Object input2, char contributor2) {
		return contentsEqual(input1, input2);
	}

	/**
	 * Performs a content compare on the two given inputs.
	 * <p>
	 * The <code>Differencer</code> implementation
	 * returns <code>true</code> if both inputs implement <code>IStreamContentAccessor</code>
	 * and their byte contents is identical. Subclasses may override to implement
	 * a different content compare on the given inputs.
	 * </p>
	 *
	 * @param input1 first input to contents compare
	 * @param input2 second input to contents compare
	 * @return <code>true</code> if content is equal
	 */
	protected boolean contentsEqual(Object input1, Object input2) {

		if (input1 == input2)
			return true;

		InputStream is1= getStream(input1);
		InputStream is2= getStream(input2);

		if (is1 == null && is2 == null)	// no byte contents
			return true;

		try {
			if (is1 == null || is2 == null)	// only one has contents
				return false;

			while (true) {
				int c1= is1.read();
				int c2= is2.read();
				if (c1 == -1 && c2 == -1)
					return true;
				if (c1 != c2)
					break;

			}
		} catch (IOException ex) {
			// NeedWork
		} finally {
			if (is1 != null) {
				try {
					is1.close();
				} catch(IOException ex) {
					// silently ignored
				}
			}
			if (is2 != null) {
				try {
					is2.close();
				} catch(IOException ex) {
					// silently ignored
				}
			}
		}
		return false;
	}

	/*
	 * Tries to return an InputStream for the given object.
	 * Returns <code>null</code> if the object not an IStreamContentAccessor
	 * or an error occurred.
	 */
	private InputStream getStream(Object o) {
		if (o instanceof IStreamContentAccessor) {
			try {
				return ((IStreamContentAccessor)o).getContents();
			} catch(CoreException ex) {
				// NeedWork
			}
		}
		return null;
	}

	/**
	 * Returns the children of the given input or <code>null</code> if there are no children.
	 * <p>
	 * The <code>Differencer</code> implementation checks whether the input
	 * implements the <code>IStructureComparator</code> interface. If yes it is used
	 * to return an array containing all children. Otherwise <code>null</code> is returned.
	 * Subclasses may override to implement a different strategy to enumerate children.
	 * </p>
	 *
	 * @param input the object for which to return children
	 * @return the children of the given input or <code>null</code> if there are no children.
	 */
	protected Object[] getChildren(Object input) {
		if (input instanceof IStructureComparator)
			return ((IStructureComparator) input).getChildren();
		return null;
	}

	/**
	 * Called for every leaf or node compare to update progress information.
	 * <p>
	 * The <code>Differencer</code> implementation shows the name of the input object
	 * as a subtask. Subclasses may override.
	 * </p>
	 *
	 * @param progressMonitor the progress monitor for reporting progress
	 * @param node the currently processed non-<code>null</code> node
	 */
	protected void updateProgress(IProgressMonitor progressMonitor, Object node) {
		if (node instanceof ITypedElement) {
			String name= ((ITypedElement) node).getName();
			String fmt= Utilities.getString("Differencer.progressFormat"); //$NON-NLS-1$
			String msg= MessageFormat.format(fmt, name );
			progressMonitor.subTask(msg);
			//progressMonitor.worked(1);
		}
	}
}

