///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2023 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
///////////////////////////////////////////////////////////////////////////////////////////////

package com.puppycrawl.tools.checkstyle.gui;

import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.File;
import java.io.IOException;
import java.util.Locale;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.gui.MainFrameModel.ParseMode;

public class MainFrameModelTest extends AbstractModuleTestSupport {

    private static final String FILE_NAME_TEST_DATA = "InputMainFrameModel.java";
    private static final String FILE_NAME_NON_EXISTENT = "non-existent.file";
    private static final String FILE_NAME_NON_JAVA = "NotJavaFile.notjava";
    private static final String FILE_NAME_NON_COMPILABLE = "InputMainFrameModelIncorrectClass.java";

    private MainFrameModel model;
    private File testData;

    @Override
    protected String getPackageLocation() {
        return "com/puppycrawl/tools/checkstyle/gui/mainframemodel";
    }

    @BeforeEach
    public void prepareTestData() throws IOException {
        model = new MainFrameModel();
        testData = new File(getPath(FILE_NAME_TEST_DATA));
    }

    @Test
    public void testParseModeEnum() {
        for (final ParseMode parseMode : ParseMode.values()) {
            switch (parseMode) {
                case PLAIN_JAVA:
                    assertWithMessage("Invalid toString result")
                        .that(parseMode.toString())
                        .isEqualTo("Plain Java");
                    break;
                case JAVA_WITH_COMMENTS:
                    assertWithMessage("Invalid toString result")
                        .that(parseMode.toString())
                        .isEqualTo("Java with comments");
                    break;
                case JAVA_WITH_JAVADOC_AND_COMMENTS:
                    assertWithMessage("Invalid toString result")
                        .that(parseMode.toString())
                        .isEqualTo("Java with comments and Javadocs");
                    break;
                default:
                    assertWithMessage("Unexpected enum value").fail();
            }
        }
    }

    @Test
    public void testOpenFileWithParseModePlainJava() throws Exception {
        // Default parse mode: Plain Java
        model.openFile(testData);
        verifyCorrectTestDataInFrameModel();

        model.setParseMode(ParseMode.PLAIN_JAVA);
        verifyCorrectTestDataInFrameModel();
    }

    @Test
    public void testOpenFileWithParseModeJavaWithComments() throws Exception {
        model.setParseMode(ParseMode.JAVA_WITH_COMMENTS);
        model.openFile(testData);

        verifyCorrectTestDataInFrameModel();
    }

    @Test
    public void testOpenFileWithParseModeJavaWithJavadocAndComments() throws Exception {
        model.setParseMode(ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS);
        model.openFile(testData);

        verifyCorrectTestDataInFrameModel();
    }

    @Test
    public void testOpenFileNullParameter() throws Exception {
        model.openFile(testData);

        model.openFile(null);

        // Model will not change with null input
        verifyCorrectTestDataInFrameModel();
    }

    @Test
    public void testOpenFileNullParameter2() throws Exception {
        model.openFile(null);

        assertWithMessage("Test is null")
            .that(model.getText())
            .isNull();
        assertWithMessage("Title is expected value")
            .that(model.getTitle())
            .isEqualTo("Checkstyle GUI");
        assertWithMessage("Reload action should be disabled")
                .that(model.isReloadActionEnabled())
                .isFalse();
        assertWithMessage("Current file is null")
            .that(model.getCurrentFile())
            .isNull();
    }

    @Test
    public void testOpenFileNonExistentFile() throws IOException {
        final File nonExistentFile = new File(getPath(FILE_NAME_NON_EXISTENT));

        try {
            model.openFile(nonExistentFile);

            assertWithMessage("Expected CheckstyleException is not thrown.").fail();
        }
        catch (CheckstyleException ex) {
            final String expectedMsg = String.format(Locale.ROOT,
                    "FileNotFoundException occurred while opening file %s.",
                    nonExistentFile.getPath());

            assertWithMessage("Invalid exception message")
                .that(ex.getMessage())
                .isEqualTo(expectedMsg);
        }
    }

    @Test
    public void testOpenFileNonCompilableFile() throws IOException {
        final File nonCompilableFile = new File(getNonCompilablePath(FILE_NAME_NON_COMPILABLE));

        try {
            model.openFile(nonCompilableFile);

            assertWithMessage("Expected CheckstyleException is not thrown.").fail();
        }
        catch (CheckstyleException ex) {
            final String expectedMsg = String.format(Locale.ROOT,
                    "IllegalStateException occurred while parsing file %s.",
                    nonCompilableFile.getPath());

            assertWithMessage("Invalid exception message")
                .that(ex.getMessage())
                .isEqualTo(expectedMsg);
        }
    }

    private void verifyCorrectTestDataInFrameModel() throws IOException {
        assertWithMessage("Invalid current file")
            .that(model.getCurrentFile())
            .isEqualTo(testData);

        final String expectedTitle = "Checkstyle GUI : " + FILE_NAME_TEST_DATA;
        assertWithMessage("Invalid model title")
            .that(model.getTitle())
            .isEqualTo(expectedTitle);

        assertWithMessage("Reload action should be enabled")
                .that(model.isReloadActionEnabled())
                .isTrue();

        final int expectedLines = 19;
        assertWithMessage("Invalid lines to position")
            .that(model.getLinesToPosition())
            .hasSize(expectedLines);

        final String testDataFileNameWithoutPostfix = FILE_NAME_TEST_DATA.replace(".java", "");
        assertWithMessage("Invalid model text: " + model.getText())
                .that(model.getText())
                .contains(testDataFileNameWithoutPostfix);

        final File expectedLastDirectory = new File(getPath(""));
        assertWithMessage("Invalid model last directory")
            .that(model.getLastDirectory())
            .isEqualTo(expectedLastDirectory);

        assertWithMessage("ParseTree table model should not be null")
            .that(model.getParseTreeTableModel())
            .isNotNull();
    }

    @Test
    public void testShouldAcceptDirectory() {
        final File directory = mock(File.class);
        when(directory.isDirectory()).thenReturn(true);
        assertWithMessage("MainFrame should accept directory")
                .that(MainFrameModel.shouldAcceptFile(directory))
                .isTrue();
    }

    @Test
    public void testShouldAcceptFile() throws IOException {
        final File javaFile = new File(getPath(FILE_NAME_TEST_DATA));
        assertWithMessage("MainFrame should accept java file")
                .that(MainFrameModel.shouldAcceptFile(javaFile))
                .isTrue();
    }

    @Test
    public void testShouldNotAcceptNonJavaFile() {
        final File nonJavaFile = mock(File.class);
        when(nonJavaFile.isDirectory()).thenReturn(false);
        when(nonJavaFile.getName()).thenReturn(FILE_NAME_NON_JAVA);
        assertWithMessage("MainFrame should not accept non-Java file")
                .that(MainFrameModel.shouldAcceptFile(nonJavaFile))
                .isFalse();
    }

    @Test
    public void testShouldNotAcceptNonExistentFile() throws IOException {
        final File nonExistentFile = new File(getPath(FILE_NAME_NON_EXISTENT));
        assertWithMessage("MainFrame should not accept non-existent file")
                .that(MainFrameModel.shouldAcceptFile(nonExistentFile))
                .isFalse();
    }

    @Test
    public void testOpenFileForUnknownParseMode() throws IOException {
        final File javaFile = new File(getPath(FILE_NAME_TEST_DATA));
        final ParseMode mock = mock(ParseMode.class);
        model.setParseMode(mock);
        final IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> {
            model.openFile(javaFile);
        });
        assertWithMessage("Invalid error message")
                .that(ex)
                .hasMessageThat()
                .startsWith("Unknown mode:");
    }

}
