package net.ranides.assira.ui.parsers;

import lombok.experimental.UtilityClass;
import net.ranides.assira.generic.Ref;
import net.ranides.assira.grammar.G4Exception;
import net.ranides.assira.grammar.G4Node;
import net.ranides.assira.grammar.G4Selector;
import net.ranides.assira.grammar.G4SelectorBuilder;
import net.ranides.assira.grammar.properties.G4NodeProperties;
import net.ranides.assira.ui.components.CustomBevelBorder;
import net.ranides.assira.ui.model.CssBorderSize;
import net.ranides.assira.ui.model.CssBorderStyle;
import net.ranides.grammars.generated.UiGrammarParser;

import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
import javax.swing.border.SoftBevelBorder;
import java.awt.*;
import java.util.List;
import java.util.NoSuchElementException;

@UtilityClass
public class ParseCssBorder {

    public static final G4Selector<CssBorderStyle> STYLE = new G4SelectorBuilder<CssBorderStyle>()
        .withName("CSS:border-style")
        .withParser(GrammarBase.PARSER)
        .valueForToken(UiGrammarParser.CSS2_none, CssBorderStyle.NONE)
        .valueForToken(UiGrammarParser.CSS2_BORDER_solid, CssBorderStyle.SOLID)
        .valueForToken(UiGrammarParser.CSS2_BORDER_dotted, CssBorderStyle.DOTTED)
        .valueForToken(UiGrammarParser.CSS2_BORDER_dashed, CssBorderStyle.DASHED)
        .valueForToken(UiGrammarParser.CSS2_BORDER_double, CssBorderStyle.DOUBLE)
        .valueForToken(UiGrammarParser.CSS2_BORDER_groove, CssBorderStyle.GROOVE)
        .valueForToken(UiGrammarParser.CSS2_BORDER_ridge, CssBorderStyle.RIDGE)
        .valueForToken(UiGrammarParser.CSS2_BORDER_inset, CssBorderStyle.INSET)
        .valueForToken(UiGrammarParser.CSS2_BORDER_outset, CssBorderStyle.OUTSET)
        .valueForToken(UiGrammarParser.SWING_BORDER_etched, CssBorderStyle.SWING_ETCHED)
        .valueForToken(UiGrammarParser.SWING_BORDER_bevel, CssBorderStyle.SWING_BEVEL)
        .valueForToken(UiGrammarParser.SWING_BORDER_bevel_soft, CssBorderStyle.SWING_BEVEL_SOFT)
        .prepare();

    public static final G4Selector<Integer> SIZE_COMPONENT = new G4SelectorBuilder<Integer>()
        .withName("CSS:border-size")
        .withParser(GrammarBase.PARSER)
        .inherit(ParseCssUnit.LENGTH)
        .valueForToken(UiGrammarParser.CSS2_BORDER_thin, 1)
        .valueForToken(UiGrammarParser.CSS2_BORDER_medium, 2)
        .valueForToken(UiGrammarParser.CSS2_BORDER_thick, 3)
        .prepare();


    public static final G4Selector<CssBorderSize> SIZE = new G4SelectorBuilder<CssBorderSize>()
        .withName("CSS:border-size")
        .withParser(GrammarBase.PARSER)
        .mapRule("#borderSize1", node -> {
            int s = SIZE_COMPONENT.single(node);
            return new CssBorderSize(s, s, s, s);
        })
        .mapRule("#borderSize2", node -> {
            java.util.List<Integer> s = SIZE_COMPONENT.list(node);
            return new CssBorderSize(s.get(0), s.get(1), s.get(0), s.get(1));
        })
        .mapRule("#borderSize4", node -> {
            List<Integer> s = SIZE_COMPONENT.list(node);
            return new CssBorderSize(s.get(0), s.get(1), s.get(2), s.get(3));
        })

        .prepare();

    public static final G4Selector<Border> BORDER = new G4SelectorBuilder<Border>()
        .withName("CSS:border")
        .withParser(GrammarBase.PARSER)
        .mapRule("borderCSS", node -> {
            CssBorderStyle style = node.children("borderCSS_Style").single()
                .map(v -> STYLE.single(v))
                .orElse(CssBorderStyle.SOLID);
            Ref<Color> color = node.children("color").single()
                .map(v -> ParseCssColor.COLOR.single(v));
            Ref<CssBorderSize> size = node.children("borderSize").single()
                .map(v -> SIZE.single(v));

            switch (style) {
                case SOLID: {
                    Color icolor = color.orElseGet(() -> GrammarBase.component(node).getBackground());
                    CssBorderSize isize = size.orElse(CssBorderSize.SINGLE);
                    if (isize.isSimple()) {
                        return BorderFactory.createLineBorder(icolor, isize.getSize());
                    } else {
                        return BorderFactory.createMatteBorder(isize.getTop(), isize.getLeft(), isize.getBottom(), isize.getRight(), icolor);
                    }
                }
                case OUTSET:
                    return CustomBevelBorder.newInstance(size.map(CssBorderSize::getSize), color, CustomBevelBorder.RAISED);
                case INSET:
                    return CustomBevelBorder.newInstance(size.map(CssBorderSize::getSize), color, CustomBevelBorder.LOWERED);
                case DASHED: {
                    Color icolor = color.orElseGet(() -> GrammarBase.component(node).getBackground());
                    int isize = size.orElse(CssBorderSize.SINGLE).getSize();
                    return BorderFactory.createDashedBorder(icolor, isize, 3.0f, 3.0f, false);
                }
                case DOTTED: {
                    Color icolor = color.orElseGet(() -> GrammarBase.component(node).getBackground());
                    int isize = size.orElse(CssBorderSize.SINGLE).getSize();
                    return BorderFactory.createDashedBorder(icolor, isize, 1.0f, 1.0f, true);
                }
                case DOUBLE: {
                    Color icolor = color.orElseGet(() -> GrammarBase.component(node).getBackground());
                    int isize = size.orElse(CssBorderSize.SINGLE).getSize();

                    return BorderFactory.createCompoundBorder(
                        BorderFactory.createCompoundBorder(
                            BorderFactory.createLineBorder(icolor, isize),
                            BorderFactory.createEmptyBorder(isize, isize, isize, isize)
                        ),
                        BorderFactory.createLineBorder(icolor, isize)
                    );
                }
                case GROOVE: {
                    return BorderFactory.createCompoundBorder(
                        CustomBevelBorder.newInstance(size.map(CssBorderSize::getSize), color, CustomBevelBorder.LOWERED),
                        CustomBevelBorder.newInstance(size.map(CssBorderSize::getSize), color, CustomBevelBorder.RAISED)
                    );
                }
                case RIDGE: {
                    return BorderFactory.createCompoundBorder(
                        CustomBevelBorder.newInstance(size.map(CssBorderSize::getSize), color, CustomBevelBorder.RAISED),
                        CustomBevelBorder.newInstance(size.map(CssBorderSize::getSize), color, CustomBevelBorder.LOWERED)
                    );
                }
                case NONE: {
                    int isize = size.orElse(CssBorderSize.SINGLE).getSize();
                    return BorderFactory.createEmptyBorder(isize, isize, isize, isize);
                }
                default:
                    throw new UnsupportedOperationException("Unsupported border style: " + style);
            }


        })
        .mapRule("borderSwingSimple", node -> {
            CssBorderStyle style = STYLE.first(node.child("borderSwingSimple_Style"));
            String type = node.child("borderSwingSimple_Type").text();
            List<Color> colors = node.children("color").map(v -> ParseCssColor.COLOR.single(v)).list();

            boolean typeRaised;
            switch (type) {
                case "lowered":
                    typeRaised = false;
                    break;
                case "raised":
                    typeRaised = true;
                    break;
                default:
                    throw new UnsupportedOperationException("Unsupported botder type: " + type);
            }

            switch (style) {
                case SWING_ETCHED: {
                    int etchedType = typeRaised ? EtchedBorder.RAISED : EtchedBorder.LOWERED;
                    if (colors.size() == 2) {
                        return BorderFactory.createEtchedBorder(etchedType, colors.get(0), colors.get(1));
                    }
                    if (colors.size() == 1) {
                        return BorderFactory.createEtchedBorder(etchedType, colors.get(0).brighter(), colors.get(0).darker());
                    }
                    if (colors.size() == 0) {
                        return BorderFactory.createEtchedBorder(etchedType);
                    }
                }
                case SWING_BEVEL: {
                    int bevelType = typeRaised ? BevelBorder.RAISED : BevelBorder.LOWERED;
                    if (colors.size() == 2) {
                        return BorderFactory.createBevelBorder(bevelType, colors.get(0), colors.get(1));
                    }
                    if (colors.size() == 1) {
                        return BorderFactory.createBevelBorder(bevelType, colors.get(0).brighter(), colors.get(0).darker());
                    }
                    if (colors.size() == 0) {
                        return BorderFactory.createBevelBorder(bevelType);
                    }
                }
                case SWING_BEVEL_SOFT: {
                    int softBevelType = typeRaised ? SoftBevelBorder.RAISED : SoftBevelBorder.LOWERED;
                    if (colors.size() == 2) {
                        return BorderFactory.createSoftBevelBorder(softBevelType, colors.get(0), colors.get(1));
                    }
                    if (colors.size() == 1) {
                        return BorderFactory.createSoftBevelBorder(softBevelType, colors.get(0).brighter(), colors.get(0).darker());
                    }
                    if (colors.size() == 0) {
                        return BorderFactory.createSoftBevelBorder(softBevelType);
                    }
                }
                default:
                    throw new UnsupportedOperationException("Unsupported border style: " + style);
            }
        })
        .prepare();


    public static Border border(String value) {
        try {
            return BORDER.single(GrammarBase.PARSER.parse("root_border", value));
        } catch (NoSuchElementException | G4Exception e) {
            throw new IllegalArgumentException("Invalid border: " + value);
        }
    }

    public static Border border(JComponent component, String value) {
        try {
            G4Node node = GrammarBase.PARSER.parse("root_border", value);
            G4NodeProperties.put(node, GrammarBase.COMPONENT, component);
            return BORDER.single(node);
        } catch (NoSuchElementException | G4Exception e) {
            throw new IllegalArgumentException("Invalid border: " + value, e);
        }
    }
}
