/*******************************************************************************
 * Copyright (c) 2010, 2018 Red Hat Inc. and others.
 * 
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/
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);
    }

}
