package net.ranides.assira.ui.primitives;

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.ImageAssert;
import net.ranides.assira.junit.NewAssert;
import net.ranides.assira.system.HostSystem;
import net.ranides.assira.text.StrAppender;
import net.ranides.assira.ui.helpers.ColorUtils;
import net.ranides.assira.ui.parsers.GrammarBase;
import net.ranides.assira.ui.parsers.ParseCssBorder;
import net.ranides.assira.ui.parsers.ParseCssColor;
import net.ranides.assira.ui.parsers.ParseCssUnit;
import net.ranides.assira.ui.parsers.ParsePrimitives;
import net.ranides.grammars.generated.UiGrammarLexer;
import net.ranides.grammars.generated.UiGrammarParser;
import org.junit.Test;

import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;

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

public class UiGrammarTest {

    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)
        .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);
        info.use(ParseCssUnit.LENGTH);
        info.use(ParseCssColor.COLOR);
        info.use(ParseCssBorder.STYLE);
        info.use(ParseCssBorder.SIZE);
        info.use(ParseCssBorder.SIZE_COMPONENT);
        info.use(ParseCssBorder.BORDER);

        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();
        });

    }

    @Test
    public void parseColorHex() {

        assertEquals(new Color(0xf0, 0x80, 0x40), ParseCssColor.color("#f08040"));
        assertEquals(new Color(0x10, 0x34, 0x72), ParseCssColor.color("#103472"));
        assertEquals(new Color(0x11, 0x33, 0x44), ParseCssColor.color("#134"));
        assertEquals(new Color(0xEE, 0xCC, 0x77), ParseCssColor.color("#eC7"));
        assertEquals(new Color(0x11, 0xCC, 0xAA, 0x55), ParseCssColor.color("#1Ca5"));
        assertEquals(new Color(0x10, 0x34, 0x72, 0x80), ParseCssColor.color("#10347280"));

        assertEquals(
            Arrays.asList("#5080a0", "#324668", "#778899"),
            ParseCssColor.colors("rgb ( 80, 128, 160 ) #324668 #789").map(ColorUtils::asCSS2).list()
        );

        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("#1C!");
        });
        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("#11");
        });

        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("#10345");
        });
        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("#1034728");
        });
        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("#103472800");
        });
    }

    @Test
    public void parseColorRGB() {
        assertEquals(new Color(15,20,30,191), ParseCssColor.color("rgba ( 15, 20, 30, 0.75 )"));
        assertEquals(new Color(15,20,30), ParseCssColor.color("rgb(15,20,30)"));

        assertEquals(new Color(10, 34, 72), ParseCssColor.color("rgb(10,34,72)"));
        assertEquals(new Color(10, 34, 72, 255), ParseCssColor.color("rgb(10,34,72)"));

        assertEquals(new Color(10, 34, 72, 0), ParseCssColor.color("rgba(10,34,72,0.00)"));
        assertEquals(new Color(10, 34, 72, 0), ParseCssColor.color("rgba (10, 34,  72, 0)"));

        assertEquals(new Color(10, 34, 72, 64), ParseCssColor.color("rgba(10,34,72,0.25)"));
        assertEquals(new Color(10, 34, 72, 191), ParseCssColor.color("rgba(10,34,72,0.75 )"));

        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("rgb(x");
        });
        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("rgba(x");
        });
    }

    @Test
    public void parseColorHSL() {

        assertEquals(new Color(0,128,0), ParseCssColor.color("hsl(120, 100%, 50%)"));
        assertEquals(new Color(120,191,48), ParseCssColor.color("hsl(90, 75%, 75%)"));
        assertEquals(new Color(191,191,48), ParseCssColor.color("hsl(60, 75%, 75%)"));

        assertEquals(new Color(0,128,0,0), ParseCssColor.color("hsl(120, 100%, 50%, 0)"));
        assertEquals(new Color(120,191,48,64), ParseCssColor.color("hsl(90, 75%, 75%, 0.25)"));
        assertEquals(new Color(191,191,48,191), ParseCssColor.color("hsl(60, 75%, 75%, 0.75)"));

        assertEquals(new Color(0,128,0,0), ParseCssColor.color("hsla(120, 100%, 50%, 0)"));
        assertEquals(new Color(120,191,48,64), ParseCssColor.color("hsla(90, 75%, 75%, 0.25)"));
        assertEquals(new Color(191,191,48,191), ParseCssColor.color("hsla(60, 75%, 75%, 0.75)"));

        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("hsl(x");
        });
        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("hsl(15,20)");
        });
        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("hsla(x");
        });
    }

    @Test
    public void parseInvalidColor() {
        NewAssert.assertThrows(IllegalArgumentException.class, ()->{
            ParseCssColor.color("nocolor");
        });
    }

    @Test
    public void testBorder0() {
        assertNotNull( GrammarBase.PARSER.parse("root_border", "solid 1px #808080") );
        assertNotNull( GrammarBase.PARSER.parse("root_border", "#808080 1px solid") );
        assertNotNull( GrammarBase.PARSER.parse("root_border", "#808080 1px") );
        assertNotNull( GrammarBase.PARSER.parse("root_border", "solid") );
    }

    @Test
    public void testBorder1() throws IOException {
        JPanel panel = new JPanel();
        panel.setBackground(new Color(128,192,192));
        panel.setForeground(Color.RED);

        ImageAssert.assertEquals("borders/border1solid1.bmp", paintBorder(panel, ParseCssBorder.border(panel, "solid 1px #f00")));
        ImageAssert.assertEquals("borders/border1solid2.bmp", paintBorder(panel, ParseCssBorder.border(panel, "solid #800 1px 2px 3px 4px")));

        ImageAssert.assertEquals("borders/border1outset1.bmp", paintBorder(panel, ParseCssBorder.border(panel, "outset 1px #f00")));
        ImageAssert.assertEquals("borders/border1outset2.bmp", paintBorder(panel, ParseCssBorder.border(panel, "outset 4px #800")));
        ImageAssert.assertEquals("borders/border1outset3.bmp", paintBorder(panel, ParseCssBorder.border(panel, "outset 4px")));
        ImageAssert.assertEquals("borders/border1outset4.bmp", paintBorder(panel, ParseCssBorder.border(panel, "outset #800")));
        ImageAssert.assertEquals("borders/border1outset5.bmp", paintBorder(panel, ParseCssBorder.border(panel, "outset")));


        ImageAssert.assertEquals("borders/border1dashed1.bmp", paintBorder(panel, ParseCssBorder.border(panel, "dashed #ff0 1px")));
        ImageAssert.assertEquals("borders/border1dashed2.bmp", paintBorder(panel, ParseCssBorder.border(panel, "dashed #ff0 4px")));

        ImageAssert.assertEquals("borders/border1dotted1.bmp", paintBorder(panel, ParseCssBorder.border(panel, "dotted #ff0 1px")));
        ImageAssert.assertEquals("borders/border1dotted2.bmp", paintBorder(panel, ParseCssBorder.border(panel, "dotted #ff0 4px")));

        ImageAssert.assertEquals("borders/border1double1.bmp", paintBorder(panel, ParseCssBorder.border(panel, "double #ff0 1px")));
        ImageAssert.assertEquals("borders/border1double2.bmp", paintBorder(panel, ParseCssBorder.border(panel, "double #ff0 4px")));

        ImageAssert.assertEquals("borders/border1groove1.bmp", paintBorder(panel, ParseCssBorder.border(panel, "groove 1px")));
        ImageAssert.assertEquals("borders/border1groove2.bmp", paintBorder(panel, ParseCssBorder.border(panel, "groove 4px")));

        ImageAssert.assertEquals("borders/border1ridge1.bmp", paintBorder(panel, ParseCssBorder.border(panel, "ridge 1px")));
        ImageAssert.assertEquals("borders/border1ridge2.bmp", paintBorder(panel, ParseCssBorder.border(panel, "ridge 4px")));
    }

    @Test
    public void testBorder2() throws IOException {
        JPanel panel = new JPanel();
        panel.setBackground(new Color(128,192,192));
        panel.setForeground(Color.RED);

        ImageAssert.assertEquals("borders/border2etched1.bmp", paintBorder(panel, ParseCssBorder.border(panel, "etched raised #800 #080")));
        ImageAssert.assertEquals("borders/border2etched2.bmp", paintBorder(panel, ParseCssBorder.border(panel, "etched raised #080")));
        ImageAssert.assertEquals("borders/border2etched3.bmp", paintBorder(panel, ParseCssBorder.border(panel, "etched raised")));

        ImageAssert.assertEquals("borders/border2soft1.bmp", paintBorder(panel, ParseCssBorder.border(panel, "soft-bevel raised #0a8 #084")));
        ImageAssert.assertEquals("borders/border2soft2.bmp", paintBorder(panel, ParseCssBorder.border(panel, "soft-bevel raised #080")));
        ImageAssert.assertEquals("borders/border2soft3.bmp", paintBorder(panel, ParseCssBorder.border(panel, "soft-bevel raised")));
    }

    @Test
    public void testBorderSize() {
        assertEquals("2px", GrammarBase.PARSER.parse("borderSize", "2px").text() );
        assertEquals("2px 4px", GrammarBase.PARSER.parse("borderSize", "2px 4px").text() );
        assertEquals("2px 4px 8px 2px", GrammarBase.PARSER.parse("borderSize", "2px 4px 8px 2px").text() );

        assertEquals("2px", GrammarBase.PARSER.parse("borderSize", "2px4px8px2px").text() );
    }

    private BufferedImage paintBorder(JPanel panel, Border border) throws IOException {
        BufferedImage output = new BufferedImage(200, 100, BufferedImage.TYPE_INT_RGB);
        Graphics graphics3 = output.getGraphics();
        graphics3.setColor(panel.getBackground());
        graphics3.fillRect(0,0,200,100);
        border.paintBorder(panel, graphics3, 1, 1, 198, 98);
        return output;
    }

}