package de.duehl.vocabulary.japanese.logic.symbol.kanji.test;

import java.util.ArrayList;
import java.util.List;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.text.Text;
import de.duehl.vocabulary.japanese.data.symbol.Kanji;
import de.duehl.vocabulary.japanese.logic.symbol.kanji.test.data.KanjiUserInputType;
import de.duehl.vocabulary.japanese.logic.symbol.kanji.test.data.SingleUserInputKanjiCheckResult;

/**
 * Diese Klasse bewertet die Benutzereingaben zu einem Kanji.
 *
 * Bei der deutschen Übersetzung trenne ich die Benutzereingabe an allen Kombinationen von
 * Leerzeichen und Kommata.
 *
 * Das wird auch bei den Lesungen gemacht, dort wird aber auch noch auf eckige Klammern geachtet.
 *
 * @version 1.01     2026-01-01
 * @author Christian Dühl
 */

public class KanjiTestChecker {

    /** Das Kanji welches abgefragt wurde. */
    private final Kanji kanji;

    /** Die vom Benutzer eingegebene deutsche Bedeutung. */
    private final String germanMeaning;

    /** Die vom Benutzer eingegebene ON-Lesung. */
    private final String onLesung;

    /** Die vom Benutzer eingegebene kun-Lesung. */
    private final String kunLesung;

    /**
     * Gibt an, ob bei der Überprüfung der deutschen Bedeutung auf Groß-/Kleinschreibung geachtet
     * werden soll.
     */
    private final boolean germanMeaningCaseSensitivity;

    /**
     * Gibt an, ob bei der Überprüfung der ON-Lesung auf Groß-/Kleinschreibung geachtet werden
     * soll.
     */
    private final boolean onLesungCaseSensitivity;

    /**
     * Gibt an, ob bei der Überprüfung der kun-Lesung auf Groß-/Kleinschreibung geachtet werden
     * soll.
     */
    private final boolean kunLesungCaseSensitivity;


    /** Die Beschreibung der Fehler, fall welche gemacht wurden. */
    private String errorDescription;

    /** Der Hinweis auf Abweichungen, die keine Fehler sind. */
    private String memorandum;

    /** Gibt an, ob alles richtig ist. */
    private boolean ok;

    /** Die Bereiche in denen Fehler gemacht wurden. */
    private final List<KanjiUserInputType> wrongKanjiUserInputTypes;

    /** Das Ergebnis der Prüfung. */
    private SingleUserInputKanjiCheckResult result;

    /**
     * Konstruktor.
     *
     * @param kanji
     *            Das Kanji welches abgefragt wurde.
     * @param germanMeaning
     *            Die vom Benutzer eingegebene deutsche Bedeutung.
     * @param onLesung
     *            Die vom Benutzer eingegebene ON-Lesung.
     * @param kunLesung
     *            Die vom Benutzer eingegebene kun-Lesung.
     * @param germanMeaningCaseSensitivity
     *            Gibt an, ob bei der Überprüfung der deutschen Bedeutung auf Groß-/Kleinschreibung
     *            geachtet werden soll.
     * @param onLesungCaseSensitivity
     *            Gibt an, ob bei der Überprüfung der ON-Lesung auf Groß-/Kleinschreibung geachtet
     *            werden soll.
     * @param kunLesungCaseSensitivity
     *            Gibt an, ob bei der Überprüfung der kun-Lesung auf Groß-/Kleinschreibung geachtet
     *            werden soll.
     */
    public KanjiTestChecker(Kanji kanji, String germanMeaning, String onLesung, String kunLesung,
            boolean germanMeaningCaseSensitivity, boolean onLesungCaseSensitivity,
            boolean kunLesungCaseSensitivity) {
        this.kanji = kanji;
        this.germanMeaning = germanMeaning;
        this.onLesung = onLesung;
        this.kunLesung = kunLesung;
        this.germanMeaningCaseSensitivity = germanMeaningCaseSensitivity;
        this.onLesungCaseSensitivity = onLesungCaseSensitivity;
        this.kunLesungCaseSensitivity = kunLesungCaseSensitivity;

        wrongKanjiUserInputTypes = new ArrayList<>();
    }

    /** Führt die Prüfung durch. */
    public void check() {
        init();
        checkGermanMeaning();
        checkOnLesung();
        checkKunLesung();
        createCheckResult();
    }

    private void init() {
        ok = true;
        errorDescription = "";
        memorandum = "";
    }

    /**
     * Überprüft die deutsche Bedeutung.
     *
     * Da es bei der deutschen Bedeutung durchaus Strings gibt, die Leerzeichen haben, kann ich
     * nicht einfach an Leerzeichen oder Kommata trennen. Beispiele:
     *     - "... Zeit"
     *     - "halb nach"
     * Daher trenne ich hier die korrekten deutschen Bedeutungen an Kommata und suche diese
     * Teile der Länge nach in der Eingabe des Benutzers.
     *
     * Am Ende werfe ich die Listen in die normale Überprüfung.
     */
    private void checkGermanMeaning() {
        List<String> correctGermanMeaningParts = kanji.getGermanMeanings();
        CollectionsHelper.sortStringListByLengthDescanding(correctGermanMeaningParts);

        List<String> userGermanMeaningParts = new ArrayList<>();
        String germanMeaningWork = germanMeaning;
        for (String correctPart : correctGermanMeaningParts) {
            int index = germanMeaningWork.indexOf(correctPart);
            if (index != -1) {
                userGermanMeaningParts.add(correctPart);
                String front = germanMeaningWork.substring(0, index);
                String rear = germanMeaningWork.substring(index + correctPart.length());
                germanMeaningWork = Text.concatenate(front, rear);
            }
            else if (!germanMeaningCaseSensitivity) {
                String loweredCorrectPart = Text.toLowerCase(correctPart);
                String loweredGermanMeaningWork = Text.toLowerCase(germanMeaningWork);
                index = loweredGermanMeaningWork.indexOf(loweredCorrectPart);
                if (index != -1) {
                    userGermanMeaningParts.add(correctPart);
                    String front = germanMeaningWork.substring(0, index);
                    String rear = germanMeaningWork.substring(index + correctPart.length());
                    germanMeaningWork = Text.concatenate(front, rear);
                }
            }
        }

        /* Den Rest nehmen wir mit, für Fehlermeldungen: */
        if (!germanMeaningWork.isEmpty()) {
            List<String> parts = Text.splitBy(germanMeaningWork, "[, ]+");
            CollectionsHelper.removeEmptyAndOnlyWhitespaceStringsFromList(parts);
            userGermanMeaningParts.addAll(parts);
        }

        check(KanjiUserInputType.GERMAN_MEANING, "deutsche Bedeutung", userGermanMeaningParts,
                correctGermanMeaningParts, germanMeaningCaseSensitivity);
    }

    private void checkOnLesung() {
        check(KanjiUserInputType.ON_LESUNG, "ON-Lesung", onLesung,
                CollectionsHelper.copyList(kanji.getOnLesungen()), onLesungCaseSensitivity);
    }

    private void checkKunLesung() {
        check(KanjiUserInputType.KUN_LESUNG, "kun-Lesung", kunLesung,
                CollectionsHelper.copyList(kanji.getKunLesungen()), kunLesungCaseSensitivity);
    }

    /**
     * Überprüft die Benutzereingaben in einer Lesung (ON-Lesung, kun-Lesung), wobei die Eingabe
     * des Benutzers an Leerzeichen oder Kommata aufgetrennt wird.
     *
     * @param type
     *            Der Typ der Sparte.
     * @param description
     *            Die Beschreibung der Sparte.
     * @param userInput
     *            Der vom Benutzer eingegebene Wert.
     * @param correctParts
     *            Die Liste der korrekten Werte aus der Kanji-Enum.
     * @param caseSensivity
     *            Gibt an, ob bei der Überprüfung auf Groß-/Kleinschreibung geachtet werden soll.
     */
    private void check(KanjiUserInputType type, String description, String userInput,
            List<String> correctParts, boolean caseSensivity) {
        List<String> userInputParts = Text.splitBy(userInput, "[, ]+");
        check(type, description, userInputParts, correctParts, caseSensivity);
    }

    /**
     * Überprüft die Benutzereingaben in einer Sparte (Deutsche Bedeutung, ON-Lesung, kun-Lesung).
     *
     * @param type
     *            Der Typ der Sparte.
     * @param description
     *            Beschreibung der Sparte.
     * @param userInputParts
     *            Die Liste der vom Benutzer eingegebene Werte.
     * @param correctParts
     *            Die Liste der korrekten Werte aus der Kanji-Enum.
     * @param caseSensivity
     *            Gibt an, ob bei der Überprüfung auf Groß-/Kleinschreibung geachtet werden soll.
     */
    private void check(KanjiUserInputType type, String description, List<String> userInputParts,
            List<String> correctParts, boolean caseSensivity) {

        int countCorrectUserParts = countCorrectPartsAndRemoveThemFromLists(description,
                correctParts, userInputParts, caseSensivity);

        int countIncorrectUserParts = userInputParts.size(); // alle verbliebenen sind falsch!
        if (countIncorrectUserParts > 0) {
            ok = false;
            for (String userInputPart : userInputParts) {
                addToErrorDescription(
                        description + ": Falsch eingegebener Wert '" + userInputPart + "'.\n");
                addToErrorTypes(type);
            }
        }

        if (correctParts.size() > 0) {
            if (countCorrectUserParts == 0) {
                ok = false;
                for (String correctPart : correctParts) {
                    addToErrorDescription(
                            description + ": Fehlender Wert '" + correctPart + "'.\n");
                    addToErrorTypes(type);
                }
            }
            else {
                for (String correctPart : correctParts) {
                    addToMemorandum(description + ": Weiterer Wert '" + correctPart + "'.\n");
                }
            }
        }
    }

    private int countCorrectPartsAndRemoveThemFromLists(String description,
            List<String> correctParts, List<String> userInputParts, boolean caseSensivity) {
        int countCorrectUserParts = 0;

        boolean loopOn = true;

        while (!userInputParts.isEmpty() && !correctParts.isEmpty() && loopOn) {
            int oldUserInputPartsSize = userInputParts.size();
            int oldCorrectPartsSize = correctParts.size();

            for (int userIndex = 0; userIndex < userInputParts.size(); ++userIndex) {
                String userInputPart = userInputParts.get(userIndex);
                if (!caseSensivity) {
                    userInputPart = Text.toLowerCase(userInputPart);
                }
                for (int correctIndex = 0; correctIndex < correctParts.size(); ++correctIndex) {
                    String correctPart = correctParts.get(correctIndex);
                    if (!caseSensivity) {
                        correctPart = Text.toLowerCase(correctPart);
                    }
                    boolean equal = userInputPart.equals(correctPart);
                    boolean equalIgnoringRoundOrSquareBrackets =
                            Text.equalsIgnoringRoundOrSquareBrackets(userInputPart, correctPart);
                    if (equalIgnoringRoundOrSquareBrackets && !equal) {
                        addToMemorandum(description + ": Abweichungen die Klammern betreffend: '"
                                + userInputPart + "' <-> '" + correctPart + "'\n");
                    }
                    if (equalIgnoringRoundOrSquareBrackets || equal) {
                        ++countCorrectUserParts;
                        userInputParts.remove(userIndex);
                        correctParts.remove(correctIndex);
                        break;
                    }
                }
            }

            int newUserInputPartsSize = userInputParts.size();
            int newCorrectPartsSize = correctParts.size();

            if (oldUserInputPartsSize == newUserInputPartsSize
                    && oldCorrectPartsSize == newCorrectPartsSize) {
                /*
                 * In diesem Fall wurde nichts übereinstimmendes mehr gefunden, in beiden Listen
                 * kann aber sehr wohl noch etwas enthalten sein. Daher brechen wir die
                 * while-Schleife an dieser Stelle ab:
                 */
                loopOn = false;
            }
        }

        return countCorrectUserParts;
    }

    private void addToErrorDescription(String toAppend) {
        errorDescription = addToText(errorDescription, toAppend);
    }

    private void addToErrorTypes(KanjiUserInputType type) {
        if (!wrongKanjiUserInputTypes.contains(type)) {
            wrongKanjiUserInputTypes.add(type);
        }
    }

    private void addToMemorandum(String toAppend) {
        memorandum = addToText(memorandum, toAppend);
    }

    private String addToText(String text, String toAppend) {
        if (text.isBlank()) {
            return toAppend;
        }
        else {
            return text + toAppend;
        }
    }

    private void createCheckResult() {
        result = new SingleUserInputKanjiCheckResult(kanji);
        result.setOk(ok);
        result.setErrorDescription(errorDescription);
        result.setWrongKanjiUserInputTypes(wrongKanjiUserInputTypes);
        result.setMemorandum(memorandum);
    }

    /** Getter für das Ergebnis der Prüfung. */
    public SingleUserInputKanjiCheckResult getResult() {
        return result;
    }

}
