///////////////////////////////////////////////////////////////////////////////////////////////
// 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.utils;

import static com.google.common.truth.Truth.assertWithMessage;
import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.isUtilsClassHasPrivateConstructor;

import java.util.List;

import org.junit.jupiter.api.Test;

import com.puppycrawl.tools.checkstyle.DetailAstImpl;
import com.puppycrawl.tools.checkstyle.api.Comment;
import com.puppycrawl.tools.checkstyle.api.DetailNode;
import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag;
import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocNodeImpl;
import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo;
import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTags;

public class JavadocUtilTest {

    @Test
    public void testTags() {
        final String[] text = {
            "/** @see elsewhere ",
            " * {@link List }, {@link List link text }",
            "   {@link List#add(Object) link text}",
            " * {@link Class link text}",
        };
        final Comment comment = new Comment(text, 1, 4, text[3].length());
        final JavadocTags allTags =
            JavadocUtil.getJavadocTags(comment, JavadocUtil.JavadocTagType.ALL);
        assertWithMessage("Invalid valid tags size")
            .that(allTags.getValidTags())
            .hasSize(5);
    }

    @Test
    public void testBlockTag() {
        final String[] text = {
            "/** @see elsewhere ",
            " */",
        };
        final Comment comment = new Comment(text, 1, 4, text[1].length());
        final JavadocTags allTags =
            JavadocUtil.getJavadocTags(comment, JavadocUtil.JavadocTagType.ALL);
        assertWithMessage("Invalid valid tags size")
            .that(allTags.getValidTags())
            .hasSize(1);
    }

    @Test
    public void testTagType() {
        final String[] text = {
            "/** @see block",
            " * {@link List inline}, {@link List#add(Object)}",
        };
        final Comment comment = new Comment(text, 1, 2, text[1].length());
        final JavadocTags blockTags =
            JavadocUtil.getJavadocTags(comment, JavadocUtil.JavadocTagType.BLOCK);
        final JavadocTags inlineTags =
            JavadocUtil.getJavadocTags(comment, JavadocUtil.JavadocTagType.INLINE);
        assertWithMessage("Invalid valid tags size")
            .that(blockTags.getValidTags())
            .hasSize(1);
        assertWithMessage("Invalid valid tags size")
            .that(inlineTags.getValidTags())
            .hasSize(2);
    }

    @Test
    public void testInlineTagLinkText() {
        final String[] text = {
            "/** {@link List link text }",
        };
        final Comment comment = new Comment(text, 1, 1, text[0].length());
        final List<JavadocTag> tags = JavadocUtil.getJavadocTags(
            comment, JavadocUtil.JavadocTagType.ALL).getValidTags();
        assertWithMessage("Invalid first arg")
            .that(tags.get(0).getFirstArg())
            .isEqualTo("List link text");
    }

    @Test
    public void testInlineTagMethodRef() {
        final String[] text = {
            "/** {@link List#add(Object)}",
        };
        final Comment comment = new Comment(text, 1, 1, text[0].length());
        final List<JavadocTag> tags = JavadocUtil.getJavadocTags(
            comment, JavadocUtil.JavadocTagType.ALL).getValidTags();
        assertWithMessage("Invalid first arg")
            .that(tags.get(0).getFirstArg())
            .isEqualTo("List#add(Object)");
    }

    @Test
    public void testTagPositions() {
        final String[] text = {
            "/** @see elsewhere",
            "    also {@link Name value} */",
        };
        final Comment comment = new Comment(text, 1, 2, text[1].length());

        final List<JavadocTag> tags = JavadocUtil.getJavadocTags(
            comment, JavadocUtil.JavadocTagType.ALL).getValidTags();

        assertWithMessage("Invalid tags size")
            .that(tags)
            .hasSize(2);

        final JavadocTag seeTag = tags.get(0);
        assertWithMessage("Invalid tag name")
            .that(seeTag.getTagName())
            .isEqualTo(JavadocTagInfo.SEE.getName());
        assertWithMessage("Invalid line number")
            .that(seeTag.getLineNo())
            .isEqualTo(1);
        assertWithMessage("Invalid column number")
            .that(seeTag.getColumnNo())
            .isEqualTo(4);

        final JavadocTag linkTag = tags.get(1);
        assertWithMessage("Invalid tag name")
            .that(linkTag.getTagName())
            .isEqualTo(JavadocTagInfo.LINK.getName());
        assertWithMessage("Invalid line number")
            .that(linkTag.getLineNo())
            .isEqualTo(2);
        assertWithMessage("Invalid column number")
            .that(linkTag.getColumnNo())
            .isEqualTo(10);
    }

    @Test
    public void testInlineTagPositions() {
        final String[] text = {"/** Also {@link Name value} */"};
        final Comment comment = new Comment(text, 1, 0, text[0].length());

        final List<JavadocTag> tags = JavadocUtil.getJavadocTags(
            comment, JavadocUtil.JavadocTagType.INLINE).getValidTags();

        assertWithMessage("Invalid tags size")
            .that(tags)
            .hasSize(1);
        final int lineNo = tags.get(0).getLineNo();
        assertWithMessage("Unexpected line number")
            .that(lineNo)
            .isEqualTo(0);
        final int columnNo = tags.get(0).getColumnNo();
        assertWithMessage("Unexpected column number")
            .that(columnNo)
            .isEqualTo(10);
    }

    @Test
    public void testInvalidTags() {
        final String[] text = {
            "/** @fake block",
            " * {@bogus inline}",
            " * {@link List valid}",
        };
        final Comment comment = new Comment(text, 1, 3, text[2].length());
        final JavadocTags allTags =
            JavadocUtil.getJavadocTags(comment, JavadocUtil.JavadocTagType.ALL);
        assertWithMessage("Unexpected invalid tags size")
            .that(allTags.getInvalidTags())
            .hasSize(2);
        assertTag("Unexpected invalid tag", new InvalidJavadocTag(1, 4, "fake"),
                allTags.getInvalidTags().get(0));
        assertTag("Unexpected invalid tag", new InvalidJavadocTag(2, 4, "bogus"),
                allTags.getInvalidTags().get(1));
        assertWithMessage("Unexpected valid tags size")
            .that(allTags.getValidTags())
            .hasSize(1);
        assertTag("Unexpected valid tag", new JavadocTag(3, 4, "link", "List valid"),
                allTags.getValidTags().get(0));
    }

    @Test
    public void testEmptyBlockComment() {
        final String emptyComment = "";
        assertWithMessage("Should return false when empty string is passed")
                .that(JavadocUtil.isJavadocComment(emptyComment))
                .isFalse();
    }

    @Test
    public void testEmptyBlockCommentAst() {
        final DetailAstImpl commentBegin = new DetailAstImpl();
        commentBegin.setType(TokenTypes.BLOCK_COMMENT_BEGIN);
        commentBegin.setText("/*");

        final DetailAstImpl commentContent = new DetailAstImpl();
        commentContent.setType(TokenTypes.COMMENT_CONTENT);
        commentContent.setText("");

        final DetailAstImpl commentEnd = new DetailAstImpl();
        commentEnd.setType(TokenTypes.BLOCK_COMMENT_END);
        commentEnd.setText("*/");

        commentBegin.setFirstChild(commentContent);
        commentContent.setNextSibling(commentEnd);

        assertWithMessage("Should return false when empty block comment is passed")
                .that(JavadocUtil.isJavadocComment(commentBegin))
                .isFalse();
    }

    @Test
    public void testEmptyJavadocComment() {
        final String emptyJavadocComment = "*";
        assertWithMessage("Should return true when empty javadoc comment is passed")
                .that(JavadocUtil.isJavadocComment(emptyJavadocComment))
                .isTrue();
    }

    @Test
    public void testEmptyJavadocCommentAst() {
        final DetailAstImpl commentBegin = new DetailAstImpl();
        commentBegin.setType(TokenTypes.BLOCK_COMMENT_BEGIN);
        commentBegin.setText("/*");

        final DetailAstImpl javadocCommentContent = new DetailAstImpl();
        javadocCommentContent.setType(TokenTypes.COMMENT_CONTENT);
        javadocCommentContent.setText("*");

        final DetailAstImpl commentEnd = new DetailAstImpl();
        commentEnd.setType(TokenTypes.BLOCK_COMMENT_END);
        commentEnd.setText("*/");

        commentBegin.setFirstChild(javadocCommentContent);
        javadocCommentContent.setNextSibling(commentEnd);

        final DetailAstImpl commentBeginParent = new DetailAstImpl();
        commentBeginParent.setType(TokenTypes.MODIFIERS);
        commentBeginParent.setFirstChild(commentBegin);

        final DetailAstImpl aJavadocPosition = new DetailAstImpl();
        aJavadocPosition.setType(TokenTypes.METHOD_DEF);
        aJavadocPosition.setFirstChild(commentBeginParent);
        assertWithMessage("Should return true when empty javadoc comment ast is passed")
                .that(JavadocUtil.isJavadocComment(commentBegin))
                .isTrue();
    }

    @Test
    public void testIsProperUtilsClass() throws ReflectiveOperationException {
        assertWithMessage("Constructor is not private")
                .that(isUtilsClassHasPrivateConstructor(JavadocUtil.class))
                .isTrue();
    }

    @Test
    public void testBranchContains() {
        final JavadocNodeImpl node = new JavadocNodeImpl();
        final JavadocNodeImpl firstChild = new JavadocNodeImpl();
        final JavadocNodeImpl secondChild = new JavadocNodeImpl();

        node.setType(JavadocTokenTypes.JAVADOC);
        firstChild.setType(JavadocTokenTypes.BODY_TAG_START);
        secondChild.setType(JavadocTokenTypes.CODE_LITERAL);

        node.setChildren(firstChild, secondChild);
        assertWithMessage("Should return true when branch contains node passed")
                .that(JavadocUtil.containsInBranch(node, JavadocTokenTypes.AUTHOR_LITERAL))
                .isFalse();

        firstChild.setParent(node);
        secondChild.setParent(node);
        assertWithMessage("Should return false when branch does not contain node passed")
                .that(JavadocUtil.containsInBranch(node, JavadocTokenTypes.AUTHOR_LITERAL))
                .isFalse();

        secondChild.setType(JavadocTokenTypes.AUTHOR_LITERAL);
        assertWithMessage("Should return true when branch contains node passed")
                .that(JavadocUtil.containsInBranch(node, JavadocTokenTypes.AUTHOR_LITERAL))
                .isTrue();
    }

    @Test
    public void testGetTokenNameForId() {
        assertWithMessage("Invalid token name")
            .that(JavadocUtil.getTokenName(JavadocTokenTypes.EOF))
            .isEqualTo("EOF");
    }

    @Test
    public void testGetTokenNameForLargeId() {
        try {
            JavadocUtil.getTokenName(30073);
            assertWithMessage("exception expected").fail();
        }
        catch (IllegalArgumentException ex) {
            assertWithMessage("Invalid exception message")
                .that(ex.getMessage())
                .isEqualTo("Unknown javadoc token id. Given id: 30073");
        }
    }

    @Test
    public void testGetTokenNameForInvalidId() {
        try {
            JavadocUtil.getTokenName(110);
            assertWithMessage("exception expected").fail();
        }
        catch (IllegalArgumentException ex) {
            assertWithMessage("Invalid exception message")
                .that(ex.getMessage())
                .isEqualTo("Unknown javadoc token id. Given id: 110");
        }
    }

    @Test
    public void testGetTokenNameForLowerBoundInvalidId() {
        try {
            JavadocUtil.getTokenName(10095);
            assertWithMessage("exception expected").fail();
        }
        catch (IllegalArgumentException ex) {
            assertWithMessage("Invalid exception message")
                .that(ex.getMessage())
                .isEqualTo("Unknown javadoc token id. Given id: 10095");
        }
    }

    @Test
    public void testGetTokenIdThatIsUnknown() {
        try {
            JavadocUtil.getTokenId("");
            assertWithMessage("exception expected").fail();
        }
        catch (IllegalArgumentException ex) {
            assertWithMessage("Invalid exception message")
                .that(ex.getMessage())
                .isEqualTo("Unknown javadoc token name. Given name ");
        }
    }

    @Test
    public void testGetTokenId() {
        final int tokenId = JavadocUtil.getTokenId("JAVADOC");

        assertWithMessage("Invalid token id")
            .that(tokenId)
            .isEqualTo(JavadocTokenTypes.JAVADOC);
    }

    @Test
    public void testGetJavadocCommentContent() {
        final DetailAstImpl detailAST = new DetailAstImpl();
        final DetailAstImpl javadoc = new DetailAstImpl();

        javadoc.setText("1javadoc");
        detailAST.setFirstChild(javadoc);
        final String commentContent = JavadocUtil.getJavadocCommentContent(detailAST);

        assertWithMessage("Invalid comment content")
            .that(commentContent)
            .isEqualTo("javadoc");
    }

    @Test
    public void testGetFirstToken() {
        final JavadocNodeImpl javadocNode = new JavadocNodeImpl();
        final JavadocNodeImpl basetag = new JavadocNodeImpl();
        basetag.setType(JavadocTokenTypes.BASE_TAG);
        final JavadocNodeImpl body = new JavadocNodeImpl();
        body.setType(JavadocTokenTypes.BODY);

        body.setParent(javadocNode);
        basetag.setParent(javadocNode);
        javadocNode.setChildren(basetag, body);

        final DetailNode result = JavadocUtil.findFirstToken(javadocNode, JavadocTokenTypes.BODY);

        assertWithMessage("Invalid first token")
            .that(result)
            .isEqualTo(body);
    }

    @Test
    public void testGetPreviousSibling() {
        final JavadocNodeImpl root = new JavadocNodeImpl();

        final JavadocNodeImpl node = new JavadocNodeImpl();
        node.setIndex(1);
        node.setParent(root);

        final JavadocNodeImpl previousNode = new JavadocNodeImpl();
        previousNode.setIndex(0);
        node.setParent(root);

        root.setChildren(previousNode, node);

        final DetailNode previousSibling = JavadocUtil.getPreviousSibling(node);

        assertWithMessage("Unexpected node")
            .that(previousSibling)
            .isEqualTo(previousNode);
    }

    @Test
    public void testGetLastTokenName() {
        assertWithMessage("Unexpected token name")
            .that(JavadocUtil.getTokenName(10094))
            .isEqualTo("RP");
    }

    @Test
    public void testEscapeAllControlChars() {
        assertWithMessage("invalid result")
            .that(JavadocUtil.escapeAllControlChars("abc"))
            .isEqualTo("abc");
        assertWithMessage("invalid result")
            .that(JavadocUtil.escapeAllControlChars("1\\r2\\n3\\t"))
            .isEqualTo("1\\r2\\n3\\t");
    }

    private static void assertTag(String message, InvalidJavadocTag expected,
            InvalidJavadocTag actual) {
        assertWithMessage(message + " line")
            .that(actual.getLine())
            .isEqualTo(expected.getLine());
        assertWithMessage(message + " column")
            .that(actual.getCol())
            .isEqualTo(expected.getCol());
        assertWithMessage(message + " name")
            .that(actual.getName())
            .isEqualTo(expected.getName());
    }

    private static void assertTag(String message, JavadocTag expected,
            JavadocTag actual) {
        assertWithMessage(message + " line")
            .that(actual.getLineNo())
            .isEqualTo(expected.getLineNo());
        assertWithMessage(message + " column")
            .that(actual.getColumnNo())
            .isEqualTo(expected.getColumnNo());
        assertWithMessage(message + " first arg")
            .that(actual.getFirstArg())
            .isEqualTo(expected.getFirstArg());
        assertWithMessage(message + " tag name")
            .that(actual.getTagName())
            .isEqualTo(expected.getTagName());
    }

}
