/*******************************************************************************
 * 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.core.formatters.tests;

import static org.junit.Assert.*;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Calendar;
import java.util.GregorianCalendar;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Path;
import org.eclipse.linuxtools.changelog.tests.fixtures.ChangeLogTestProject;
import org.eclipse.linuxtools.changelog.tests.helpers.EditorHelper;
import org.eclipse.linuxtools.internal.changelog.core.formatters.GNUFormat;

import static org.eclipse.linuxtools.changelog.tests.helpers.EditorHelper.closeEditor;
import static org.eclipse.linuxtools.changelog.tests.helpers.EditorHelper.openEditor;
import static org.eclipse.linuxtools.changelog.tests.helpers.EditorHelper.getContent;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.eclipse.jface.text.IDocument;

public class GNUFormatTest {

	// The instance under test
	private GNUFormat gnuFormatter;
	// A faked project
	private ChangeLogTestProject project;
	// The IEditorPart corresponding to the ChangeLog file
	private IEditorPart changelogEditorPart = null;
	
	// Tokens for the GNU format
	private static final String TWO_SPACES = "  ";
	private static final String SPACE = " ";
	private static final String TAB = "\t";
	private static final String LEFT_EMAIL_BRACKET = "<";
	private static final String RIGHT_EMAIL_BRACKET = ">";
	private static final String FILE_ENTRY_START_MARKER = "* ";
	private static final String FILE_ENTRY_END_MARKER = ": ";
	private static final String FUNCTION_START_MARKER = "(";
	private static final String FUNCTION_END_MARKER = ")";
	private static final String NEW_LINE = "\n";
	
	@Before
	public void setUp() throws Exception {
		gnuFormatter = new GNUFormat();
		project = new ChangeLogTestProject("GNUFormatterTest");
	}
	
	@After
	public void tearDown() throws Exception {
		// Most tests in this class use changelogEditorPart. In order to avoid
		// spill-over from previous runs, truncate content (i.e. manually set 
		// content to the empty string).
		if (changelogEditorPart != null) { // testFormatDateLine does not use it
			AbstractTextEditor castEditor = (AbstractTextEditor) changelogEditorPart;
			IDocumentProvider iDocProvider = castEditor.getDocumentProvider();
			IDocument changelogContentDoc = iDocProvider.getDocument(castEditor.getEditorInput());
			changelogContentDoc.set(""); // empty content
			changelogEditorPart.doSave(null);
			// Also close open editor in order for default content to work.
			// I.e. avoid spill over from previous test runs
			closeEditor(changelogEditorPart);
		}
		project.getTestProject().delete(true, true, null); // dispose
	}

	/**
	 * GNU ChangeLog style date lines are of the following format
	 *  
	 * YYYY-MM-DD  Author Name  <author.email@domain.com>
	 */
	@Test
	public void testFormatDateLine() {
		// Today's date in ISO format
		Calendar c = new GregorianCalendar();
		String isoDate = String.format("%1$tY-%1$tm-%1$td", c);
		String authorName = "William Shakespeare";
		String authorEmail = "william.shakespeare@medieval.com";
		final String expectedDateFormatting = isoDate +
											  TWO_SPACES +
											  authorName +
											  TWO_SPACES +
											  LEFT_EMAIL_BRACKET +
											  authorEmail +
											  RIGHT_EMAIL_BRACKET +
											  "\n\n";
		assertEquals(expectedDateFormatting,
				gnuFormatter.formatDateLine(authorName, authorEmail));
	}

	/**
	 * Assume the following initial content of ChangeLog:
	 * 
	 * <code>
	 * 2010-11-26  Severin Gehwolf  <sgehwolf@redhat.com>
	 *
	 *   * src/org/eclipse/linuxtools/changelog/parsers/java/HelloWorld.java (test): New Method\n\t\n
	 * </code>
	 * 
	 * Note the trailing new-line, tab, new-line combination. If another change in the same file but
	 * in a different method (or function) is merged, the empty line containing a tab only should be
	 * removed.
	 * 
	 */
	@Test
	public void twoChangesInSameFileAreProperlyMergedWhenThereIsATrailingTabNewLine() throws Exception {
		// set date/author line
		String authorName = "Test Foo";
		String email = "test@example.com";
		final String dateLine = gnuFormatter.formatDateLine(authorName, email);
	
		// full absolute path to ChangeLog file (relative to project root)
		String changelogPath = "/" + project.getTestProject().getName() + "/path/to";
		final String changelogFilePath = changelogPath + "/ChangeLog";
		
		// entry file path (need overlap with changelogPath)
		String fileEntryRelPath = "eclipse/example/test/NewCoffeeMaker.java";
		final String entryFilePath = changelogPath + "/" + fileEntryRelPath;
	
		final String firstMethodName = "main";
		final String firstChangeComment = "Fix args parsing.";
		// Setup proper pre-existing content
		String content = dateLine + NEW_LINE + NEW_LINE + TAB + FILE_ENTRY_START_MARKER +
						 fileEntryRelPath + SPACE + FUNCTION_START_MARKER + 
						 firstMethodName + FUNCTION_END_MARKER + FILE_ENTRY_END_MARKER +
						 firstChangeComment + NEW_LINE +
						 TAB + NEW_LINE; // produces an empty line which should be removed
		
		assertNull(project.getTestProject().findMember(new Path("/path/to/ChangeLog")));
		
		// add a ChangeLog file to our test project
		InputStream newFileInputStream = new ByteArrayInputStream(
				content.getBytes());
		IFile changelogFile = project.addFileToProject( "/path/to", "ChangeLog",
				newFileInputStream);
		
		assertNotNull(project.getTestProject().findMember(new Path("/path/to/ChangeLog")));
		
		// Open a document and get the IEditorPart
		changelogEditorPart = EditorHelper.openEditor(changelogFile);
		
		// make sure changelog editor content is right before merging
		assertEquals(content, getContent(changelogEditorPart));
		
		final String secondMethodName = "toString";
		
		// Do the merge with the existing content
		gnuFormatter.mergeChangelog(dateLine, secondMethodName,
				"" /* no default content */, changelogEditorPart,
				changelogFilePath, entryFilePath);
	 
		final String actualMergeResult = getContent(changelogEditorPart);		
		
		// Expect trailing tab+newline combination to not show up in merge
		final String expectedResult = dateLine + NEW_LINE + NEW_LINE + TAB + FILE_ENTRY_START_MARKER +
		 							  fileEntryRelPath + SPACE + FUNCTION_START_MARKER + 
		 							  firstMethodName + FUNCTION_END_MARKER + FILE_ENTRY_END_MARKER +
		 							  firstChangeComment + NEW_LINE + TAB + FUNCTION_START_MARKER +
		 							  secondMethodName + FUNCTION_END_MARKER + FILE_ENTRY_END_MARKER +
		 							  NEW_LINE + NEW_LINE;
		assertEquals(expectedResult, actualMergeResult);
	}
	
	/**
	 * Test merge of two separate changes to an existing file within the same changelog entry.
	 * I.e. test for
	 * 
	 *    * path/to/file (method1): First change.
	 *    (method2): Another change in same file but different function/method.
	 */
	@Test
	public void twoChangesInSameFileAreProperlyMerged() throws Exception {
		// set date/author line
		String authorName = "Test Author";
		String email = "test@example.com";
		final String dateLine = gnuFormatter.formatDateLine(authorName, email);
	
		// full absolute path to ChangeLog file (relative to project root)
		String changelogPath = "/" + project.getTestProject().getName() + "/path/to";
		final String changelogFilePath = changelogPath + "/ChangeLog";
		
		// entry file path (need overlap with changelogPath)
		String fileEntryRelPath = "eclipse/example/test/NewCoffeeMaker.java";
		final String entryFilePath = changelogPath + "/" + fileEntryRelPath;
	
		final String firstMethodName = "main";
		final String firstChangeComment = "Fix args parsing.";
		// Setup proper pre-existing content
		String content = dateLine + NEW_LINE + NEW_LINE + TAB + FILE_ENTRY_START_MARKER +
						 fileEntryRelPath + SPACE + FUNCTION_START_MARKER + 
						 firstMethodName + FUNCTION_END_MARKER + FILE_ENTRY_END_MARKER +
						 firstChangeComment + NEW_LINE;
		
		assertNull(project.getTestProject().findMember(new Path("/path/to/ChangeLog")));
		
		// add a ChangeLog file to our test project
		InputStream newFileInputStream = new ByteArrayInputStream(
				content.getBytes());
		IFile changelogFile = project.addFileToProject( "/path/to", "ChangeLog",
				newFileInputStream);
		
		assertNotNull(project.getTestProject().findMember(new Path("/path/to/ChangeLog")));
		
		// Open a document and get the IEditorPart
		changelogEditorPart = openEditor(changelogFile);
		// make sure changelog editor content is right before merging
		assertEquals(content, getContent(changelogEditorPart));
		
		final String secondMethodName = "toString";
		
		// Do the merge with the existing content
		gnuFormatter.mergeChangelog(dateLine, secondMethodName,
				"" /* no default content */, changelogEditorPart,
				changelogFilePath, entryFilePath);
	 
		final String actualMergeResult = getContent(changelogEditorPart);		
		
		final String expectedResult = dateLine + NEW_LINE + NEW_LINE + TAB + FILE_ENTRY_START_MARKER +
		 							  fileEntryRelPath + SPACE + FUNCTION_START_MARKER + 
		 							  firstMethodName + FUNCTION_END_MARKER + FILE_ENTRY_END_MARKER +
		 							  firstChangeComment + NEW_LINE + TAB + FUNCTION_START_MARKER +
		 							  secondMethodName + FUNCTION_END_MARKER + FILE_ENTRY_END_MARKER +
		 							  NEW_LINE;
		
		assertEquals(expectedResult, actualMergeResult);
	}
	
	/**
	 * If two different authors make modifications on the same day, both modifications must
	 * show up as separate changelog entries for that day.
	 */
	@Test
	public void newChangeLogEntryForNewAuthorOnSameDay() throws Exception {
		// first author
		String authorName = "Test Author";
		String email = "test@example.com";
		final String firstDateLine = gnuFormatter.formatDateLine(authorName, email);
		// second author
		authorName = "William Shakespeare";
		email = "will@pear.com";
		final String secondDateLine = gnuFormatter.formatDateLine(authorName, email);

		// full absolute path to ChangeLog file
		String changelogPath = "/" + project.getTestProject().getName() + "/path/to";
		final String changelogFilePath = changelogPath + "/ChangeLog";

		// No existing content in ChangeLog file
		String content = "";
		
		assertNull(project.getTestProject().findMember(new Path("/path/to/ChangeLog")));
		
		// add a ChangeLog file to our test project
		InputStream newFileInputStream = new ByteArrayInputStream(
				content.getBytes());
		IFile changelogFile = project.addFileToProject( changelogPath, "ChangeLog",
				newFileInputStream);
		// Open a document and get the IEditorPart
		changelogEditorPart = openEditor(changelogFile);

		// entry file path (needs overlap with changelogPath)
		String fileEntryRelPath = "eclipse/example/test/NewCoffeeMaker.java";
		final String entryFilePath = changelogPath + "/" + fileEntryRelPath;
		
		final String guessedFunctionName = "bazinga"; 
		
		// merge first changelog entry with empty content
		gnuFormatter.mergeChangelog(firstDateLine, guessedFunctionName,
				"" /* empty default content */, changelogEditorPart,
				changelogFilePath, entryFilePath);
		
		String actualMergeResult = getContent(changelogEditorPart);		
		
		String expectedResult = firstDateLine + TAB + FILE_ENTRY_START_MARKER + 
									  fileEntryRelPath + SPACE + FUNCTION_START_MARKER +
									  guessedFunctionName + FUNCTION_END_MARKER +
									  FILE_ENTRY_END_MARKER;
		
		assertEquals(expectedResult, actualMergeResult);
		
		// add second changelog entry on same date but by different author
		gnuFormatter.mergeChangelog(secondDateLine, guessedFunctionName,
				"" /* empty default content */, changelogEditorPart,
				changelogFilePath, entryFilePath);
		
		actualMergeResult = getContent(changelogEditorPart);		
		
		expectedResult = secondDateLine + TAB + FILE_ENTRY_START_MARKER + 
									  fileEntryRelPath + SPACE + FUNCTION_START_MARKER +
									  guessedFunctionName + FUNCTION_END_MARKER +
									  FILE_ENTRY_END_MARKER + NEW_LINE + NEW_LINE +
									  expectedResult;
		
		assertEquals(expectedResult, actualMergeResult);
	}
	
	/**
	 * Basic format check.
	 */
	@Test
	public void mergeChangeLogHasProperFormat() throws Exception {
		// set date/author line
		String authorName = "Test Author";
		String email = "test@example.com";
		final String dateLine = gnuFormatter.formatDateLine(authorName, email);

		// full absolute path to ChangeLog file (relative to project root)
		String changelogPath = "/" + project.getTestProject().getName() + "/path/to";
		final String changelogFilePath = changelogPath + "/ChangeLog";

		// Content to merge into
		String content = "";
		
		assertNull(project.getTestProject().findMember(new Path("/path/to/ChangeLog")));
		
		// add a ChangeLog file to our test project
		InputStream newFileInputStream = new ByteArrayInputStream(
				content.getBytes());
		IFile changelogFile = project.addFileToProject( "/path/to", "ChangeLog",
				newFileInputStream);
		// Open a document and get the IEditorPart
		changelogEditorPart = openEditor(changelogFile);
		
		// make sure changelog editor content is empty
		assertEquals(content, getContent(changelogEditorPart));

		// entry file path (need overlap with changelogPath)
		String fileEntryRelPath = "eclipse/example/test/NewCoffeeMaker.java";
		final String entryFilePath = changelogPath + "/" + fileEntryRelPath;
		
		// Will show up surrounded by "(" and ")" in ChangeLog
		final String guessedFunctionName = "bazinga"; 
		
		// This always returns an empty String (should probably be changed...)
		// merge result will be written to editorContent
		gnuFormatter.mergeChangelog(dateLine, guessedFunctionName,
				"" /* empty default content */, changelogEditorPart,
				changelogFilePath, entryFilePath);
		
		final String actualMergeResult = getContent(changelogEditorPart);		
		
		final String expectedResult = dateLine + TAB + FILE_ENTRY_START_MARKER + 
									  fileEntryRelPath + SPACE + FUNCTION_START_MARKER +
									  guessedFunctionName + FUNCTION_END_MARKER +
									  FILE_ENTRY_END_MARKER;
		
		assertEquals(expectedResult, actualMergeResult);
	}
	
	/**
	 * Here's the scenario for this test. It may be that there are only files removed and
	 * new files added for a commit. This should yield to changelog entries for the following
	 * form:
	 * 
	 *<code>
	 * 2010-11-26  Some Author  <some.author@example.com>
	 * 
	 *     * path/to/deleted/file.c: Removed.
	 *     * path/to/new/file.c: New file.
	 *</code>
	 *
	 * Prior to the fix for Eclipse Bz #331244 the result was:
	 * 
	 *<code>
	 * 2010-11-26  Some Author  <some.author@example.com>
	 * 
	 *     * path/to/deleted/file.c: Removed.
	 *     * path/to/new/file.c:
	 *</code>
	 * 
	 * This regression test should catch this.
	 */
	@Test
	public void canHaveEntriesWithDefaultTextOnly() throws Exception {
		// set date/author line
		String authorName = "Test Author";
		String email = "test@example.com";
		final String dateLine = gnuFormatter.formatDateLine(authorName, email);

		// full absolute path to ChangeLog file (relative to project root)
		String changelogPath = "/" + project.getTestProject().getName() + "/path/example";
		final String changelogFilePath = changelogPath + "/ChangeLog";

		// Content to merge into
		String content = "";
		
		// add a ChangeLog file to our test project
		InputStream newFileInputStream = new ByteArrayInputStream(
				content.getBytes());
		IFile changelogFile = project.addFileToProject( "/path/example", "ChangeLog",
				newFileInputStream);
		// Open a document and get the IEditorPart
		changelogEditorPart = openEditor(changelogFile);
		
		// make sure changelog editor content is empty
		assertEquals(content, getContent(changelogEditorPart));

		// entry file path (need overlap with changelogPath)
		final String firstFileEntryRelPath = "eclipse/example/test/NewCoffeeMaker.java";
		String entryFilePath = changelogPath + "/" + firstFileEntryRelPath;
		
		final String firstDefaultContent = "New file.";
		
		// Note: This always returns an empty String (should probably be changed...)
		// merge result will be written to editorContent.
		// 
		// Create a line with default text "New file"
		gnuFormatter.mergeChangelog(dateLine, "" /* no guessed function name */,
				firstDefaultContent, changelogEditorPart,
				changelogFilePath, entryFilePath);
		final String secondFileEntryRelPath = "eclipse/example/test/OldCoffeeMaker.java";
		entryFilePath = changelogPath + "/" + secondFileEntryRelPath;
		final String secondDefaultContent = "Removed.";
		// Add entry for removed file
		gnuFormatter.mergeChangelog(dateLine, "" /* no guessed function name */,
				secondDefaultContent,	changelogEditorPart,
				changelogFilePath, entryFilePath);
				
		final String actualMergeResult = getContent(changelogEditorPart);		
		
		// Note: Removed files occur in the list first, new file items come after that.
		final String expectedResult = dateLine + TAB + FILE_ENTRY_START_MARKER + 
									  secondFileEntryRelPath+ FILE_ENTRY_END_MARKER +
									  secondDefaultContent + NEW_LINE + TAB + FILE_ENTRY_START_MARKER +
									  firstFileEntryRelPath + FILE_ENTRY_END_MARKER +
									  firstDefaultContent; 
		
		assertEquals(expectedResult, actualMergeResult);
	}
	
	/**
	 * Test for a changelog entry with items for removed files, new files and modified
	 * existing files. This test differs from the previous in that the ChangeLog is
	 * empty to start with.  This verifies Bz #366854 fix.
	 * 
	 * @throws Exception
	 */
	@Test
	public void canHaveEntriesWithDefaultTextAndSomeModificationToAnExistingFile2() throws Exception {
		// set date/author line
		String authorName = "Test Author";
		String email = "test@example.com";
		final String dateLine = gnuFormatter.formatDateLine(authorName, email);

		// full absolute path to ChangeLog file (relative to project root)
		String changelogPath = "/" + project.getTestProject().getName() + "/test/example";
		final String changelogFilePath = changelogPath + "/ChangeLog";

		// add a new empty ChangeLog file to our test project
		InputStream newFileInputStream = new ByteArrayInputStream(new byte[0]);
		IFile changelogFile = project.addFileToProject( "/test/example", "ChangeLog",
				newFileInputStream);
		// Open a document and get the IEditorPart
		changelogEditorPart = openEditor(changelogFile);
		
		// entry file path (need overlap with changelogPath)
		final String firstFileEntryRelPath = "eclipse/example/test/NewCoffeeMaker.java";
		String entryFilePath = changelogPath + "/" + firstFileEntryRelPath;
		
		final String firstDefaultContent = "New file.";
		
		// Note: This always returns an empty String (should probably be changed...)
		// merge result will be written to editorContent.
		// 
		// Create an item with default text "New file"
		gnuFormatter.mergeChangelog(dateLine, "" /* no guessed function name */,
				firstDefaultContent, changelogEditorPart,
				changelogFilePath, entryFilePath);
		final String secondFileEntryRelPath = "eclipse/example/test/OldCoffeeMaker.java";
		entryFilePath = changelogPath + "/" + secondFileEntryRelPath;
		final String secondDefaultContent = "Removed.";
		// Add entry for removed file
		gnuFormatter.mergeChangelog(dateLine, "" /* no guessed function name */,
				secondDefaultContent,	changelogEditorPart,
				changelogFilePath, entryFilePath);
		final String thirdFileEntryRelPath = "eclipse/example/test/ModifiedFile.java";
		entryFilePath = changelogPath + "/" + thirdFileEntryRelPath;
		final String guessedFunctionName = "main";
		// Create a bullet point describing a change in some existing file
		gnuFormatter.mergeChangelog(dateLine, guessedFunctionName,
				"" /* no default content */, changelogEditorPart,
				changelogFilePath, entryFilePath);
	 
		final String actualMergeResult = getContent(changelogEditorPart);		
		
		// Note that changes to existing files appear first in the changelog entries.
		// Second are removed files and new files are last.
		final String expectedResult = dateLine + TAB + FILE_ENTRY_START_MARKER +
		  							  thirdFileEntryRelPath + SPACE + FUNCTION_START_MARKER + 
		  							  guessedFunctionName + FUNCTION_END_MARKER + FILE_ENTRY_END_MARKER +
		  							  NEW_LINE + TAB + FILE_ENTRY_START_MARKER + 
									  secondFileEntryRelPath+ FILE_ENTRY_END_MARKER +
									  secondDefaultContent + NEW_LINE + TAB + FILE_ENTRY_START_MARKER +
									  firstFileEntryRelPath + FILE_ENTRY_END_MARKER +
									  firstDefaultContent; 
		
		assertEquals(expectedResult, actualMergeResult);
	}

}
