package net.ranides.assira.ui.parsers;

import lombok.experimental.UtilityClass;
import net.ranides.assira.generic.Ref;
import net.ranides.assira.grammar.G4Node;
import net.ranides.assira.grammar.G4Selector;
import net.ranides.assira.grammar.G4SelectorBuilder;
import net.ranides.assira.text.StringUtils;
import net.ranides.assira.ui.model.CssFontStyle;
import net.ranides.assira.ui.model.CssFontWeight;
import net.ranides.grammars.generated.UiGrammarParser;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;

@UtilityClass
public class ParseCssFont {

    public static final G4Selector<Integer> FONT_SWING_STYLE = new G4SelectorBuilder<Integer>()
        .withName("CssFont:FONT_STYLE")
        .withParser(GrammarBase.PARSER)
        .valueForToken(UiGrammarParser.SWING_FONT_STYLE_PLAIN, Font.PLAIN)
        .valueForToken(UiGrammarParser.SWING_FONT_STYLE_BOLD, Font.BOLD)
        .valueForToken(UiGrammarParser.SWING_FONT_STYLE_ITALIC, Font.ITALIC)
        .valueForToken(UiGrammarParser.SWING_FONT_STYLE_BOLD_ITALIC, Font.BOLD | Font.ITALIC)
        .prepare();

    public static final G4Selector<CssFontStyle> FONT_CSS_STYLE = new G4SelectorBuilder<CssFontStyle>()
        .withName("CssFont:FONT_CSS_STYLE")
        .withParser(GrammarBase.PARSER)
        .mapRule("fontStyle", node -> {
            boolean italic = !node.children("CSS3_FONT_STYLE_ITALIC").isEmpty();
            boolean oblique = !node.children("CSS3_FONT_STYLE_OBLIQUE").isEmpty();
            double angle = oblique ? node.children("angle").single().map(ParseCssUnit.ANGLE::single).orElse(10.0) : 0.0;
            return new CssFontStyle(italic, oblique, angle);
        })
        .prepare();

    public static final G4Selector<CssFontWeight> FONT_CSS_WEIGHT = new G4SelectorBuilder<CssFontWeight>()
        .withName("CssFont:FONT_CSS_STYLE")
        .withParser(GrammarBase.PARSER)
        .mapRule("fontWeight", node -> {
            boolean bold = !node.children("CSS3_FONT_WEIGHT_BOLD").isEmpty();
            boolean bolder = !node.children("CSS3_FONT_WEIGHT_LIGHTER").isEmpty();
            boolean lighter = !node.children("CSS3_FONT_WEIGHT_BOLDER").isEmpty();
            int absolute = node.children("numberInt").single().map(ParsePrimitives::parseInt).orElse(400);
            return new CssFontWeight(bold, bolder, lighter, absolute);
        })
        .prepare();

    public static final G4Selector<String> FONT_CSS_FAMILY = new G4SelectorBuilder<String>()
        .withName("CssFont:FONT_CSS_FAMILY")
        .withParser(GrammarBase.PARSER)
        .mapRule(UiGrammarParser.RULE_word, node -> {
            return node.text();
        })
        .mapToken(UiGrammarParser.TEXT, node -> {
            return StringUtils.substr(node.text(), 1, -2);
        })
        .mapToken(UiGrammarParser.CSS3_FONT_GENERIC_SERIF, node -> {
            return Font.SERIF;
        })
        .mapToken(UiGrammarParser.CSS3_FONT_GENERIC_SAN_SERIF, node -> {
            return Font.SANS_SERIF;
        })
        .mapToken(UiGrammarParser.CSS3_FONT_GENERIC_MONOSPACE, node -> {
            return Font.MONOSPACED;
        })
        .prepare();

    public static final G4Selector<Font> FONT = new G4SelectorBuilder<Font>()
        .withName("CssColor:FONT")
        .withParser(GrammarBase.PARSER)
        .mapRule("fontSwingLong", node -> {
            return createSwingFont(node);
        })
        .mapRule("fontSwingShort", node -> {
            return createSwingFont(node);
        })
        .mapRule("fontCss", node -> {
            return createCssFont(node);
        })
        .prepare();

    @NotNull
    private static Font createCssFont(G4Node node) {
        int style = fontStyle(FONT_CSS_STYLE.optional(node), FONT_CSS_WEIGHT.optional(node));

        int size = node.children("fontSize")
            .map(ParseCssUnit.LENGTH::single).first()
            .orElseGet( () -> GrammarBase.component(node).getParent().getFont().getSize());

        String name = FONT_CSS_FAMILY.optional(node)
            .orElseGet(() -> GrammarBase.component(node).getParent().getFont().getName());

        return new Font(name, style, size);
    }

    private static int fontStyle(Ref<CssFontStyle> style, Ref<CssFontWeight> weight) {
        int attrs = 0;

        if(style.isPresent()) {
            CssFontStyle s = style.get();
            if(s.isItalic() || (s.isOblique() && s.getObliqueAngle()>0)) {
                attrs |= Font.ITALIC;
            }
        }
        if(weight.isPresent()) {
            CssFontWeight w = weight.get();
            if(w.isBold() || w.isBolder() || w.getAbsolute() > 400) {
                attrs |= Font.BOLD;
            }
        }
        return attrs;
    }

    private static Font createSwingFont(G4Node node) {
        String name = node.child("@name").text();

        int style = node.children("@style")
            .map(FONT_SWING_STYLE::single).first()
            .orElse(Font.PLAIN);

        int size = node.children("@size")
            .map(ParsePrimitives.INT::single).first()
            .orElseGet( () -> GrammarBase.component(node).getFont().getSize());

        return new Font(name, style, size);
    }


    public static Font font(String value) {
        return GrammarBase.parseSingle(FONT, "root_font", value);
    }

    public static Font font(String value, JComponent component) {
        return GrammarBase.parseSingle(FONT, "root_font", component, value);
    }

}
