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

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

import static net.sourceforge.pmd.lang.apex.ast.ApexParserTestHelpers.parse;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.Test;

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

import apex.jorje.semantic.ast.compilation.Compilation;

public class ApexParserTest {

    @Test
    public void understandsSimpleFile() {

        // Setup
        String code = "@isTest\n public class SimpleClass {\n" + "    @isTest\n public static void testAnything() {\n"
                + "        \n" + "    }\n" + "}";

        // Exercise
        ApexNode<Compilation> rootNode = parse(code);

        // Verify
        List<ASTMethod> methods = rootNode.findDescendantsOfType(ASTMethod.class);
        assertEquals(4, methods.size());
    }

    private String testCodeForLineNumbers =
              "public class SimpleClass {\n" // line 1
            + "    public void method1() {\n" // line 2
            + "        System.out.println(\"abc\");\n" // line 3
            + "        // this is a comment\n" // line 4
            + "    }\n" // line 5
            + "}\n"; // line 6

    @Test
    public void verifyLineColumNumbers() {
        ApexNode<Compilation> rootNode = parse(testCodeForLineNumbers);
        assertLineNumbersForTestCode(rootNode);
    }

    @Test
    public void verifyLineColumNumbersWithWindowsLineEndings() {
        String windowsLineEndings = testCodeForLineNumbers.replaceAll(" \n", "\r\n");
        ApexNode<Compilation> rootNode = parse(windowsLineEndings);
        assertLineNumbersForTestCode(rootNode);
    }

    private void assertLineNumbersForTestCode(ApexNode<Compilation> rootNode) {
        // whole source code, well from the beginning of the class
        // name Modifier of the class - doesn't work. This node just
        // sees the identifier ("SimpleClass")
        // assertPosition(rootNode.jjtGetChild(0), 1, 1, 1, 6);

        // "public"
        assertPosition(rootNode, 1, 14, 6, 2);

        // "method1" - starts with identifier until end of its block statement
        Node method1 = rootNode.jjtGetChild(1);
        assertPosition(method1, 2, 17, 5, 5);
        // Modifier of method1 - doesn't work. This node just sees the
        // identifier ("method1")
        // assertPosition(method1.jjtGetChild(0), 2, 17, 2, 20); // "public" for
        // method1

        // BlockStatement - the whole method body
        Node blockStatement = method1.jjtGetChild(1);
        assertTrue(((ASTBlockStatement) blockStatement).hasCurlyBrace());
        assertPosition(blockStatement, 2, 27, 5, 5);

        // the expression ("System.out...")
        Node expressionStatement = blockStatement.jjtGetChild(0);
        assertPosition(expressionStatement, 3, 9, 3, 34);
    }

    @Test
    public void verifyEndLine() {

        String code = "public class SimpleClass {\n" // line 1
                + "    public void method1() {\n" // line 2
                + "    }\n" // line 3
                + "    public void method2() {\n" // line 4
                + "    }\n" // line 5
                + "}\n"; // line 6

        ApexNode<Compilation> rootNode = parse(code);

        Node method1 = rootNode.jjtGetChild(1);
        assertEquals("Wrong begin line", 2, method1.getBeginLine());
        assertEquals("Wrong end line", 3, method1.getEndLine());

        Node method2 = rootNode.jjtGetChild(2);
        assertEquals("Wrong begin line", 4, method2.getBeginLine());
        assertEquals("Wrong end line", 5, method2.getEndLine());
    }

    @Test
    public void parsesRealWorldClasses() throws Exception {
        File directory = new File("src/test/resources");
        File[] fList = directory.listFiles();

        for (File file : fList) {
            if (file.isFile() && file.getName().endsWith(".cls")) {
                String sourceCode = FileUtils.readFileToString(file);
                ApexNode<Compilation> rootNode = parse(sourceCode);
                Assert.assertNotNull(rootNode);
            }
        }
    }

    /**
     * See bug #1485
     * 
     * @see <a href="https://sourceforge.net/p/pmd/bugs/1485/">#1485 [apex] Analysis of some apex classes cause a stackoverflow error</a>
     */
    @Test
    public void stackOverflowDuringClassParsing() throws Exception {
        String source = IOUtils.toString(ApexParserTest.class.getResourceAsStream("StackOverflowClass.cls"));
        ApexNode<Compilation> rootNode = parse(source);
        Assert.assertNotNull(rootNode);

        int count = visitPosition(rootNode, 0);
        Assert.assertEquals(427, count);
    }

    private int visitPosition(Node node, int count) {
        int result = count + 1;
        Assert.assertTrue(node.getBeginLine() > 0);
        Assert.assertTrue(node.getBeginColumn() > 0);
        Assert.assertTrue(node.getEndLine() > 0);
        Assert.assertTrue(node.getEndColumn() > 0);
        for (int i = 0; i < node.jjtGetNumChildren(); i++) {
            result = visitPosition(node.jjtGetChild(i), result);
        }
        return result;
    }

    // TEST HELPER

    private static void assertPosition(Node node, int beginLine, int beginColumn, int endLine, int endColumn) {
        assertEquals("Wrong begin line", beginLine, node.getBeginLine());
        assertEquals("Wrong begin column", beginColumn, node.getBeginColumn());
        assertEquals("Wrong end line", endLine, node.getEndLine());
        assertEquals("Wrong end column", endColumn, node.getEndColumn());
    }
}
