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

import java.util.List;

import javax.swing.SwingUtilities;

import de.duehl.basics.datetime.DateAndTime;
import de.duehl.swing.logic.LongTimeProcessInformer;
import de.duehl.vocabulary.japanese.common.data.InternalAdditionalVocableData;
import de.duehl.vocabulary.japanese.common.data.TranslationDirection;
import de.duehl.vocabulary.japanese.common.persistence.Options;
import de.duehl.vocabulary.japanese.common.persistence.data.EmptyTranslationsAcceptance;
import de.duehl.vocabulary.japanese.data.FumikoDataStructures;
import de.duehl.vocabulary.japanese.data.Vocable;
import de.duehl.vocabulary.japanese.data.Vocabulary;
import de.duehl.vocabulary.japanese.logic.VocabularyTrainerLogic;
import de.duehl.vocabulary.japanese.logic.internal.InternalDataRequester;
import de.duehl.vocabulary.japanese.logic.translation.GermanToJapaneseTranslation;
import de.duehl.vocabulary.japanese.logic.translation.JapaneseToGermanTranslation;
import de.duehl.vocabulary.japanese.ui.data.FumikoUiObjects;
import de.duehl.vocabulary.japanese.ui.dialog.testing.VocableListTesterDialog;

/**
 * Diese Klasse fragt eine Liste von Vokabeln nacheinander ab.
 *
 * Als nur in der Richtung Japanisch - Deutsch abgefragt wurde und dabei sowohl die Kanji als auch
 * die Kana angezeigt wurden, war die Welt noch recht einfach. Da auf der Liste der Vokabeln in
 * allen Vokabularen sowohl die Kombination (Kanji, Kana) als auch die Kombination (Kana,
 * erste Übersetzung) eindeutig ist (dies wird beim Startup sichergestellt), hatte man immer eine
 * 1:1 Beziehung zwischen dem was man abfragt und den dazu passenden Vokabeln (nämlich genau einer),
 * der Benutzer konnte falsch oder richtig antworten, es war aber klar, was man anzeigt und wo man
 * das Ergebnis der Übersetzung vermerkt.
 *
 * Nun gibt es aber sowohl die Abfrage Deutsch - Japanisch, die zu einer Übersetzung mehrere
 * zugehörige Vokabeln kennt (null - maru, rei, zero) als auch die Möglichkeit, sich bei der
 * Übersetzung Japanisch - Deutsch nur die Kana anzeigen zu lassen, wodurch ebenfalls
 * Mehrdeutigkeiten entstehen (kami - Haar, Papier, Gott).
 *
 * Alle Informationen darüber, wie abgefragt wird, lassen sich hier aus den Optionen erhalten:
 *     options.getTranslationDirection()
 *     options.isShowKanjiWhenTestingVocable()
 *
 * Hier muss man nun unterscheiden zwischen den Vokabeln, welche aufgrund der Informationen, welche
 * dem Benutzer bei der Abfrage der Vokabel angezeigt werden, in Frage kommen, und welche bei der
 * richtigen Beantwortung passen.
 *
 * Hat der Benutzer allerdings die Vokabel falsch übersetzt, dann aber angegeben, dass es sich um
 * einen Tippfehler handelt, dann wissen wir nicht, welche der in Frage kommenden Vokabeln denn
 * seiner Meinung nach die passende war (und bei der er sich vertippt hat). Daher müssen wir in
 * diesem Fall, wenn die Liste der in Frage kommenden Vokabeln nicht eindeutig ist, den Benutzer
 * fragen, welche denn (fast) richtig übersetzt wurde.
 *
 * @version 1.01     2025-11-21
 * @author Christian Dühl
 */

public class VocableListTesterLogic {

    /**
     * Die Angabe nach welchem Zeitraum in Sekunden auf eine leere Übersetzung reagiert werden
     * soll, wenn in den Optionen 'nicht sofort' ausgewählt wurde.
     */
    private static final long
            MAX_TIME_IN_SECONDS_TO_ACCEPT_EMPTY_TRANSLATION_AFTER_LAST_TRANSLATION = 3;


    /** Die Logik des Vokabel-Trainers. */
    private final VocabularyTrainerLogic logic;

    /** Die Datenstrukturen des Vokabeltrainers. */
    private final FumikoDataStructures dataStructures;

    /** Die häufig verwendeten Funktionen der grafischen Oberfläche des Vokabeltrainers. */
    private final FumikoUiObjects uiObjects;

    /** Der zu dieser Klasse gehörige Dialog, also die grafische Oberfläche der Abfrage. */
    private VocableListTesterDialog gui;

    /** Die dem Benutzer bei der Übersetzung von Japanisch in Deutsch angezeigten Kana. */
    private String kana;

    /** Die dem Benutzer bei der Übersetzung von Japanisch in Deutsch angezeigten Kanji. */
    private String kanji;

    /**
     * Der dem Benutzer bei der Übersetzung von Deutsch in Japanisch angezeigte deutsche Begriff.
     */
    private String germanTerm;

    /**
     * Die vom Benutzer eingegebenen Übersetzung zu einer Vokabel (je nach Abfragerichtung kann
     * dies Deutsch oder Japanisch sein und wird entweder mit den deutschen Übersetzungen oder mit
     * Kana, Kanji und Romaji verglichen).
     */
    private String translationByUser;

    /**
     * Die Liste der zu den dem Benutzer angezeigten Informationen (bei Japanisch in Deutsch sind
     * das kana und kanji, bei Deutsch in Japanisch ist das germanTerm) passenden Vokabeln.
     */
    private List<Vocable> matchingVocables;

    /** Der Zeitpunkt der letzten Übersetzung durch den Benutzer. */
    private DateAndTime lastTranslationMoment;

    /**
     * Konstruktor.
     *
     * @param vocables
     *            Die Liste mit den abzufragenden Vokabeln.
     * @param testTitle
     *            Der Titel für die Art der Abfrage.
     * @param logic
     *            Die Logik des Vokabel-Trainers.
     * @param dataStructures
     *            Die Datenstrukturen des Vokabeltrainers.
     * @param uiObjects
     *            Die häufig verwendeten Funktionen der grafischen Oberfläche des Vokabeltrainers.
     */
    public VocableListTesterLogic(List<Vocable> vocables, String testTitle,
            VocabularyTrainerLogic logic, FumikoDataStructures dataStructures,
            FumikoUiObjects uiObjects) {
        this.logic = logic;
        this.dataStructures = dataStructures;
        this.uiObjects = uiObjects;

        Options options = dataStructures.getOptions();
        if (options.getTranslationDirection() == TranslationDirection.GERMAN_TO_JAPANESE
                && !options.isCreateGermanJapaneseTranslationAtStartup()
                && doWeHaveToCreateGermanJapaneseTranslation()
                ) {
            createGermanJapaneseTranslation(() -> createGui(vocables, testTitle));
        }
        else {
            createGui(vocables, testTitle);
        }
    }

    /**
     * Gibt an, ob wir (im Fall der lazy initialization) das Verzeichnis für die Übersetzung von
     * Deutsch zu Japanisch noch erzeugen müssen.
     */
    private boolean doWeHaveToCreateGermanJapaneseTranslation() {
        GermanToJapaneseTranslation germanToJapaneseTranslation =
                dataStructures.getGermanToJapaneseTranslation();
        return germanToJapaneseTranslation == null;
    }

    /**
     * Erzeugt die Deutsch-Jaopanischen Übersetzungen, falls diese nicht beim Startup erzeugt
     * werden.
     */
    private void createGermanJapaneseTranslation(Runnable runAfter) {
        LongTimeProcessInformer informer = uiObjects.getInformer();
        informer.startLongTimeProcess("Erzeuge Datenstrukturen für die Zuordnung von Kana und Kanji zu "
                + "Vokabeln sowie mehrdeutige Zuordnungen von Kana ohne Kanji zu Vokabeln für die "
                + "Übersetzung von Japanisch in Deutsch");
        new Thread(() -> createGermanJapaneseTranslationInThread(runAfter)).start();
    }

    private void createGermanJapaneseTranslationInThread(Runnable runAfter) {
        List<Vocabulary> vocabularies = dataStructures.getVocabularies();
        GermanToJapaneseTranslation germanToJapaneseTranslation =
                new GermanToJapaneseTranslation(vocabularies);
        SwingUtilities.invokeLater(() -> afterCreationGermanJapaneseTranslationInEdt(
                germanToJapaneseTranslation, runAfter));
    }

    private void afterCreationGermanJapaneseTranslationInEdt(
            GermanToJapaneseTranslation germanToJapaneseTranslation, Runnable runAfter) {
        dataStructures.setGermanToJapaneseTranslation(germanToJapaneseTranslation);
        LongTimeProcessInformer informer = uiObjects.getInformer();
        informer.endLongTimeProcess();
        runAfter.run();
    }

    private void createGui(List<Vocable> vocables, String testTitle) {
        lastTranslationMoment = new DateAndTime();

        gui = new VocableListTesterDialog(vocables, testTitle, this, dataStructures, uiObjects);
        gui.setVisible(true);
    }

    /** Führt den Test des Vokabulars durch. */
    public void test() {
        // früher wurde hier gui.setVisible(true); aufgerufen, aber das ist nun zu früh.
    }

    /**
     * Wird aufgerufen, wenn der Benutzer eine Übersetzung eines japanischen Begriffs eingegeben
     * hat.
     *
     * @param kana
     *            Die Darstellung in Kana der Vokabel welche abgefragt wird.
     * @param kanji
     *            Die Darstellung in Kanji der Vokabel welche abgefragt wird.
     * @param translationByUser
     *            Die vom Benutzer eingegebene Übersetzung.
     */
    public void userEnteredJapaneseToGermanTranslation(String kana, String kanji,
            String translationByUser) {
        this.kana = kana;
        this.kanji = kanji;
        this.germanTerm = "";
        this.translationByUser = translationByUser;
        matchingVocables = createMatchingVocablesForJapaneseToGerman();

        userEnteredTranslation();
    }

    /**
     * Erzeugt die Liste der passenden Vokabeln.
     *
     * Die Optionen sind hier schon berücksichtigt, vgl. VocableTester#determineKanjiToTest()
     *
     * Je nach
     *     options.isShowKanjiWhenTestingVocable()
     * bzw.
     *     options.isHideKanjiWhenTestingVocableAndKanaContainsOnlyHiragana()
     *     und
     *     Hiragana.containsOnlyHiragana(kana)
     * ist kanji an dieser Stelle dann leer.
     */
    private List<Vocable> createMatchingVocablesForJapaneseToGerman() {
        if (kanji.isEmpty()) {
            return getMatchingVocablesForKana();
        }
        else {
            return getMatchingVocablesForKanaAndKanji();
        }
    }

    /** Gibt die Liste aller Vokabeln zurück, welche die gesuchten Kana aufweisen. */
    private List<Vocable> getMatchingVocablesForKana() {
        JapaneseToGermanTranslation japaneseToGermanTranslation =
                dataStructures.getJapaneseToGermanTranslation();
        return japaneseToGermanTranslation.getMatchingVocablesForKana(kana);
    }

    /**
     * Gibt die Liste aller Vokabeln zurück, welche die gesuchten Kana und Kanji aufweisen. Diese
     * sollte immer genau ein Element beinhalten.
     */
    private List<Vocable> getMatchingVocablesForKanaAndKanji() {
        JapaneseToGermanTranslation japaneseToGermanTranslation =
                dataStructures.getJapaneseToGermanTranslation();
        return japaneseToGermanTranslation.getMatchingVocablesForKanaAndKanji(kana, kanji);
    }

    /**
     * Wird aufgerufen, wenn der Benutzer eine Übersetzung eines japanischen Begriffs eingegeben
     * hat.
     *
     * @param germanTerm
     *            Der abgefragte deutsche Begriff.
     * @param translationByUser
     *            Die vom Benutzer eingegebene Übersetzung.
     * @return Gibt an, ob die Übersetzung korrekt war.
     */
    public void userEnteredGermanToJapaneseTranslation(String germanTerm,
            String translationByUser) {
        this.kana = "";
        this.kanji = "";
        this.germanTerm = germanTerm;
        this.translationByUser = translationByUser;
        matchingVocables = createMatchingVocablesForGermanToJapanese();

        userEnteredTranslation();
    }

    /** Gibt zu einem deutschen Begriff die passenden Vokabeln zurück. */
    private List<Vocable> createMatchingVocablesForGermanToJapanese() {
        GermanToJapaneseTranslation germanToJapaneseTranslation =
                dataStructures.getGermanToJapaneseTranslation();
        if (null == germanToJapaneseTranslation) {
            throw new RuntimeException("Da ging etwas mit der 'lazy initialization' von "
                    + "germanToJapaneseTranslation schief.");
        }
        return germanToJapaneseTranslation.getMatchingVocablesForGermanTerm(germanTerm);
    }

    private void userEnteredTranslation() {
        gui.increaseNumberOfDoneVocables();

        Options options = dataStructures.getOptions();
        UserAnswerCorrectnessChecker checker = new UserAnswerCorrectnessChecker(options,
                translationByUser, matchingVocables);
        checker.check();
        boolean correct = checker.isCorrect();
        Vocable correctVocable = checker.getCorrectVocable();

        if (correct) {
            gui.increaseNumberOfCorrectDoneVocables();
        }
        gui.setNumbersAndPercent();

        boolean wasOnlyTypingError = gui.informAboutTranslationSuccess(matchingVocables, correct,
                correctVocable);
        if (!correct && wasOnlyTypingError) {
            gui.increaseNumberOfCorrectDoneVocables();
            gui.setNumbersAndPercent();
            if (matchingVocables.size() == 1) {
                correctVocable = matchingVocables.get(0);
            }
            else {
                correctVocable = gui.letUserSelectMatchingVocableForHisTranslation(
                        translationByUser, matchingVocables);
            }
            correct = true;
        }

        saveUserTestAnswerInInternalData(matchingVocables, correct, correctVocable);

        lastTranslationMoment = new DateAndTime();
        if (options.isSwitchToNextVocableAfterEntringTranslation()) {
            gui.switchToNextVocable();
        }
    }

    /**
     * Wird aufgerufen, wenn der Benutzer eine Übersetzung zu einer Variablen eingegeben hat und
     * wir endgültig wissen, ob diese falsch oder richtig (inklusive Tippfehler) war.
     *
     * Außerdem speichern oder entfernen wir hier die Vokabel(n) aus den Listen mit den falsch
     * beantworteten Vokabeln.
     *
     * @param vocable
     *            Die abgefragte Vokabel.
     * @param correct
     *            Gibt an, ob der Benutzer richtig übersetzt hat (einschließlich Tippfehlern).
     * @param correctVocable
     *            Falls der Benutzer richtig übersetzt hat, ist dies die zugehörige Vokabel.
     */
    private void saveUserTestAnswerInInternalData(List<Vocable> matchingVocables, boolean correct,
            Vocable correctVocable) {
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        if (correct) {
            InternalAdditionalVocableData data = requester.getInternalDataForVocable(correctVocable);
            tested(data, correct);

            int count = getLastCorrectTestsCount(correctVocable);
            if (count >= 10) { // Das wäre als Konstante irgendwo schöner. Aber ich glaube die 10
                               // ist durch den Code festgelegt.
                logic.removeVocableFromWrongTestedVocablesList(correctVocable);
            }
        }
        else {
            for (Vocable vocable : matchingVocables) {
                InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
                tested(data, correct);

                logic.addVocableToWrongTestedVocablesList(vocable);
            }
        }
    }

    private void tested(InternalAdditionalVocableData data, boolean correct) {
        Options options = dataStructures.getOptions();
        TranslationDirection translationDirection = options.getTranslationDirection();
        if (translationDirection == TranslationDirection.JAPANESE_TO_GERMAN) {
            data.testedJapaneseToGerman(correct);
        }
        else if (translationDirection == TranslationDirection.GERMAN_TO_JAPANESE) {
            data.testedGermanToJapanese(correct);
        }
        else {
            throw new RuntimeException("Unbekannte Übersetzungsrichtung " + translationDirection);
        }
    }

    private int getLastCorrectTestsCount(Vocable correctVocable) {
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        InternalAdditionalVocableData data = requester.getInternalDataForVocable(correctVocable);
        Options options = dataStructures.getOptions();
        TranslationDirection translationDirection = options.getTranslationDirection();
        if (translationDirection == TranslationDirection.JAPANESE_TO_GERMAN) {
            return data.getLastCorrectJapaneseToGermanTestsCount();
        }
        else if (translationDirection == TranslationDirection.GERMAN_TO_JAPANESE) {
            return data.getLastCorrectGermanToJapaneseTestsCount();
        }
        else {
            throw new RuntimeException("Unbekannte Übersetzungsrichtung " + translationDirection);
        }
    }

    /** Gibt an, ob eine leere Übersetzung ausgewertet werden soll. */
    public boolean doWeHaveToReactOnEmptyTranslation() {
        Options options = dataStructures.getOptions();
        EmptyTranslationsAcceptance emptyTranslationsAcceptance =
                options.getEmptyTranslationsAcceptance();
        switch (emptyTranslationsAcceptance) {
            case ALWAYS:
                return true;
            case NOT_IMMEDIATELY:
                DateAndTime now = new DateAndTime();
                long difference = lastTranslationMoment.difference(now) ;
                return difference >
                        MAX_TIME_IN_SECONDS_TO_ACCEPT_EMPTY_TRANSLATION_AFTER_LAST_TRANSLATION;
            case NEVER:
                return false;
            default:
                throw new RuntimeException(
                        "Unbekannte Art auf eine leere Übersetzung zu reagieren: '"
                                + emptyTranslationsAcceptance.getOptionDescription() + "'");
        }
    }

}
