package net.ranides.assira.grammar;

import net.ranides.assira.grammar.impl.G4ParserInterpreter;
import net.ranides.assira.io.ResourceUtils;
import net.ranides.assira.junit.NewAssert;
import org.junit.Test;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;

public class SelectorTest {

    static final G4Selector<Number> num1 = new G4SelectorBuilder<Number>()
        .withName("number")
        .mapRule("numberInt", node -> Integer.parseInt(node.text()))
        .mapRule("numberReal", node -> Double.parseDouble(node.text()))
        .prepare();

    static final G4Selector<Number> ops1 = new G4SelectorBuilder<Number>()
        .withName("operations")
        .inherit(num1)
        .mapRule("expression#expr_2", (node, that) -> {
            Number ia = that.single(node.child("@first"));
            Number ib = that.single(node.child("@second"));
            return ia.doubleValue() * ib.doubleValue();
        })
        .mapRule("expression#expr_3", (node, that) -> {
            Number ia = that.single(node.child("@first"));
            Number ib = that.single(node.child("@second"));
            return ia.doubleValue() + ib.doubleValue();
        })
        .prepare();

    @Test
    public void compose() throws IOException {
        G4Parser parser = G4Parser.builder().resource("Calculator.g4").build();
        assertEquals(34, ops1.first(parser.parse("expression", "2*3 + 7*4")).intValue() );
    }

    @Test
    public void skip() throws IOException {
        G4Parser parser = G4Parser.builder().resource("SkipTest.g4").build();

        G4Selector<Integer> num1 = new G4SelectorBuilder<Integer>()
            .withName("number")
            .mapRule("number", node -> Integer.parseInt(node.text()))
            .prepare();

        G4Selector<Integer> num2 = new G4SelectorBuilder<Integer>()
            .withName("number")
            .skipRule("expression#call")
            .mapRule("number", node -> Integer.parseInt(node.text()))
            .prepare();

        G4Node tree = parser.parse("expression", "1 + 2 * 3 ^ run(4,5+6) * 7");

        assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7), num1.list(tree));
        assertEquals(Arrays.asList(1, 2, 3, 7), num2.list(tree));

    }

    @Test
    public void findIf() throws IOException {
        G4Parser parser = G4Parser.builder().resource("Calculator.g4").build();

        G4Selector<Number> num1 = new G4SelectorBuilder<Number>()
            .withName("number")
            .mapRule("numberInt", node -> Integer.parseInt(node.text()))
            .mapRule("numberReal", node -> Double.parseDouble(node.text()))
            .prepare();

        G4Selector<Integer> pow1 = new G4SelectorBuilder<Integer>()
            .withName("pow")
            .mapRule("operatorMul", ctx -> {
                List<G4Node> args = ctx.parent(2).get().children("expression").list();
                Number a = num1.first(args.get(0));
                Number b = num1.first(args.get(1));
                return a.intValue() * b.intValue();
            })
            .prepare();

        int out1 = pow1.stream(parser.parse("expression", "2+5")).first().orElse(0);
        assertEquals(0, out1);

        int out2 = pow1
            .bind()
            .input(parser.parse("expression", "2+5"))
            .input(parser.parse("expression", "2*5"))
            .input(parser.parse("expression", "7*2"))
            .optional()
            .orElse(0);
        assertEquals(10, out2);
    }

    @Test
    public void nested() throws IOException {
        G4Parser parser = G4Parser.builder().resource("Calculator.g4").build();

        G4Selector<Number> selectNum = new G4SelectorBuilder<Number>()
            .withName("number")
            .mapRule("numberInt", node -> Integer.parseInt(node.text()))
            .mapRule("numberReal", node -> Double.parseDouble(node.text()))
            .prepare();

        G4Selector<Number> selectExpression = new G4SelectorBuilder<Number>()
            .withName("operators")
            .inherit(selectNum)
            .mapRule("expression#expr_2", (node, that) -> {
                Number a = that.first(node.child("@first"));
                Number b = that.first(node.child("@second"));

                assertEquals(2, node.children("expression").size());
                assertThrows(NoSuchElementException.class, () -> {
                    node.child("expression").text();
                });

                switch (node.child("operator2").text()) {
                    case "*": return a.doubleValue() * b.doubleValue();
                    case "/": return a.doubleValue() / b.doubleValue();
                }

                throw new IllegalArgumentException(node.text());
            })
            .mapRule("expression#expr_3", (node, that) -> {
                Number a = that.first(node.child("@second"));
                Number b = that.first(node.child("@first"));

                switch (node.child(1).text()) {
                    case "+": return a.doubleValue() + b.doubleValue();
                    case "-": return a.doubleValue() - b.doubleValue();
                }

                throw new IllegalArgumentException(node.text());
            })
            .prepare();


        assertEquals(9, selectExpression.first(parser.parse("expression", "6 + 9 / 3")).intValue());
        assertEquals(5, selectExpression.first(parser.parse("expression", "(6 + 9) / 3")).intValue());
    }

    @Test
    public void wrongSelector() throws IOException {
        G4Parser parser = G4Parser.builder().resource("Calculator.g4").build();

        // without validation: will not throw
        new G4SelectorBuilder<Number>()
            .withName("number")
            .mapRule("numberInt", node -> Integer.parseInt(node.text()))
            .mapRule("numberReal", node -> Double.parseDouble(node.text()))
            .mapRule("stupidRule", node -> 0)
            .mapRule("expression#expr_bracket", node -> 0) // correct
            .mapRule("#expr_bracket", node -> 0) // correct
            .mapRule("expression#stupid", node -> 0) // stupid
            .mapRule("numberInt#expr_bracket", node -> 0) // stupid
            .mapRule("#stupid", node -> 0) // stupid

            .prepare();

        NewAssert.assertThrows(e -> e.getMessage().contains("stupidRule"), () -> {
            new G4SelectorBuilder<Number>()
                .withName("number")
                .withParser(parser)
                .mapRule("numberReal", node -> Double.parseDouble(node.text()))
                .mapRule("stupidRule", node -> 0)
                .prepare();
        });

        NewAssert.assertThrows(e -> e.getMessage().contains("stupid_branch"), () -> {
            new G4SelectorBuilder<Number>()
                .withName("number")
                .withParser(parser)
                .mapRule("expression#expr_bracket", node -> 0) // correct
                .mapRule("#expr_bracket", node -> 0) // correct
                .mapRule("expression#stupid_branch", node -> 0) // stupid
                .prepare();
        });

        NewAssert.assertThrows(e -> e.getMessage().contains("expression#opmul"), () -> {
            new G4SelectorBuilder<Number>()
                .withName("number")
                .withParser(parser)
                .mapRule("operator2#opmul", node -> 0) // correct
                .mapRule("#opmul", node -> 0) // correct
                .mapRule("expression#opmul", node -> 0) // stupid
                .prepare();
        });

    }

}