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

import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.duehl.basics.datetime.date.ImmutualDate;
import de.duehl.basics.text.Text;
import de.duehl.math.stochastic.RandomSample;
import de.duehl.math.stochastic.RandomSampleWithoutPutBack;
import de.duehl.swing.ui.GuiTools;
import de.duehl.vocabulary.japanese.common.data.InternalAdditionalKanjiData;
import de.duehl.vocabulary.japanese.common.persistence.Options;
import de.duehl.vocabulary.japanese.data.FumikoDataStructures;
import de.duehl.vocabulary.japanese.data.symbol.Kanji;
import de.duehl.vocabulary.japanese.logic.symbol.kanji.internal.InternalKanjiDataRequester;
import de.duehl.vocabulary.japanese.logic.symbol.kanji.test.data.KanjiForTestSelectionMethod;
import de.duehl.vocabulary.japanese.ui.data.FumikoUiObjects;
import de.duehl.vocabulary.japanese.ui.dialog.kanji.kanjitest.KanjiTestParameterChooser;
import de.duehl.vocabulary.japanese.ui.dialog.kanji.kanjitest.KanjiTester;

/**
 * Diese Klasse lässt zunächst auswählen, ob einige oder alle Kanji abgefragt werden sollen und ob
 * die Kanji in der originalen Reihenfolge oder zufällig sortiert abgefragt werden sollen.
 *
 * Anschließend fragt es ein Kanji nach dem anderen ab: Das Symbol wird angezeigt und der Benutzer
 * kann deutsche Bedeutungen sowie ON- und kun-Lesungen eingeben.
 *
 * Anhand der Kanji-Enum-Objekten wird dann überprüft, ob (ausreichend viel) eingegeben wurde
 * und im Dialog zur Bewertung der einen Abfrage werden alle Daten zu dem Kanji angezeigt.
 *
 * 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     2025-12-27
 * @author Christian Dühl
 */

public class KanjiTesterLogic {

    private static final boolean DEBUG = false;


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

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

    /** Gibt an, ob man fortsetzen soll. */
    private boolean goAhead;

    /** Die Liste an Kanji, aus denen ausgewählt wird. */
    private List<Kanji> kanjiList;

    /** Die Anzahl der abzufragenden Kanji. */
    private int numberOfKanjiToTest;

    /** Gibt an ob die abzufragenden Kanji in der originalen Reihenfolge belassen werden sollen. */
    private boolean useOriginalKanjiOrder;

    /** Die Liste der abzufragenden Kanji. */
    private List<Kanji> kanjiToTest;

    /** Eine Beschreibung der abgefragten Kanji. */
    private String kanjiToTestDescription;

    /** Die Art wie Kanji für die Abfrage ausgewählt werden. */
    private KanjiForTestSelectionMethod selectionMethod;

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

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

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

    /** Die Liste der nicht zuletzt zehn mal richtig abgefragten Kanji. */
    private List<Kanji> notTenTimesGoodTestedKanjiList;

    /** Die Position des Dialogs zum Erfragen der Parameter. */
    private Point chooserLocation;

    /**
     * Konstruktor.
     *
     * @param dataStructures
     *            Die Datenstrukturen des Vokabeltrainers.
     * @param uiObjects
     *            Die häufig verwendeten Funktionen der grafischen Oberfläche des Vokabeltrainers.
     */
    public KanjiTesterLogic(FumikoDataStructures dataStructures, FumikoUiObjects uiObjects) {
        this.dataStructures = dataStructures;
        this.uiObjects = uiObjects;
    }

    /** Führt den Test durch. */
    public void test() {
        init();

        if (goAhead) askTestParameters();
        if (goAhead) determineKanjiToTest();
        if (goAhead) startRealTest();
    }

    private void init() {
        goAhead = true;
        determineNotTenTimesGoodTestedKanjiList();
    }

    private void determineNotTenTimesGoodTestedKanjiList() {
        notTenTimesGoodTestedKanjiList = new ArrayList<>();

        InternalKanjiDataRequester requester = dataStructures.getInternalKanjiDataRequester();
        for (Kanji kanji : Kanji.getAllKanjiAsList()) {
            InternalAdditionalKanjiData data = requester.getInternalDataForKanji(kanji);
            if (data.getLastCorrectTestsCount() < 10) {
                notTenTimesGoodTestedKanjiList.add(kanji);
            }
        }
    }

    private void askTestParameters() {
        KanjiTestParameterChooser chooser = new KanjiTestParameterChooser(
                dataStructures.getKanjiSets(), dataStructures.getOptions(),
                notTenTimesGoodTestedKanjiList, uiObjects.getGuiLocation(),
                uiObjects.getProgramImage());
        chooser.setVisible(true);
        if (chooser.isApplied()) {
            kanjiList = chooser.getKanjiList();
            numberOfKanjiToTest = chooser.getNumberOfKanjiToTest();
            useOriginalKanjiOrder = chooser.isUseOriginalKanjiOrder();
            selectionMethod = chooser.getSelectionMethod();
            germanMeaningCaseSensitivity = chooser.isGermanMeaningCaseSensitivity();
            onLesungCaseSensitivity = chooser.isOnLesungCaseSensitivity();
            kunLesungCaseSensitivity = chooser.isKunLesungCaseSensitivity();
            if (DEBUG) Text.say("numberOfKanjiToTest  = " + numberOfKanjiToTest);
            if (DEBUG) Text.say("sortKanjiToTest      = " + useOriginalKanjiOrder);

            if (kanjiList.isEmpty()) {
                String title = "Leer Menge an Kanji ausgewählt";
                String message = "Sie haben eine leere Menge an abzufragenden Kanji ausgewählt, "
                        + "daher ist nichts abzufragen.";
                GuiTools.informUser(title, message);
                goAhead = false;
            }
            else {
                chooserLocation = chooser.getLocation();
            }
        }
        else {
            goAhead = false;
        }
    }

    private void determineKanjiToTest() {
        switch (selectionMethod) {
            case RANDOM_BY_NUMBER:
                determineKanjiToTestByRandomNumber();
                return;
            case ALL:
                useAllKanji();
                return;
            case LAST_N:
                useLastNKanji();
                return;
            case NOT_TEN_TIMES_GOOD_TESTED:
                useNotTenTimesGoodTestedKanjiList();
                return;
            case OLDEST_TESTED_N:
                useOldestNKanji();
                return;
            case MOST_SELDOM_TESTED_N:
                useMostSeldomTestedNKanji();
                return;
            case UNKNOWN:
            default:
                goAhead = false;
        }
    }

    private void determineKanjiToTestByRandomNumber() {
        List<Integer> kanjiPositionsSample = createKanjiPositionSample();
        fillKanjiToTestWithKanjiPositionSample(kanjiPositionsSample);
        kanjiToTestDescription = numberOfKanjiToTest + " zufällige Kanji";
    }

    /**
     * Erstellt eine zufällige Menge von Positionen zwischen 1 und der Anzahl der bekannten Kanji
     * ohne zurücklegen.
     *
     * Falls die originale Reihenfolge benutzt werden soll, werden die Positionen sortiert.
     */
    private List<Integer> createKanjiPositionSample() {
        int from = 1;
        int to = kanjiList.size();
        int sampleSize = Math.min(kanjiList.size(), numberOfKanjiToTest);

        RandomSample randomSample = new RandomSampleWithoutPutBack(from, to, sampleSize);
        randomSample.drawSample();
        List<Integer> kanjiPositionsSample = randomSample.getSample();

        if (useOriginalKanjiOrder) {
            Collections.sort(kanjiPositionsSample);
        }

        if (DEBUG) Text.say("kanjiPositionsSample = " + kanjiPositionsSample);

        return kanjiPositionsSample;
    }

    /**
     * Erstellt aus der Positionen zwischen 1 und der Anzahl der bekannten Kanji die entsprechende
     * Liste mit den Kanji.
     *
     * @param kanjiPositionsSample
     *            Liste mit den Positionen zwischen 1 und der Anzahl der bekannten Kanji.
     */
    private void fillKanjiToTestWithKanjiPositionSample(List<Integer> kanjiPositionsSample) {
        kanjiToTest = new ArrayList<>();

        for (int kanjiPosition : kanjiPositionsSample) {
            int kanjiIndex = kanjiPosition - 1;
            Kanji kanji = kanjiList.get(kanjiIndex);
            kanjiToTest.add(kanji);
        }
    }

    private void useAllKanji() {
        kanjiToTest = new ArrayList<>();
        kanjiToTest.addAll(kanjiList);

        if (!useOriginalKanjiOrder) {
            Collections.shuffle(kanjiToTest);
        }
        kanjiToTestDescription = "alle Kanji";
    }

    private void useLastNKanji() {
        kanjiToTest = new ArrayList<>();

        int size = kanjiList.size();

        int startIndex = Math.max(0, size - numberOfKanjiToTest);

        for (int index = startIndex; index <= size - 1; ++index) {
            Kanji kanji = kanjiList.get(index);
            kanjiToTest.add(kanji);
        }

        if (!useOriginalKanjiOrder) {
            Collections.shuffle(kanjiToTest);
        }
        kanjiToTestDescription = "die letzten " + numberOfKanjiToTest + " Kanji";
    }

    private void useNotTenTimesGoodTestedKanjiList() {
        kanjiToTest = new ArrayList<>();
        kanjiToTest.addAll(notTenTimesGoodTestedKanjiList);

        if (!useOriginalKanjiOrder) {
            Collections.shuffle(kanjiToTest);
        }
        kanjiToTestDescription = "Kanji die nicht zuletzt 10 x richtig abgefragt wurden";
    }

    private void useOldestNKanji() {
        kanjiToTest = new ArrayList<>();

        Map<Kanji, Integer> daysNotTestedByKanji = createDaysNotTestedByKanjiMap();
        List<Kanji> kanjiListSortedByDaysNotTested =
                createKanjiListSortedByDaysNotTested(daysNotTestedByKanji);
        kanjiListSortedByDaysNotTested = shuffleEqualLongNotTestedKanjis(
                kanjiListSortedByDaysNotTested, daysNotTestedByKanji);

        for (int index = 0; index < numberOfKanjiToTest; ++index) {
            Kanji kanji = kanjiListSortedByDaysNotTested.get(index);
            kanjiToTest.add(kanji);
        }

        if (!useOriginalKanjiOrder) { // hier nicht sehr sinnvoll, aber schadet auch nicht.
            Collections.shuffle(kanjiToTest);
        }
        kanjiToTestDescription = "die " + numberOfKanjiToTest + " am längsten nicht mehr "
                + "abgefragten Kanji";
    }

    /**
     * Erzeugt ein Verzeichnis der Kanji nach der Anzahl der Tage, die die letzte Abfrage zurück
     * liegt.
     */
    private Map<Kanji, Integer> createDaysNotTestedByKanjiMap() {
        Map<Kanji, Integer> daysNotTestedByKanji = new HashMap<>();
        ImmutualDate today = new ImmutualDate();
        InternalKanjiDataRequester requester = dataStructures.getInternalKanjiDataRequester();
        for (Kanji kanji : kanjiList) {
            InternalAdditionalKanjiData data = requester.getInternalDataForKanji(kanji);
            ImmutualDate lastTestDate = data.getLastTestDate();
            int numberOfDaysNotTested = lastTestDate.difference(today);
            daysNotTestedByKanji.put(kanji, numberOfDaysNotTested);
        }
        return daysNotTestedByKanji;
    }

    /**
     * Erzeugt eine sortierte Liste der Kanji nach der Anzahl der Tage, die sie nicht mehr
     * abgefragt worden.
     *
     * Die am längsten nicht abgefragten Kanji liegen in der Liste vorne.
     *
     * @param daysNotTestedByKanji
     *            Das Verzeichnis der Kanji nach der Anzahl der Tage, die die letzte Abfrage zurück
     *            liegt.
     * @return Sortiert Liste der Kanji.
     */
    private List<Kanji> createKanjiListSortedByDaysNotTested(
            Map<Kanji, Integer> daysNotTestedByKanji) {
        List<Kanji> kanjiListSortedByDaysNotTested = new ArrayList<>();
        kanjiListSortedByDaysNotTested.addAll(kanjiList);

        Collections.sort(kanjiListSortedByDaysNotTested, new Comparator<Kanji>() {
            @Override
            public int compare(Kanji kanji1, Kanji kanji2) {
                int numberOfDaysNotTested1 = daysNotTestedByKanji.get(kanji1);
                int numberOfDaysNotTested2 = daysNotTestedByKanji.get(kanji2);
                return numberOfDaysNotTested2 - numberOfDaysNotTested1; // größer gewinnt
            }
        });

        return kanjiListSortedByDaysNotTested;
    }

    /**
     * Damit Kanji, bei denen die letzte Abfrage gleich lange her ist, nicht immer nach der
     * gleichen Reihenfolge sortiert werden, werden die Abschnitte der Liste mit Kanji, welche
     * gleich lange nicht abgefragt wurden, zufällig sortiert.
     *
     * @param kanjiListSortedByDaysNotTested
     *            Die Liste der Kanji sortiert danach, wie lange die letzte Abfrage her ist. Die am
     *            längsten nicht abgefragten Kanji liegen in der Liste vorne.
     * @param daysNotTestedByKanji
     *            Das Verzeichnis der Kanji nach der Anzahl der Tage, die die letzte Abfrage zurück
     *            liegt.
     * @return Sortiert Liste der Kanji.
     */
    private List<Kanji> shuffleEqualLongNotTestedKanjis(List<Kanji> kanjiListSortedByDaysNotTested,
            Map<Kanji, Integer> daysNotTestedByKanji) {
        List<List<Kanji>> listOfListsWithEqualLongNotTestedKanji = new ArrayList<>();

        List<Kanji> actualList = new ArrayList<>();
        int lastNumberOfDaysNotTested = -1;
        for (Kanji kanji : kanjiListSortedByDaysNotTested) {
            int numberOfDaysNotTested = daysNotTestedByKanji.get(kanji);
            if (numberOfDaysNotTested != lastNumberOfDaysNotTested) {
                if (!actualList.isEmpty()) {
                    listOfListsWithEqualLongNotTestedKanji.add(actualList);
                    actualList = new ArrayList<>();
                }
                lastNumberOfDaysNotTested = numberOfDaysNotTested;
            }
            actualList.add(kanji);
        }
        if (!actualList.isEmpty()) {
            listOfListsWithEqualLongNotTestedKanji.add(actualList);
        }

        for (List<Kanji> listsWithEqualLongNotTestedKanji : listOfListsWithEqualLongNotTestedKanji) {
            Collections.shuffle(listsWithEqualLongNotTestedKanji);
        }

        List<Kanji> kanjiListBetterSortedByDaysNotTested = new ArrayList<>();
        for (List<Kanji> listsWithEqualLongNotTestedKanji : listOfListsWithEqualLongNotTestedKanji) {
            kanjiListBetterSortedByDaysNotTested.addAll(listsWithEqualLongNotTestedKanji);
        }

        return kanjiListBetterSortedByDaysNotTested;
    }

    private void useMostSeldomTestedNKanji() {
        kanjiToTest = new ArrayList<>();

        List<Kanji> kanjiListSortedByNumberTested = createKanjiListSortedByNumberTested();

        int size = kanjiListSortedByNumberTested.size();

        int startIndex = Math.max(0, size - numberOfKanjiToTest);

        for (int index = startIndex; index <= size - 1; ++index) {
            Kanji kanji = kanjiListSortedByNumberTested.get(index);
            kanjiToTest.add(kanji);
        }

        if (!useOriginalKanjiOrder) { // hier nicht sinnvoll, aber schadet auch nicht.
            Collections.shuffle(kanjiToTest);
        }
        kanjiToTestDescription = "die " + numberOfKanjiToTest + " am seltesten abgefragten Kanji";
    }

    private List<Kanji> createKanjiListSortedByNumberTested() {
        List<Kanji> kanjiListSortedByNumberTested = new ArrayList<>();
        kanjiListSortedByNumberTested.addAll(kanjiList);

        Collections.sort(kanjiListSortedByNumberTested, new Comparator<Kanji>() {
            @Override
            public int compare(Kanji kanji1, Kanji kanji2) {
                InternalKanjiDataRequester requester = dataStructures.getInternalKanjiDataRequester();
                InternalAdditionalKanjiData data1 = requester.getInternalDataForKanji(kanji1);
                InternalAdditionalKanjiData data2 = requester.getInternalDataForKanji(kanji2);
                int testCount1 = data1.getTestCount();
                int testCount2 = data2.getTestCount();
                return testCount2 - testCount1; // größer gewinnt
            }
        });

        return kanjiListSortedByNumberTested;
    }

    private void startRealTest() {
        Point testerParentPosition;
        Options options = dataStructures.getOptions();
        if (options.isShowKanjiTestAtParameterDialogPosition()) {
            testerParentPosition = uiObjects.getGuiLocation();
        }
        else {
            testerParentPosition = chooserLocation;
        }

        KanjiTester tester = new KanjiTester(kanjiToTest, kanjiToTestDescription,
                germanMeaningCaseSensitivity, onLesungCaseSensitivity, kunLesungCaseSensitivity,
                dataStructures, uiObjects, testerParentPosition);
        tester.setVisible(true);
    }

}
