////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2020 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 3 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 org.sonar.plugins.checkstyle;

import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;

import java.io.File;

import org.junit.Before;
import org.junit.Test;
import org.sonar.api.batch.fs.FilePredicate;
import org.sonar.api.batch.fs.FilePredicates;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputComponent;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.TextRange;
import org.sonar.api.batch.fs.internal.DefaultTextPointer;
import org.sonar.api.batch.fs.internal.DefaultTextRange;
import org.sonar.api.batch.rule.ActiveRule;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
import org.sonar.api.rule.RuleKey;

import com.puppycrawl.tools.checkstyle.TreeWalker;
import com.puppycrawl.tools.checkstyle.api.AuditEvent;
import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;

public class CheckstyleAuditListenerTest {

    private final File file = new File("file1");
    private final AuditEvent event = new AuditEvent(this, file.getAbsolutePath(),
            new LocalizedMessage(42, "", "", null, "", CheckstyleAuditListenerTest.class, "msg"));
    private ActiveRules ruleFinder;
    private InputFile inputFile;
    private SensorContext context;
    private FileSystem fileSystem;

    @Before
    public void before() {
        fileSystem = mock(FileSystem.class);
        context = mock(SensorContext.class);
        inputFile = mock(InputFile.class);
        ruleFinder = mock(ActiveRules.class);

        final FilePredicates predicates = mock(FilePredicates.class);
        final FilePredicate filePredicate = mock(FilePredicate.class);
        when(fileSystem.inputFile(any(FilePredicate.class))).thenReturn(inputFile);
        when(fileSystem.predicates()).thenReturn(predicates);
        when(predicates.hasAbsolutePath(anyString())).thenReturn(filePredicate);
    }

    /**
     * We do suppression to keep code the same in view
     * @noinspection TooBroadScope
     */
    @Test
    public void testUtilityMethods() {
        AuditEvent eventTest;

        eventTest = new AuditEvent(this, "", new LocalizedMessage(0, "", "", null, "",
                CheckstyleAuditListenerTest.class, "msg"));
        assertThat(CheckstyleAuditListener.getLineId(eventTest)).isEqualTo(1);
        assertThat(CheckstyleAuditListener.getMessage(eventTest)).isEqualTo("msg");
        assertThat(CheckstyleAuditListener.getRuleKey(eventTest)).isEqualTo(
                CheckstyleAuditListenerTest.class.getName());

        eventTest = new AuditEvent(this, "", new LocalizedMessage(1, "", "", null, "",
                CheckstyleAuditListenerTest.class, "msg"));
        assertThat(CheckstyleAuditListener.getLineId(eventTest)).isEqualTo(1);
        assertThat(CheckstyleAuditListener.getMessage(eventTest)).isEqualTo("msg");
        assertThat(CheckstyleAuditListener.getRuleKey(eventTest)).isEqualTo(
                CheckstyleAuditListenerTest.class.getName());

        eventTest = new AuditEvent(this);
        assertThat(CheckstyleAuditListener.getLineId(eventTest)).isEqualTo(1);
        assertThat(CheckstyleAuditListener.getMessage(eventTest)).isNull();
        assertThat(CheckstyleAuditListener.getRuleKey(eventTest)).isNull();

        eventTest = new AuditEvent(this, "", new LocalizedMessage(0, "", "", null, "module",
                CheckstyleAuditListenerTest.class, "msg"));
        assertThat(CheckstyleAuditListener.getLineId(eventTest)).isEqualTo(1);
        assertThat(CheckstyleAuditListener.getMessage(eventTest)).isEqualTo("msg");
        assertThat(CheckstyleAuditListener.getRuleKey(eventTest)).isEqualTo("module");
    }

    @Test
    public void addErrorTest() {
        addErrorTestForLine(42);
    }

    @Test
    public void addErrorLine0Test() {
        addErrorTestForLine(0);
    }

    @Test
    public void addErrorLine1Test() {
        addErrorTestForLine(1);
    }

    private void addErrorTestForLine(final int pLineNo) {
        final ActiveRule rule = setupRule("repo", "key");

        final NewIssue newIssue = mock(NewIssue.class);
        final NewIssueLocation newLocation = mock(NewIssueLocation.class);
        when(context.newIssue()).thenReturn(newIssue);
        when(newIssue.newLocation()).thenReturn(newLocation);
        when(newIssue.forRule(rule.ruleKey())).thenReturn(newIssue);
        when(newIssue.at(newLocation)).thenReturn(newIssue);
        when(newLocation.on(any(InputComponent.class))).thenReturn(newLocation);
        when(newLocation.at(any(TextRange.class))).thenReturn(newLocation);
        when(newLocation.message(anyString())).thenReturn(newLocation);

        when(inputFile.selectLine(anyInt())).thenReturn(new DefaultTextRange(
            new DefaultTextPointer(1, 1), new DefaultTextPointer(1, 2)));

        final AuditEvent eventAdded = new AuditEvent(this, file.getAbsolutePath(),
            new LocalizedMessage(pLineNo, "", "", null, "", CheckstyleAuditListenerTest.class,
                "msg"));
        addErrorToListener(eventAdded);

        verify(newIssue, times(1)).save();
        verify(newIssue, times(1)).forRule(rule.ruleKey());
        verify(newIssue, times(1)).at(newLocation);
        verify(newIssue, times(1)).newLocation();
        verify(newLocation, times(1)).on(any());
        verify(newLocation, times(1)).at(any());
        verify(newLocation, times(1)).message(any());
    }

    @Test
    public void addErrorOnUnknownRule() {
        when(context.newIssue()).thenReturn(null);
        addErrorToListener(event);
        verify(context, times(1)).newIssue();
    }

    @Test
    public void addErrorOnTreeWalkerRule() {
        final AuditEvent treeWalkerEvent = new AuditEvent(
                this,
                file.getAbsolutePath(),
                new LocalizedMessage(42, "", "", null, "",
                        TreeWalker.class, "msg"));

        when(context.newIssue()).thenReturn(null);
        addErrorToListener(treeWalkerEvent);
        verify(context, times(1)).newIssue();
    }

    @Test
    public void addErrorOnUnknownFile() {
        final ActiveRule rule = setupRule("repo", "key");
        addErrorToListener(event);
        verifyZeroInteractions(rule);
    }

    private CheckstyleAuditListener addErrorToListener(AuditEvent auditEvent) {
        final CheckstyleAuditListener listener = new CheckstyleAuditListener(
                ruleFinder,
                fileSystem);
        listener.setContext(context);
        listener.addError(auditEvent);
        return listener;
    }

    private ActiveRule setupRule(String repo, String key) {
        final ActiveRule rule = mock(ActiveRule.class);
        when(rule.ruleKey()).thenReturn(RuleKey.of(repo, key));
        when(
                ruleFinder.find(RuleKey.of(CheckstyleConstants.REPOSITORY_KEY,
                        CheckstyleAuditListenerTest.class.getCanonicalName()))).thenReturn(rule);
        return rule;
    }
}