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 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.web.server.ResponseStatusException;

import javax.transaction.Transactional;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import static com.jakubu9333.grammarManager.RuleServiceTest.getFromString;
import static com.jakubu9333.grammarManager.RuleServiceTest.setOfRightSidePart;

/**
 * @author Jakub Uhlarik
 */


@SpringBootTest
public class GrammarServiceTest {
    @Autowired
    private GrammarService grammarService;


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

    @Test
    public void testAddBasicGrammars() {
        Grammar grammarEmpty = new Grammar();
        Grammar grammarJustRootRule = new Grammar("xdd", null, new RuleModel(null, "<S>", setOfRightSidePart("pp"), false));
        Set<RuleModel> oldrules = new HashSet<>();
        oldrules.add(new RuleModel(null, "xd", setOfRightSidePart("xd"), true));
        Grammar grammarRootRule = new Grammar("xddd", oldrules, null);
        grammarRootRule = grammarService.processAddGrammar(grammarRootRule);
        assert grammarRootRule.getRootRule() != null;
        assert grammarRootRule.getRootRule().getLeftSymbol().equals("<xd>");
        grammarEmpty = grammarService.processAddGrammar(grammarEmpty);
        grammarJustRootRule = grammarService.processAddGrammar(grammarJustRootRule);
        assert null != grammarEmpty.getId();
        assert grammarEmpty.getRules() != null;
        assert grammarEmpty.getUri().equals("null");
        assert grammarJustRootRule.getId() != null;
        assert grammarJustRootRule.getUri().equals("xdd");
        Set<RuleModel> rules = grammarJustRootRule.getRules();
        assert rules != null;
        assert rules.size() == 1;
        RuleModel rootRule = grammarJustRootRule.getRootRule();
        assert rootRule.getRoot();
        assert rootRule.getLeftSymbol().equals("<S>");
        assert rootRule.getRightSide().equals(getFromString("pp"));
        assert rootRule.getId() != null;
        assert rootRule.getGrammar().equals(grammarJustRootRule);

    }

    @Test
    public void testSameUriGrammar() {
        Grammar grammar1 = new Grammar("demoUri", null, null);
        Grammar grammar2 = new Grammar("demoUri", null, null);
        grammar1 = grammarService.processAddGrammar(grammar1);
        grammar2 = grammarService.processAddGrammar(grammar2);

        assert grammar1.getUri().equals("demoUri");
        assert grammar2.getUri().matches("demoUri[0-9]+");
        Long id = grammar2.getId();

        Grammar grammar3 = grammarService.processAddGrammar(new Grammar("demoUri" + (id + 2), null, null));
        assert grammar3.getId() == id + 1;
        Grammar grammar4 = grammarService.processAddGrammar(new Grammar("demoUri", null, null));
        assert grammar4.getId() == id + 2;
        assert grammar4.getUri().matches("demoUri[0-9]+.[0-9]+");
    }

    @Test
    public void testMoreThan256() {
        String largeUri = """
                 uri1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
                uri1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
                uri1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
                 """;
        grammarService.processAddGrammar(new Grammar(largeUri));
    }

    @Test
    public void testWithRules() {
        Set<RuleModel> differentRules = new HashSet<>();

        RuleModel rule1 = new RuleModel(null, "<p>", setOfRightSidePart("pp"), false);
        RuleModel rule2 = new RuleModel(null, "<g>", setOfRightSidePart("gg"), false);
        RuleModel rule3 = new RuleModel(null, "<r>", setOfRightSidePart("rr"), false);
        differentRules.add(rule1);
        differentRules.add(rule2);
        differentRules.add(rule3);
        Grammar grammarDifRules = new Grammar("difRules", differentRules, null);
        grammarDifRules = grammarService.processAddGrammar(grammarDifRules);
        assert grammarDifRules.getRules().equals(differentRules);
        for (RuleModel rule : grammarDifRules.getRules()) {
            assert rule.getGrammar().equals(grammarDifRules);
        }
    }

    @Test
    public void testSameRules() {
        Set<RuleModel> sameLeftRules = new HashSet<>();
        RuleModel rule1 = new RuleModel(null, "<p>", setOfRightSidePart("pp"), false);
        RuleModel rule2 = new RuleModel(null, "p", setOfRightSidePart("prst"), false);
        RuleModel rule3 = new RuleModel(null, "<p>", setOfRightSidePart("p"), false);
        RuleModel rule4 = new RuleModel(null, "<p>", setOfRightSidePart("pp|pog"), false);
        sameLeftRules.add(rule1);
        sameLeftRules.add(rule2);
        sameLeftRules.add(rule3);
        sameLeftRules.add(rule4);
        Grammar grammarSameRules = new Grammar("sameRules", sameLeftRules, null);
        grammarSameRules = grammarService.processAddGrammar(grammarSameRules);
        assert grammarSameRules.getRules().size() == 1;
        RuleModel processedRule = grammarSameRules.getRules().stream().toList().get(0);
        assert processedRule.getGrammar().equals(grammarSameRules);
        assert processedRule.getLeftSymbol().equals("<p>");
        Set<RightSidePart> splitString = processedRule.getRightSide();
        int pp = 0;
        int prst = 0;
        int p = 0;
        int pog = 0;
        int def = 0;
        for (RightSidePart smallStr : splitString) {
            switch (smallStr.getPart()) {
                case "pp" -> pp++;
                case "p" -> p++;
                case "prst" -> prst++;
                case "pog" -> pog++;
                default -> def++;
            }

        }
        assert (pp == 1) && pog == 1 && prst == 1 && p == 1 && def == 0;


    }

    @Test
    public void testRootRuleWithRules() {
        Set<RuleModel> rulesRootRule = new HashSet<>();
        RuleModel rule1 = new RuleModel(null, "<p>", setOfRightSidePart("pp"), true);
        RuleModel rule2 = new RuleModel(null, "<g>", setOfRightSidePart("pp"), false);
        RuleModel rule3 = new RuleModel(null, "<s>", setOfRightSidePart("pp"), false);
        rulesRootRule.add(rule1);
        rulesRootRule.add(rule2);
        rulesRootRule.add(rule3);
        boolean exp = false;
        Grammar rootRuleGrammar = new Grammar("root", rulesRootRule, rule2);
        try {
            grammarService.processAddGrammar(rootRuleGrammar);
        } catch (ResponseStatusException e) {
            exp = true;
        }
        assert exp;

    }

    @Test
    public void testGrammarRootParseRule() {
        Set<RuleModel> rules = new HashSet<>();
        RuleModel rule = new RuleModel(null, "xd", new HashSet<>(List.of(new RightSidePart("(1|2)2"))), false);
        rules.add(rule);
        RuleModel cpy = new RuleModel(rule);
        Grammar grammar1 = new Grammar("1", rules, cpy);
        grammar1 = grammarService.processAddGrammar(grammar1);
        assert grammar1.getRules().size() == 1;
    }

    @Test
    public void testAllGetDelete() {
        Set<RuleModel> ruleSet = new HashSet<>();
        RuleModel rule1 = new RuleModel(null, "rule1", new HashSet<>(List.of(new RightSidePart("smile"))), false);
        RuleModel rule2 = new RuleModel(null, "rule2", new HashSet<>(List.of(new RightSidePart("cry|sis"))), false);
        ruleSet.add(rule1);
        ruleSet.add(rule2);
        Grammar grammar1 = new Grammar("1", ruleSet, null);
        Grammar grammar2 = new Grammar("2", null, null);
        Grammar grammar3 = new Grammar("3", null, null);
        Grammar grammar4 = new Grammar("4", null, null);
        grammarService.processAddGrammar(grammar1);
        grammarService.processAddGrammar(grammar2);
        grammarService.processAddGrammar(grammar3);
        grammarService.processAddGrammar(grammar4);
        List<Grammar> grammars = grammarService.getGrammars();
        assert grammars.size() == 4;
        int one = 0;
        int two = 0;
        int three = 0;
        int four = 0;
        int five = 0;
        for (Grammar iterGram : grammars) {
            switch (iterGram.getUri()) {
                case "1" -> one++;
                case "2" -> two++;
                case "3" -> three++;
                case "4" -> four++;
                default -> five++;
            }
        }
        assert one == 1 && two == 1 && three == 1 && four == 1 && five == 0;
        grammarService.deleteAll();
        List<Grammar> grammars2 = grammarService.getGrammars();
        assert grammars2.size() == 0;
    }

    @Test
    public void testChangeUri() {
        Grammar grammar1 = new Grammar("1x", null, null);
        Grammar grammar2 = new Grammar("2x", null, null);
        grammar1 = grammarService.processAddGrammar(grammar1);
        grammar2 = grammarService.processAddGrammar(grammar2);
        Long id = grammar2.getId();
        Grammar changeGr = new Grammar("xd", null, null);
        grammar2 = grammarService.changeUri(id, changeGr);
        assert Objects.equals(grammar2.getId(), id);
        assert grammar2.getUri().equals("xd");
        grammar1 = grammarService.changeUri(grammar1.getId(), changeGr);
        assert grammar1.getUri().equals("xd" + grammar1.getId());
        Grammar grammar3 = new Grammar("3x", null, null);
        Grammar changeGr2;
        grammar3 = grammarService.processAddGrammar(grammar3);
        changeGr2 = grammarService.changeUri(grammar3.getId(), new Grammar(null, null, null));
        assert changeGr2.getUri().equals("3x");
        changeGr2 = grammarService.changeUri(grammar3.getId(), new Grammar("", null, null));
        assert changeGr2.getUri().equals("3x");
        changeGr2 = grammarService.changeUri(grammar3.getId(), new Grammar("    \n   " +
                "           ", null, null));
        assert changeGr2.getUri().equals("3x");
        changeGr2 = grammarService.changeUri(grammar3.getId(), new Grammar("  ", null, null));
        assert changeGr2.getUri().equals("3x");
    }

    @Test
    @Transactional
    public void testDuplicate() {
        Set<RuleModel> ruleSet = new HashSet<>();
        RuleModel rule1 = new RuleModel(null, "rule1", new HashSet<>(List.of(new RightSidePart("smile"))), false);
        RuleModel rule = new RuleModel(null, "rule1", new HashSet<>(List.of(new RightSidePart("smile"))), false);
        RuleModel rule2 = new RuleModel(null, "rule2", new HashSet<>(List.of(new RightSidePart("cry|sis"))), false);
        ruleSet.add(rule1);
        ruleSet.add(rule2);
        Grammar grammar1 = new Grammar("1x", null, rule);
        Grammar grammar2 = new Grammar("2x", ruleSet, null);
        grammar1 = grammarService.processAddGrammar(grammar1);
        grammar2 = grammarService.processAddGrammar(grammar2);
        grammarService.duplicateGrammar(grammar1.getId());
        grammarService.getGrammars();
        grammarService.duplicateGrammar(grammar2.getId());
        grammarService.getGrammars();
    }

    @Test
    public void testGetIdDeleteId() {
        Set<RuleModel> ruleSet = new HashSet<>();
        RuleModel rule1 = new RuleModel(null, "rule1", new HashSet<>(List.of(new RightSidePart("smile"))), false);
        RuleModel rule2 = new RuleModel(null, "rule2", new HashSet<>(List.of(new RightSidePart("cry|sis"))), false);
        ruleSet.add(rule1);
        ruleSet.add(rule2);
        Grammar grammar1 = new Grammar("1x", null, null);
        Grammar grammar2 = new Grammar("2x", ruleSet, null);
        Grammar grammar3 = new Grammar("3x", null, null);
        Grammar grammar4 = new Grammar("4x", null, null);
        grammar1 = grammarService.processAddGrammar(grammar1);
        grammar2 = grammarService.processAddGrammar(grammar2);
        grammarService.processAddGrammar(grammar3);
        grammarService.processAddGrammar(grammar4);
        assert grammarService.getGrammarById(grammar1.getId()).getUri().equals("1x");
        grammarService.deleteById(grammar2.getId());
        List<Grammar> grammars = grammarService.getGrammars();
        assert grammars.size() == 3;
        int one = 0;
        int two = 0;
        int three = 0;
        int four = 0;
        int five = 0;
        for (Grammar iterGram : grammars) {
            switch (iterGram.getUri()) {
                case "1x" -> one++;
                case "2x" -> two++;
                case "3x" -> three++;
                case "4x" -> four++;
                default -> five++;
            }
        }
        assert one == 1 && two == 0 && three == 1 && four == 1 && five == 0;
    }

    @Test
    @Transactional
    public void testSpecialRootRuleGetters() {
        RuleModel rule1 = new RuleModel(null, "<S>", setOfRightSidePart(""), true);
        RuleModel rule2 = new RuleModel(null, "<S>", setOfRightSidePart(""), true);
        RuleModel rule3 = new RuleModel(null, "<p>", setOfRightSidePart(""), true);
        RuleModel rule4 = new RuleModel(null, "<S>", setOfRightSidePart(""), false);
        Grammar grammar = new Grammar("g1", null, rule1);
        Grammar grammar2 = new Grammar("g2", null, rule2);
        Grammar grammar3 = new Grammar("g3", null, rule3);
        Set<RuleModel> rules = new HashSet<>();
        rules.add(rule4);
        Grammar grammar4 = new Grammar("g4", rules, null);
        grammarService.processAddGrammar(grammar);
        grammarService.processAddGrammar(grammar2);
        grammarService.processAddGrammar(grammar3);
        grammarService.processAddGrammar(grammar4);
        List<Grammar> grammars = grammarService.getGrammarByRootRule("<S>");
        assert grammars.size() == 2;
        int one = 0;
        int two = 0;
        int three = 0;
        int four = 0;
        int five = 0;
        for (Grammar iterGram : grammars) {
            switch (iterGram.getUri()) {
                case "g1" -> one++;
                case "g2" -> two++;
                case "g3" -> three++;
                case "g4" -> four++;
                default -> five++;
            }
        }
        assert one == 1 && two == 1 && three == 0 && four == 0 && five == 0;

    }

    @Test
    @Transactional
    public void testUriStartUri() {
        Grammar grammar = new Grammar("go1", null, null);
        Grammar grammar2 = new Grammar("go2", null, null);
        Grammar grammar3 = new Grammar("gl3", null, null);
        Grammar grammar4 = new Grammar("gofsdfjdsfpsdfosddslfksflk3", null, null);
        grammarService.processAddGrammar(grammar);
        grammarService.processAddGrammar(grammar2);
        grammarService.processAddGrammar(grammar3);
        grammarService.processAddGrammar(grammar4);
        List<Grammar> grammars = grammarService.getGrammarsByUriStart("go");
        assert grammars.size() == 3;
        for (Grammar iterGram : grammars) {
            assert iterGram.getUri().startsWith("go");
        }
        assert grammarService.getGrammarsByUriStart("fsdfsdf").size() == 0;
    }


    @Test
    public void testGrammarEquality() {
        RuleModel rule1 = new RuleModel(null, null, "xd", setOfRightSidePart("xd|dd"), false);
        RuleModel rule2 = new RuleModel(null, null, "xd", setOfRightSidePart("dd|xd"), false);
        Grammar grammar = new Grammar("hah", new HashSet<>(List.of(rule1)), null);
        Grammar grammar1 = new Grammar("hah", new HashSet<>(List.of(rule2)), null);
        assert !grammar.equals(grammar1);
        grammar = grammarService.preProcessGrammar(grammar);
        grammar1 = grammarService.preProcessGrammar(grammar1);
        assert grammar.equals(grammar1);
        RuleModel rule4 = new RuleModel(null, null, "xd", setOfRightSidePart("xd|dd"), false);
        RuleModel rule3 = new RuleModel(null, null, "xd", setOfRightSidePart("dd|xd"), true);
        Grammar grammar2 = new Grammar("hah", new HashSet<>(List.of(rule4)), rule4);
        Grammar grammar3 = new Grammar("hah", new HashSet<>(List.of(rule3)), rule3);
        assert !grammar2.equals(grammar3);
        grammar = grammarService.preProcessGrammar(grammar);
        grammar1 = grammarService.preProcessGrammar(grammar1);
        assert grammar.equals(grammar1);
    }
}
