/*
 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0 which is available at
 * https://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.eclipse.jgit.treewalk.filter;

import java.io.IOException;

import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;

/**
 * Selects interesting tree entries during walking.
 * <p>
 * This is an abstract interface. Applications may implement a subclass, or use
 * one of the predefined implementations already available within this package.
 * <p>
 * Unless specifically noted otherwise a TreeFilter implementation is not thread
 * safe and may not be shared by different TreeWalk instances at the same time.
 * This restriction allows TreeFilter implementations to cache state within
 * their instances during {@link #include(TreeWalk)} if it is beneficial to
 * their implementation. Deep clones created by {@link #clone()} may be used to
 * construct a thread-safe copy of an existing filter.
 *
 * <p>
 * <b>Path filters:</b>
 * <ul>
 * <li>Matching pathname:
 * {@link org.eclipse.jgit.treewalk.filter.PathFilter}</li>
 * </ul>
 *
 * <p>
 * <b>Difference filters:</b>
 * <ul>
 * <li>Only select differences: {@link #ANY_DIFF}.</li>
 * </ul>
 *
 * <p>
 * <b>Boolean modifiers:</b>
 * <ul>
 * <li>AND: {@link org.eclipse.jgit.treewalk.filter.AndTreeFilter}</li>
 * <li>OR: {@link org.eclipse.jgit.treewalk.filter.OrTreeFilter}</li>
 * <li>NOT: {@link org.eclipse.jgit.treewalk.filter.NotTreeFilter}</li>
 * </ul>
 */
public abstract class TreeFilter {
	/** Selects all tree entries. */
	public static final TreeFilter ALL = new AllFilter();

	private static final class AllFilter extends TreeFilter {
		@Override
		public boolean include(TreeWalk walker) {
			return true;
		}

		@Override
		public boolean shouldBeRecursive() {
			return false;
		}

		@Override
		public TreeFilter clone() {
			return this;
		}

		@Override
		public String toString() {
			return "ALL"; //$NON-NLS-1$
		}
	}

	/**
	 * Selects only tree entries which differ between at least 2 trees.
	 * <p>
	 * This filter also prevents a TreeWalk from recursing into a subtree if all
	 * parent trees have the identical subtree at the same path. This
	 * dramatically improves walk performance as only the changed subtrees are
	 * entered into.
	 * <p>
	 * If this filter is applied to a walker with only one tree it behaves like
	 * {@link #ALL}, or as though the walker was matching a virtual empty tree
	 * against the single tree it was actually given. Applications may wish to
	 * treat such a difference as "all names added".
	 * <p>
	 * When comparing {@link WorkingTreeIterator} and {@link DirCacheIterator}
	 * applications should use {@link IndexDiffFilter}.
	 */
	public static final TreeFilter ANY_DIFF = new AnyDiffFilter();

	private static final class AnyDiffFilter extends TreeFilter {
		private static final int baseTree = 0;

		@Override
		public boolean include(TreeWalk walker) {
			final int n = walker.getTreeCount();
			if (n == 1) // Assume they meant difference to empty tree.
				return true;

			final int m = walker.getRawMode(baseTree);
			for (int i = 1; i < n; i++)
				if (walker.getRawMode(i) != m || !walker.idEqual(i, baseTree))
					return true;
			return false;
		}

		@Override
		public boolean shouldBeRecursive() {
			return false;
		}

		@Override
		public TreeFilter clone() {
			return this;
		}

		@Override
		public String toString() {
			return "ANY_DIFF"; //$NON-NLS-1$
		}
	}

	/**
	 * Create a new filter that does the opposite of this filter.
	 *
	 * @return a new filter that includes tree entries this filter rejects.
	 */
	public TreeFilter negate() {
		return NotTreeFilter.create(this);
	}

	/**
	 * Determine if the current entry is interesting to report.
	 * <p>
	 * This method is consulted for subtree entries even if
	 * {@link org.eclipse.jgit.treewalk.TreeWalk#isRecursive()} is enabled. The
	 * consultation allows the filter to bypass subtree recursion on a
	 * case-by-case basis, even when recursion is enabled at the application
	 * level.
	 *
	 * @param walker
	 *            the walker the filter needs to examine.
	 * @return true if the current entry should be seen by the application;
	 *         false to hide the entry.
	 * @throws org.eclipse.jgit.errors.MissingObjectException
	 *             an object the filter needs to consult to determine its answer
	 *             does not exist in the Git repository the walker is operating
	 *             on. Filtering this current walker entry is impossible without
	 *             the object.
	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
	 *             an object the filter needed to consult was not of the
	 *             expected object type. This usually indicates a corrupt
	 *             repository, as an object link is referencing the wrong type.
	 * @throws java.io.IOException
	 *             a loose object or pack file could not be read to obtain data
	 *             necessary for the filter to make its decision.
	 */
	public abstract boolean include(TreeWalk walker)
			throws MissingObjectException, IncorrectObjectTypeException,
			IOException;

	/**
	 * Determine if the current entry is a parent, a match, or no match.
	 * <p>
	 * This method extends the result returned by {@link #include(TreeWalk)}
	 * with a third option (-1), splitting the value true. This gives the
	 * application a possibility to distinguish between an exact match and the
	 * case when a subtree to the current entry might be a match.
	 *
	 * @param walker
	 *            the walker the filter needs to examine.
	 * @return -1 if the current entry is a parent of the filter but no exact
	 *         match has been made; 0 if the current entry should be seen by the
	 *         application; 1 if it should be hidden.
	 * @throws org.eclipse.jgit.errors.MissingObjectException
	 *             as thrown by {@link #include(TreeWalk)}
	 * @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
	 *             as thrown by {@link #include(TreeWalk)}
	 * @throws java.io.IOException
	 *             as thrown by {@link #include(TreeWalk)}
	 * @since 4.7
	 */
	public int matchFilter(TreeWalk walker)
			throws MissingObjectException, IncorrectObjectTypeException,
			IOException
	{
		return include(walker) ? 0 : 1;
	}

	/**
	 * Does this tree filter require a recursive walk to match everything?
	 * <p>
	 * If this tree filter is matching on full entry path names and its pattern
	 * is looking for a '/' then the filter would require a recursive TreeWalk
	 * to accurately make its decisions. The walker is not required to enable
	 * recursive behavior for any particular filter, this is only a hint.
	 *
	 * @return true if the filter would like to have the walker recurse into
	 *         subtrees to make sure it matches everything correctly; false if
	 *         the filter does not require entering subtrees.
	 */
	public abstract boolean shouldBeRecursive();

	/**
	 * {@inheritDoc}
	 *
	 * Clone this tree filter, including its parameters.
	 * <p>
	 * This is a deep clone. If this filter embeds objects or other filters it
	 * must also clone those, to ensure the instances do not share mutable data.
	 */
	@Override
	public abstract TreeFilter clone();

	/** {@inheritDoc} */
	@Override
	public String toString() {
		String n = getClass().getName();
		int lastDot = n.lastIndexOf('.');
		if (lastDot >= 0) {
			n = n.substring(lastDot + 1);
		}
		return n.replace('$', '.');
	}
}
