package com.jakubu9333.grammarManager;

import com.jakubu9333.grammarManager.model.Grammar;
import com.jakubu9333.grammarManager.model.RightSidePart;
import com.jakubu9333.grammarManager.model.RuleModel;
import com.jakubu9333.grammarManager.service.GrammarService;
import com.jakubu9333.grammarManager.service.RuleService;
import edu.cmu.sphinx.jsgf.parser.TokenMgrError;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Jakub Uhlarik
 */
@SpringBootTest
public class RuleServiceTest {

    @Autowired
    private RuleService ruleService;
    @Autowired
    private GrammarService grammarService;


    @AfterEach
    public void tearDown() {
        grammarService.deleteAll();
    }

    @Test
    public void twoBackslashTest() {
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        RuleModel rule = new RuleModel(null, "h", new HashSet<>(List.of(new RightSidePart("xd\\\\|dc"))), false);
        rule = ruleService.processAddRule(rule, grammar).getValue0();
        assert rule.getRightSide().size() == 2;
        assert rule.getRightSide().equals(new HashSet<>(List.of(new RightSidePart("xd"), new RightSidePart("dc"))));
    }

    @Test
    public void basicRulesAdd() {
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        RuleModel rule = new RuleModel(null, "h", setOfRightSidePart("xd"), false);
        RuleModel rule1 = new RuleModel(null, "g", setOfRightSidePart("xd"), true);
        rule = ruleService.processAddRule(rule, grammar).getValue0();
        ruleService.processAddRule(rule1, grammar);
        assert grammar.getRootRule().getLeftSymbol().equals("<g>");
        assert rule.getId() != null;
        Set<RuleModel> rules = grammar.getRules();
        assert rules.size() == 2;
        int one = 0;
        int two = 0;
        int three = 0;
        for (RuleModel smallRule : rules) {
            switch (smallRule.getLeftSymbol()) {
                case "<h>" -> one++;
                case "<g>" -> two++;
                default -> three++;
            }
        }
        assert one == 1 && two == 1 && three == 0;


    }

    public static Set<RightSidePart> setOfRightSidePart(String string) {
        return new HashSet<>(List.of(new RightSidePart(string)));
    }

    @Test
    @Transactional
    public void sameLeftSideRule() {
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        RuleModel rule = new RuleModel(null, "g", setOfRightSidePart("xd"), false);
        RuleModel rule1 = new RuleModel(null, "<g>", setOfRightSidePart("xd|good"), true);
        RuleModel rule2 = new RuleModel(null, "g>", setOfRightSidePart("xd|bad"), true);
        RuleModel rule3 = new RuleModel(null, "d", setOfRightSidePart("xd|smile"), false);
        ruleService.processAddRule(rule, grammar);
        ruleService.processAddRule(rule1, grammar);
        rule = ruleService.processAddRule(rule2, grammar).getValue0();
        ruleService.processAddRule(rule3, grammar);
        int xd = 0;
        int good = 0;
        int smile = 0;
        int bad = 0;
        int def = 0;
        assert grammar.getRules().size() == 2;
        for (RightSidePart leftPart : rule.getRightSide()) {
            switch (leftPart.getPart()) {
                case "xd" -> xd++;
                case "good" -> good++;
                case "smile" -> smile++;
                case "bad" -> bad++;
                default -> def++;
            }
        }
        assert xd == 1 && good == 1 && smile == 0 && bad == 1 && def == 0;
    }

    @Test
    public void testMultiplePartSet() {
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        Set<RightSidePart> input1 = new HashSet<>(List.of(new RightSidePart("xd"), new RightSidePart("xd")));
        RuleModel rule = new RuleModel(null, "b", input1, false);
        rule = ruleService.processAddRule(rule, grammar).getValue0();
        assert rule.getRightSide().equals(new HashSet<>(List.of(new RightSidePart("xd"))));

        Set<RightSidePart> input2 = new HashSet<>(List.of(new RightSidePart("xd"), new RightSidePart("dx")));
        RuleModel rule2 = new RuleModel(null, "g", input2, false);
        rule2 = ruleService.processAddRule(rule2, grammar).getValue0();
        assert rule2.getRightSide().equals(new HashSet<>(List.of(new RightSidePart("xd"), new RightSidePart("dx"))));

        Set<RightSidePart> input3 = new HashSet<>(List.of(new RightSidePart("(1|2)1"), new RightSidePart(" 21 ")));
        RuleModel rule3 = new RuleModel(null, "d", input3, false);
        rule3 = ruleService.processAddRule(rule3, grammar).getValue0();
        assert rule3.getRightSide().equals(new HashSet<>(List.of(new RightSidePart("11"), new RightSidePart("21"))));
    }

    @Test
    public void addMultipleRuleTest() {
        RuleModel rule2 = new RuleModel(null, "<rule2>", new HashSet<>(List.of(new RightSidePart("ddggd"))), true);
        RuleModel rule3 = new RuleModel(null, "<rule3>", new HashSet<>(List.of(new RightSidePart("smiling"))), false);
        RuleModel rule4 = new RuleModel(null, "<rule4>", new HashSet<>(List.of(new RightSidePart("xdd"))), false);
        RuleModel rule5 = new RuleModel(null, "<rule5>", new HashSet<>(List.of(new RightSidePart("xdgd"))), false);
        Grammar grammar = grammarService.addGrammar(new Grammar());
        Set<RuleModel> rules = new HashSet<>(List.of(rule4, rule2, rule3, rule5));
        ruleService.addRuleMultiple(rules, grammar);
        assert grammar.getRules().equals(rules);

        assert !ruleService.addRuleMultiple(new HashSet<>(), grammar).getValue1();
        assert grammar.getRules().equals(rules);

        Grammar grammar1 = grammarService.addGrammar(new Grammar());
        ruleService.addRuleMultiple(new HashSet<>(), grammar1);
        assert grammar1.getRules().size() == 0;
        RuleModel rule6 = new RuleModel(null, "<rule2>", new HashSet<>(List.of(new RightSidePart("(d|g)dggd"))), true);
        RuleModel rule7 = new RuleModel(null, "<rule3>", new HashSet<>(List.of(new RightSidePart("smiling"))), false);
        Set<RuleModel> rules2 = new HashSet<>(List.of(rule6, rule7));
        Grammar grammar3 = grammarService.addGrammar(new Grammar());
        ruleService.addRuleMultiple(rules2, grammar3);
        RuleModel rule8 = new RuleModel(null, "<rule2>", new HashSet<>(List.of(new RightSidePart("ddggd"), new RightSidePart("gdggd"))), true);
        assert grammar3.getRules().equals(new HashSet<>(List.of(rule7, rule8)));
    }

    @Test
    public void testEmptyAdd() {
        Grammar gr = new Grammar();
        grammarService.addGrammar(gr);
        RuleModel nullRule = new RuleModel();
        RuleModel nullRightStr = new RuleModel(null, "dd", new HashSet<>(), null);
        RuleModel emptyStrRule = new RuleModel(null, "", setOfRightSidePart(""), null);
        try {
            ruleService.rulePreProcess(nullRule);
            assert false;
        } catch (ResponseStatusException ignored) {

        }
        emptyStrRule = ruleService.processAddRule(emptyStrRule, gr).getValue0();
        nullRightStr = ruleService.processAddRule(nullRightStr, gr).getValue0();
        assert emptyStrRule.getLeftSymbol().equals("<empty>");
        assert emptyStrRule.getRightSide().equals(getFromString("<NULL>"));
        assert nullRightStr.getRightSide().equals(getFromString("<NULL>"));
    }

    public static Set<RightSidePart> getFromString(String string) {
        return Arrays.stream(string.split("\\|")).map(RightSidePart::new).collect(Collectors.toSet());
    }

    @Test
    public void testNormalBrackets() {
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        RuleModel rule = new RuleModel(null, "g", setOfRightSidePart("(xd|dd)"), false);
        RuleModel rule1 = new RuleModel(null, "l", setOfRightSidePart("smile (good|cheer)"), false);
        RuleModel rule2 = new RuleModel(null, "p", setOfRightSidePart("(pain|bad) cry"), false);


        rule = ruleService.processAddRule(rule, grammar).getValue0();
        assert rule.getRightSide().equals(getFromString("xd|dd"));
        rule1 = ruleService.processAddRule(rule1, grammar).getValue0();
        assert rule1.getRightSide().equals(getFromString("smile good|smile cheer"));
        rule2 = ruleService.processAddRule(rule2, grammar).getValue0();
        assert rule2.getRightSide().equals(getFromString("pain cry|bad cry"));
        RuleModel rule3 = new RuleModel(null, "s", setOfRightSidePart("(a|b) (c|d) (e|f)"), false);
        rule3 = ruleService.processAddRule(rule3, grammar).getValue0();
        assert rule3.getRightSide().equals(getFromString("a c e|a c f|a d e|a d f|b c e|b c f|b d e|b d f"));
        RuleModel rule5 = new RuleModel(null, "5", setOfRightSidePart("sk(pain|bad)cz"), false);
        rule5 = ruleService.processAddRule(rule5, grammar).getValue0();
        assert rule5.getRightSide().equals(getFromString("skpaincz|skbadcz"));
        RuleModel rule6 = new RuleModel(null, "6", setOfRightSidePart("((1|2)|(3|4))"), false);
        rule6 = ruleService.processAddRule(rule6, grammar).getValue0();
        assert rule6.getRightSide().equals(getFromString("1|2|3|4"));
        RuleModel rule7 = new RuleModel(null, "7", setOfRightSidePart("((12)(34))"), false);
        rule7 = ruleService.processAddRule(rule7, grammar).getValue0();
        assert rule7.getRightSide().equals(getFromString("1234"));

    }


    @Test
    public void testQuoteAndAngleBracket() {
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        RuleModel rule = new RuleModel(null, "0", setOfRightSidePart("\" xd \""), false);
        rule = ruleService.processAddRule(rule, grammar).getValue0();
        assert rule.getRightSide().equals(getFromString("\" xd \""));
        List<String> rightSides = new ArrayList<>();
        List<String> messages = new ArrayList<>();
        rightSides.add("\" xd \"\"");
        messages.add("last quote not ended");
        rightSides.add("<xd><");
        messages.add("ruleref not ended");
        rightSides.add("<xd><dcdc>>");
        messages.add("ending angle bracket without starting bracket");


        for (int i = 0; i < rightSides.size(); i++) {
            RuleModel errRule = new RuleModel(null, "0", setOfRightSidePart(rightSides.get(i)), false);
            try {
                ruleService.rulePreProcess(errRule);
                assert false;
            } catch (ResponseStatusException e) {

                assert Objects.equals(e.getReason(), messages.get(i));
            } catch (TokenMgrError ignored) {

            }
        }

        RuleModel withAn = new RuleModel(null, "45", setOfRightSidePart("< xd ><l>"), false);
        ruleService.rulePreProcess(withAn);
        assert withAn.getRightSide().equals(getFromString("< xd ><l>"));

        RuleModel back = new RuleModel(null, "3", setOfRightSidePart(" \" xd \" \\\" "), false);
        ruleService.rulePreProcess(back);
        assert back.getRightSide().equals(getFromString("\" xd \" \\\""));

        RuleModel backNull = new RuleModel(null, "4", setOfRightSidePart(" \" \\\\\"  "), false);
        ruleService.rulePreProcess(backNull);
        assert backNull.getRightSide().equals(getFromString("\" \\\\\""));

        RuleModel backAngleBrNull = new RuleModel(null, "5", setOfRightSidePart(" < \\\\>  "), false);
        ruleService.rulePreProcess(backAngleBrNull);
        assert backAngleBrNull.getRightSide().equals(getFromString("< \\\\>"));

        RuleModel backAngleBr = new RuleModel(null, "6", setOfRightSidePart(" < \\> >  "), false);
        ruleService.rulePreProcess(backAngleBr);
        assert backAngleBr.getRightSide().equals(getFromString("< \\> >"));

    }


    @Test
    public void testCommonLeftMerge() {
        Set<RuleModel> mergeRule = new HashSet<>();
        RuleModel rule1 = new RuleModel(null, "xd", setOfRightSidePart("lul"), true);
        RuleModel rule2 = new RuleModel(null, "xd", setOfRightSidePart("lul"), true);
        RuleModel rule3 = new RuleModel(null, "xd", setOfRightSidePart("ul"), true);
        mergeRule.add(rule1);
        mergeRule.add(rule2);
        mergeRule.add(rule3);

        RuleModel rule1Merged = ruleService.commonLeftMerge(mergeRule).stream().toList().get(0);
        assert rule1Merged.getRoot();
        assert rule1Merged.getRightSide().equals(getFromString("lul|ul"));
    }

    @Test
    public void testStraightBrackets() {
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        RuleModel rule = new RuleModel(null, "0", setOfRightSidePart("[xd]"), false);
        ruleService.rulePreProcess(rule);
        assert rule.getRightSide().equals(getFromString("xd|<NULL>"));
        RuleModel rule1 = new RuleModel(null, "1", setOfRightSidePart("[a|b]"), false);
        ruleService.rulePreProcess(rule1);
        assert rule1.getRightSide().equals(getFromString("a|b|<NULL>"));


        RuleModel rule2 = new RuleModel(null, "2", setOfRightSidePart("[(a|b) c]"), false);
        rule2 = ruleService.processAddRule(rule2, grammar).getValue0();
        assert rule2.getRightSide().equals(getFromString("a c|b c|<NULL>"));
        RuleModel rule3 = new RuleModel(null, "3", setOfRightSidePart("ha[a]"), false);
        rule3 = ruleService.processAddRule(rule3, grammar).getValue0();
        assert rule3.getRightSide().equals(getFromString("ha|haa"));
        RuleModel rule4 = new RuleModel(null, "4", setOfRightSidePart("ha[a]b"), false);
        rule4 = ruleService.processAddRule(rule4, grammar).getValue0();
        assert rule4.getRightSide().equals(getFromString("hab|haab"));
        RuleModel rule5 = new RuleModel(null, "5", setOfRightSidePart("[a]b"), false);
        rule5 = ruleService.processAddRule(rule5, grammar).getValue0();
        assert rule5.getRightSide().equals(getFromString("ab|b"));
        RuleModel rule6 = new RuleModel(null, "6", setOfRightSidePart("[a] "), false);
        rule6 = ruleService.processAddRule(rule6, grammar).getValue0();
        assert rule6.getRightSide().equals(getFromString("a|<NULL>"));
        RuleModel rule7 = new RuleModel(null, "7", setOfRightSidePart("[[a]] "), false);
        rule7 = ruleService.processAddRule(rule7, grammar).getValue0();
        assert rule7.getRightSide().equals(getFromString("a|<NULL>"));
    }

    @Test
    public void testListDeleteAll() {
        ruleService.deleteRules();
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        RuleModel rule = new RuleModel(null, "1", setOfRightSidePart("xd"), false);
        RuleModel rule1 = new RuleModel(null, "2", setOfRightSidePart("d"), true);
        ruleService.processAddRule(rule, grammar);
        ruleService.processAddRule(rule1, grammar);
        int one = 0;
        int two = 0;
        int three = 0;
        List<RuleModel> rules = ruleService.getAllRules();
        assert rules.size() == 2;
        for (RuleModel partRule : rules) {
            switch (partRule.getLeftSymbol()) {
                case "<1>" -> one++;
                case "<2>" -> two++;
                default -> three++;
            }
        }
        assert one == 1 && two == 1 && three == 0;
        ruleService.deleteRules();
        assert ruleService.getAllRules().size() == 0;
    }


    @Test
    @Transactional
    public void testUpdateRule() {
        grammarService.deleteAll();
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        RuleModel rule = new RuleModel(null, "old", setOfRightSidePart("xd"), false);
        RuleModel newRule = new RuleModel(null, "new", setOfRightSidePart("d|d|d"), false);
        RuleModel root = new RuleModel(null, "root", setOfRightSidePart("l|S|l"), true);

        rule = ruleService.processAddRule(rule, grammar).getValue0();
        root = ruleService.processAddRule(root, grammar).getValue0();
        Long id = rule.getId();
        newRule = ruleService.updateRule(rule.getId(), newRule);
        assert Objects.equals(id, newRule.getId());
        assert newRule.getRightSide().equals(getFromString("d"));
        assert newRule.getLeftSymbol().equals("<new>");
        root = ruleService.changeRootStatus(root.getId());
        assert !root.getRoot();
        root = ruleService.changeRootStatus(root.getId());
        assert root.getRoot();
        RuleModel tryNull = new RuleModel(null, "noch", setOfRightSidePart("noch"), false);
        tryNull = ruleService.processAddRule(tryNull, grammar).getValue0();
        RuleModel empty = new RuleModel(null, null, null, null);
        tryNull = ruleService.updateRule(tryNull.getId(), empty);
        assert tryNull.getLeftSymbol().equals("<noch>");
        assert tryNull.getRightSide().equals(getFromString("noch"));
        RuleModel zeroLen = new RuleModel(null, "", new HashSet<>(), null);
        tryNull = ruleService.updateRule(tryNull.getId(), zeroLen);
        assert tryNull.getLeftSymbol().equals("<noch>");
        assert tryNull.getRightSide().equals(getFromString("noch"));
        RuleModel xd = new RuleModel(null, "xd", setOfRightSidePart("sm"), null);
        xd = ruleService.processAddRule(xd, grammar).getValue0();
        xd = ruleService.updateRule(xd.getId(), new RuleModel(null, null, setOfRightSidePart("1(1|2)"), null));
        Set<RightSidePart> result = xd.getRightSide();
        for (RightSidePart part : result) {
            assert !part.getPart().contains("(");
        }
        RuleModel old2 = new RuleModel(null, "da", setOfRightSidePart("xd"), null);
        RuleModel old = new RuleModel(null, "old2", setOfRightSidePart("sm"), null);
        RuleModel new2 = new RuleModel(null, "old2", new HashSet<>(), null);
        ruleService.processAddRule(old, grammar);
        old2 = ruleService.processAddRule(old2, grammar).getValue0();
        old2 = ruleService.updateRule(old2.getId(), new2);
        assert old2.getRightSide().size() == 2;

        RuleModel old4 = new RuleModel(null, "dfa", setOfRightSidePart("xd"), null);
        RuleModel old3 = new RuleModel(null, "old4", setOfRightSidePart("sm"), null);
        RuleModel new4 = new RuleModel(null, "old4", setOfRightSidePart("li"), null);
        ruleService.processAddRule(old3, grammar);
        old4 = ruleService.processAddRule(old4, grammar).getValue0();
        old4 = ruleService.updateRule(old4.getId(), new4);
        System.out.println("\n\n\n " + old4.getRightSide().size() + "\n\n\n");
        //assert  old4.getRightSide().size() == 2;
        ruleService.getAllRules();

        RuleModel ru1 = new RuleModel("xd", setOfRightSidePart("dfsdf"));
        RuleModel ru2 = new RuleModel("td", setOfRightSidePart("ddf"), true);
        Grammar gr = new Grammar("root", null, null);
        gr = grammarService.processAddGrammar(gr);
        ru1 = ruleService.processAddRule(ru1, gr).getValue0();
        ruleService.processAddRule(ru2, gr);
        try {
            ruleService.updateRule(ru1.getId(), new RuleModel("xd", setOfRightSidePart("dfsdf"), true));
            assert false;
        } catch (Exception ignored) {

        }
        Grammar gr2 = new Grammar("root", null, null);
        RuleModel ru3 = new RuleModel("td", setOfRightSidePart("ddf"), true);
        gr = grammarService.processAddGrammar(gr2);
        ru3 = ruleService.processAddRule(ru3, gr).getValue0();
        ru3 = ruleService.updateRule(ru3.getId(), new RuleModel("xd", setOfRightSidePart("dfsdf"), null));
        assert ru3.getRoot();
    }

    @Test
    public void testFindDeleteById() {
        ruleService.deleteRules();
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        RuleModel rule = new RuleModel(null, "1", setOfRightSidePart("xd"), false);
        RuleModel rule1 = new RuleModel(null, "2", setOfRightSidePart("d"), true);
        RuleModel rule3 = new RuleModel(null, "3", setOfRightSidePart("d"), false);
        rule = ruleService.processAddRule(rule, grammar).getValue0();
        rule1 = ruleService.processAddRule(rule1, grammar).getValue0();
        ruleService.processAddRule(rule3, grammar);
        assert ruleService.getById(rule.getId()).getLeftSymbol().equals("<1>");
        ruleService.deleteById(rule1.getId());
        List<RuleModel> rules = ruleService.getAllRules();
        assert rules.size() == 2;
        int one = 0;
        int def = 0;
        int three = 0;
        for (RuleModel smallRule : rules) {
            switch (smallRule.getLeftSymbol()) {
                case "<1>" -> one++;
                case "<3>" -> three++;
                default -> def++;
            }
        }
        assert one == 1 && three == 1 && def == 0;


    }

    @Test
    @Transactional
    public void testGetByRightSide() {
        ruleService.deleteRules();
        Grammar grammar = new Grammar();
        grammar = grammarService.addGrammar(grammar);
        Grammar grammar2 = new Grammar();
        grammar2 = grammarService.addGrammar(grammar2);
        RuleModel rule = new RuleModel(null, "1", setOfRightSidePart("xd"), false);
        RuleModel rule1 = new RuleModel(null, "2", setOfRightSidePart("d"), true);
        RuleModel rule3 = new RuleModel(null, "1", setOfRightSidePart("d"), false);
        ruleService.processAddRule(rule, grammar);
        ruleService.processAddRule(rule1, grammar);
        ruleService.processAddRule(rule3, grammar2);
        List<RuleModel> rules = ruleService.getByLeftSide("1");
        assert rules.size() == 2;
        for (RuleModel smallRule : rules) {
            assert smallRule.getLeftSymbol().equals("<1>");
        }
        List<RuleModel> rulesFromGrammar = ruleService.getByLeftSide("1", grammar.getId());
        assert rulesFromGrammar.size() == 1;
        for (RuleModel smallRule : rulesFromGrammar) {
            assert smallRule.getLeftSymbol().equals("<1>") && smallRule.getRightSide().equals(getFromString("xd"));
        }
        assert ruleService.getByLeftSide("5").size() == 0;
    }
}
