/*******************************************************************************
 * Copyright (c) 2010 Red Hat Inc. 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
 *******************************************************************************/
package org.eclipse.linuxtools.changelog.parsers.tests;

import static org.eclipse.linuxtools.changelog.tests.helpers.EditorHelper.closeEditor;
import static org.eclipse.linuxtools.changelog.tests.helpers.EditorHelper.getContent;
import static org.eclipse.linuxtools.changelog.tests.helpers.EditorHelper.openEditor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.linuxtools.changelog.core.IParserChangeLogContrib;
import org.eclipse.linuxtools.changelog.tests.fixtures.ChangeLogTestProject;
import org.eclipse.linuxtools.internal.changelog.core.ChangeLogExtensionManager;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * JavaParser test suite.
 * 
 */
public class JavaParserTest {

	// A unique string to mark the place of current selection in source code
	private static final String OFFSET_MARKER = "<# selection #>";
	// The parser under test
	private IParserChangeLogContrib javaParser;
	// A faked project
	private ChangeLogTestProject project;
	// The IEditorPart corresponding to the ChangeLog file
	private IEditorPart javaSourceEditorPart = null;

	@Before
	public void setUp() throws Exception {
		javaParser = ChangeLogExtensionManager.getExtensionManager().getParserContributor("CompilationUnitEditor");
		project = new ChangeLogTestProject("java-parser-test-project");
		// make it a Java project
		project.addJavaNature();
	}

	@After
	public void tearDown() throws Exception {
		// Tests in this class use javaSourceEditorPart. In order to avoid
		// spill-over from previous runs, truncate content (i.e. manually set 
		// content to the empty string).
		if (javaSourceEditorPart != null) {
			AbstractTextEditor castEditor = (AbstractTextEditor) javaSourceEditorPart;
			IDocumentProvider iDocProvider = castEditor.getDocumentProvider();
			IDocument changelogContentDoc = iDocProvider.getDocument(castEditor.getEditorInput());
			changelogContentDoc.set("");
			javaSourceEditorPart.doSave(null);
			// Also close open editor in order for default content to work.
			// I.e. avoid spill over from previous test runs
			closeEditor(javaSourceEditorPart);
		}		
		project.getTestProject().delete(true, true, null); // dispose
	}

	/**
	 * Given an IEditorPart we should be able to retrieve the current function
	 * we are in.
	 * 
	 * @throws Exception
	 */
	@Test
	public void canParseCurrentMethod() throws Exception {
		final String expectedMethodName = "doSomething";
		final String javaSourceCode = "public class JavaParserExampleClass {\n"
				+ "private void " + expectedMethodName + "(String param) {\n"
					+ "// "	+ OFFSET_MARKER + "\n"
					+ "}\n"
				+ "}\n";

		assertNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));

		// Add JavaParserExampleClass.java to project
		InputStream newFileInputStream = new ByteArrayInputStream(
				javaSourceCode.getBytes());
		IFile javaSourceFile = project.addFileToProject(
				"/src/org/eclipse/changelog/tests",
				"JavaParserExampleClass.java", newFileInputStream);

		assertNotNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));
		
		// Open a source file and get the IEditorPart
		javaSourceEditorPart = openEditor(javaSourceFile);
		// make sure changelog editor content is right before merging
		assertEquals(javaSourceCode, getContent(javaSourceEditorPart));
		
		// make sure we have the proper editor type
		assertTrue( javaSourceEditorPart instanceof AbstractDecoratedTextEditor );

		// Select the snippet we want
		int selectionStart = javaSourceCode.indexOf(OFFSET_MARKER);
		assertTrue(selectionStart >= 0);
		int selectionLength = OFFSET_MARKER.length();
		AbstractDecoratedTextEditor javaEditor = (AbstractDecoratedTextEditor) javaSourceEditorPart;		
		javaEditor.getSelectionProvider().setSelection(
				new TextSelection(selectionStart, selectionLength));
		
		final String actualMethodName = javaParser.parseCurrentFunction(javaSourceEditorPart);
		
		assertEquals(expectedMethodName, actualMethodName);
	}
	
	/**
	 * Given an IEditorPart we should be able to retrieve the currently selected
	 * field.
	 * 
	 * @throws Exception
	 */
	@Test
	public void canParseSelectedField() throws Exception {
		final String expectedFieldName = "testVar";
		final String javaSourceCode = "public class JavaParserExampleClass {\n"
			 	+ "private String " + expectedFieldName + " = null;\n"
				+ "private void someMethod(String param) {\n"
					+ "// empty \n"
					+ "}\n"
				+ "}\n";

		assertNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));

		// Add JavaParserExampleClass.java to project
		InputStream newFileInputStream = new ByteArrayInputStream(
				javaSourceCode.getBytes());
		IFile javaSourceFile = project.addFileToProject(
				"/src/org/eclipse/changelog/tests",
				"JavaParserExampleClass.java", newFileInputStream);

		assertNotNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));
		
		// Open a source file and get the IEditorPart
		javaSourceEditorPart = openEditor(javaSourceFile);
		// make sure changelog editor content is right before merging
		assertEquals(javaSourceCode, getContent(javaSourceEditorPart));
		
		// make sure we have the proper editor type
		assertTrue( javaSourceEditorPart instanceof AbstractDecoratedTextEditor );

		// Select the snippet we want
		int selectionStart = javaSourceCode.indexOf(expectedFieldName);
		assertTrue(selectionStart >= 0);
		int selectionLength = expectedFieldName.length() - 3; // Shouldn't need to select the entire field
		AbstractDecoratedTextEditor javaEditor = (AbstractDecoratedTextEditor) javaSourceEditorPart;		
		javaEditor.getSelectionProvider().setSelection(
				new TextSelection(selectionStart, selectionLength));
		
		final String actualFieldName = javaParser.parseCurrentFunction(javaSourceEditorPart);
		
		assertEquals(expectedFieldName, actualFieldName);
	}
	
	/**
	 * Given an IEditorPart and current selection is in a static instance initializer
	 * block, JavaParser should be able to figure out that we were in an static
	 * initializer block.
	 * 
	 * @throws Exception
	 */
	@Test
	public void canIdentifyStaticInitializerWhenInStaticInstanceInitializer() throws Exception {
		final String javaSourceCode = "public class JavaParserExampleClass {\n"
			 	+ "private String someStrVariable = null;\n"
			 	// create static instance initializer block
			 	+ "{\n"
			 		+ "// "	+ OFFSET_MARKER + "\n"
			 	+ "}\n"
				+ "private void someMethod(String param) {\n"
					+ "// empty \n"
					+ "}\n"
				+ "}\n";

		assertNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));

		// Add JavaParserExampleClass.java to project
		InputStream newFileInputStream = new ByteArrayInputStream(
				javaSourceCode.getBytes());
		IFile javaSourceFile = project.addFileToProject(
				"/src/org/eclipse/changelog/tests",
				"JavaParserExampleClass.java", newFileInputStream);

		assertNotNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));
		
		// Open a source file and get the IEditorPart
		javaSourceEditorPart = openEditor(javaSourceFile);
		// make sure changelog editor content is right before merging
		assertEquals(javaSourceCode, getContent(javaSourceEditorPart));
		
		// make sure we have the proper editor type
		assertTrue( javaSourceEditorPart instanceof AbstractDecoratedTextEditor );

		// Select the snippet we want
		int selectionStart = javaSourceCode.indexOf(OFFSET_MARKER);
		assertTrue(selectionStart >= 0);
		int selectionLength = OFFSET_MARKER.length();
		AbstractDecoratedTextEditor javaEditor = (AbstractDecoratedTextEditor) javaSourceEditorPart;		
		javaEditor.getSelectionProvider().setSelection(
				new TextSelection(selectionStart, selectionLength));
		
		final String actualFunctionName = javaParser.parseCurrentFunction(javaSourceEditorPart);
		
		assertEquals("static initializer", actualFunctionName);
	}
	
	/**
	 * Given an IEditorPart and current selection is in a static class initializer
	 * block, JavaParser should be able to figure out that we were in an static
	 * initializer block.
	 * 
	 * @throws Exception
	 */
	@Test
	public void canIdentifyStaticInitializerWhenInStaticClassInitializer() throws Exception {
		final String javaSourceCode = "public class JavaParserExampleClass {\n"
			 	+ "private String someStrVariable = null;\n"
			 	// create static class initializer block
			 	+ "static {\n"
			 		+ "// "	+ OFFSET_MARKER + "\n"
			 	+ "}\n"
				+ "private void someMethod(String param) {\n"
					+ "// empty \n"
					+ "}\n"
				+ "}\n";

		assertNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));

		// Add JavaParserExampleClass.java to project
		InputStream newFileInputStream = new ByteArrayInputStream(
				javaSourceCode.getBytes());
		IFile javaSourceFile = project.addFileToProject(
				"/src/org/eclipse/changelog/tests",
				"JavaParserExampleClass.java", newFileInputStream);

		assertNotNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));
		
		// Open a source file and get the IEditorPart
		javaSourceEditorPart = openEditor(javaSourceFile);
		// make sure changelog editor content is right before merging
		assertEquals(javaSourceCode, getContent(javaSourceEditorPart));
		
		// make sure we have the proper editor type
		assertTrue( javaSourceEditorPart instanceof AbstractDecoratedTextEditor );

		// Select the snippet we want
		int selectionStart = javaSourceCode.indexOf(OFFSET_MARKER);
		assertTrue(selectionStart >= 0);
		int selectionLength = OFFSET_MARKER.length();
		AbstractDecoratedTextEditor javaEditor = (AbstractDecoratedTextEditor) javaSourceEditorPart;		
		javaEditor.getSelectionProvider().setSelection(
				new TextSelection(selectionStart, selectionLength));
		
		final String actualFunctionName = javaParser.parseCurrentFunction(javaSourceEditorPart);
		
		assertEquals("static initializer", actualFunctionName);
	}
	
	/**
	 * Given an IEditorPart and current selection is inside a method within a nested
	 * class, JavaParser should return a "nestedClass.methodName" construct for the
	 * current function.
	 * 
	 * @throws Exception
	 */
	@Test
	public void canIdentifyMethodWithinNestedClass() throws Exception {
		final String nestedClassName = "Encapsulated";
		final String currentMethodName = "getString";
		final String javaSourceCode = "public class JavaParserExampleClass {\n"
			 	+ "private String someStrVariable = null;\n"
			 	+ "static {\n"
			 		+ "// empty \n"
			 	+ "}\n"
				+ "private void someMethod(String param) {\n"
					+ "// empty \n"
					+ "}\n"
				+ "private class " + nestedClassName + "{\n"
						+ "public String " + currentMethodName + "() throws Exception {\n"
						+ "// " + OFFSET_MARKER + "\n"
						+ "return \"returnString\";\n"
						+ "}\n"
					+ "}\n"
				+ "}\n";

		assertNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));

		// Add JavaParserExampleClass.java to project
		InputStream newFileInputStream = new ByteArrayInputStream(
				javaSourceCode.getBytes());
		IFile javaSourceFile = project.addFileToProject(
				"/src/org/eclipse/changelog/tests",
				"JavaParserExampleClass.java", newFileInputStream);

		assertNotNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));
		
		// Open a source file and get the IEditorPart
		javaSourceEditorPart = openEditor(javaSourceFile);
		// make sure changelog editor content is right before merging
		assertEquals(javaSourceCode, getContent(javaSourceEditorPart));
		
		// make sure we have the proper editor type
		assertTrue( javaSourceEditorPart instanceof AbstractDecoratedTextEditor );

		// Select the snippet we want
		int selectionStart = javaSourceCode.indexOf(OFFSET_MARKER);
		assertTrue(selectionStart >= 0);
		int selectionLength = OFFSET_MARKER.length();
		AbstractDecoratedTextEditor javaEditor = (AbstractDecoratedTextEditor) javaSourceEditorPart;		
		javaEditor.getSelectionProvider().setSelection(
				new TextSelection(selectionStart, selectionLength));
		
		final String expectedFunctionName = nestedClassName + "." + currentMethodName;
		final String actualFunctionName = javaParser.parseCurrentFunction(javaSourceEditorPart);
		
		assertEquals(expectedFunctionName, actualFunctionName);
	}
	
	/**
	 * Given an IEditorPart and currently a field within a nested
	 * class is selected, JavaParser should return a "nestedClass.fieldName"
	 * construct for the current function.
	 * 
	 * @throws Exception
	 */
	@Test
	public void canIdentifyFieldWithinNestedClass() throws Exception {
		final String nestedClassName = "Encapsulated";
		final String currentFieldName = "variableInNestedClass";
		final String javaSourceCode = "public class JavaParserExampleClass {\n"
			 	+ "private String someStrVariable = null;\n"
			 	+ "static {\n"
			 		+ "// empty \n"
			 	+ "}\n"
				+ "private void someMethod(String param) {\n"
					+ "// empty \n"
					+ "}\n"
				+ "private class " + nestedClassName + "{\n"
						+ "private int " + currentFieldName + " = 10;\n"
						+ "public String getString() throws Exception {\n"
						+ "// return a String, yay ;-)\n"
						+ "return \"returnString\";\n"
						+ "}\n"
					+ "}\n"
				+ "}\n";

		assertNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));

		// Add JavaParserExampleClass.java to project
		InputStream newFileInputStream = new ByteArrayInputStream(
				javaSourceCode.getBytes());
		IFile javaSourceFile = project.addFileToProject(
				"/src/org/eclipse/changelog/tests",
				"JavaParserExampleClass.java", newFileInputStream);

		assertNotNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));
		
		// Open a source file and get the IEditorPart
		javaSourceEditorPart = openEditor(javaSourceFile);
		// make sure changelog editor content is right before merging
		assertEquals(javaSourceCode, getContent(javaSourceEditorPart));
		
		// make sure we have the proper editor type
		assertTrue( javaSourceEditorPart instanceof AbstractDecoratedTextEditor );

		// Select the snippet we want
		int selectionStart = javaSourceCode.indexOf(currentFieldName);
		assertTrue(selectionStart >= 0);
		// select only a part of field name, as this should be sufficient
		int selectionLength = currentFieldName.length() - 3;
		AbstractDecoratedTextEditor javaEditor = (AbstractDecoratedTextEditor) javaSourceEditorPart;		
		javaEditor.getSelectionProvider().setSelection(
				new TextSelection(selectionStart, selectionLength));
		
		final String expectedFunctionName = nestedClassName + "." + currentFieldName;
		final String actualFunctionName = javaParser.parseCurrentFunction(javaSourceEditorPart);
		
		assertEquals(expectedFunctionName, actualFunctionName);
	}
	
	/**
	 * Given an IEditorPart and current selection is inside a class but not within a
	 * method, not selecting a field and not in a nested class (somewhere else in the
	 * class) it should return an empty string for the function.
	 * 
	 * @throws Exception
	 */
	@Test
	public void canDetermineThatSelectionIsJustInClass() throws Exception {
		// Apparently comments don't show up in the compilation units. Makes
		// sense, right? But we can't use the OFFSET_MARKER trick in this case.
		final String javaSourceCode = "public class JavaParserExampleClass {\n"
		 	+ "private String someStrVariable = null;\n"
		 	+ "\n" // want to select this line indexOf(';') + 2
		 	+ "\n"
			+ "private void someMethod(String param) {\n"
				+ "// empty \n"
				+ "}\n"
			+ "}\n";

		assertNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));

		// Add JavaParserExampleClass.java to project
		InputStream newFileInputStream = new ByteArrayInputStream(
				javaSourceCode.getBytes());
		IFile javaSourceFile = project.addFileToProject(
				"/src/org/eclipse/changelog/tests",
				"JavaParserExampleClass.java", newFileInputStream);

		assertNotNull(project.getTestProject().findMember( new Path(
								"/src/org/eclipse/changelog/tests/JavaParserExampleClass.java")));
		
		// Open a source file and get the IEditorPart
		javaSourceEditorPart = openEditor(javaSourceFile);
		// make sure changelog editor content is right before merging
		assertEquals(javaSourceCode, getContent(javaSourceEditorPart));
		
		// make sure we have the proper editor type
		assertTrue( javaSourceEditorPart instanceof AbstractDecoratedTextEditor );

		// Select the right point
		int selectionStart = javaSourceCode.indexOf(';') + 2;
		assertTrue(selectionStart >= 2);
		int selectionLength = 0;
		AbstractDecoratedTextEditor javaEditor = (AbstractDecoratedTextEditor) javaSourceEditorPart;		
		javaEditor.getSelectionProvider().setSelection(
				new TextSelection(selectionStart, selectionLength));
		
		final String actualFunctionName = javaParser.parseCurrentFunction(javaSourceEditorPart);
		
		assertEquals("" /* expect empty string */, actualFunctionName);
	}
}
