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

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

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.datetime.time.watch.StopWatch;
import de.duehl.basics.io.FileHelper;
import de.duehl.swing.ui.GuiTools;
import de.duehl.vocabulary.japanese.common.data.InternalAdditionalVocableData;
import de.duehl.vocabulary.japanese.common.persistence.Options;
import de.duehl.vocabulary.japanese.common.persistence.SessionManager;
import de.duehl.vocabulary.japanese.data.FumikoDataStructures;
import de.duehl.vocabulary.japanese.data.KanjiSet;
import de.duehl.vocabulary.japanese.data.Vocable;
import de.duehl.vocabulary.japanese.data.Vocabulary;
import de.duehl.vocabulary.japanese.logic.internal.InternalDataRequester;
import de.duehl.vocabulary.japanese.logic.ownlists.OwnLists;
import de.duehl.vocabulary.japanese.logic.symbol.kana.internal.InternalKanaDataRequester;
import de.duehl.vocabulary.japanese.logic.symbol.kanji.internal.InternalKanjiDataRequester;
import de.duehl.vocabulary.japanese.logic.translation.GermanToJapaneseTranslation;
import de.duehl.vocabulary.japanese.logic.translation.JapaneseToGermanTranslation;
import de.duehl.vocabulary.japanese.logic.wrongtested.WrongTestedVocables;
import de.duehl.vocabulary.japanese.startup.logic.steps.StartupStep;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step01ReadVocabularies;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step02CheckVocabelIntegrety;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step03FindMP3AndStoreInVocables;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step04ReadInternalVocableData;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step05JoinVocablesAndInternalData;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step06StoreDateOfOldestVocableInEachVocabulary;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step07CreateJapaneseToGermanTranslation;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step08ACreateGermanToJapaneseTranslation;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step08BSkipCreateGermanToJapaneseTranslation;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step09InitOwnLists;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step10ReadWrongTestedVocableLists;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step11CheckDoubleKanji;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step12ReadInternalKanjiData;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step13LoadKanjiSets;
import de.duehl.vocabulary.japanese.startup.logic.steps.Step14LoadInternalKanaData;
import de.duehl.vocabulary.japanese.startup.ui.SplashScreen;
import de.duehl.vocabulary.japanese.ui.VocabularyTrainerGui;

/**
 * Diese Klasse lädt beim Starten des Vokabeltrainers die Vokabulare ein, sucht die Sound-Files und
 * stellt die Verbindung zu den benutzerabhängigen, internen Daten jeder Variablen her.
 *
 * Außerdem werden Datenstrukturen aufgebaut, die vom Vokabel-Trainer benötigt werden.
 *
 * @version 1.01     2025-11-24
 * @author Christian Dühl
 */

public class StartupLoader {

    /**
     * Die Extension für den Dateien mit den benutzerabhängigen, internen Daten zu jeder Vokabel.
     */
    public static final String INTERNAL_VARIABLE_DATA_EXTENSION = ".ivd";

    /** Das Verzeichnis mit den internen Daten zu den Vokabeln. */
    public static final String INTERNAL_DATA_DIRECTORY = FileHelper.concatPathes(
            SessionManager.VOCABLE_TRAINER_DIRECTORY, "internal_vocabulary_data");

    private static final String STEP_LOAD_VOCABLES_AND_MP3S =
            "Vokabeln laden";
    private static final String STEP_CHECK_VOCABLE_INTEGRITY =
            "Vokabeln auf Integrität prüfen";
    private static final String STEP_FIND_MP3_AND_STORE_IN_VOCABLES =
            "MP3s finden und den Vokabeln zuordnen";
    private static final String STEP_LOAD_INTERNAL_VOCABLES_DATA =
            "Interne Daten zu den Vokabeln laden";
    private static final String STEP_LOAD_JOIN_INTERNAL_VOCABLES_DATA =
            "Interne Daten den Vokabeln zuordnen";
    private static final String STEP_STORE_OLDEST_VOCABLES_DATE =
            "älteste Vokabel in Vokabularien eintragen";
    private static final String STEP_CREATE_TRANSLATION_J_D =
            "Erzeuge Übersetzungen Japanisch-Deutsch";
    private static final String STEP_CREATE_TRANSLATION_D_J =
            "Erzeuge Übersetzungen Deutsch-Japanisch";
    private static final String STEP_INIT_OWN_LISTS =
            "Erzeuge Verwaltung der eigenen Listen";
    private static final String STEP_READ_WRONG_TESTED_VOCABLES =
            "Lese falsch übersetzte Vokabeln";
    private static final String STEP_CHECK_DOUBLE_KANJI =
            "Prüfe Kanji auf Dublettenfreiheit";
    private static final String STEP_LOAD_INTERNAL_KANJI_DATA =
            "Interne Daten zu den Kanji laden";
    private static final String STEP_LOAD_KANJI_SETS =
            "Kanji-Mengen laden";
    private static final String STEP_LOAD_INTERNAL_KANA_DATA =
            "Interne Daten zu den Kana laden";


    /** Die grafische Oberfläche. */
    private final VocabularyTrainerGui gui;

    /** Die Programmoptionen. */
    private final Options options;

    /** Die Liste mit den bekannten Vokabularien. */
    private List<Vocabulary> vocabularies;

    /**
     * Die Nachricht mit der Anzahl Vokabeln und Vokabularien, die nach dem Laden in der
     * Statuszeile der Oberfläche angezeigt werden soll.
     */
    private String vocabularyLoadMessage;

    /** Das Verzeichnis der internen Daten zu einer Vokabel nach ihrem Schlüssel. */
    private Map<String, InternalAdditionalVocableData> key2InternalDataMap;

    /** Die Liste aller Schlüssel der eingelesenen internen Daten. */
    private List<String> allKeysFromReadInternalData;

    /** Das Verzeichnis der internen Daten zu einer Vokabel nach der zugehörigen Variablen. */
    private Map<Vocable, InternalAdditionalVocableData> vocable2InternalDataMap;

    /** Das Objekt das zu Vokabeln die internen Daten abfragt. */
    private InternalDataRequester internalDataRequester;

    /**
     * Das Objekt das sich um die Übersetzung von Japanisch in Deutsch kümmert (für Abfragen die
     * nur Kana, aber nicht Kanji anzeigen).
     */
    private JapaneseToGermanTranslation japaneseToGermanTranslation;

    /** Das Objekt das sich um die Übersetzung von Deutsch in Japanisch kümmert. */
    private GermanToJapaneseTranslation germanToJapaneseTranslation;

    /** Das Verzeichnis der Vokabeln nach ihrem Schlüssel. */
    private Map<String, Vocable> keyToVocable;

    /** Die Verwaltung der eigenen Vokabellisten. */
    private OwnLists ownLists;

    /**
     * Die Verwaltung der beiden automatisch gepflegten Listen mit falsch abgefragten Vokabeln aus
     * Gruppen und anderen Vokabularien.
     */
    private WrongTestedVocables wrongTestedVocables;

    /** Das Objekt, das zu einem Kanji die internen, benutzerabhängigen Daten abrufen kann. */
    private InternalKanjiDataRequester internalKanjiDataRequester;

    /** Die Liste der benutzerdefinierten Kanji-Mengen. */
    private List<KanjiSet> kanjiSets;

    /** Das Objekt das zu einem Kana die internen, benutzerabhängigen Daten abrufen kann. */
    private InternalKanaDataRequester internalKanaDataRequester;

    /** Die grafische Oberfläche beim Start in der die Meldungen angezeigt werden. */
    private final SplashScreen splashScreen;

    /** Misst die Laufzeit des gesamten Startups. */
    private StopWatch watch;

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

    /** Gibt an, ob nach beim Startup Fehler aufgetreten sind. */
    private boolean startUpErrorOccured;

    /** Der Fehler der den Start verhindert. */
    private String startupError;

    /** Die Meldungen zu Warnungen, die beim Start auftreten. */
    private List<String> startupWarnings;

    /**
     * Konstruktor.
     *
     * @param gui
     *            Die grafische Oberfläche.
     * @param options
     *            Die Programmoptionen.
     */
    public StartupLoader(VocabularyTrainerGui gui, Options options) {
        this.gui = gui;
        this.options = options;

        List<String> steps = CollectionsHelper.buildListFrom(
                STEP_LOAD_VOCABLES_AND_MP3S,
                STEP_CHECK_VOCABLE_INTEGRITY,
                STEP_FIND_MP3_AND_STORE_IN_VOCABLES,
                STEP_LOAD_INTERNAL_VOCABLES_DATA,
                STEP_LOAD_JOIN_INTERNAL_VOCABLES_DATA,
                STEP_STORE_OLDEST_VOCABLES_DATE,
                STEP_CREATE_TRANSLATION_J_D,
                STEP_CREATE_TRANSLATION_D_J,
                STEP_INIT_OWN_LISTS,
                STEP_READ_WRONG_TESTED_VOCABLES,
                STEP_CHECK_DOUBLE_KANJI,
                STEP_LOAD_INTERNAL_KANJI_DATA,
                STEP_LOAD_KANJI_SETS,
                STEP_LOAD_INTERNAL_KANA_DATA
                );

        splashScreen = new SplashScreen(steps, gui.getProgramImage());
        startUpErrorOccured = false;
        startupError = "";
        startupWarnings = new ArrayList<>();
    }

    /** Führt das Laden, Suchen und Verknüpfen durch. */
    public void load() {
        try {
            tryToLoad();
        }
        catch (Exception exception) {
            startUpErrorOccured = true;
            startupError = "Fehler (Exception) beim Startup: " + exception.getMessage();
            exception.printStackTrace();
            closeSplashScreen();
        }
    }

    private void tryToLoad() {
        init();
        showSplashScreen();
        startup();
        closeSplashScreen();

        if (!startUpErrorOccured) createFumikoDataStructures();
    }

    private void init() {
        watch = new StopWatch();
    }

    private void showSplashScreen() {
        splashScreen.setVisible(true);
    }

    private void startup() {
        if (!startUpErrorOccured) step01ReadVocabularies();
        if (!startUpErrorOccured) step02CheckVocabelintegrity();
        if (!startUpErrorOccured) step03FindMP3AndStoreInVocables();
        if (!startUpErrorOccured) step04ReadInternalVocableData();
        if (!startUpErrorOccured) step05JoinVocablesWithInternalData();
        if (!startUpErrorOccured) step06StoreDateOfOldestVocableInEachVocabulary();
        if (!startUpErrorOccured) step07CreateJapaneseToGermanTranslation();
        if (!startUpErrorOccured) step08CreateGermanToJapaneseTranslation();
        if (!startUpErrorOccured) step09InitOwnListsAndReadWrongTestedVocableLists();
        if (!startUpErrorOccured) step10ReadWrongTestedVocableLists();
        if (!startUpErrorOccured) step11CheckDoubleKanji();
        if (!startUpErrorOccured) step12LoadInternalKanjiData();
        if (!startUpErrorOccured) step13LoadKanjiSets();
        if (!startUpErrorOccured) step14LoadInternalKanaData();
    }

    private void step01ReadVocabularies() {
        Step01ReadVocabularies actualStep = new Step01ReadVocabularies(STEP_LOAD_VOCABLES_AND_MP3S,
                options, splashScreen, watch);
        actualStep.runStep();
        vocabularies = actualStep.getVocabularies();
        vocabularyLoadMessage = actualStep.getVocabularyLoadMessage();
        postprocessActualStep(actualStep);
    }

    /** Überprüft die Integrität der Vokabeln. */
    private void step02CheckVocabelintegrity() {
        Step02CheckVocabelIntegrety actualStep =
                new Step02CheckVocabelIntegrety(
                        STEP_CHECK_VOCABLE_INTEGRITY, options, splashScreen, watch,
                        vocabularies);
        actualStep.runStep();
        postprocessActualStep(actualStep);
    }

    private void step03FindMP3AndStoreInVocables() {
        Step03FindMP3AndStoreInVocables actualStep = new Step03FindMP3AndStoreInVocables(
                STEP_FIND_MP3_AND_STORE_IN_VOCABLES, options, splashScreen, watch, vocabularies);
                actualStep.runStep();
                postprocessActualStep(actualStep);
    }

    /** Lädt alle bereits bekannten internen Datensätze zu Vokabeln. */
    private void step04ReadInternalVocableData() {
        Step04ReadInternalVocableData actualStep = new Step04ReadInternalVocableData(
                STEP_LOAD_INTERNAL_VOCABLES_DATA,
                options, splashScreen, watch);
        actualStep.runStep();
        key2InternalDataMap = actualStep.getKey2InternalDataMap();
        allKeysFromReadInternalData = actualStep.getAllKeysFromReadInternalData();
        postprocessActualStep(actualStep);
    }

    /** Verbindet die Vokabeln mit den internen Daten. */
    private void step05JoinVocablesWithInternalData() {
        Step05JoinVocablesAndInternalData actualStep = new Step05JoinVocablesAndInternalData(
                STEP_LOAD_JOIN_INTERNAL_VOCABLES_DATA, options, splashScreen, watch, vocabularies,
                key2InternalDataMap, allKeysFromReadInternalData);
        actualStep.runStep();
        vocable2InternalDataMap = actualStep.getVocable2InternalDataMap();
        internalDataRequester = actualStep.getInternalDataRequester();
        postprocessActualStep(actualStep);
    }

    private void step06StoreDateOfOldestVocableInEachVocabulary() {
        Step06StoreDateOfOldestVocableInEachVocabulary actualStep =
                new Step06StoreDateOfOldestVocableInEachVocabulary(STEP_STORE_OLDEST_VOCABLES_DATE,
                        options, splashScreen, watch, vocabularies, vocable2InternalDataMap);
        actualStep.runStep();
        postprocessActualStep(actualStep);
    }

    private void step07CreateJapaneseToGermanTranslation() {
        Step07CreateJapaneseToGermanTranslation actualStep =
                new Step07CreateJapaneseToGermanTranslation(STEP_CREATE_TRANSLATION_J_D,
                        options, splashScreen, watch, vocabularies);
        actualStep.runStep();
        japaneseToGermanTranslation = actualStep.getJapaneseToGermanTranslation();
        postprocessActualStep(actualStep);
    }

    private void step08CreateGermanToJapaneseTranslation() {
        if (options.isCreateGermanJapaneseTranslationAtStartup()) {
            Step08ACreateGermanToJapaneseTranslation actualStep =
                    new Step08ACreateGermanToJapaneseTranslation(STEP_CREATE_TRANSLATION_D_J,
                            options, splashScreen, watch, vocabularies);
            actualStep.runStep();
            germanToJapaneseTranslation = actualStep.getGermanToJapaneseTranslation();
            postprocessActualStep(actualStep);
        }
        else {
            Step08BSkipCreateGermanToJapaneseTranslation actualStep =
                    new Step08BSkipCreateGermanToJapaneseTranslation(STEP_CREATE_TRANSLATION_D_J,
                            options, splashScreen, watch);
            actualStep.runStep();
            postprocessActualStep(actualStep);
        }
    }

    private void step09InitOwnListsAndReadWrongTestedVocableLists() {
        Step09InitOwnLists actualStep = new Step09InitOwnLists(STEP_INIT_OWN_LISTS, gui, options,
                splashScreen, watch, vocabularies, internalDataRequester);
        actualStep.runStep();
        keyToVocable = actualStep.getKeyToVocable();
        ownLists = actualStep.getOwnLists();
        postprocessActualStep(actualStep);
    }

    private void step10ReadWrongTestedVocableLists() {
        Step10ReadWrongTestedVocableLists actualStep = new Step10ReadWrongTestedVocableLists(
                STEP_READ_WRONG_TESTED_VOCABLES, options, splashScreen, watch, vocabularies,
                internalDataRequester, keyToVocable);
        actualStep.runStep();
        wrongTestedVocables = actualStep.getWrongTestedVocables();
        postprocessActualStep(actualStep);
    }

    /** Überprüft, ob es bei den Kanji jedes Kanji-Zeichen nur einmal gibt. */
    private void step11CheckDoubleKanji() {
        Step11CheckDoubleKanji actualStep = new Step11CheckDoubleKanji(STEP_CHECK_DOUBLE_KANJI,
                options, splashScreen, watch);
        actualStep.runStep();
        postprocessActualStep(actualStep);
    }

    private void step12LoadInternalKanjiData() {
        Step12ReadInternalKanjiData actualStep = new Step12ReadInternalKanjiData(
                STEP_LOAD_INTERNAL_KANJI_DATA, options, splashScreen, watch);
        actualStep.runStep();
        internalKanjiDataRequester = actualStep.getInternalKanjiDataRequester();
        postprocessActualStep(actualStep);
    }

    private void step13LoadKanjiSets() {
        Step13LoadKanjiSets actualStep = new Step13LoadKanjiSets(
                STEP_LOAD_KANJI_SETS, options, splashScreen, watch);
        actualStep.runStep();
        kanjiSets = actualStep.getKanjiSets();
        postprocessActualStep(actualStep);
    }

    private void step14LoadInternalKanaData() {
        Step14LoadInternalKanaData actualStep = new Step14LoadInternalKanaData(
                STEP_LOAD_INTERNAL_KANA_DATA, options, splashScreen, watch);
        actualStep.runStep();
        internalKanaDataRequester = actualStep.getInternalKanaDataRequester();
        postprocessActualStep(actualStep);
    }

    private void postprocessActualStep(StartupStep actualStep) {
        if (actualStep.isErrorsInStep()) {
            startUpErrorOccured = true;
            startupError = actualStep.getErrorMessage();

        }
        startupWarnings.addAll(actualStep.getWarningMessages());
    }

    private void closeSplashScreen() {
        if (startUpErrorOccured) {
            String message = "Es ist ein Fehler beim Startup aufgetreten, der den Start des "
                    + "Vokabeltrainers verhindert:\n\n"
                    + startupError
                    + "\n\nDa Fehler auftraten, wird die Anwendung geschlossen.";
            splashScreen.appendMessage(message);
            System.err.println(message);
            GuiTools.informUser(splashScreen.getWindowAsComponent(), "Es traten Fehler auf",
                    message);
            splashScreen.closeDialog();
            System.err.println("Wegen Fehlern beim Startup wird der Vokabeltrainer beendet.");
            System.exit(1);
        }
        else if (options.isStopAfterStartup()) {
            GuiTools.informUser(splashScreen.getWindowAsComponent(), "Halt nach dem Startup",
                    "Nach Bestätigung geht es weiter.");
            splashScreen.closeDialog();
        }
        else if (options.isWaitForXAfterStartup()) {
            splashScreen.canClose();
        }
        else {
            splashScreen.closeDialog();
        }
    }

    private void createFumikoDataStructures() {
        dataStructures = new FumikoDataStructures(options, vocabularies, internalDataRequester,
                japaneseToGermanTranslation, germanToJapaneseTranslation, ownLists,
                wrongTestedVocables, internalKanjiDataRequester, kanjiSets,
                internalKanaDataRequester);
    }

    /** Getter für die Datenstrukturen des Vokabeltrainers. */
    public FumikoDataStructures getDataStructures() {
        return dataStructures;
    }

    /**
     * Getter für die Nachricht mit der Anzahl Vokabeln und Vokabularien, die nach dem Laden in der
     * Statuszeile der Oberfläche angezeigt werden soll.
     */
    public String getVocabularyLoadMessage() {
        return vocabularyLoadMessage;
    }

    /** Getter für den Text, der beim Starten des Vokabeltrainers angezeigt wurde. */
    public String getStartUpLog() {
        return splashScreen.getSplashText();
    }

    /** Getter für die Meldungen zu Warnungen, die beim Start auftreten. */
    public List<String> getStartupWarnings() {
        return startupWarnings;
    }

}
