/*
 * Copyright (C) 2010, Red Hat Inc.
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.eclipse.jgit.ignore;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;

import org.eclipse.jgit.junit.Assert;
import org.junit.Test;

/**
 * Tests ignore pattern matches
 */
public class IgnoreMatcherParametrizedTest {

	@Test
	public void testBasic() {
		String pattern = "/test.stp";
		assertMatched(pattern, "/test.stp");

		pattern = "#/test.stp";
		assertNotMatched(pattern, "/test.stp");
	}

	@Test
	public void testFileNameWildcards() {
		// Test basic * and ? for any pattern + any character
		String pattern = "*.st?";
		assertMatched(pattern, "/test.stp");
		assertMatched(pattern, "/anothertest.stg");
		assertMatched(pattern, "/anothertest.st0");
		assertNotMatched(pattern, "/anothertest.sta1");
		// Check that asterisk does not expand to "/"
		assertNotMatched(pattern, "/another/test.sta1");

		// Same as above, with a leading slash to ensure that doesn't cause
		// problems
		pattern = "/*.st?";
		assertMatched(pattern, "/test.stp");
		assertMatched(pattern, "/anothertest.stg");
		assertMatched(pattern, "/anothertest.st0");
		assertNotMatched(pattern, "/anothertest.sta1");
		// Check that asterisk does not expand to "/"
		assertNotMatched(pattern, "/another/test.sta1");

		// Test for numbers
		pattern = "*.sta[0-5]";
		assertMatched(pattern, "/test.sta5");
		assertMatched(pattern, "/test.sta4");
		assertMatched(pattern, "/test.sta3");
		assertMatched(pattern, "/test.sta2");
		assertMatched(pattern, "/test.sta1");
		assertMatched(pattern, "/test.sta0");
		assertMatched(pattern, "/anothertest.sta2");
		assertNotMatched(pattern, "test.stag");
		assertNotMatched(pattern, "test.sta6");

		// Test for letters
		pattern = "/[tv]est.sta[a-d]";
		assertMatched(pattern, "/test.staa");
		assertMatched(pattern, "/test.stab");
		assertMatched(pattern, "/test.stac");
		assertMatched(pattern, "/test.stad");
		assertMatched(pattern, "/vest.stac");
		assertNotMatched(pattern, "test.stae");
		assertNotMatched(pattern, "test.sta9");

		// Test child directory/file is matched
		pattern = "/src/ne?";
		assertMatched(pattern, "/src/new/");
		assertMatched(pattern, "/src/new");
		assertMatched(pattern, "/src/new/a.c");
		assertMatched(pattern, "/src/new/a/a.c");
		assertNotMatched(pattern, "/src/new.c");

		// Test name-only fnmatcher matches
		pattern = "ne?";
		assertMatched(pattern, "/src/new/");
		assertMatched(pattern, "/src/new");
		assertMatched(pattern, "/src/new/a.c");
		assertMatched(pattern, "/src/new/a/a.c");
		assertMatched(pattern, "/neb");
		assertNotMatched(pattern, "/src/new.c");
	}

	@Test
	public void testTargetWithoutLeadingSlash() {
		// Test basic * and ? for any pattern + any character
		String pattern = "/*.st?";
		assertMatched(pattern, "test.stp");
		assertMatched(pattern, "anothertest.stg");
		assertMatched(pattern, "anothertest.st0");
		assertNotMatched(pattern, "anothertest.sta1");
		// Check that asterisk does not expand to ""
		assertNotMatched(pattern, "another/test.sta1");

		// Same as above, with a leading slash to ensure that doesn't cause
		// problems
		pattern = "/*.st?";
		assertMatched(pattern, "test.stp");
		assertMatched(pattern, "anothertest.stg");
		assertMatched(pattern, "anothertest.st0");
		assertNotMatched(pattern, "anothertest.sta1");
		// Check that asterisk does not expand to ""
		assertNotMatched(pattern, "another/test.sta1");

		// Test for numbers
		pattern = "/*.sta[0-5]";
		assertMatched(pattern, "test.sta5");
		assertMatched(pattern, "test.sta4");
		assertMatched(pattern, "test.sta3");
		assertMatched(pattern, "test.sta2");
		assertMatched(pattern, "test.sta1");
		assertMatched(pattern, "test.sta0");
		assertMatched(pattern, "anothertest.sta2");
		assertNotMatched(pattern, "test.stag");
		assertNotMatched(pattern, "test.sta6");

		// Test for letters
		pattern = "/[tv]est.sta[a-d]";
		assertMatched(pattern, "test.staa");
		assertMatched(pattern, "test.stab");
		assertMatched(pattern, "test.stac");
		assertMatched(pattern, "test.stad");
		assertMatched(pattern, "vest.stac");
		assertNotMatched(pattern, "test.stae");
		assertNotMatched(pattern, "test.sta9");

		// Test child directory/file is matched
		pattern = "/src/ne?";
		assertMatched(pattern, "src/new/");
		assertMatched(pattern, "src/new");
		assertMatched(pattern, "src/new/a.c");
		assertMatched(pattern, "src/new/a/a.c");
		assertNotMatched(pattern, "src/new.c");

		// Test name-only fnmatcher matches
		pattern = "ne?";
		assertMatched(pattern, "src/new/");
		assertMatched(pattern, "src/new");
		assertMatched(pattern, "src/new/a.c");
		assertMatched(pattern, "src/new/a/a.c");
		assertMatched(pattern, "neb");
		assertNotMatched(pattern, "src/new.c");
	}

	@Test
	public void testParentDirectoryGitIgnores() {
		// Contains git ignore patterns such as might be seen in a parent
		// directory

		// Test for wildcards
		String pattern = "/*/*.c";
		assertMatched(pattern, "/file/a.c");
		assertMatched(pattern, "/src/a.c");
		assertNotMatched(pattern, "/src/new/a.c");

		// Test child directory/file is matched
		pattern = "/src/new";
		assertMatched(pattern, "/src/new/");
		assertMatched(pattern, "/src/new");
		assertMatched(pattern, "/src/new/a.c");
		assertMatched(pattern, "/src/new/a/a.c");
		assertNotMatched(pattern, "/src/new.c");

		// Test child directory is matched, slash after name
		pattern = "/src/new/";
		assertMatched(pattern, "/src/new/");
		assertMatched(pattern, "/src/new/a.c");
		assertMatched(pattern, "/src/new/a/a.c");
		assertNotMatched(pattern, "/src/new");
		assertNotMatched(pattern, "/src/new.c");

		// Test directory is matched by name only
		pattern = "b1";
		assertMatched(pattern, "/src/new/a/b1/a.c");
		assertNotMatched(pattern, "/src/new/a/b2/file.c");
		assertNotMatched(pattern, "/src/new/a/bb1/file.c");
		assertNotMatched(pattern, "/src/new/a/file.c");
	}

	@Test
	public void testDirModeAndNoRegex() {
		String pattern = "/src/";
		assertMatched(pattern, "/src/");
		assertMatched(pattern, "/src/new");
		assertMatched(pattern, "/src/new/a.c");
		assertMatched(pattern, "/src/a.c");
		// no match as a "file" pattern, because rule is for directories only
		assertNotMatched(pattern, "/src");
		assertNotMatched(pattern, "/srcA/");
	}

	@Test
	public void testDirModeAndRegex1() {
		String pattern = "a/*/src/";
		assertMatched(pattern, "a/b/src/");
		assertMatched(pattern, "a/b/src/new");
		assertMatched(pattern, "a/b/src/new/a.c");
		assertMatched(pattern, "a/b/src/a.c");
		// no match as a "file" pattern, because rule is for directories only
		assertNotMatched(pattern, "a/b/src");
		assertNotMatched(pattern, "a/b/srcA/");
	}

	@Test
	public void testDirModeAndRegex2() {
		String pattern = "a/[a-b]/src/";
		assertMatched(pattern, "a/b/src/");
		assertMatched(pattern, "a/b/src/new");
		assertMatched(pattern, "a/b/src/new/a.c");
		assertMatched(pattern, "a/b/src/a.c");
		// no match as a "file" pattern, because rule is for directories only
		assertNotMatched(pattern, "a/b/src");
		assertNotMatched(pattern, "a/b/srcA/");
	}

	@Test
	public void testDirModeAndRegex3() {
		String pattern = "**/src/";
		assertMatched(pattern, "a/b/src/");
		assertMatched(pattern, "a/b/src/new");
		assertMatched(pattern, "a/b/src/new/a.c");
		assertMatched(pattern, "a/b/src/a.c");
		// no match as a "file" pattern, because rule is for directories only
		assertNotMatched(pattern, "a/b/src");
		assertNotMatched(pattern, "a/b/srcA/");
	}

	@Test
	public void testNameOnlyMatches() {
		/*
		 * Name-only matches do not contain any path separators
		 */
		// Test matches for file extension
		String pattern = "*.stp";
		assertMatched(pattern, "/test.stp");
		assertMatched(pattern, "/src/test.stp");
		assertNotMatched(pattern, "/test.stp1");
		assertNotMatched(pattern, "/test.astp");

		// Test matches for name-only, applies to file name or folder name
		pattern = "src";
		assertMatched(pattern, "/src");
		assertMatched(pattern, "/src/");
		assertMatched(pattern, "/src/a.c");
		assertMatched(pattern, "/src/new/a.c");
		assertMatched(pattern, "/new/src/a.c");
		assertMatched(pattern, "/file/src");

		// Test matches for name-only, applies only to folder names
		pattern = "src/";
		assertMatched(pattern, "/src/");
		assertMatched(pattern, "/src/a.c");
		assertMatched(pattern, "/src/new/a.c");
		assertMatched(pattern, "/new/src/a.c");
		assertNotMatched(pattern, "/src");
		assertNotMatched(pattern, "/file/src");

		// Test matches for name-only, applies to file name or folder name
		// With a small wildcard
		pattern = "?rc";
		assertMatched(pattern, "/src/a.c");
		assertMatched(pattern, "/src/new/a.c");
		assertMatched(pattern, "/new/src/a.c");
		assertMatched(pattern, "/file/src");
		assertMatched(pattern, "/src/");

		// Test matches for name-only, applies to file name or folder name
		// With a small wildcard
		pattern = "?r[a-c]";
		assertMatched(pattern, "/src/a.c");
		assertMatched(pattern, "/src/new/a.c");
		assertMatched(pattern, "/new/src/a.c");
		assertMatched(pattern, "/file/src");
		assertMatched(pattern, "/src/");
		assertMatched(pattern, "/srb/a.c");
		assertMatched(pattern, "/grb/new/a.c");
		assertMatched(pattern, "/new/crb/a.c");
		assertMatched(pattern, "/file/3rb");
		assertMatched(pattern, "/xrb/");
		assertMatched(pattern, "/3ra/a.c");
		assertMatched(pattern, "/5ra/new/a.c");
		assertMatched(pattern, "/new/1ra/a.c");
		assertMatched(pattern, "/file/dra");
		assertMatched(pattern, "/era/");
		assertNotMatched(pattern, "/crg");
		assertNotMatched(pattern, "/cr3");
	}

	@Test
	public void testNegation() {
		String pattern = "!/test.stp";
		assertMatched(pattern, "/test.stp");
	}

	/**
	 * Check for a match. If target ends with "/", match will assume that the
	 * target is meant to be a directory.
	 *
	 * @param pattern
	 *            Pattern as it would appear in a .gitignore file
	 * @param target
	 *            Target file path relative to repository's GIT_DIR
	 * @param assume
	 */
	private void assertMatched(String pattern, String target, Boolean... assume) {
		boolean value = match(pattern, target);
		if (assume.length == 0 || !assume[0].booleanValue())
			assertTrue("Expected a match for: " + pattern + " with: " + target,
					value);
		else
			assumeTrue("Expected a match for: " + pattern + " with: " + target,
					value);
	}

	/**
	 * Check for a match. If target ends with "/", match will assume that the
	 * target is meant to be a directory.
	 *
	 * @param pattern
	 *            Pattern as it would appear in a .gitignore file
	 * @param target
	 *            Target file path relative to repository's GIT_DIR
	 * @param assume
	 */
	private void assertNotMatched(String pattern, String target,
			Boolean... assume) {
		boolean value = match(pattern, target);
		if (assume.length == 0 || !assume[0].booleanValue())
			assertFalse("Expected no match for: " + pattern + " with: "
					+ target, value);
		else
			assumeFalse("Expected no match for: " + pattern + " with: "
					+ target, value);
	}

	/**
	 * Check for a match. If target ends with "/", match will assume that the
	 * target is meant to be a directory.
	 *
	 * @param pattern
	 *            Pattern as it would appear in a .gitignore file
	 * @param target
	 *            Target file path relative to repository's GIT_DIR
	 * @return Result of {@link FastIgnoreRule#isMatch(String, boolean)}
	 */
	private boolean match(String pattern, String target) {
		boolean isDirectory = target.endsWith("/");
		boolean match;
		FastIgnoreRule r = new FastIgnoreRule(pattern);
		match = r.isMatch(target, isDirectory);

		if (isDirectory) {
			boolean noTrailingSlash = matchAsDir(pattern,
					target.substring(0, target.length() - 1));
			if (match != noTrailingSlash) {
				String message = "Difference in result for directory pattern: "
						+ pattern + " with: " + target
						+ " if target is given without trailing slash";
				Assert.assertEquals(message, match, noTrailingSlash);
			}
		}
		return match;
	}

	/**
	 *
	 * @param target
	 *            must not ends with a slash!
	 * @param pattern
	 *            same as {@link #match(String, String)}
	 * @return same as {@link #match(String, String)}
	 */
	private boolean matchAsDir(String pattern, String target) {
		assertFalse(target.endsWith("/"));
		FastIgnoreRule r = new FastIgnoreRule(pattern);
		return r.isMatch(target, true);
	}
}
