package de.renew.faformalism.util;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import de.renew.simulatorontology.shadow.SyntaxException;
import de.renew.unify.Tuple;

public class FASyntaxChecker {

    private final static Pattern NFA_ARC = Pattern.compile("^[0-9A-Za-z]?(,[0-9A-Za-z]?)*$");
    private final static Pattern PDA_ARC = Pattern.compile(
        "^[0-9A-Za-z]?,[0-9A-Za-z$]?->[0-9A-Za-z$]?(\n([0-9A-Za-z]?,[0-9A-Za-z$]?->[0-9A-Za-z$]?))*$");

    private FASyntaxChecker() {}

    /**
     * Checks if the given arc inscription is syntactically correct.
     * @param arcInscription the inscription of the arc to be tested
     */
    public static void checkArcSyntax(String arcInscription) throws SyntaxException {
        if (arcInscription == null) {
            arcInscription = "";
        }
        arcInscription = arcInscription.replace(" ", "");
        switch (SimulationSettingsManager.getAutomatonModel()) {
            case DFA, NFA, BUECHI -> {
                Matcher m = NFA_ARC.matcher(arcInscription);
                if (m.matches()) {
                    return;
                }
                Pattern symbols = Pattern.compile("[0-9A-Za-z,]*");
                if (!symbols.matcher(arcInscription).matches()) {
                    throw new SyntaxException(
                        "Arc contains invalid characters! Allowed are digits, letters (english alphabet), ''(empty string), and ','.");
                } else if ((!arcInscription.contains(",")) && (arcInscription.length() > 1)) {
                    throw new SyntaxException("Arcs may only read one letter at a time.");
                }
            }
            case PDA -> {
                Matcher m = PDA_ARC.matcher(arcInscription);
                if (m.matches()) {
                    return;
                }
                if (!arcInscription.contains("->")) {
                    throw new SyntaxException(
                        "Arc is missing instructions for updating the stack (->)!");
                }
                throw new SyntaxException(
                    "Arc inscription has invalid format. Allowed format is \"x,y->z\". For multiple inscriptions, separate them with a new line");
            }
        }
        throw new SyntaxException("Unknown automaton type set!");
    }

    public static void checkWordSyntax(String word) throws SyntaxException {
        if ((word == null) || (!SimulationSettingsManager.getSimulateWordMode())) {
            return;
        }
        word = word.replace(" ", "");
        switch (SimulationSettingsManager.getAutomatonModel()) {
            case DFA, NFA -> {
                checkNFAWordSyntax(word);
                return;
            }
            case BUECHI -> {
                checkNFAWordSyntax(word.replace("°", "*"));
                //canFireBUECHI checks if all possible words (regarding union operations) end with '°'
                if (!FAAutomatonSimulationHelper
                    .canFire(new Tuple(new Object[] { word, null }, null), "")) {
                    throw new SyntaxException(
                        "One ore more words separated by a union operator do not end with '°'!");
                }
                return;
            }
            case PDA -> {
                checkPDAWordSyntax(word);
                return;
            }
        }
        throw new SyntaxException("Unknown automaton type set!");
    }

    private static void checkPDAWordSyntax(String word) throws SyntaxException {
        if (!Pattern.compile("^[a-zA-Z0-9]*$").matcher(word).matches()) {
            throw new SyntaxException("Word contains letters that are not allowed. ");
        }
    }

    /**
     * This method checks if the given input describes a valid regular expression.
     * If not, a {@link SyntaxException} is thrown.
     * @param word the word to be checked
     * @throws SyntaxException if the word does not describe a valid regular expression.
     */
    public static void checkNFAWordSyntax(String word) throws SyntaxException {
        if (word == null || word.isEmpty()) {
            return;
        }
        if (word.contains("°")) {
            throw new SyntaxException(
                "Expression contains °, but automaton is interpreted as NFA. Change interpretation in 'FA Drawing Tool' -> 'FA Simulation Settings'!");
        }
        Pattern symbols = Pattern.compile("[0-9A-Za-z\\^+*()|]+");
        if (!symbols.matcher(word).matches()) {
            throw new SyntaxException(
                "Word contains invalid characters! Allowed are digits, letters (english alphabet), '+', '*', '^', '(' and ')'.");
        }
        if (word.endsWith("^")) {
            throw new SyntaxException("Word can't end with '^'!");
        } else if (word.charAt(0) == '*' || word.charAt(0) == '^') {
            throw new SyntaxException("Word starts with illegal character!");
        } else if (word.contains("^|")) {
            throw new SyntaxException("Illegal use of operands: ^| at index " + word.indexOf("^|"));
        }

        // we check the validity by compiling it into a pattern.
        // we first replace characters such as '+' or '^' in order to generate valid Java syntax
        word = word.replaceAll("\\^[*]", "*");
        word = word.replaceAll("[+]", "|");
        word = word.replaceAll("[\\^][|]", "+");
        for (int i = 0; i < word.length(); i++) {
            if (word.charAt(i) == '^') {
                throw new SyntaxException(
                    "Misplaced character in word at index " + i + ": duplicate '^'");
            }
        }
        try {
            Pattern.compile(word);
        } catch (PatternSyntaxException pse) {
            throw new SyntaxException(pse.getMessage());
        }
    }
}
