/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.lang.java.ast;

import static net.sourceforge.pmd.lang.java.ParserTstUtil.getNodes;
import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava14;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.jaxen.JaxenException;
import org.junit.Ignore;
import org.junit.Test;

import net.sourceforge.pmd.PMD;
import net.sourceforge.pmd.lang.ast.Node;

public class SimpleNodeTest {

    @Test
    public void testMethodDiffLines() {
        Set<ASTMethodDeclaration> methods = getNodes(ASTMethodDeclaration.class, METHOD_DIFF_LINES);
        verifyNode(methods.iterator().next(), 2, 9, 4, 2);
    }

    @Test
    public void testMethodSameLine() {
        Set<ASTMethodDeclaration> methods = getNodes(ASTMethodDeclaration.class, METHOD_SAME_LINE);
        verifyNode(methods.iterator().next(), 2, 9, 2, 21);
    }

    @Test
    public void testNoLookahead() {
        String code = NO_LOOKAHEAD; // 1, 8 -> 1, 20
        Set<ASTClassOrInterfaceDeclaration> uCD = getNodes(ASTClassOrInterfaceDeclaration.class, code);
        verifyNode(uCD.iterator().next(), 1, 8, 1, 20);
    }

    @Test
    public void testHasExplicitExtends() {
        String code = HAS_EXPLICIT_EXTENDS;
        ASTClassOrInterfaceDeclaration ucd = getNodes(ASTClassOrInterfaceDeclaration.class, code).iterator().next();
        assertTrue(ucd.jjtGetChild(0) instanceof ASTExtendsList);
    }

    @Test
    public void testNoExplicitExtends() {
        String code = NO_EXPLICIT_EXTENDS;
        ASTClassOrInterfaceDeclaration ucd = getNodes(ASTClassOrInterfaceDeclaration.class, code).iterator().next();
        assertFalse(ucd.jjtGetChild(0) instanceof ASTExtendsList);
    }

    @Test
    public void testHasExplicitImplements() {
        String code = HAS_EXPLICIT_IMPLEMENTS;
        ASTClassOrInterfaceDeclaration ucd = getNodes(ASTClassOrInterfaceDeclaration.class, code).iterator().next();
        assertTrue(ucd.jjtGetChild(0) instanceof ASTImplementsList);
    }

    @Test
    public void testNoExplicitImplements() {
        String code = NO_EXPLICIT_IMPLEMENTS;
        ASTClassOrInterfaceDeclaration ucd = getNodes(ASTClassOrInterfaceDeclaration.class, code).iterator().next();
        assertFalse(ucd.jjtGetChild(0) instanceof ASTImplementsList);
    }

    @Test
    public void testColumnsOnQualifiedName() {
        Set<ASTName> name = getNodes(ASTName.class, QUALIFIED_NAME);
        Iterator<ASTName> i = name.iterator();
        while (i.hasNext()) {
            Node node = i.next();
            if (node.getImage().equals("java.io.File")) {
                verifyNode(node, 1, 8, 1, 19);
            }
        }
    }

    @Test
    public void testLineNumbersForNameSplitOverTwoLines() {
        Set<ASTName> name = getNodes(ASTName.class, BROKEN_LINE_IN_NAME);
        Iterator<ASTName> i = name.iterator();
        while (i.hasNext()) {
            Node node = i.next();
            if (node.getImage().equals("java.io.File")) {
                verifyNode(node, 1, 8, 2, 4);
            }
            if (node.getImage().equals("Foo")) {
                verifyNode(node, 2, 15, 2, 18);
            }
        }
    }

    @Test
    public void testLineNumbersAreSetOnAllSiblings() {
        for (ASTBlock b : getNodes(ASTBlock.class, LINE_NUMBERS_ON_SIBLINGS)) {
            assertTrue(b.getBeginLine() > 0);
        }
        for (ASTVariableInitializer b : getNodes(ASTVariableInitializer.class, LINE_NUMBERS_ON_SIBLINGS)) {
            assertTrue(b.getBeginLine() > 0);
        }
        for (ASTExpression b : getNodes(ASTExpression.class, LINE_NUMBERS_ON_SIBLINGS)) {
            assertTrue(b.getBeginLine() > 0);
        }
    }

    @Test
    public void testFindDescendantsOfType() {
        ASTBlock block = new ASTBlock(2);
        block.jjtAddChild(new ASTReturnStatement(1), 0);
        assertEquals(1, block.findDescendantsOfType(ASTReturnStatement.class).size());
    }

    @Test
    public void testFindDescendantsOfTypeMultiple() {
        ASTBlock block = new ASTBlock(1);
        block.jjtAddChild(new ASTBlockStatement(2), 0);
        block.jjtAddChild(new ASTBlockStatement(3), 1);
        List<ASTBlockStatement> nodes = block.findDescendantsOfType(ASTBlockStatement.class);
        assertEquals(2, nodes.size());
    }

    @Test
    public void testFindDescendantsOfTypeRecurse() {
        ASTBlock block = new ASTBlock(1);
        ASTBlock childBlock = new ASTBlock(2);
        block.jjtAddChild(childBlock, 0);
        childBlock.jjtAddChild(new ASTMethodDeclaration(3), 0);
        List<ASTMethodDeclaration> nodes = block.findDescendantsOfType(ASTMethodDeclaration.class);
        assertEquals(1, nodes.size());
    }

    @Test
    public void testGetFirstChild() {
        ASTBlock block = new ASTBlock(1);
        ASTStatement x = new ASTStatement(2);
        block.jjtAddChild(x, 0);
        block.jjtAddChild(new ASTStatement(3), 1);

        Node n = block.getFirstDescendantOfType(ASTStatement.class);
        assertNotNull(n);
        assertTrue(n instanceof ASTStatement);
        assertEquals(x, n);
    }

    @Test
    public void testGetFirstChildNested() {
        ASTBlock block = new ASTBlock(1);
        ASTStatement x = new ASTStatement(2);
        ASTAssignmentOperator x1 = new ASTAssignmentOperator(4);
        x.jjtAddChild(x1, 0);
        block.jjtAddChild(x, 0);
        block.jjtAddChild(new ASTStatement(3), 1);

        Node n = block.getFirstDescendantOfType(ASTAssignmentOperator.class);
        assertNotNull(n);
        assertTrue(n instanceof ASTAssignmentOperator);
        assertEquals(x1, n);
    }

    @Test
    public void testGetFirstChildNestedDeeper() {
        ASTBlock block = new ASTBlock(1);
        ASTStatement x = new ASTStatement(2);
        ASTAssignmentOperator x1 = new ASTAssignmentOperator(4);
        ASTName x2 = new ASTName(5);

        x.jjtAddChild(x1, 0);
        x1.jjtAddChild(x2, 0);
        block.jjtAddChild(x, 0);
        block.jjtAddChild(new ASTStatement(3), 1);

        Node n = block.getFirstDescendantOfType(ASTName.class);
        assertNotNull(n);
        assertTrue(n instanceof ASTName);
        assertEquals(x2, n);
    }

    @Test
    public void testParentMethods() {
        ASTCompilationUnit u = parseJava14(TEST1);

        ASTMethodDeclarator d = u.getFirstDescendantOfType(ASTMethodDeclarator.class);
        assertSame("getFirstParentOfType ASTMethodDeclaration", d.jjtGetParent(),
                d.getFirstParentOfType(ASTMethodDeclaration.class));
        assertNull("getFirstParentOfType ASTName", d.getFirstParentOfType(ASTName.class));

        assertSame("getNthParent 1", d.jjtGetParent(), d.getNthParent(1));
        assertSame("getNthParent 2", d.jjtGetParent().jjtGetParent(), d.getNthParent(2));
        assertSame("getNthParent 6", u, d.getNthParent(6));
        assertNull("getNthParent 7", d.getNthParent(7));
        assertNull("getNthParent 8", d.getNthParent(8));
    }

    private static final String TEST1 = "public class Test {" + PMD.EOL + "  void bar(String s) {" + PMD.EOL
            + "   s = s.toLowerCase();" + PMD.EOL + "  }" + PMD.EOL + "}";

    @Ignore
    @Test
    public void testContainsNoInner() {
        ASTCompilationUnit c = getNodes(ASTCompilationUnit.class, CONTAINS_NO_INNER).iterator().next();
        List<ASTFieldDeclaration> res = new ArrayList<>();
        c.findDescendantsOfType(ASTFieldDeclaration.class, res, false);
        assertTrue(res.isEmpty());
        /*
         * String expectedXml =
         * "<CompilationUnit BeginColumn=\"1\" BeginLine=\"5\" EndColumn=\"1\" EndLine=\"5\">"
         * +
         * "<TypeDeclaration BeginColumn=\"1\" BeginLine=\"1\" EndColumn=\"1\" EndLine=\"5\">"
         * +
         * "<ClassOrInterfaceDeclaration Abstract=\"false\" BeginColumn=\"8\" BeginLine=\"1\" EndColumn=\"1\" "
         * +
         * "EndLine=\"5\" Final=\"false\" Image=\"Test\" Interface=\"false\" Native=\"false\" Nested=\"false\" PackagePrivate=\"false\" Private=\"false\" Protected=\"false\" Public=\"true\" Static=\"false\" Strictfp=\"false\" Synchronized=\"false\" Transient=\"false\" Volatile=\"false\">"
         * +
         * "<ClassOrInterfaceBody BeginColumn=\"19\" BeginLine=\"1\" EndColumn=\"1\" EndLine=\"5\">"
         * +
         * "<ClassOrInterfaceBodyDeclaration AnonymousInnerClass=\"false\" BeginColumn=\"3\" BeginLine=\"2\" EndColumn=\"3\" EndLine=\"4\">"
         * +
         * "<ClassOrInterfaceDeclaration Abstract=\"false\" BeginColumn=\"10\" BeginLine=\"2\" EndColumn=\"3\" EndLine=\"4\" Final=\"false\" "
         * +
         * "Image=\"Inner\" Interface=\"false\" Native=\"false\" Nested=\"true\" PackagePrivate=\"false\" Private=\"false\" Protected=\"false\" "
         * +
         * "Public=\"true\" Static=\"false\" Strictfp=\"false\" Synchronized=\"false\" Transient=\"false\" Volatile=\"false\">"
         * +
         * "<ClassOrInterfaceBody BeginColumn=\"22\" BeginLine=\"2\" EndColumn=\"3\" EndLine=\"4\">"
         * +
         * "<ClassOrInterfaceBodyDeclaration AnonymousInnerClass=\"false\" BeginColumn=\"4\" BeginLine=\"3\" EndColumn=\"11\" EndLine=\"3\">"
         * +
         * "<FieldDeclaration Abstract=\"false\" Array=\"false\" ArrayDepth=\"0\" BeginColumn=\"4\" BeginLine=\"3\" EndColumn=\"11\" EndLine=\"3\" Final=\"false\" Native=\"false\" PackagePrivate=\"true\" Private=\"false\" Protected=\"false\" Public=\"false\" Static=\"false\" Strictfp=\"false\" Synchronized=\"false\" Transient=\"false\" VariableName=\"foo\" Volatile=\"false\"><Type Array=\"false\" ArrayDepth=\"0\" BeginColumn=\"4\" BeginLine=\"3\" EndColumn=\"6\" EndLine=\"3\">"
         * +
         * "<PrimitiveType Array=\"false\" ArrayDepth=\"0\" BeginColumn=\"4\" BeginLine=\"3\" Boolean=\"false\" EndColumn=\"6\" EndLine=\"3\" Image=\"int\"/>"
         * + "</Type>" +
         * "<VariableDeclarator BeginColumn=\"8\" BeginLine=\"3\" EndColumn=\"10\" EndLine=\"3\">"
         * +
         * "<VariableDeclaratorId Array=\"false\" ArrayDepth=\"0\" BeginColumn=\"8\" BeginLine=\"3\" EndColumn=\"10\" EndLine=\"3\" ExceptionBlockParameter=\"false\" Image=\"foo\"/>"
         * +
         * "</VariableDeclarator></FieldDeclaration></ClassOrInterfaceBodyDeclaration></ClassOrInterfaceBody>"
         * +
         * "</ClassOrInterfaceDeclaration></ClassOrInterfaceBodyDeclaration></ClassOrInterfaceBody></ClassOrInterfaceDeclaration>"
         * + "</TypeDeclaration></CompilationUnit>"; assertEquals( expectedXml,
         * getXmlString( c ) );
         */ }

    @Test
    public void testContainsNoInnerWithAnonInner() {
        ASTCompilationUnit c = getNodes(ASTCompilationUnit.class, CONTAINS_NO_INNER_WITH_ANON_INNER).iterator().next();
        List<ASTFieldDeclaration> res = new ArrayList<>();
        c.findDescendantsOfType(ASTFieldDeclaration.class, res, false);
        assertTrue(res.isEmpty());
    }

    @Test
    public void testContainsChildOfType() {
        ASTClassOrInterfaceDeclaration c = getNodes(ASTClassOrInterfaceDeclaration.class, CONTAINS_CHILDREN_OF_TYPE)
                .iterator().next();
        assertTrue(c.hasDescendantOfType(ASTFieldDeclaration.class));
    }

    @Test
    public void testXPathNodeSelect() throws JaxenException {
        ASTClassOrInterfaceDeclaration c = getNodes(ASTClassOrInterfaceDeclaration.class, TEST_XPATH).iterator().next();
        List<Node> nodes = c.findChildNodesWithXPath("//FieldDeclaration");
        assertEquals(2, nodes.size());
        assertTrue(nodes.get(0) instanceof ASTFieldDeclaration);

        assertTrue(c.hasDescendantMatchingXPath("//FieldDeclaration"));
        assertFalse(c.hasDescendantMatchingXPath("//MethodDeclaration"));
    }

    @Test
    public void testUserData() {
        ASTClassOrInterfaceDeclaration c = getNodes(ASTClassOrInterfaceDeclaration.class, HAS_EXPLICIT_EXTENDS)
                .iterator().next();
        assertNull(c.getUserData());
        c.setUserData("foo");
        assertEquals("foo", c.getUserData());
        c.setUserData(null);
        assertNull(c.getUserData());
    }

    private void verifyNode(Node node, int beginLine, int beginCol, int endLine, int endCol) {
        assertEquals("Unexpected beginning line: ", beginLine, node.getBeginLine());
        assertEquals("Unexpected beginning column: ", beginCol, node.getBeginColumn());
        assertEquals("Unexpected ending line:", endLine, node.getEndLine());
        assertEquals("Unexpected ending column:", endCol, node.getEndColumn());
    }

    private static final String HAS_EXPLICIT_EXTENDS = "public class Test extends Foo {}";

    private static final String NO_EXPLICIT_EXTENDS = "public class Test {}";

    private static final String HAS_EXPLICIT_IMPLEMENTS = "public class Test implements Foo {}";

    private static final String NO_EXPLICIT_IMPLEMENTS = "public class Test {}";

    private static final String METHOD_SAME_LINE = "public class Test {" + PMD.EOL + " public void foo() {}" + PMD.EOL
            + "}";

    private static final String QUALIFIED_NAME = "import java.io.File;" + PMD.EOL + "public class Foo{}";

    private static final String BROKEN_LINE_IN_NAME = "import java.io." + PMD.EOL + "File;" + PMD.EOL
            + "public class Foo{}";

    private static final String LINE_NUMBERS_ON_SIBLINGS = "public class Foo {" + PMD.EOL + " void bar() {" + PMD.EOL
            + "  try {" + PMD.EOL + "  } catch (Exception1 e) {" + PMD.EOL + "   int x =2;" + PMD.EOL + "  }" + PMD.EOL
            + " if (x != null) {}" + PMD.EOL + " }" + PMD.EOL + "}";

    private static final String NO_LOOKAHEAD = "public class Foo { }";

    private static final String METHOD_DIFF_LINES = "public class Test {" + PMD.EOL + " public void foo() {" + PMD.EOL
            + "  int x;" + PMD.EOL + " }" + PMD.EOL + "}";

    private static final String CONTAINS_CHILDREN_OF_TYPE = "public class Test {" + PMD.EOL + "  int x;" + PMD.EOL
            + "}";

    private static final String CONTAINS_NO_INNER = "public class Test {" + PMD.EOL + "  public class Inner {" + PMD.EOL
            + "   int foo;" + PMD.EOL + "  }" + PMD.EOL + "}";

    private static final String CONTAINS_NO_INNER_WITH_ANON_INNER = "public class Test {" + PMD.EOL + "  void bar() {"
            + PMD.EOL + "   foo(new Fuz() { int x = 2;});" + PMD.EOL + "  }" + PMD.EOL + "}";

    private static final String TEST_XPATH = "public class Test {" + PMD.EOL + "  int x = 2;" + PMD.EOL
            + "  int y = 42;" + PMD.EOL + "}";
}
