package net.ranides.assira.ui.parsers;

import net.ranides.assira.grammar.G4Node;
import net.ranides.assira.grammar.G4Parser;
import net.ranides.assira.grammar.G4ParserCoverage;
import net.ranides.assira.grammar.G4Selector;
import net.ranides.assira.grammar.G4SelectorBuilder;
import net.ranides.assira.grammar.G4Support;
import net.ranides.assira.io.ResourceUtils;
import net.ranides.assira.junit.NewAssert;
import net.ranides.assira.system.HostSystem;
import net.ranides.assira.text.StrAppender;
import net.ranides.grammars.generated.UiGrammarLexer;
import net.ranides.grammars.generated.UiGrammarParser;
import org.junit.Test;

import java.awt.*;
import java.io.IOException;
import java.io.StringWriter;

import static net.ranides.assira.junit.NewAssert.assertThrows;
import static org.junit.Assert.assertEquals;

public class GrammarUtilsTest {

    G4Parser CSS_PARSER = G4Parser.builder()
        .lexer(UiGrammarLexer.class)
        .parser(UiGrammarParser.class)
        .build();

    G4Selector<Boolean> IGNORE = new G4SelectorBuilder<Boolean>()
        .withParser(CSS_PARSER)
        .valueForToken("WS", true)
        .valueForToken("CS", true)
        .valueForToken("DOT", true)
        .valueForToken("LB", true)
        .valueForToken("RB", true)
        .valueForToken("LP", true)
        .valueForToken("RP", true)
        .valueForToken("LT", true)
        .valueForToken("GT", true)
        .valueForToken("PREFIX_HSL", true)
        .valueForToken("PREFIX_HSLA", true)
        .valueForToken("PREFIX_RGB", true)
        .valueForToken("PREFIX_RGBA", true)
        .valueForToken("ANY", true)
        .prepare();

    @Test
    public void grammarInspectionGenerated() throws IOException {
        G4Parser gp = G4Parser.builder()
            .lexer(UiGrammarLexer.class)
            .parser(UiGrammarParser.class)
            .build();
        grammarInspection(gp);
    }

    @Test
    public void grammarInspectionInterpreted() throws IOException {
        G4Parser gp = G4Parser.builder()
            .sourceFile("src\\main\\antlr4\\net\\ranides\\grammars\\generated\\UiGrammar.g4")
            .imports("src\\main\\antlr4\\imports")
            .handler((message, error) -> System.out.println(message))
            .build();
        grammarInspection(gp);
    }

    public void grammarInspection(G4Parser gp) throws IOException {

        G4Node out = gp.parse("color", "rgba(15,20,30,0.75)");

        assertEquals(ResourceUtils.text("grammars/CssColors.tree.txt"), G4Support.printTree(out));
        assertEquals(ResourceUtils.text("grammars/CssColors.paths.txt"), G4Support.printPath(out));

        assertEquals("rgba(15,20,30,0.75)", out.child("colorRGBA").text());
        assertEquals("0.75", out.child("colorRGBA").child("@alpha").text());

        assertEquals("color/colorRGBA/componentAlpha", out.find("colorRGBA/@alpha").path());

        assertThrows(e -> e.getMessage().contains("There is no path 'colorRGBA/@unknown' inside node 'color#color_rgba=rgba(15,20,30,0.75)'"), () -> {
            out.find("colorRGBA/@unknown").path();
        });

        assertThrows(e -> e.getMessage().contains("There is no path 'unknown' inside node 'color/colorRGBA$1=rgba(15,20,30,0.75)'"), () -> {
            out.child("colorRGBA").find("unknown").path();
        });
    }

    @Test
    public void grammarInterpretedCoverage() throws IOException {
        G4Parser gp = G4Parser.builder()
            .sourceFile("src\\main\\antlr4\\net\\ranides\\grammars\\generated\\UiGrammar.g4")
            .imports("src\\main\\antlr4\\imports")
            .handler((message, error) -> System.out.println(message))
            .build();
        grammarCoverage(gp);
    }

    @Test
    public void grammarGeneratedCoverage() throws IOException {
        G4Parser gp = G4Parser.builder()
            .lexer(UiGrammarLexer.class)
            .parser(UiGrammarParser.class)
            .build();
        grammarCoverage(gp);
    }

    public void grammarCoverage(G4Parser gp) throws IOException {
        G4ParserCoverage info = gp.rules();

        info.use(IGNORE);

        info.use(ParsePrimitives.NUMBER);
        info.use(ParsePrimitives.INT);
        info.use(ParsePrimitives.REAL);
        info.use(ParsePrimitives.CODE);
        info.use(ParseCssUnit.UNIT_LENGTH);
        info.use(ParseCssUnit.UNIT_ANGLE);
        info.use(ParseCssUnit.LENGTH);
        info.use(ParseCssUnit.ANGLE);
        info.use(ParseCssColor.COLOR);
        info.use(ParseCssBorder.BORDER_STYLE);
        info.use(ParseCssBorder.BORDER_SIZE);
        info.use(ParseCssBorder.SIZE_COMPONENT);
        info.use(ParseCssBorder.BORDER);
        info.use(ParseCssFont.FONT);
        info.use(ParseCssFont.FONT_SWING_STYLE);
        info.use(ParseCssFont.FONT_CSS_STYLE);
        info.use(ParseCssFont.FONT_CSS_WEIGHT);
        info.use(ParseCssFont.FONT_CSS_FAMILY);

        StringWriter sw = new StringWriter();
        printCoverage(info, new StrAppender(sw));
        if(HostSystem.JUNIT.detected()) {
            printCoverage(info);
        }

        assertEquals(ResourceUtils.text("grammars/CssCoverage.txt"), sw.toString());
    }

    private void printCoverage(G4ParserCoverage info, StrAppender output) {
        info.rules().forEach(rule -> {
            if(rule.inner().isPresent()) {
                output.printf("%d: %s <= %s%n", rule.used(), rule.name(), rule.inner().get());
            } else {
                output.printf("%d: %s%n", rule.used(), rule.name());
            }
            for(G4ParserCoverage.BranchCoverage branch : rule.branches()) {
                if(branch.inner().isPresent()) {
                    output.printf("   %d: %s%s <= %s%n", branch.used(), branch.rule(), branch.qualifier(), branch.inner().get());
                } else {
                    output.printf("   %d: %s%s%n", branch.used(), branch.rule(), branch.qualifier());
                }
            }
        });
    }

    private void printCoverage(G4ParserCoverage info) {
        info.rules().forEach(rule -> {
            int c = rule.used()>0 ? 32 : 33;
            if(rule.inner().isPresent()) {
                System.out.printf("\u001B[%dm%d: %s <= %s%n", c, rule.used(), rule.name(), rule.inner().get());
            } else {
                System.out.printf("\u001B[%dm%d: %s%n", c, rule.used(), rule.name());
            }
            for(G4ParserCoverage.BranchCoverage branch : rule.branches()) {
                int g = rule.used()>0 ? 32 : 33;
                if(branch.inner().isPresent()) {
                    System.out.printf("\u001B[%dm   %d: %s%s <= %s%n", g, branch.used(), branch.rule(), branch.qualifier(), branch.inner().get());
                } else {
                    System.out.printf("\u001B[%dm   %d: %s%s%n", g, branch.used(), branch.rule(), branch.qualifier());
                }
            }
        });
    }

    @Test
    public void grammarSelector() {
        G4Parser parser = G4Parser.builder()
            .lexer(UiGrammarLexer.class)
            .parser(UiGrammarParser.class)
            .build();

        // without validation
        new G4SelectorBuilder<Color>()
            .withName("selector:color")
            .mapRule("invalidGrammarRule", node -> Color.BLUE)
            .mapRule("colorRGBA#stupid", node -> Color.BLUE)
            .mapRule("#stupid", node -> Color.BLUE)
            .prepare();

        NewAssert.assertThrows(e -> e.getMessage().contains("stupid"), () -> {
            new G4SelectorBuilder<Color>()
                .withName("selector:color")
                .withParser(parser)
                .mapRule("colorRGBA", node -> Color.BLUE)
                .mapRule("stupid", node -> Color.BLUE)
                .prepare();
        });

        NewAssert.assertThrows(e -> e.getMessage().contains("componentHue#color_rgba"), () -> {
            new G4SelectorBuilder<Color>()
                .withName("selector:color")
                .withParser(parser)
                .mapRule("color#color_rgba", node -> Color.BLUE)
                .mapRule("componentHue#color_rgba", node -> Color.BLUE)
                .prepare();
        });

        NewAssert.assertThrows(e -> e.getMessage().contains("#stupid"), () -> {
            new G4SelectorBuilder<Color>()
                .withName("selector:color")
                .withParser(parser)
                .mapRule("#color_rgba", node -> Color.BLUE)
                .mapRule("#stupid", node -> Color.BLUE)
                .prepare();
        });

    }

}