/*******************************************************************************
 * Copyright (c) 2006, 2013 Wind River Systems, Inc. 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:
 *     Markus Schorn - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.internal.index.tests;

import java.util.LinkedList;
import java.util.regex.Pattern;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.IPDOMNode;
import org.eclipse.cdt.core.dom.IPDOMVisitor;
import org.eclipse.cdt.core.dom.ast.IEnumeration;
import org.eclipse.cdt.core.dom.ast.IEnumerator;
import org.eclipse.cdt.core.dom.ast.IFunction;
import org.eclipse.cdt.core.dom.ast.IVariable;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPClassType;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPField;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPMethod;
import org.eclipse.cdt.core.dom.ast.cpp.ICPPNamespace;
import org.eclipse.cdt.core.index.IIndex;
import org.eclipse.cdt.core.index.IIndexBinding;
import org.eclipse.cdt.core.index.IndexFilter;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.testplugin.CProjectHelper;
import org.eclipse.cdt.internal.core.index.CIndex;
import org.eclipse.cdt.internal.core.pdom.PDOM;
import org.eclipse.cdt.internal.core.pdom.dom.PDOMBinding;
import org.eclipse.core.runtime.CoreException;

import junit.framework.TestSuite;

public class IndexSearchTest extends IndexTestBase {
	private static final IndexFilter INDEX_FILTER = IndexFilter.ALL_DECLARED;

	public static TestSuite suite() {
		TestSuite suite = suite(IndexSearchTest.class, "_");
		suite.addTest(new IndexSearchTest("deleteProject"));
		return suite;
	}

	private ICProject fProject;
	private IIndex fIndex;

	public IndexSearchTest(String name) {
		super(name);
	}

	@Override
	public void setUp() throws Exception {
		super.setUp();
		if (fProject == null) {
			fProject = createProject(true, "resources/indexTests/search");
		}
		fIndex = CCorePlugin.getIndexManager().getIndex(fProject);
		fIndex.acquireReadLock();
	}

	@Override
	public void tearDown() throws Exception {
		fIndex.releaseReadLock();
		super.tearDown();
	}

	public void deleteProject() {
		if (fProject != null) {
			CProjectHelper.delete(fProject);
		}
	}

	private void checkIsClass(IIndexBinding binding) {
		assertTrue(binding instanceof ICPPClassType);
	}

	private void checkIsNamespace(IIndexBinding binding) {
		assertTrue(binding instanceof ICPPNamespace);
	}

	private void checkIsEnumerator(IIndexBinding binding) {
		assertTrue(binding instanceof IEnumerator);
	}

	private void checkIsEnumeration(IIndexBinding binding) {
		assertTrue(binding instanceof IEnumeration);
	}

	private void checkIsFunction(IIndexBinding binding) {
		assertTrue(binding instanceof IFunction);
		assertTrue(!(binding instanceof ICPPMethod));
	}

	private void checkIsVariable(IIndexBinding binding) {
		assertTrue(binding instanceof IVariable);
		assertTrue(!(binding instanceof ICPPField));
	}

	public void testFindClassInNamespace() throws CoreException {
		String scl = "C160913";
		Pattern pcl = Pattern.compile(scl);
		String sns = "ns160913";
		Pattern pns = Pattern.compile(sns);

		IIndexBinding[] bindings;

		bindings = fIndex.findBindings(pcl, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsClass(bindings[0]);

		bindings = fIndex.findBindings(scl.toCharArray(), INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsClass(bindings[0]);

		bindings = fIndex.findBindings(pcl, false, INDEX_FILTER, npm());
		assertEquals(3, bindings.length);
		checkIsClass(bindings[0]);
		checkIsClass(bindings[1]);
		checkIsClass(bindings[2]);

		bindings = fIndex.findBindings(new Pattern[] { pns, pcl }, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsClass(bindings[0]);

		bindings = fIndex.findBindings(new char[][] { sns.toCharArray(), scl.toCharArray() }, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsClass(bindings[0]);

		bindings = fIndex.findBindings(new Pattern[] { pns, pcl }, false, INDEX_FILTER, npm());
		assertEquals(2, bindings.length);
		checkIsClass(bindings[0]);
		checkIsClass(bindings[1]);

		bindings = fIndex.findBindings(new Pattern[] { pns, pns, pcl }, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsClass(bindings[0]);

		bindings = fIndex.findBindings(new char[][] { sns.toCharArray(), sns.toCharArray(), scl.toCharArray() },
				INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsClass(bindings[0]);

		bindings = fIndex.findBindings(new Pattern[] { pns, pns, pcl }, false, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsClass(bindings[0]);
	}

	public void testFindNamespaceInNamespace() throws CoreException {
		Pattern pcl = Pattern.compile("C160913");
		Pattern pns = Pattern.compile("ns160913");
		char[] scl = pcl.pattern().toCharArray();
		char[] sns = pns.pattern().toCharArray();

		IIndexBinding[] bindings;

		bindings = fIndex.findBindings(pns, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsNamespace(bindings[0]);

		bindings = fIndex.findBindings(sns, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsNamespace(bindings[0]);

		bindings = fIndex.findBindings(pns, false, INDEX_FILTER, npm());
		assertEquals(2, bindings.length);
		checkIsNamespace(bindings[0]);
		checkIsNamespace(bindings[1]);

		bindings = fIndex.findBindings(new Pattern[] { pns, pns }, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsNamespace(bindings[0]);

		bindings = fIndex.findBindings(new char[][] { sns, sns }, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsNamespace(bindings[0]);

		bindings = fIndex.findBindings(new Pattern[] { pns, pns }, false, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsNamespace(bindings[0]);
	}

	public void testClassInUnnamedNamespace() throws CoreException {
		Pattern pcl = Pattern.compile("CInUnnamed160913");

		IIndexBinding[] bindings;

		// The binding in the unnamed namespace is not visible in global scope.
		bindings = fIndex.findBindings(pcl, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		assertTrue(bindings[0].isFileLocal());

		bindings = fIndex.findBindings(pcl.pattern().toCharArray(), INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		assertTrue(bindings[0].isFileLocal());
	}

	public void testFindEnumerator() throws CoreException {
		Pattern pEnumeration = Pattern.compile("E20061017");
		Pattern pEnumerator = Pattern.compile("e20061017");
		char[] sEnumeration = pEnumeration.pattern().toCharArray();
		char[] sEnumerator = pEnumerator.pattern().toCharArray();

		IIndexBinding[] bindings;

		// Enumerators are found in global scope.
		bindings = fIndex.findBindings(pEnumerator, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsEnumerator(bindings[0]);

		bindings = fIndex.findBindings(pEnumerator, false, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsEnumerator(bindings[0]);

		bindings = fIndex.findBindings(new Pattern[] { pEnumeration, pEnumerator }, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);

		bindings = fIndex.findBindings(new char[][] { sEnumeration, sEnumerator }, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);

		bindings = fIndex.findBindings(new Pattern[] { pEnumeration, pEnumerator }, false, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);

		bindings = fIndex.findBindings(pEnumeration, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsEnumeration(bindings[0]);

		bindings = fIndex.findBindings(sEnumeration, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsEnumeration(bindings[0]);

		bindings = fIndex.findBindings(pEnumeration, false, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
		checkIsEnumeration(bindings[0]);
	}

	public void testCaseInsensitivePatternSearch_239669() throws CoreException {
		IIndexBinding[] bindings;

		Pattern pEnumAndEnumeration = Pattern.compile("E20061017", Pattern.CASE_INSENSITIVE);
		Pattern pEnumeration = Pattern.compile("E20061017");
		bindings = fIndex.findBindings(pEnumAndEnumeration, true, INDEX_FILTER, npm());
		assertEquals(2, bindings.length);
		bindings = fIndex.findBindings(pEnumeration, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);

		pEnumAndEnumeration = Pattern.compile("E2006101.*", Pattern.CASE_INSENSITIVE);
		pEnumeration = Pattern.compile("E2006101.*");
		bindings = fIndex.findBindings(pEnumAndEnumeration, true, INDEX_FILTER, npm());
		assertEquals(2, bindings.length);
		bindings = fIndex.findBindings(pEnumeration, true, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);

		Pattern macro1 = Pattern.compile("Foo", Pattern.CASE_INSENSITIVE);
		Pattern macro2 = Pattern.compile("Foo");
		bindings = fIndex.findMacroContainers(macro1, INDEX_FILTER, npm());
		assertEquals(2, bindings.length);
		bindings = fIndex.findMacroContainers(macro2, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);

		macro1 = Pattern.compile("Foo.*", Pattern.CASE_INSENSITIVE);
		macro2 = Pattern.compile("Foo.*");
		bindings = fIndex.findMacroContainers(macro1, INDEX_FILTER, npm());
		assertEquals(2, bindings.length);
		bindings = fIndex.findMacroContainers(macro2, INDEX_FILTER, npm());
		assertEquals(1, bindings.length);
	}

	public void testFindStatic_161216() throws CoreException {
		Pattern pFunc = Pattern.compile("staticFunc20061017");
		Pattern pVar = Pattern.compile("staticVar20061017");

		IIndexBinding[] bindings;

		bindings = fIndex.findBindings(pFunc, false, INDEX_FILTER, npm());
		assertEquals(2, bindings.length);
		checkIsFunction(bindings[0]);
		checkIsFunction(bindings[1]);

		bindings = fIndex.findBindings(pVar, false, INDEX_FILTER, npm());
		assertEquals(2, bindings.length);
		checkIsVariable(bindings[0]);
		checkIsVariable(bindings[1]);
	}

	public void testSanityOfMayHaveChildren() throws CoreException {
		PDOM pdom = (PDOM) ((CIndex) fIndex).getFragments()[0];
		pdom.accept(new IPDOMVisitor() {
			LinkedList stack = new LinkedList();

			@Override
			public boolean visit(IPDOMNode node) throws CoreException {
				if (!stack.isEmpty()) {
					Object last = stack.getLast();
					if (last instanceof PDOMBinding) {
						assertTrue(((PDOMBinding) last).mayHaveChildren());
					}
				}
				stack.add(node);
				return true;
			}

			@Override
			public void leave(IPDOMNode node) throws CoreException {
				assertEquals(stack.removeLast(), node);
			}
		});
	}
}
