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

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import de.duehl.basics.datetime.date.ImmutualDate;
import de.duehl.basics.io.Charset;
import de.duehl.basics.io.FileHelper;
import de.duehl.basics.text.NumberString;
import de.duehl.basics.text.Text;

/**
 * Diese Klasse stellt die ergänzenden Daten zu einem Kanji dar, die sich auf die Abfrage des
 * Kanji durch einen speziellen Benutzer bezieht. Daher werden diese Daten auch unterhalb des
 * Home-Verzeichnisses des Benutzers abgelegt.
 *
 * Die Zuordnung zu den eigentlichen Daten eines Kanji erfolgt über den Namen der Enum-Konstante.
 *
 * Siehe auch die Enum-Klasse Kanji.
 *
 * @version 1.01     2024-11-07
 * @author Christian Dühl
 */

public class InternalAdditionalKanjiData {

    /** Der Dateiname in der diese zusätzlichen Informationen gespeichert werden. */
    private String filename;

    /** Der Schlüssel basierend auf dem Namen der Enum-Konstante des Kanji. */
    private String key;

    /** Das Datum an dem das Kanji das erste Mal eingelesen wurde. */
    private ImmutualDate firstSeenDate;

    /** Der Zähler wie oft dieses Kanji getestet wurde. */
    private int testCount;

    /** Der Zähler wie oft dieses Kanji von getestet und richtig beantwortet wurde. */
    private int correctTestCount;

    /** Das Datum an dem das Kanji zuletzt getestet wurde. */
    private ImmutualDate lastTestDate;

    /** Das Datum an dem das Kanji zuletzt getestet und richtig beantwortet wurde. */
    private ImmutualDate lastCorrectTestDate;

    /**
     * Die Liste mit den letzten 10 (oder weniger) Abfrage-Ergebnissen.
     *
     * Der letzte Eintrag ist das Ergebnis des neusten Tests.
     */
    private List<Boolean> lastTenTestResults;

    /** Konstruktor. */
    public InternalAdditionalKanjiData() {
        filename = "";
        key = "";
        firstSeenDate = InternalAdditionalVocableData.NOT_SEEN_DATE;

        testCount = 0;
        correctTestCount = 0;
        lastTestDate = InternalAdditionalVocableData.NOT_SEEN_DATE;
        lastCorrectTestDate = InternalAdditionalVocableData.NOT_SEEN_DATE;
        lastTenTestResults = new ArrayList<>();
    }

    /** Getter für den Dateinamen in der diese zusätzlichen Informationen gespeichert werden. */
    public String getFilename() {
        return filename;
    }

    /** Setter für den Dateinamen in der diese zusätzlichen Informationen gespeichert werden. */
    public InternalAdditionalKanjiData setFilename(String filename) {
        this.filename = filename;
        return this;
    }

    /** Getter für den Schlüssel basierend auf dem Namen der Enum-Konstante des Kanji. */
    public String getKey() {
        return key;
    }

    /** Setter für den Schlüssel basierend auf dem Namen der Enum-Konstante des Kanji. */
    public InternalAdditionalKanjiData setKey(String key) {
        this.key = key;
        return this;
    }

    /** Getter für das Datum an dem das Kanji das erste Mal eingelesen wurde. */
    public ImmutualDate getFirstSeenDate() {
        return firstSeenDate;
    }

    /** Setter für das Datum an dem das Kanji das erste Mal eingelesen wurde. */
    public InternalAdditionalKanjiData setFirstSeenDate(ImmutualDate firstSeenDate) {
        this.firstSeenDate = firstSeenDate;
        return this;
    }

    /** Getter für den Zähler, wie oft dieses Kanji getestet wurde. */
    public int getTestCount() {
        return testCount;
    }

    /** Setter für den Zähler, wie oft dieses Kanji getestet wurde. */
    public InternalAdditionalKanjiData setTestCount(int testCount) {
        this.testCount = testCount;
        return this;
    }

    /** Getter für den Zähler, wie oft dieses Kanji getestet und richtig beantwortet wurde. */
    public int getCorrectTestCount() {
        return correctTestCount;
    }

    /** Setter für den Zähler, wie oft dieses Kanji getestet und richtig beantwortet wurde. */
    public InternalAdditionalKanjiData setCorrectTestCount(
            int correctTestCount) {
        this.correctTestCount = correctTestCount;
        return this;
    }

    /** Getter für das Datum an dem das Kanji zuletzt getestet wurde. */
    public ImmutualDate getLastTestDate() {
        return lastTestDate;
    }

    /** Setter für das Datum an dem das Kanji zuletzt getestet wurde. */
    public InternalAdditionalKanjiData setLastTestDate(ImmutualDate lastTestDate) {
        this.lastTestDate = lastTestDate;
        return this;
    }

    /** Getter für das Datum an dem das Kanji zuletzt getestet und richtig beantwortet wurde. */
    public ImmutualDate getLastCorrectTestDate() {
        return lastCorrectTestDate;
    }

    /** Setter für das Datum an dem das Kanji zuletzt getestet und richtig beantwortet wurde. */
    public InternalAdditionalKanjiData setLastCorrectTestDate(ImmutualDate lastCorrectTestDate) {
        this.lastCorrectTestDate = lastCorrectTestDate;
        return this;
    }

    /**
     * Getter für die Anzeige Liste mit den letzten 10 (oder weniger) Abfrage-Ergebnissen in der
     * Tabelle.
     *
     * Der letzte Eintrag ist das Ergebnis des neusten Tests.
     */
    public String getLastTenTestResultsAsStorageString() {
        return InternalAdditionalVocableData.lastTenTestResultsToStorageString(lastTenTestResults);
    }

    /**
     * Getter für die Liste mit den letzten 10 (oder weniger) Abfrage-Ergebnissen.
     *
     * Der letzte Eintrag ist das Ergebnis des neusten Tests.
     */
    public List<Boolean> getLastTenTestResults() {
        return lastTenTestResults;
    }

    /** Gibt zurück, wie oft das Kanji zuletzt getestet wurde. */
    public int getLastTenTestsCount() {
        return lastTenTestResults.size();
    }

    /** Gibt zurück, wie oft das Kanji zuletzt richtig getestet wurde. */
    public int getLastCorrectTestsCount() {
        return InternalAdditionalVocableData.getLastCorrectTestsCount(lastTenTestResults);
    }

    /**
     * Initialisiert aus einen String mit den letzten 10 (oder weniger) Abfrage-Ergebnissen die
     * Liste mit den letzten 10 (oder weniger) Abfrage-Ergebnissen.
     *
     * @param storageString
     *            Der String mit den letzten Ergebnissen. Enthält '+' für richtig beantwortete und
     *            '-' für falsch beantwortete Abfragen. Das letzte Zeichen ist das Ergebnis des
     *            neusten Tests.
     */
    public InternalAdditionalKanjiData initLastTenTestResultsFromStorageString(
            String storageString) {
        InternalAdditionalVocableData.initLastTenTestResultsFromStorageString(lastTenTestResults,
                storageString);
        return this;
    }

    /** Fügt eine Abfrage des Kanji hinzu. */
    public void tested(boolean correct) {
        testedWithoutSave(correct);
        save();
    }

    private void testedWithoutSave(boolean correct) {
        ImmutualDate today = new ImmutualDate();

        increaseTestCount();
        setLastTestDate(today);
        if (correct) {
            increaseCorrectTestCount();
            setLastCorrectTestDate(today);
        }

        lastTenTestResults.add(correct);
        while (lastTenTestResults.size() > 10) {
            lastTenTestResults.remove(0);
        }
    }

    /** Erhöht den Zähler wie oft dieses Kanji getestet wurde. */
    private void increaseTestCount() {
        ++testCount;
    }

    /** Erhöht den Zähler wie oft dieses Kanji getestet und richtig beantwortet wurde. */
    private void increaseCorrectTestCount() {
        ++correctTestCount;
    }

    private static final int NUMBER_OF_LINES = 7;

    /**
     * Speichert die Daten dieses Objekts.
     *
     * Der Dateiname wird nicht in der Datei selbst mit abgespeichert, sondern nur in dem Objekt.
     *
     * @param filename
     *            Dateiname der einzulesenden Datei.
     * @return Eingelesenes Objekt
     */
    public static InternalAdditionalKanjiData load(String filename) {
        List<String> lines = FileHelper.readFileToList(filename, Charset.UTF_8);

        if (lines.size() == NUMBER_OF_LINES) {
            return loadFromNormalFormat(filename, lines);
        }
        else {
            throw new RuntimeException("Die Datei " + filename + " enthält nicht wie erwartet "
                    + NUMBER_OF_LINES + " Zeilen.");
        }
    }

    private static InternalAdditionalKanjiData loadFromNormalFormat(String filename,
            List<String> lines) {
        int index = 0;
        String key = lines.get(index++);
        String firstSeenDateString = lines.get(index++);

        String testCountString = lines.get(index++);
        String correctTestCountString = lines.get(index++);
        String lastTestDateString = lines.get(index++);
        String lastCorrectTestDateString = lines.get(index++);
        String lastTenTestResultsStorageString = lines.get(index++);

        int testCount = NumberString.parseInt(testCountString);
        int correctTestCount = NumberString.parseInt(correctTestCountString);

        ImmutualDate firstSeenDate = new ImmutualDate(firstSeenDateString);
        ImmutualDate lastTestDate = new ImmutualDate(lastTestDateString);
        ImmutualDate lastCorrectTestDate = new ImmutualDate(lastCorrectTestDateString);

        InternalAdditionalKanjiData data = new InternalAdditionalKanjiData();
        data.setFilename(filename);
        data.setKey(key);
        data.setFirstSeenDate(firstSeenDate);
        data.setTestCount(testCount);
        data.setCorrectTestCount(correctTestCount);
        data.setLastTestDate(lastTestDate);
        data.setLastCorrectTestDate(lastCorrectTestDate);
        data.initLastTenTestResultsFromStorageString(lastTenTestResultsStorageString);

        return data;
    }

    /**
     * Speichert die Daten dieses Objekts.
     *
     * Sollte nicht außerhalb vom StartupLoader und dieser Klasse aufgerufen werden!
     *
     * Der Dateiname wird nicht in der Datei selbst mit abgespeichert, sondern nur in dem Objekt.
     */
    public void save() {
        List<String> lines = new ArrayList<>();
        lines.add(key);
        lines.add(firstSeenDate.toString());

        lines.add(Integer.toString(testCount));
        lines.add(Integer.toString(correctTestCount));
        lines.add(lastTestDate.toString());
        lines.add(lastCorrectTestDate.toString());
        lines.add(InternalAdditionalVocableData.lastTenTestResultsToStorageString(
                lastTenTestResults));

        FileHelper.writeLinesToFile(lines, filename, Charset.UTF_8);
    }

    @Override
    public String toString() {
        return "InternalAdditionalKanjiData [filename=" + filename + ", key=" + key
                + ", firstSeenDate=" + firstSeenDate + ", testCount=" + testCount
                + ", correctTestCount=" + correctTestCount + ", lastTestDate=" + lastTestDate
                + ", lastCorrectTestDate=" + lastCorrectTestDate + ", lastTenTestResults="
                + lastTenTestResults + "]";
    }

    /** Erzeugt eine leserliche, schöne Ausgabe. */
    public String toNiceString(int numberOfInsertionSpaces) {
        StringBuilder builder = new StringBuilder();

        String insertion = Text.multipleString(" ", numberOfInsertionSpaces);

        builder.append(insertion + "InternalAdditionalKanjiData:\n");
        builder.append(insertion + "filename           : " + filename + "\n");
        builder.append(insertion + "key                : " + key + "\n");
        builder.append(insertion + "firstSeenDate      : " + firstSeenDate + "\n");

        builder.append(insertion + "getestet          : "
                + testCount + "\n");
        builder.append(insertion + "correctTestCount   : " + correctTestCount + "\n");
        builder.append(insertion + "lastTestDate       : " + lastTestDate + "\n");
        builder.append(insertion + "lastCorrectTestDate: " + lastCorrectTestDate + "\n");
        builder.append(insertion + "lastTenTestResults : "
                + InternalAdditionalVocableData.lastTenTestResultsToStorageString(lastTenTestResults)
                + "\n");

        return builder.toString();
    }

    @Override
    public int hashCode() {
        return Objects.hash(correctTestCount, filename, firstSeenDate, key, lastCorrectTestDate,
                lastTenTestResults, lastTestDate, testCount);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        InternalAdditionalKanjiData other = (InternalAdditionalKanjiData) obj;
        return correctTestCount == other.correctTestCount
                && Objects.equals(filename, other.filename)
                && Objects.equals(firstSeenDate, other.firstSeenDate)
                && Objects.equals(key, other.key)
                && Objects.equals(lastCorrectTestDate, other.lastCorrectTestDate)
                && Objects.equals(lastTenTestResults, other.lastTenTestResults)
                && Objects.equals(lastTestDate, other.lastTestDate) && testCount == other.testCount;
    }

}
