package de.duehl.vocabulary.japanese.tools;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.basics.datetime.date.ImmutualDate;
import de.duehl.basics.text.Text;
import de.duehl.swing.ui.GuiTools;
import de.duehl.vocabulary.japanese.common.data.InternalAdditionalVocableData;
import de.duehl.vocabulary.japanese.common.data.TranslationDirection;
import de.duehl.vocabulary.japanese.common.data.VocablesShuffleType;
import de.duehl.vocabulary.japanese.common.persistence.Options;
import de.duehl.vocabulary.japanese.data.FumikoDataStructures;
import de.duehl.vocabulary.japanese.data.Vocable;
import de.duehl.vocabulary.japanese.logic.internal.InternalDataRequester;
import de.duehl.vocabulary.japanese.tools.data.VocableSortData;

/**
 * Diese Klasse sortiert eine Liste von Vokabeln auf die gewünschte Weise.
 *
 * @version 1.01     2025-11-20
 * @author Christian Dühl
 */

public class VocableListShuffler {

    private static final boolean DEBUG = false;


    /** Die Liste mit den umzusortierenden Vokabeln. */
    private final List<Vocable> vocables;

    /** Die Art, wie die Liste sortiert werden soll. */
    private final VocablesShuffleType type;

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

    /** Die Richtung, in die übersetzt wird (Japanisch - Deutsch oder Deutsch - Japanisch). */
    private final TranslationDirection translationDirection;

    /** Das Verzeichnis der Analysedaten für komplizierter Sortierungen nach den Vokabeln. */
    private Map<Vocable, VocableSortData> sortDataByVocableMap;
    /* Als schlichte Liste würde die Liste der SortDatasets ja nicht mit umsortiert! */

    /**
     * Konstruktor.
     *
     * @param vocables
     *            Die Liste mit den umzusortierenden Vokabeln.
     * @param type
     *            Die Art, wie die Liste sortiert werden soll.
     * @param dataStructures
     *            Die Datenstrukturen des Vokabeltrainers.
     */
    public VocableListShuffler(List<Vocable> vocables, VocablesShuffleType type,
            FumikoDataStructures dataStructures) {
        this.vocables = CollectionsHelper.copyList(vocables);
        this.type = type;
        this.dataStructures = dataStructures;

        Options options = dataStructures.getOptions();
        translationDirection = options.getTranslationDirection();
    }

    /** Erstellt eine passend sortierte Liste. */
    public void shuffle() {
        switch (type) {
            case NATURAL:
                shuffleNatural();
                break;
            case ALPHABETICAL:
                shuffleAlphabetical();
                break;
            case RANDOM:
                shuffleRandom();
                break;
            case LAST_CORRECT_TEST_DATE:
                shuffleByLastCorrectTestDate();
                break;
            case FIRST_SEEN_DATE:
                shuffleByFirstSeenDate();
                break;
            case LAST_ANSWERS_SUCCESS:
                shuffleByLastAnswersSuccess();
                break;
            case MIX:
                shuffleByMix();
                break;
            default:
                shuffleUnknown();
                break;
        }

        Options options = dataStructures.getOptions();
        if (options.isReverseShuffledVocables()) {
            revers();
        }
    }

    private void shuffleNatural() {
        say("shuffleNatural()");
        // Die Liste wird so belassen.
    }

    private void shuffleAlphabetical() {
        say("shuffleAlphabetical()");
        Collections.sort(vocables, new Comparator<Vocable>() {
            @Override
            public int compare(Vocable o1, Vocable o2) {
                String romaji1 = o1.getRomaji();
                String romaji2 = o2.getRomaji();
                return romaji1.compareTo(romaji2);
            }
        });
    }

    private void shuffleRandom() {
        say("shuffleRandom()");
        Collections.shuffle(vocables);
    }

    /** Sortiert  basierend auf dem Zeitpunkt der letzten richtigen Antwort. */
    private void shuffleByLastCorrectTestDate() {
        say("shuffleByLastCorrectTestDate()");
        determineVocableSortData();

        Collections.sort(vocables, new Comparator<Vocable>() {
            @Override
            public int compare(Vocable o1, Vocable o2) {
                return compareByLastCorrectTestDate(o1, o2);
            }
        });
    }

    private int compareByLastCorrectTestDate(Vocable vocable1, Vocable vocable2) {
        say("compareByLastCorrectTestDate(vocable1, vocable2)");
        say("vocable1 : " + vocable1);
        say("vocable2 : " + vocable2);

        VocableSortData sortData1 = sortDataByVocableMap.get(vocable1);
        VocableSortData sortData2 = sortDataByVocableMap.get(vocable2);
        say("sortData1 : " + sortData1);
        say("sortData2 : " + sortData2);

        int daysSinceLastCorrect1 = sortData1.getLastCorrectAgoInDays();
        int daysSinceLastCorrect2 = sortData2.getLastCorrectAgoInDays();
        say("daysSinceLastCorrect1 : " + daysSinceLastCorrect1);
        say("daysSinceLastCorrect2 : " + daysSinceLastCorrect2);

        int cmp = daysSinceLastCorrect1 - daysSinceLastCorrect2; // besser wenn kleiner
        say("cmp : " + cmp);
        return cmp;
    }

    /** Sortiert basierend auf dem Zeitpunkt des ursprünglichen Hinzufügens. */
    private void shuffleByFirstSeenDate() {
        say("shuffleByFirstSeenDate()");
        determineVocableSortData();

        Collections.sort(vocables, new Comparator<Vocable>() {
            @Override
            public int compare(Vocable o1, Vocable o2) {
                return compareByFirstSeenDate(o1, o2);
            }
        });
    }

    private int compareByFirstSeenDate(Vocable vocable1, Vocable vocable2) {
        say("compareByFirstSeenDate(vocable1, vocable2)");
        say("vocable1 : " + vocable1);
        say("vocable2 : " + vocable2);

        VocableSortData sortData1 = sortDataByVocableMap.get(vocable1);
        VocableSortData sortData2 = sortDataByVocableMap.get(vocable2);
        say("sortData1 : " + sortData1);
        say("sortData2 : " + sortData2);

        int daysSinceFirstSeen1 = sortData1.getAgeInDays();
        int daysSinceFirstSeen2 = sortData2.getAgeInDays();
        say("daysSinceFirstSeen1 : " + daysSinceFirstSeen1);
        say("daysSinceFirstSeen2 : " + daysSinceFirstSeen2);

        int cmp = daysSinceFirstSeen1 - daysSinceFirstSeen2; // besser wenn kleiner da
                                                             // neuer -> mehr trainieren
        say("cmp : " + cmp);
        return cmp;
    }

    /** Sortiert basierend auf dem Erfolg bei den letzten Abfragen. */
    private void shuffleByLastAnswersSuccess() {
        say("shuffleByLastAnswersSuccess()");
        determineVocableSortData();

        Collections.sort(vocables, new Comparator<Vocable>() {
            @Override
            public int compare(Vocable o1, Vocable o2) {
                return compareByLastAnswersSuccess(o1, o2);
            }
        });
    }

    private int compareByLastAnswersSuccess(Vocable vocable1, Vocable vocable2) {
        say("compareByLastAnswersSuccess(vocable1, vocable2)");
        say("vocable1 : " + vocable1);
        say("vocable2 : " + vocable2);

        VocableSortData sortData1 = sortDataByVocableMap.get(vocable1);
        VocableSortData sortData2 = sortDataByVocableMap.get(vocable2);
        say("sortData1 : " + sortData1);
        say("sortData2 : " + sortData2);

        double lastSuccess1 = sortData1.getLastAnswersValue();
        double lastSuccess2 = sortData2.getLastAnswersValue();

        int cmp = (int) (lastSuccess1 - lastSuccess2); // besser wenn kleiner, da die
                                                       // wichtiger abzufragen sind
        say("cmp lastSuccess: " + cmp);
        if (cmp == 0) {
            int countLastCorrectAnswers1 = sortData1.getCountLastCorrectAnswers();
            int countLastCorrectAnswers2 = sortData2.getCountLastCorrectAnswers();
            cmp = countLastCorrectAnswers1 - countLastCorrectAnswers2;
            // besser wenn kleiner, da die wichtiger abzufragen sind
            say("cmp countLastCorrectAnswers: " + cmp);
            if (cmp == 0) {
                int countLastIncorrectAnswers1 = sortData1.getCountLastIncorrectAnswers();
                int countLastIncorrectAnswers2 = sortData2.getCountLastIncorrectAnswers();
                cmp = countLastIncorrectAnswers2 - countLastIncorrectAnswers1;
                // besser wenn größer, da die wichtiger abzufragen sind
                say("cmp countLastIncorrectAnswers: " + cmp);
            }
        }

        return cmp;
    }

    private void shuffleByMix() {
        say("shuffleByMix()");
        determineVocableSortData();

        Collections.sort(vocables, new Comparator<Vocable>() {
            @Override
            public int compare(Vocable o1, Vocable o2) {
                return compareByMix(o1, o2);
            }
        });
    }

    private static final int TEST_COUNT_FACTOR = 1;
    private static final int LAST_CORRECT_TEST_DATE_FACTOR = 20;
    //private static final int FIRST_SEEN_DATE_FACTOR = 10;
    private static final int LAST_ANSWERS_SUCCESS_FACTOR = 100;

    /**
     * Vergleicht zwei Vokabeln basierend auf einem Mix aus den Zeitpunkten und dem Erfolg bei den
     * letzten Abfragen sowie der Anzahl der Abfragen.
     */
    private int compareByMix(Vocable vocable1, Vocable vocable2) {
        say("compareByMix(vocable1, vocable2)");
        say("vocable1 : " + vocable1);
        say("vocable2 : " + vocable2);

        VocableSortData sortData1 = sortDataByVocableMap.get(vocable1);
        VocableSortData sortData2 = sortDataByVocableMap.get(vocable2);
        say("sortData1 : " + sortData1);
        say("sortData2 : " + sortData2);

        int testCount1 = sortData1.getTestCount();
        int testCount2 = sortData2.getTestCount();
        int cmpTestCount = testCount1 - testCount2; // besser wenn kleiner, da die
                                                    // wichtiger abzufragen sind
        say("cmpTestCount = " + cmpTestCount);

        int cmpByLastCorrectTestDate = compareByLastCorrectTestDate(vocable1, vocable2);
        say("cmpByLastCorrectTestDate = " + cmpByLastCorrectTestDate);
        //int cmpByFirstSeenDate = compareByFirstSeenDate(vocable1, vocable2);
        //say("cmpByFirstSeenDate = " + cmpByFirstSeenDate);
        int cmpByLastAnswersSuccess = compareByLastAnswersSuccess(vocable1, vocable2);
        say("cmpByLastAnswersSuccess = " + cmpByLastAnswersSuccess);

        int cmp = 0
                + TEST_COUNT_FACTOR * cmpTestCount
                + LAST_CORRECT_TEST_DATE_FACTOR * cmpByLastCorrectTestDate
                //+ FIRST_SEEN_DATE_FACTOR * cmpByFirstSeenDate
                + LAST_ANSWERS_SUCCESS_FACTOR * cmpByLastAnswersSuccess
                ;
        say("cmp = " + cmp);

        return cmp;
        /*
         * Ich habe das first seen date erstmal hier rausgenommen, da es sonst eine
         * "Comparison method violates its general contract!" Exception gab, die laut
         * Internet darauf hinweist, dass die Symmetrie oder Transitivität der Sortierung
         * nicht gegeben sei.
         *
         *     sgn(compare(x, y)) == -sgn(compare(y, x))
         *
         * Wie das hier der Fall sein soll ist mir völlig unklar, aber erstmal behelfe ich
         * mir so.
         */
    }

    private void determineVocableSortData() {
        say("determineVocableSortData()");
        sortDataByVocableMap = new HashMap<>();
        for (Vocable vocable : vocables) {
            VocableSortData sortData = determineVocableSortData(vocable);
            sortDataByVocableMap.put(vocable, sortData);

            if (DEBUG) {
                System.out.println(vocable);
                InternalDataRequester requester = dataStructures.getInternalDataRequester();
                InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
                System.out.println(data);
                System.out.println(sortData);
                System.out.println();
            }
        }
    }

    private VocableSortData determineVocableSortData(Vocable vocable) {
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
        ImmutualDate today = new ImmutualDate();

        int lastCorrectAgoInDays = getLastCorectAgoInDays(data, today);
        int ageInDays = data.getFirstSeenDate().difference(today);
        double lastAnswersValue = 0d;
        int countLastCorrectAnswers = 0;
        int countLastIncorrectAnswers = 0;
        List<Boolean> results = getLastTenTestResults(data);
        if (!results.isEmpty()) {
            for (boolean success : results) {
                if (success) {
                    lastAnswersValue += 1;
                    ++countLastCorrectAnswers;
                }
                else {
                    ++countLastIncorrectAnswers;
                }
            }
            lastAnswersValue /= results.size();
            lastAnswersValue *= 100d;
        }
        int testCount = getTestCount(data);

        return new VocableSortData(lastCorrectAgoInDays, ageInDays, lastAnswersValue,
                countLastCorrectAnswers, countLastIncorrectAnswers, testCount);
    }

    private int getLastCorectAgoInDays(InternalAdditionalVocableData data, ImmutualDate today) {
        ImmutualDate lastCorrectTestDate = getLastCorrectTestDate(data);
        return lastCorrectTestDate.difference(today);
    }

    private ImmutualDate getLastCorrectTestDate(InternalAdditionalVocableData data) {
        if (translationDirection == TranslationDirection.JAPANESE_TO_GERMAN) {
            return data.getLastCorrectJapaneseToGermanTestDate();
        }
        else if (translationDirection == TranslationDirection.GERMAN_TO_JAPANESE) {
            return data.getLastCorrectGermanToJapaneseTestDate();
        }
        else {
            throw new RuntimeException("Unbekannte Übersetzungsrichtung " + translationDirection);
        }
    }

    private List<Boolean> getLastTenTestResults(InternalAdditionalVocableData data) {
        if (translationDirection == TranslationDirection.JAPANESE_TO_GERMAN) {
            return data.getLastTenJapaneseToGermanTestResults();
        }
        else if (translationDirection == TranslationDirection.GERMAN_TO_JAPANESE) {
            return data.getLastTenGermanToJapaneseTestResults();
        }
        else {
            throw new RuntimeException("Unbekannte Übersetzungsrichtung " + translationDirection);
        }
    }

    private int getTestCount(InternalAdditionalVocableData data) {
        if (translationDirection == TranslationDirection.JAPANESE_TO_GERMAN) {
            return data.getJapaneseToGermanTestCount();
        }
        else if (translationDirection == TranslationDirection.GERMAN_TO_JAPANESE) {
            return data.getGermanToJapaneseTestCount();
        }
        else {
            throw new RuntimeException("Unbekannte Übersetzungsrichtung " + translationDirection);
        }
    }

    private void shuffleUnknown() {
        GuiTools.informUser("Unbekannte Art eine Vokabelliste zu sortieren", ""
                + "Die Art '" + type + "' die Vokabelliste zu sortieren ist unbekannt.\n\n"
                + "Daher wird die Vokabelliste rein zufällig sortiert.");
        shuffleRandom();
    }

    private void revers() {
        Collections.reverse(vocables);
    }

    /** Die Liste mit den sortierten Vokabeln. */
    public List<Vocable> getVocables() {
        return vocables;
    }

    private static void say(String message) {
        if (DEBUG) {
            Text.say(message);
        }
    }

}
