package de.duehl.vocabulary.japanese.statistics;

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.collections.CollectionsHelper;
import de.duehl.basics.text.NumberString;
import de.duehl.basics.text.html.generation.SwingHtmlBuilder;
import de.duehl.vocabulary.japanese.common.data.InternalAdditionalKanjiData;
import de.duehl.vocabulary.japanese.common.data.InternalAdditionalVocableData;
import de.duehl.vocabulary.japanese.common.data.TranslationDirection;
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.data.symbol.Kanji;
import de.duehl.vocabulary.japanese.logic.internal.InternalDataRequester;
import de.duehl.vocabulary.japanese.logic.success.VocabularyTestSuccesssCalculator;
import de.duehl.vocabulary.japanese.logic.symbol.kanji.internal.InternalKanjiDataRequester;
import de.duehl.vocabulary.japanese.statistics.data.KanjiKriterium;
import de.duehl.vocabulary.japanese.statistics.data.KanjiWithCounter;
import de.duehl.vocabulary.japanese.statistics.data.VocableCounterPicker;
import de.duehl.vocabulary.japanese.statistics.data.VocableListPicker;
import de.duehl.vocabulary.japanese.statistics.data.VocableWithCounter;
import de.duehl.vocabulary.japanese.tools.VocabularyTools;
import de.duehl.vocabulary.japanese.ui.components.text.KanjiAndKanaTextCreator;
import de.duehl.vocabulary.japanese.ui.data.FumikoUiObjects;

/**
 * Diese Klasse erstellt eine Statistik über die Vokabulare.
 *
 * @version 1.01     2025-12-12
 * @author Christian Dühl
 */

public class VocabularyTrainerStatisticCreator {

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

    /** Die Liste der Kategorien der Vokabularien. */
    private List<String> categories;

    /** Die Liste der Unterkategorien der Vokabularien. */
    private List<String> subCategories;

    /** Verzeichnis der Vokabulare nach der Kategorie. */
    private Map<String, List<Vocabulary>> vocabulariesByCategory;

    /** Hiermit wird die Statistik in HTML erzeugt. */
    private SwingHtmlBuilder html;

    /** Die Liste aller Vokabeln in allen Vokabularien. */
    private final List<Vocable> allVocables;

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

        allVocables = getAllVocablesOfAllVocabularies();
    }

    private List<Vocable> getAllVocablesOfAllVocabularies() {
        List<Vocabulary> vocabularies = dataStructures.getVocabularies();
        return getAllVocablesOfAllVocabularies(vocabularies);
    }

    private List<Vocable> getAllVocablesOfAllVocabularies(List<Vocabulary> vocabularies) {
        List<Vocable> allVocables = new ArrayList<>();
        for (Vocabulary vocabulary : vocabularies) {
            allVocables.addAll(vocabulary.getVocables());
        }
        return allVocables;
    }

    /** Erzeugt den Statistik-Report. */
    public String createStatisticReport() {
        createStatisticsInternal();
        return html.toString();
    }

    private void createStatisticsInternal() {
        initHtml();
        determineCategories();
        determineSubCategories();
        separateVocabulariesByCategory();
        createStatisticForAllVocables();
        createStatisticForAllCategories();
        createStatisticForAllCategoriesAndSubCategories();
        createStatisticForSearchWords();
        createStatisticForPartsOfSpeach();
        createStatisticForMostTestedEtc();
        createKanjiStatistic();
        finalizeHtml();
    }

    private void initHtml() {
        html = new SwingHtmlBuilder()
                .appendHtml5HeadWithOwnExtendedCssUtf8("Statstik", """
                        h2, h3 {
                            margin-bottom:12;
                            margin-top:25;
                        }""".indent(4))
                .appendTopLinksToH2();

        html.appendP("&nbsp;");
        html.appendH1("Statistik");
    }

    private void determineCategories() {
        List<Vocabulary> vocabularies = dataStructures.getVocabularies();
        categories = VocabularyTools.determineCategories(vocabularies);
    }

    private void determineSubCategories() {
        List<Vocabulary> vocabularies = dataStructures.getVocabularies();
        subCategories = VocabularyTools.determineSubCategories(vocabularies);
    }

    private void separateVocabulariesByCategory() {
        vocabulariesByCategory = new HashMap<>();

        List<Vocabulary> vocabularies = dataStructures.getVocabularies();
        for (Vocabulary vocabulary : vocabularies) {
            String category = vocabulary.getCategory();

            if (!vocabulariesByCategory.containsKey(category)) {
                vocabulariesByCategory.put(category, new ArrayList<>());
            }
            List<Vocabulary> list = vocabulariesByCategory.get(category);
            list.add(vocabulary);
        }
    }

    private void createStatisticForAllVocables() {
        html.appendH2("Alle Vokabeln");

        String numberOfCategories = NumberString.taupu(categories.size());
        String numberOfSubCategories = NumberString.taupu(subCategories.size());
        String numberOfVocables = NumberString.taupu(allVocables.size());
        String percent = calculatePercent(allVocables);
        String numberOfTenTimesCorrectVocablesJapaneseToGerman =
                calculateNumberOfTenTimesCorrectVocables(TranslationDirection.JAPANESE_TO_GERMAN);
        String numberOfTenTimesCorrectVocablesGermanToJapanese =
                calculateNumberOfTenTimesCorrectVocables(TranslationDirection.GERMAN_TO_JAPANESE);
        String numberOfPhrases = calculateNumberOfPhrases();

        html.appendOpeningTableWithBorderWidth(2);

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Anzahl Kategorien");
        html.appendRightAlignedTd(numberOfCategories);
        html.appendClosingTr();

        html.appendLeftAlignedTh("Anzahl Unterkategorien");
        html.appendRightAlignedTd(numberOfSubCategories);
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Anzahl Vokabeln");
        html.appendRightAlignedTd(numberOfVocables);
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Davon Phrasen");
        html.appendRightAlignedTd(numberOfPhrases);
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Prozent Erfolg der letzten Abfragen aller Vokabeln");
        html.appendRightAlignedTd(percent);
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Anzahl mindestens 10 Mal richtig übersetzter Vokabeln "
                + "(Japanisch - Deutsch)");
        html.appendRightAlignedTd(numberOfTenTimesCorrectVocablesJapaneseToGerman);
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Anzahl mindestens 10 Mal richtig übersetzter Vokabeln "
                + "(Deutsch - Japanisch)");
        html.appendRightAlignedTd(numberOfTenTimesCorrectVocablesGermanToJapanese);
        html.appendClosingTr();

        html.appendClosingTable();

        html.appendH3("Übersicht über die Kategorien");
        html.appendOpeningTableWithBorderWidth(2);

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Kategorie");
        html.appendRightAlignedTh("Anzahl Vokabulare");
        html.appendRightAlignedTh("Anzahl Vokabeln");
        html.appendRightAlignedTh("Prozent Erfolg der letzten Abfragen der Vokabeln");
        html.appendClosingTr();

        for (String category : categories) {
            createStatisticRowForCategory(category);
        }
        // TODO wie die Unterkategorie mit einbeziehen?

        html.appendClosingTable();
    }

    private String calculateNumberOfTenTimesCorrectVocables(TranslationDirection direction) {
        int count = 0;
        InternalDataRequester requester = dataStructures.getInternalDataRequester();

        for (Vocable vocable : allVocables) {
            InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
            int correctTranslations;
            if (direction == TranslationDirection.JAPANESE_TO_GERMAN) {
                correctTranslations = data.getCorrectJapaneseToGermanTestCount();
            }
            else if (direction == TranslationDirection.GERMAN_TO_JAPANESE) {
                correctTranslations = data.getCorrectGermanToJapaneseTestCount();
            }
            else {
                throw new RuntimeException("Unbekannte Übersetzungsrichtung " + direction + ".");
            }
            if (correctTranslations >= 10) {
                ++count;
            }
        }

        return NumberString.taupu(count);
    }

    private String calculateNumberOfPhrases() {
        int numberOfPhrases = 0;

        for (Vocable vocable : allVocables) {
            if (vocable.getPartsOfSpeech().contains("Phrase")) {
                ++numberOfPhrases;
            }
        }

        return NumberString.taupu(numberOfPhrases);
    }

    private void createStatisticRowForCategory(String category) {
        List<Vocabulary> vocabularies = vocabulariesByCategory.get(category);

        List<Vocable> allVocablesOfCategory = getAllVocablesOfAllVocabularies(vocabularies);

        String numberOfVocablulries = NumberString.taupu(vocabularies.size());
        String numberOfVocables = NumberString.taupu(allVocablesOfCategory.size());
        String percent = calculatePercent(allVocablesOfCategory);

        html.appendOpeningTr();
        html.appendTd(category);
        html.appendRightAlignedTd(numberOfVocablulries);
        html.appendRightAlignedTd(numberOfVocables);
        html.appendRightAlignedTd(percent);
        html.appendClosingTr();
    }

    private String calculatePercent(List<Vocable> vocables) {
        /*
         * TODO: Der beachtet die Übersetzungsrichtung aus den Optionen. Das ist vielleicht auch
         * noch nicht ganz sauber...
         */
        VocabularyTestSuccesssCalculator calculator = new VocabularyTestSuccesssCalculator(vocables,
                dataStructures);
        calculator.calculate();
        return calculator.getPercentTextWithTwoDigitsAfterComma();
    }

    private void createStatisticForAllCategories() {
        for (String category : categories) {
            createStatisticForCategory(category);
        }
    }

    private void createStatisticForCategory(String category) {
        html.appendH2("Kategorie " + category);

        List<Vocabulary> vocabularies = vocabulariesByCategory.get(category);

        List<Vocable> allVocablesOfCategory = getAllVocablesOfAllVocabularies(vocabularies);

        String numberOfVocablulries = NumberString.taupu(vocabularies.size());
        String numberOfVocables = NumberString.taupu(allVocablesOfCategory.size());
        String percent = calculatePercent(allVocablesOfCategory);

        html.appendOpeningTableWithBorderWidth(2);

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Anzahl Vokabulare");
        html.appendRightAlignedTd(numberOfVocablulries);
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Anzahl Vokabeln");
        html.appendRightAlignedTd(numberOfVocables);
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Prozent Erfolg der letzten Abfragen der Vokabeln");
        html.appendRightAlignedTd(percent);
        html.appendClosingTr();

        html.appendClosingTable();


        html.appendH3("Vokabulare");
        html.appendOpeningTableWithBorderWidth(2);

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Vokabular");
        html.appendRightAlignedTh("Anzahl Vokabeln");
        html.appendRightAlignedTh("Prozent Erfolg der letzten Abfragen der Vokabeln");
        html.appendClosingTr();

        for (Vocabulary vocabulary : vocabularies) {
            createStatisticRowForVocabulary(vocabulary);
        }

        html.appendClosingTable();
    }

    private void createStatisticRowForVocabulary(Vocabulary vocabulary) {
        List<Vocable> vocables = vocabulary.getVocables();

        String numberOfVocables = NumberString.taupu(vocables.size());
        String percent = calculatePercent(vocables);

        html.appendOpeningTr();
        html.appendTd(vocabulary.getDescription());
        html.appendRightAlignedTd(numberOfVocables);
        html.appendRightAlignedTd(percent);
        html.appendClosingTr();
    }

    private void createStatisticForAllCategoriesAndSubCategories() {
        for (String category : categories) {
            createStatisticForCategoryAndAllSubCategories(category);
        }
    }

    private void createStatisticForCategoryAndAllSubCategories(String category) {
        html.appendH2("Kategorie " + category + " mit Betrachtung der Unterkategorien");

        List<Vocabulary> vocabulariesOfCategory = vocabulariesByCategory.get(category);

        List<String> subCategoriesOfCategory = VocabularyTools.determineSubCategoriesOfCategory(
                vocabulariesOfCategory, category);

        for (String subCategory : subCategoriesOfCategory) {
            html.appendH3("Unterkategorie " + subCategory);

            List<Vocabulary> vocabulariesOfCategoryAndSubCategory =
                    determineVocabulariesOfCategoryAndSubCategory(category, subCategory);

            List<Vocable> allVocablesOfCategoryAndSubCategory =
                    getAllVocablesOfAllVocabularies(vocabulariesOfCategoryAndSubCategory);

            String numberOfVocablulries = NumberString.taupu(vocabulariesOfCategory.size());
            String numberOfVocables = NumberString.taupu(allVocablesOfCategoryAndSubCategory.size());
            String percent = calculatePercent(allVocablesOfCategoryAndSubCategory);

            html.appendOpeningTableWithBorderWidth(2);

            html.appendOpeningTr();
            html.appendLeftAlignedTh("Anzahl Vokabulare");
            html.appendRightAlignedTd(numberOfVocablulries);
            html.appendClosingTr();

            html.appendOpeningTr();
            html.appendLeftAlignedTh("Anzahl Vokabeln");
            html.appendRightAlignedTd(numberOfVocables);
            html.appendClosingTr();

            html.appendOpeningTr();
            html.appendLeftAlignedTh("Prozent Erfolg der letzten Abfragen der Vokabeln");
            html.appendRightAlignedTd(percent);
            html.appendClosingTr();

            html.appendClosingTable();


            html.appendH4("Vokabulare");
            html.appendOpeningTableWithBorderWidth(2);

            html.appendOpeningTr();
            html.appendLeftAlignedTh("Vokabular");
            html.appendRightAlignedTh("Anzahl Vokabeln");
            html.appendRightAlignedTh("Prozent Erfolg der letzten Abfragen der Vokabeln");
            html.appendClosingTr();

            for (Vocabulary vocabulary : vocabulariesOfCategory) {
                createStatisticRowForVocabulary(vocabulary);
            }

            html.appendClosingTable();
        }
    }

    private List<Vocabulary> determineVocabulariesOfCategoryAndSubCategory(String category,
            String subCategory) {
        List<Vocabulary> vocabulariesOfCategoryAndSubCategory = new ArrayList<>();

        List<Vocabulary> vocabularies = dataStructures.getVocabularies();
        for (Vocabulary vocabulary : vocabularies) {
            String vocabularyCategory = vocabulary.getCategory();
            String vocabularySubCategory = vocabulary.getSubCategory();
            if (vocabularyCategory.equals(category) && vocabularySubCategory.equals(subCategory)) {
                vocabulariesOfCategoryAndSubCategory.add(vocabulary);
            }
        }

        return vocabulariesOfCategoryAndSubCategory;
    }

    private void createStatisticForSearchWords() {
        html.appendH2("Suchbegriffe");

        Map<String, Integer> countBySearchWord = createCountByWordMap(
                vocable -> vocable.getSearchWords());

        int countMissing = countMissing(vocable -> vocable.getSearchWords());

        createCountByWordHtml("Suchbegriff", countBySearchWord, countMissing);
    }

    private void createStatisticForPartsOfSpeach() {
        html.appendH2("Wortarten");

        Map<String, Integer> countByPartOfSpeach = createCountByWordMap(
                vocable -> vocable.getPartsOfSpeech());

        int countMissing = countMissing(vocable -> vocable.getPartsOfSpeech());

        createCountByWordHtml("Wortart", countByPartOfSpeach, countMissing);
    }

    private Map<String, Integer> createCountByWordMap(VocableListPicker picker) {
        Map<String, Integer> countByWord = new HashMap<>();

        for (Vocable vocable : allVocables) {
            List<String> words = picker.pickWordList(vocable);
            for (String word : words) {
                if (!word.isBlank()) {
                    if (!countByWord.containsKey(word)) {
                        countByWord.put(word, 0);
                    }
                    int count = countByWord.get(word);
                    ++count;
                    countByWord.put(word, count);
                }
            }
        }

        return countByWord;
    }

    private int countMissing(VocableListPicker picker) {
        int count = 0;

        for (Vocable vocable : allVocables) {
            List<String> words = picker.pickWordList(vocable);
            if (words.isEmpty()) {
                ++count;
            }
        }

        return count;
    }

    private void createCountByWordHtml(String wordDescription,
            Map<String, Integer> countBySearchWord, int countMissing) {
        html.appendOpeningTableWithBorderWidth(2);

        html.appendOpeningTr();
        html.appendLeftAlignedTh(wordDescription);
        html.appendRightAlignedTd("Anzahl");
        html.appendClosingTr();


        for (String word : CollectionsHelper.getSortedMapStringIndices(countBySearchWord)) {
            html.appendOpeningTr();
            html.appendTd(word);
            html.appendRightAlignedTd(NumberString.taupu(countBySearchWord.get(word)));
            html.appendClosingTr();
        }

        html.appendClosingTable();

        html.appendP("Vokabeln ohne " + wordDescription + ": " + NumberString.taupu(countMissing)
                + " (" + NumberString.percent(countMissing, allVocables .size()) + "%)");
    }

    private static final int NUMBER_OF_OFTEN_TESTED_VOCABLES = 20;

    private void createStatisticForMostTestedEtc() {
        html.appendH2("Häufig abgefragte Vokabeln");

        createCountTestSum();

        createStatisticForOftenUsedVocables(
                determineOftenJapaneseToGermanTestedVocables(),
                "Am häufigsten abgefragte Vokabeln Japanisch-Deutsch");
        createStatisticForOftenUsedVocables( // TODO das vielleicht später weglassen
                determineRarelyJapaneseToGermanTestedVocables(),
                "Am seltesten abgefragte Vokabeln Japanisch-Deutsch");
        createStatisticForOftenUsedVocables(
                determineOftenCorrectJapaneseToGermanTestedVocables(),
                "Am häufigsten richtig abgefragte Vokabeln Japanisch-Deutsch");
        createStatisticForOftenUsedVocables(
                determineRarelyCorrectJapaneseToGermanTestedVocables(),
                "Am seltensten richtig abgefragte Vokabeln Japanisch-Deutsch");
        createStatisticForOftenUsedVocables(
                determineOftenWrongJapaneseToGermanTestedVocables(),
                "Am häufigsten falsch abgefragte Vokabeln Japanisch-Deutsch");
        createStatisticForOftenUsedVocables(
                determineRarelyWrongJapaneseToGermanTestedVocables(),
                "Am seltensten falsch abgefragte Vokabeln Japanisch-Deutsch");

        createStatisticForOftenUsedVocables(
                determineOftenGermanToJapaneseTestedVocables(),
                "Am häufigsten abgefragte Vokabeln Deutsch-Japanisch");
        createStatisticForOftenUsedVocables( // TODO das vielleicht später weglassen
                determineRarelyGermanToJapaneseTestedVocables(),
                "Am seltesten abgefragte Vokabeln Deutsch-Japanisch");
        createStatisticForOftenUsedVocables(
                determineOftenCorrectGermanToJapaneseTestedVocables(),
                "Am häufigsten richtig abgefragte Vokabeln Deutsch-Japanisch");
        createStatisticForOftenUsedVocables(
                determineRarelyCorrectGermanToJapaneseTestedVocables(),
                "Am seltensten richtig abgefragte Vokabeln Deutsch-Japanisch");
        createStatisticForOftenUsedVocables(
                determineOftenWrongGermanToJapaneseTestedVocables(),
                "Am häufigsten falsch abgefragte Vokabeln Deutsch-Japanisch");
        createStatisticForOftenUsedVocables(
                determineRarelyWrongGermanToJapaneseTestedVocables(),
                "Am seltensten falsch abgefragte Vokabeln Deutsch-Japanisch");
    }

    private void createCountTestSum() {
        int japaneseToGermanSum = 0;
        int germanToJapaneseSum = 0;
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        for (Vocable vocable : allVocables) {
            InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
            japaneseToGermanSum += data.getJapaneseToGermanTestCount();
            germanToJapaneseSum += data.getGermanToJapaneseTestCount();
        }

        html.appendH3("Wie oft wurden Vokabeln abgefragt");
        html.appendP("Es wurde " + NumberString.taupu(japaneseToGermanSum)
                + " mal eine einzelne Vokabel von Japanisch nach Deutsch abgefragt.");
        html.appendP("Es wurde " + NumberString.taupu(germanToJapaneseSum)
                + " mal eine einzelne Vokabel von Deutsch nach Japanisch abgefragt.");
        html.appendP("Summe: Es wurde "
                + NumberString.taupu(japaneseToGermanSum + germanToJapaneseSum)
                + " mal eine einzelne Vokabel abgefragt.");
    }

    private List<VocableWithCounter> determineOftenJapaneseToGermanTestedVocables() {
        return determineOftenTestedVocablesHighest(
                vocable -> howOftenJapaneseToGermanTested(vocable));
    }

    private List<VocableWithCounter> determineRarelyJapaneseToGermanTestedVocables() {
        return determineOftenTestedVocablesLowest(
                vocable -> howOftenJapaneseToGermanTested(vocable));
    }

    private int howOftenJapaneseToGermanTested(Vocable vocable) {
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
        return data.getJapaneseToGermanTestCount();
    }

    private List<VocableWithCounter> determineOftenCorrectJapaneseToGermanTestedVocables() {
        return determineOftenTestedVocablesHighest(
                vocable -> howOftenCorrectJapaneseToGermanTested(vocable));
    }

    private List<VocableWithCounter> determineRarelyCorrectJapaneseToGermanTestedVocables() {
        return determineOftenTestedVocablesLowest(
                vocable -> howOftenCorrectJapaneseToGermanTested(vocable));
    }

    private int howOftenCorrectJapaneseToGermanTested(Vocable vocable) {
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
        return data.getCorrectJapaneseToGermanTestCount();
    }

    private List<VocableWithCounter> determineOftenWrongJapaneseToGermanTestedVocables() {
        return determineOftenTestedVocablesHighest(
                vocable -> howOftenWrongJapaneseToGermanTested(vocable));
    }

    private List<VocableWithCounter> determineRarelyWrongJapaneseToGermanTestedVocables() {
        return determineOftenTestedVocablesLowest(
                vocable -> howOftenWrongJapaneseToGermanTested(vocable));
    }

    private int howOftenWrongJapaneseToGermanTested(Vocable vocable) {
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
        return data.getJapaneseToGermanTestCount() - data.getCorrectJapaneseToGermanTestCount();
    }

    private List<VocableWithCounter> determineOftenGermanToJapaneseTestedVocables() {
        return determineOftenTestedVocablesHighest(
                vocable -> howOftenGermanToJapaneseTested(vocable));
    }

    private List<VocableWithCounter> determineRarelyGermanToJapaneseTestedVocables() {
        return determineOftenTestedVocablesLowest(
                vocable -> howOftenGermanToJapaneseTested(vocable));
    }

    private int howOftenGermanToJapaneseTested(Vocable vocable) {
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
        return data.getGermanToJapaneseTestCount();
    }

    private List<VocableWithCounter> determineOftenCorrectGermanToJapaneseTestedVocables() {
        return determineOftenTestedVocablesHighest(
                vocable -> howOftenCorrectGermanToJapaneseTested(vocable));
    }

    private List<VocableWithCounter> determineRarelyCorrectGermanToJapaneseTestedVocables() {
        return determineOftenTestedVocablesLowest(
                vocable -> howOftenCorrectGermanToJapaneseTested(vocable));
    }

    private int howOftenCorrectGermanToJapaneseTested(Vocable vocable) {
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
        return data.getCorrectGermanToJapaneseTestCount();
    }

    private List<VocableWithCounter> determineOftenWrongGermanToJapaneseTestedVocables() {
        return determineOftenTestedVocablesHighest(
                vocable -> howOftenWrongGermanToJapaneseTested(vocable));
    }

    private List<VocableWithCounter> determineRarelyWrongGermanToJapaneseTestedVocables() {
        return determineOftenTestedVocablesLowest(
                vocable -> howOftenWrongGermanToJapaneseTested(vocable));
    }

    private int howOftenWrongGermanToJapaneseTested(Vocable vocable) {
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
        return data.getGermanToJapaneseTestCount() - data.getCorrectGermanToJapaneseTestCount();
    }

    private List<VocableWithCounter> determineOftenTestedVocablesHighest(
            VocableCounterPicker picker) {
        return determineOftenTestedVocables(picker, true);
    }

    private List<VocableWithCounter> determineOftenTestedVocablesLowest(
            VocableCounterPicker picker) {
        return determineOftenTestedVocables(picker, false);
    }

    private List<VocableWithCounter> determineOftenTestedVocables(VocableCounterPicker picker,
            boolean highest) {
        List<VocableWithCounter> vocablesWithCounter = new ArrayList<>();

        for (Vocable vocable : allVocables) {
            int count = picker.pickCounter(vocable);
            VocableWithCounter vocableWithCounter = new VocableWithCounter(vocable, count);
            vocablesWithCounter.add(vocableWithCounter);
        }

        Collections.sort(vocablesWithCounter, new Comparator<VocableWithCounter>() {
            @Override
            public int compare(VocableWithCounter v1, VocableWithCounter v2) {
                int count1 = v1.getCount();
                int count2 = v2.getCount();

                if (highest) {
                    return count2 - count1;
                }
                else {
                    return count1 - count2;
                }
            }
        });

        int maxIndex = Math.min(NUMBER_OF_OFTEN_TESTED_VOCABLES, vocablesWithCounter.size());
        vocablesWithCounter = CollectionsHelper.sublist(vocablesWithCounter, 0, maxIndex);

        return vocablesWithCounter;
    }

    private void createStatisticForOftenUsedVocables(List<VocableWithCounter> vocablesWithCounter,
            String titel) {
        html.appendH3(titel);
        html.appendOpeningTableWithBorderWidth(2);

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Vokabel");
        html.appendRightAlignedTh("Anzahl");
        html.appendClosingTr();

        for (VocableWithCounter vocableWithCounter : vocablesWithCounter) {
            Vocable vocable = vocableWithCounter.getVocable();
            int count = vocableWithCounter.getCount();
            String vocableText = createVocableText(vocable);
            html.appendOpeningTr();
            html.appendTd(vocableText);
            html.appendRightAlignedTd(NumberString.taupu(count));
            html.appendClosingTr();
        }

        html.appendClosingTable();
    }

    private String createVocableText(Vocable vocable) {
        String romaji = vocable.getRomaji();
        String translation = vocable.getTranslations().get(0);

        boolean showKana = true;
        boolean showKanji = true;
        KanjiAndKanaTextCreator creator = new KanjiAndKanaTextCreator(vocable, showKana, showKanji);
        creator.create();
        String firstTextPart = creator.getFirstTextPart();
        String textPartInBrace = creator.getTextPartInBrace();
        boolean braceInOwnLine = creator.isBraceInOwnLine();

        StringBuilder builder = new StringBuilder();
        builder.append(firstTextPart);

        if (braceInOwnLine) {
            builder.append("<br>");
        }
        if (!textPartInBrace.isBlank()) {
            builder.append(" (");
            builder.append(textPartInBrace);
            builder.append(")");
        }

        if (braceInOwnLine) {
            builder.append("<br>");
        }
        else {
            builder.append(" - ");
        }

        builder.append(romaji);

        if (braceInOwnLine) {
            builder.append("<br>");
        }
        else {
            builder.append(" - ");
        }

        builder.append(translation);

        return builder.toString();
    }

    private void createKanjiStatistic() {
        List<Kanji> kanjiList = Kanji.getAllKanjiAsList();

        html.appendH2("Kanji");

        createKanjiTestsOverview(kanjiList);
        createLastTenKanjiTestsStatistic(kanjiList);

        createStatisticForKanji(determineKanjiWithKriteriumHighestValue(
                kanjiList, 10, kanji -> getTestCount(kanji)),
                "Die am häufigsten abgefragten Kanji");

        createStatisticForKanji(determineKanjiWithKriteriumHighestValue(
                kanjiList, 10, kanji -> getCorrectTestCount(kanji)),
                "Die am häufigsten richtig abgefragten Kanji");

        createStatisticForKanji(determineKanjiWithKriteriumLowestValue(
                kanjiList, 10, kanji -> lastTenCorrectTestedCount(kanji)),
                "Die die letzten zehn Male am schlechtesten abgefragten Kanji");

        createStatisticForKanji(determineKanjiWithKriteriumHighestValue(
                kanjiList, 10, kanji -> getRatioCorrectAgainstTestCount(kanji), true),
                "Kanji mit dem besten Verhältnis von Anzahl richtig zu Anzahl abgefragt");

        createStatisticForKanji(determineKanjiWithKriteriumLowestValue(
                kanjiList, 10, kanji -> getRatioCorrectAgainstTestCount(kanji), true),
                "Kanji mit dem schlechtesten Verhältnis von Anzahl richtig zu Anzahl abgefragt");

        /*
         * In den Gruppen jeweils sortiert nach Anzahl abgefragt?
         */
    }

    private void createKanjiTestsOverview(List<Kanji> kanjiList) {
        html.appendP(
                "Es gibt im Vokabeltrainer " + NumberString.taupu(kanjiList.size()) + " Kanji.");

        int tested = 0;
        int correctTested = 0;
        InternalKanjiDataRequester requester = dataStructures.getInternalKanjiDataRequester();
        for (Kanji kanji : kanjiList) {
            InternalAdditionalKanjiData data = requester.getInternalDataForKanji(kanji);
            tested += data.getTestCount();
            correctTested += data.getCorrectTestCount();
        }
        html.appendP("Insgesamt waren es " + NumberString.taupu(tested) + " Abfragen von Kanji.");
        html.appendP("Dabei gab es " + NumberString.taupu(correctTested)
                + " richtig beantwortete Abfragen von Kanji ("
                + NumberString.percent(correctTested, tested)
                + " Prozent aller Abfragen war richtig).");
    }

    private void createLastTenKanjiTestsStatistic(List<Kanji> kanjiList) {
        html.appendH3("Erfolg in den letzten zehn Abfragen");
        int tested = 0;
        int correctTested = 0;
        InternalKanjiDataRequester requester = dataStructures.getInternalKanjiDataRequester();
        for (Kanji kanji : kanjiList) {
            InternalAdditionalKanjiData data = requester.getInternalDataForKanji(kanji);
            tested += data.getLastTenTestsCount();
            correctTested += data.getLastCorrectTestsCount();
        }
        html.appendP(NumberString.percent(correctTested, tested)
                + " Prozent aller letzten zehn Abfragen war richtig.");
    }

    private int getTestCount(Kanji kanji) {
        InternalKanjiDataRequester requester = dataStructures.getInternalKanjiDataRequester();
        InternalAdditionalKanjiData data = requester.getInternalDataForKanji(kanji);
        return data.getTestCount();
    }

    private int getCorrectTestCount(Kanji kanji) {
        InternalKanjiDataRequester requester = dataStructures.getInternalKanjiDataRequester();
        InternalAdditionalKanjiData data = requester.getInternalDataForKanji(kanji);
        return data.getCorrectTestCount();
    }

    private int lastTenCorrectTestedCount(Kanji kanji) {
        InternalKanjiDataRequester requester = dataStructures.getInternalKanjiDataRequester();
        InternalAdditionalKanjiData data = requester.getInternalDataForKanji(kanji);
        return data.getLastCorrectTestsCount();
    }

    private double getRatioCorrectAgainstTestCount(Kanji kanji) {
        InternalKanjiDataRequester requester = dataStructures.getInternalKanjiDataRequester();
        InternalAdditionalKanjiData data = requester.getInternalDataForKanji(kanji);
        double correct = (double) data.getCorrectTestCount();
        double total = (double) data.getTestCount();
        return correct / total;
    }

    private List<KanjiWithCounter> determineKanjiWithKriteriumHighestValue(List<Kanji> kanjiList,
            int numberOfWanted, KanjiKriterium kriterium) {
        boolean highestValue = true;
        boolean wantsDouble = false;
        return determineKanjiWithKriteriumHighestValue(kanjiList, numberOfWanted, kriterium,
                highestValue, wantsDouble);
    }

    private List<KanjiWithCounter> determineKanjiWithKriteriumLowestValue(List<Kanji> kanjiList,
            int numberOfWanted, KanjiKriterium kriterium) {
        boolean highestValue = false;
        boolean wantsDouble = false;
        return determineKanjiWithKriteriumHighestValue(kanjiList, numberOfWanted, kriterium,
                highestValue, wantsDouble);
    }

    private List<KanjiWithCounter> determineKanjiWithKriteriumHighestValue(List<Kanji> kanjiList,
            int numberOfWanted, KanjiKriterium kriterium, boolean wantsDouble) {
        boolean highestValue = true;
        return determineKanjiWithKriteriumHighestValue(kanjiList, numberOfWanted, kriterium,
                highestValue, wantsDouble);
    }

    private List<KanjiWithCounter> determineKanjiWithKriteriumLowestValue(List<Kanji> kanjiList,
            int numberOfWanted, KanjiKriterium kriterium, boolean wantsDouble) {
        boolean highestValue = false;
        return determineKanjiWithKriteriumHighestValue(kanjiList, numberOfWanted, kriterium,
                highestValue, wantsDouble);
    }

    private List<KanjiWithCounter> determineKanjiWithKriteriumHighestValue(List<Kanji> kanjiList,
            int numberOfWanted, KanjiKriterium kriterium, boolean highestValue, boolean wantsDouble) {
        List<Kanji> sortedKanjiList = CollectionsHelper.copyList(kanjiList);


        Collections.sort(sortedKanjiList, new Comparator<Kanji>() {
            @Override
            public int compare(Kanji k1, Kanji k2) {
                double n1 = kriterium.createKanjiKriterium(k1);
                double n2 = kriterium.createKanjiKriterium(k2);

                if (n2 > n1) {
                    if (highestValue) {
                        return 1;
                    }
                    else {
                        return -1;
                    }
                }
                else if (n1 > n2) {
                    if (highestValue) {
                        return -1;
                    }
                    else {
                        return 1;
                    }
                }
                else {
                    return 0;
                }
            }
        });

        int lastNumber = Math.min(sortedKanjiList.size(), numberOfWanted);

        List<KanjiWithCounter> mostTestedKanji = new ArrayList<>();
        for (int index = 0; index < lastNumber; ++index) {
            Kanji kanji = sortedKanjiList.get(index);
            double counter = kriterium.createKanjiKriterium(kanji);
            KanjiWithCounter kanjiWithCounter = new KanjiWithCounter(kanji, counter, wantsDouble);
            mostTestedKanji.add(kanjiWithCounter);
        }

        return mostTestedKanji;
    }

    private void createStatisticForKanji(List<KanjiWithCounter> kanjiWithCounterList,
            String titel) {
        html.appendH3(titel);
        html.appendOpeningTableWithBorderWidth(2);

        String headerComlunTwo;
        if (kanjiWithCounterList.get(0).isWantsDouble()) {
            headerComlunTwo = "Prozent";
        }
        else {
            headerComlunTwo = "Anzahl";
        }
        html.appendOpeningTr();
        html.appendLeftAlignedTh("Kanji");
        html.appendRightAlignedTh(headerComlunTwo);
        html.appendClosingTr();

        for (KanjiWithCounter kanjiWithCounter : kanjiWithCounterList) {
            Kanji kanji = kanjiWithCounter.getKanji();
            double count = kanjiWithCounter.getCount();
            String number;
            if (kanjiWithCounter.isWantsDouble()) {
                number = NumberString.twoDecimalPlaces(100 * count);
            }
            else {
                int countAsInt = (int) count;
                number = NumberString.taupu(countAsInt);
            }
            String text = "<span style=\"font-size: 2.5em;\">" + kanji.getCharacter() + "</span>";
            html.appendOpeningTr();
            html.appendTd(text);
            html.appendRightAlignedTd(number);
            html.appendClosingTr();
        }

        html.appendClosingTable();
    }

    private void finalizeHtml() {
        html.appendFoot();
        html.insertContentWithTopLinkAnker();
    }

}
