package de.duehl.vocabulary.japanese.common.color;

import java.awt.Color;
import java.util.List;

import de.duehl.swing.ui.colors.ColorTool;
import de.duehl.vocabulary.japanese.common.color.data.ColorModificationType;
import de.duehl.vocabulary.japanese.common.color.data.TotalAndCorrectTotalCount;
import de.duehl.vocabulary.japanese.common.data.InternalAdditionalKanaData;
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.common.persistence.Options;

/**
 * Diese Klasse erzeugt die passenden Farben zur Darstellung im den Vokabeltrainer.
 *
 * Es gibt elf Farben, diese beziehen sich auf die elf Möglichkeiten, wie man die letzten zehn
 * Abfragen beantwortet haben kann:
 *
 *      1. Keine Abfrage richtig beantwortet, zehn Abfragen falsch beantwortet.
 *      2. Eine Abfrage richtig beantwortet, neun Abfragen falsch beantwortet.
 *      3. Zwei Abfragen richtig beantwortet, acht Abfragen falsch beantwortet.
 *      4. Drei Abfragen richtig beantwortet, sieben Abfragen falsch beantwortet.
 *      5. Vier Abfragen richtig beantwortet, sechs Abfragen falsch beantwortet.
 *      6. Fünf Abfragen richtig beantwortet, fünf Abfragen falsch beantwortet.
 *      7. Sechs Abfragen richtig beantwortet, vier Abfragen falsch beantwortet.
 *      8. Sieben Abfragen richtig beantwortet, drei Abfragen falsch beantwortet.
 *      9. Acht Abfragen richtig beantwortet, zwei Abfragen falsch beantwortet.
 *     10. Neun Abfragen richtig beantwortet, eine Abfrage falsch beantwortet.
 *     11. Zehn Abfragen richtig beantwortet, keine Abfrage falsch beantwortet.
 *
 * Die Farben gehen von rot (schlecht) über gelb (mittel) bis grün (gut).
 *
 * Natürlich funktioniert das auch, wenn eine Vokabel weniger als 10 Mal abgefragt wurde oder
 * ganz andere Werte für die Anzahl aller Abfragen (total) und die Anzahl der richtig beantworteten
 * Abfragen (correct) übergeben werden.
 *
 * @version 1.01     2025-02-02
 * @author Christian Dühl
 */

public class VocableColors {

    /** Der Default-Wert um den die Farben für eine Vordergrundfarbe abgedunkelt werden. */
    public static final int DEFAULT_DELTA_FOR_FOREGROUND_COLOR = -110; // -75 / -85

    /** Der  Default-Wert um den die Farben für eine Hintergrundfarbe aufgehellt werden. */
    public static final int DEFAULT_DELTA_FOR_BACKGROUND_COLOR = 180; // 150

    /**
     * Die Anzahl der unterschiedlichen Farben. Nämlich elf Stück für die folgenden Fälle:
     *
     *      1. Keine Abfrage richtig beantwortet, zehn Abfragen falsch beantwortet.
     *      2. Eine Abfrage richtig beantwortet, neun Abfragen falsch beantwortet.
     *      3. Zwei Abfragen richtig beantwortet, acht Abfragen falsch beantwortet.
     *      4. Drei Abfragen richtig beantwortet, sieben Abfragen falsch beantwortet.
     *      5. Vier Abfragen richtig beantwortet, sechs Abfragen falsch beantwortet.
     *      6. Fünf Abfragen richtig beantwortet, fünf Abfragen falsch beantwortet.
     *      7. Sechs Abfragen richtig beantwortet, vier Abfragen falsch beantwortet.
     *      8. Sieben Abfragen richtig beantwortet, drei Abfragen falsch beantwortet.
     *      9. Acht Abfragen richtig beantwortet, zwei Abfragen falsch beantwortet.
     *     10. Neun Abfragen richtig beantwortet, eine Abfrage falsch beantwortet.
     *     11. Zehn Abfragen richtig beantwortet, keine Abfrage falsch beantwortet.
     */
    public static final int NUMBER_OF_COLORS = 11;

    private static final int FAILURE_COLOR_INDEX = 0;
    private static final int SUCCESS_COLOR_INDEX = NUMBER_OF_COLORS - 1;

    /** Die elf Farben. */
    private final List<Color> colors;

    /** Der Wert um den die Farben für eine Vordergrundfarbe abgedunkelt werden. */
    private final int deltaForForegroundColor;

    /** Der Wert um den die Farben für eine Hintergrundfarbe aufgehellt werden. */
    private final int deltaForBackgroundColor;

    /**
     * Die Art wie Farben für die Erzeugung von Vorder- bzw. Hintergrundfarben modifiziert werden.
     */
    private final ColorModificationType colorModificationType;

    /**
     * Konstruktor mit den Parametern aus den Optionen wie z.B. den Farben.
     *
     * @param options
     *            Die Programmoptionen.
     */
    public VocableColors(Options options) {
        this(options.getColors(), options.getDeltaForForegroundColor(),
                options.getDeltaForBackgroundColor(), options.getColorModificationType());
    }

    /**
     * Konstruktor mit eigenen Einstellungen.
     *
     * @param colors
     *            Die elf Farben.
     * @param deltaForForegroundColor
     *            Der Wert um den die Farben für eine Vordergrundfarbe abgedunkelt werden.
     * @param deltaForBackgroundColor
     *            Der Wert um den die Farben für eine Hintergrundfarbe aufgehellt werden.
     * @param colorModificationType
     *            Die Art wie Farben für die Erzeugung von Vorder- bzw. Hintergrundfarben
     *            modifiziert werden.
     */
    VocableColors(List<Color> colors, int deltaForForegroundColor,
            int deltaForBackgroundColor, ColorModificationType colorModificationType) {
        this.colors = colors;
        this.deltaForForegroundColor = deltaForForegroundColor;
        this.deltaForBackgroundColor = deltaForBackgroundColor;
        this.colorModificationType = colorModificationType;

        checkNumberOfColors();
    }

    private void checkNumberOfColors() {
        if (colors.size() != NUMBER_OF_COLORS) {
            throw new RuntimeException("Es gibt nicht wie erwartet " + NUMBER_OF_COLORS
                    + " Farben, sondern " + colors.size() + ".");
        }
    }

    /**
     * Ermittelt die Hintergrundfarbe um eine Vokabel anzuzeigen abhängig vom Erfolg der letzten
     * Abfragen.
     *
     * @param data
     *            Die ergänzenden, benutzerabhängigen Daten zu einer Vokabel.
     * @param colorVocableDependingOnLastSuccess
     *            Gibt an, ob die Hintergrundfarbe abhängig vom Erfolg der letzten Abfragen sein
     *            soll. Anderenfalls wird der Hintergrund einfach weiß.
     * @param translationDirection
     *            Die Richtung, in der übersetzt wird.
     * @return Die Hintergrundfarbe.
     */
    public Color determineBackgroundColor(InternalAdditionalVocableData data,
            boolean colorVocableDependingOnLastSuccess, TranslationDirection translationDirection) {
        if (colorVocableDependingOnLastSuccess) {
            TotalAndCorrectTotalCount totalAndCorrect =
                    createTotalAndCorrectCountOfLastTests(data, translationDirection);
            return determineBackgroundColor(totalAndCorrect);
        }
        else {
            return Color.WHITE;
        }
    }

    /**
     * Ermittelt die Vordergrundfarbe um eine Vokabel anzuzeigen abhängig vom Erfolg der letzten
     * Abfragen.
     *
     * @param data
     *            Die ergänzenden, benutzerabhängigen Daten zu einer Vokabel.
     * @param colorVocableDependingOnLastSuccess
     *            Gibt an, ob die Vordergrundfarbe abhängig vom Erfolg der letzten Abfragen sein
     *            soll. Anderenfalls wird der Vordergrund einfach schwarz.
     * @param translationDirection
     *            Die Richtung, in der übersetzt wird.
     * @return Die Vordergrundfarbe.
     */
    public Color determineForegroundColor(InternalAdditionalVocableData data,
            boolean colorVocableDependingOnLastSuccess, TranslationDirection translationDirection) {
        if (colorVocableDependingOnLastSuccess)  {
            TotalAndCorrectTotalCount totalAndCorrect =
                    createTotalAndCorrectCountOfLastTests(data, translationDirection);
            return determineForegroundColor(totalAndCorrect);
        }
        else {
            return Color.BLACK;
        }
    }

    private static TotalAndCorrectTotalCount createTotalAndCorrectCountOfLastTests(
            InternalAdditionalVocableData data, TranslationDirection translationDirection) {
        int total;
        int correct;

        if (translationDirection == TranslationDirection.JAPANESE_TO_GERMAN) {
            total = data.getLastTenJapaneseToGermanTestsCount();
            correct = data.getLastCorrectJapaneseToGermanTestsCount();
        }
        else if (translationDirection == TranslationDirection.GERMAN_TO_JAPANESE) {
            total = data.getLastGermanToJapaneseTestsCount();
            correct = data.getLastCorrectGermanToJapaneseTestsCount();
        }
        else {
            throw new RuntimeException(
                    "Unbekannte Übersetzungsrichgtung " + translationDirection);
        }

        return new TotalAndCorrectTotalCount(total, correct);
    }

    private Color determineForegroundColor(TotalAndCorrectTotalCount totalAndCorrect) {
        int total = totalAndCorrect.getTotal();
        int correct = totalAndCorrect.getCorrect();
        return determineForegroundColor(total, correct);
    }

    private Color determineBackgroundColor(TotalAndCorrectTotalCount totalAndCorrect) {
        int total = totalAndCorrect.getTotal();
        int correct = totalAndCorrect.getCorrect();
        return determineBackgroundColor(total, correct);
    }

    /**
     * Ermittelt die Vordergrundfarbe um ein Vokabular abhängig vom Erfolg der letzten Abfragen
     * anzuzeigen.
     *
     * Anders als bei der einzelnen Vokabel, bei der eine unbekannte einen weißen Hintergrund
     * haben soll, färben wir hier nicht abgefragte Mengen rot ein. Davor wurde für total 0 die
     * Farbe Color.BLACK zurückgegeben.
     *
     * @param total
     *            Die Anzahl der letzten Abfragen.
     * @param correct
     *            Die Anzahl der richtig beantworteten letzten Abfragen.
     * @return Die Hintergrundfarbe.
     */
    public Color determineForegroundColor(int total, int correct) {
        int colorIndex = determineColorIndex(total, correct);
        return createForegroundColor(colorIndex);
    }

    /**
     * Ermittelt die Hintergrundfarbe um eine Vokabel oder einen Erfolg anzuzeigen abhängig vom
     * Erfolg der letzten Abfragen anzuzeigen.
     *
     * @param total
     *            Die Anzahl der letzten Abfragen.
     * @param correct
     *            Die Anzahl der richtig beantworteten letzten Abfragen.
     * @return Die Hintergrundfarbe.
     */
    public Color determineBackgroundColor(int total, int correct) {
        if (total == 0) {
            return Color.WHITE;
        }
        else {
            int colorIndex = determineColorIndex(total, correct);
            return createBackgroundColor(colorIndex);
        }
    }

    /**
     * Diese Methode bestimmt aus den Anzahlen aller und der erfolgreich beantworteter Abfragen den
     * Index der passenden Farbe.
     *
     * Die Anzahlen beziehen sich in der Regel auf die letzten (bis zu zehn) abgefragten Tests.
     *
     * Es gibt daher elf verschiedene Farb-Werte und somit auch elf verschiedene Indices für die
     * Ausprägungen 0/10, 1/10, ... 9/10 und 10/10 richtig beantworteter Abfragen
     *
     * @param total
     *            Die Anzahl aller Abfragen.
     * @param correct
     *            Die Anzahl der richtig beantworteten Abfragen.
     * @return Ein Index zwischen Einschließlich 0 und 11.
     */
    static int determineColorIndex(int total, int correct) {
        if (total == 0 || correct == 0)  {
            return FAILURE_COLOR_INDEX;
        }
        else if (total == correct) {
            return SUCCESS_COLOR_INDEX;
        }
        else {
            double percent = (100d * correct) / total;
            return 1 + (int) (percent / NUMBER_OF_COLORS);
            /*
             * Es wird hier 1 addiert, weil wir elf Werte haben, deren erster für "alles falsch"
             * steht und die zehn Bereiche der zehn-Prozent-Streifen für die anderen zehn
             * Ergebnisse. Daher muss man 1 addieren, da wir hier mindestens eine richtige
             * Antwort vorliegen haben und so zumindest im ersten Streifen sind.
             */
        }
    }

    Color createForegroundColor(int index) {
        Color color = colors.get(index);
        color = createForegroundColor(color);
        return color;
    }

    Color createBackgroundColor(int index) {
        Color color = colors.get(index);
        color = createBackgroundColor(color);
        return color;
    }

    /** Erzeugt aus einer normalen Farbe eine etwas dunklere Farbe als Vordergrundfarbe. */
    public Color createForegroundColor(Color color) {
        return modifyColor(color, deltaForForegroundColor);
    }

    /** Erzeugt aus einer normalen Farbe eine etwas hellere Farbe als Hintergrundfarbe. */
    public Color createBackgroundColor(Color color) {
        return modifyColor(color, deltaForBackgroundColor);
    }

    private Color modifyColor(Color color, int delta) {
        switch (colorModificationType) {
            case ADDITIVE:
                return ColorTool.changeColor(color, delta);
            case MULTIPLICATIVE:
                return ColorTool.changeColorMultiplicative(color, delta);
            default:
                throw new RuntimeException("Unbekannte Art '" + colorModificationType + "' wie "
                        + "Farben für die Erzeugung von Vorder- bzw. Hintergrundfarben "
                        + "modifiziert werden.");
        }
    }

    /** Gibt die Hintergrundfarbe für eine richtige Übersetzung zurück. */
    public Color getSuccessColor() {
        return createBackgroundColor(SUCCESS_COLOR_INDEX);
    }

    /** Gibt die Hintergrundfarbe für eine falsche Übersetzung zurück. */
    public Color getFailureColor() {
        return createBackgroundColor(FAILURE_COLOR_INDEX);
    }

    /**
     * Ermittelt die Hintergrundfarbe um einen Kanji anzuzeigen abhängig vom Erfolg der letzten
     * Abfragen.
     *
     * @param data
     *            Die ergänzenden, benutzerabhängigen Daten zu einem Kanji.
     * @param colorKanjiDependingOnLastSuccess
     *            Gibt an, ob die Hintergrundfarbe abhängig vom Erfolg der letzten Abfragen sein
     *            soll. Anderenfalls wird der Hintergrund einfach weiß.
     * @return Die Hintergrundfarbe.
     */
    public Color determineKanjiBackgroundColor(InternalAdditionalKanjiData data,
            boolean colorKanjiDependingOnLastSuccess) {
        if (colorKanjiDependingOnLastSuccess) {
            TotalAndCorrectTotalCount totalAndCorrect =
                    createTotalAndCorrectCountOfLastKanjiTests(data);
            return determineBackgroundColor(totalAndCorrect);
        }
        else {
            return Color.WHITE;
        }
    }

    /**
     * Ermittelt die Vordergrundfarbe um einen Kanji anzuzeigen abhängig vom Erfolg der letzten
     * Abfragen.
     *
     * @param data
     *            Die ergänzenden, benutzerabhängigen Daten zu einem Kanji.
     * @param colorKanjiDependingOnLastSuccess
     *            Gibt an, ob die Vordergrundfarbe abhängig vom Erfolg der letzten Abfragen sein
     *            soll. Anderenfalls wird der Vordergrund einfach schwarz.
     * @return Die Vordergrundfarbe.
     */
    public Color determineKanjiForegroundColor(InternalAdditionalKanjiData data,
            boolean colorKanjiDependingOnLastSuccess) {
        if (colorKanjiDependingOnLastSuccess)  {
            TotalAndCorrectTotalCount totalAndCorrect =
                    createTotalAndCorrectCountOfLastKanjiTests(data);
            return determineForegroundColor(totalAndCorrect);
        }
        else {
            return Color.BLACK;
        }
    }

    private static TotalAndCorrectTotalCount createTotalAndCorrectCountOfLastKanjiTests(
            InternalAdditionalKanjiData data) {
        int total = data.getLastTenTestsCount();
        int correct = data.getLastCorrectTestsCount();

        return new TotalAndCorrectTotalCount(total, correct);
    }

    /**
     * Ermittelt die Hintergrundfarbe um ein Kana anzuzeigen abhängig vom Erfolg der letzten
     * Abfragen.
     *
     * @param data
     *            Die ergänzenden, benutzerabhängigen Daten zu einem Kana.
     * @param colorKanaDependingOnLastSuccess
     *            Gibt an, ob die Hintergrundfarbe abhängig vom Erfolg der letzten Abfragen sein
     *            soll. Anderenfalls wird der Hintergrund einfach weiß.
     * @return Die Hintergrundfarbe.
     */
    public Color determineKanaBackgroundColor(InternalAdditionalKanaData data,
            boolean colorKanaDependingOnLastSuccess) {
        if (colorKanaDependingOnLastSuccess) {
            TotalAndCorrectTotalCount totalAndCorrect =
                    createTotalAndCorrectCountOfLastKanaTests(data);
            return determineBackgroundColor(totalAndCorrect);
        }
        else {
            return Color.WHITE;
        }
    }

    /**
     * Ermittelt die Vordergrundfarbe um ein Kana anzuzeigen abhängig vom Erfolg der letzten
     * Abfragen.
     *
     * @param data
     *            Die ergänzenden, benutzerabhängigen Daten zu einem Kana.
     * @param colorKanaDependingOnLastSuccess
     *            Gibt an, ob die Vordergrundfarbe abhängig vom Erfolg der letzten Abfragen sein
     *            soll. Anderenfalls wird der Vordergrund einfach schwarz.
     * @return Die Vordergrundfarbe.
     */
    public Color determineKanaForegroundColor(InternalAdditionalKanaData data,
            boolean colorKanaDependingOnLastSuccess) {
        if (colorKanaDependingOnLastSuccess)  {
            TotalAndCorrectTotalCount totalAndCorrect =
                    createTotalAndCorrectCountOfLastKanaTests(data);
            return determineForegroundColor(totalAndCorrect);
        }
        else {
            return Color.BLACK;
        }
    }

    private TotalAndCorrectTotalCount createTotalAndCorrectCountOfLastKanaTests(
            InternalAdditionalKanaData data) {
        int total = data.getLastTenTestsCount();
        int correct = data.getLastCorrectTestsCount();

        return new TotalAndCorrectTotalCount(total, correct);
    }

}
