package de.duehl.vocabulary.japanese.ui.dialog.testing;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;

import de.duehl.basics.text.NumberString;
import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.components.selections.StringSelection;
import de.duehl.swing.ui.components.selections.tools.SelectionsHelper;
import de.duehl.swing.ui.dialogs.base.ModalDialogBase;
import de.duehl.swing.ui.key.BindKeysOnRootPane;
import de.duehl.vocabulary.japanese.common.data.InternalAdditionalVocableData;
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.logic.test.VocableListTesterLogic;
import de.duehl.vocabulary.japanese.tools.VocableListShuffler;
import de.duehl.vocabulary.japanese.ui.components.VocableTester;
import de.duehl.vocabulary.japanese.ui.components.data.VocableTestReactor;
import de.duehl.vocabulary.japanese.ui.data.FumikoUiObjects;
import de.duehl.vocabulary.japanese.ui.tools.VocabularyTrainerUiTools;

/**
 * Diese Klasse fragt eine Liste von Vokabeln nacheinander ab.
 *
 * @version 1.01     2025-11-20
 * @author Christian Dühl
 */

public class VocableListTesterDialog extends ModalDialogBase implements VocableTestReactor {

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

    /** Der Titel für die Art der Abfrage. */
    private final String testTitle;

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

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

    private final VocableTester vocableTester;
    private final JButton quitButton;
    private final StringSelection numberOfVocablesSelection;
    private final StringSelection numberOfDoneVocablesSelection;
    private final StringSelection numberOfCorrectDoneVocablesSelection;
    private final StringSelection correctDonePercentVocablesSelection;

    /** Die Anzahl der abgefragten Variablen. */
    private int numberOfDoneVocables;

    /** Die Anzahl der abgefragten Variablen, die richtig beantwortet wurden. */
    private int numberOfCorrectDoneVocables;

    /** Der Index der angezeigten Vokabel (0-basiert). */
    private int indexOfShownVocable;

    /** Die zum Dialog gehörige Logik. */
    private final VocableListTesterLogic logic;

    /**
     * Die Prozentzahl des Erfolgs der letzten zehn Abfragen dieser Vokabeln bevor der aktuelle
     * Test gelaufen ist.
     */
    private final double lastTenTestsPercentBefore;

    /**
     * Konstruktor.
     *
     * @param vocables
     *            Die Liste mit den abzufragenden Vokabeln.
     * @param testTitle
     *            Der Titel für die Art der Abfrage.
     * @param logic
     *            Die zum Dialog gehörige Logik.
     * @param dataStructures
     *            Die Datenstrukturen des Vokabeltrainers.
     * @param uiObjects
     *            Die häufig verwendeten Funktionen der grafischen Oberfläche des Vokabeltrainers.
     */
    public VocableListTesterDialog(List<Vocable> vocables, String testTitle,
            VocableListTesterLogic logic, FumikoDataStructures dataStructures,
            FumikoUiObjects uiObjects) {
        super(uiObjects.getGuiLocation(), uiObjects.getProgramImage(),
                createDialogTitle(vocables, testTitle, dataStructures));
        this.testTitle = testTitle;
        this.logic = logic;
        this.dataStructures = dataStructures;
        this.uiObjects = uiObjects;

        Options options = dataStructures.getOptions();
        VocablesShuffleType type = options.getVocablesShuffleType();
        VocableListShuffler shuffler = new VocableListShuffler(vocables, type, dataStructures);
        shuffler.shuffle();
        this.vocables = shuffler.getVocables();

        vocableTester = new VocableTester((VocableTestReactor) this, options);
        quitButton = new JButton();
        numberOfVocablesSelection = new StringSelection("Anzahl abzufragender Vokabeln");
        numberOfDoneVocablesSelection = new StringSelection("Anzahl bereits abgefragter Vokabeln");
        numberOfCorrectDoneVocablesSelection = new StringSelection(
                "Anzahl korrekt beantworteter Vokabeln");
        correctDonePercentVocablesSelection = new StringSelection(
                "Prozent korrekt beantworteter Vokabeln");

        lastTenTestsPercentBefore =
                VocabularyTrainerUiTools.createLastTenTestsPercent(vocables, dataStructures);

        fillDialog();
    }

    private static String createDialogTitle(List<Vocable> vocables, String testTitle,
            FumikoDataStructures dataStructures) {
        return VocabularyTrainerUiTools.generateTitleWithVocabularyTestSuccesss(vocables, testTitle,
                dataStructures);
    }

    /** Baut die Gui auf. */
    @Override
    protected void populateDialog() {
        initElements();

        add(createCenterPart(), BorderLayout.CENTER);
        add(createButtonsPart(),  BorderLayout.SOUTH);

        showFirstVocable();
        keybindingsForPlaySound();
    }

    private void initElements() {
        SelectionsHelper.initSelectionAsViewer(numberOfVocablesSelection);
        SelectionsHelper.initSelectionAsViewer(numberOfDoneVocablesSelection);
        SelectionsHelper.initSelectionAsViewer(numberOfCorrectDoneVocablesSelection);
        SelectionsHelper.initSelectionAsViewer(correctDonePercentVocablesSelection);
    }

    private Component createCenterPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createSummeryPart(), BorderLayout.NORTH);
        panel.add(createVocabularyPart(), BorderLayout.CENTER);

        return panel;
    }

    private Component createSummeryPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(1, 0, 3, 3));

        panel.add(numberOfVocablesSelection.getPanel());
        panel.add(numberOfDoneVocablesSelection.getPanel());
        panel.add(numberOfCorrectDoneVocablesSelection.getPanel());
        panel.add(correctDonePercentVocablesSelection.getPanel());

        return panel;
    }

    private Component createVocabularyPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(vocableTester.getPanel());

        return panel;
    }

    private Component createButtonsPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(createPlaySoundKeyCombinationLabel(), BorderLayout.WEST);
        panel.add(createQuitButton(), BorderLayout.EAST);

        return panel;
    }

    private Component createPlaySoundKeyCombinationLabel() {
        JLabel label = new JLabel("Abspielen mit F9");
        label.setBorder(BorderFactory.createEmptyBorder(0,  5,  0,  5));
        return label;
    }

    private void keybindingsForPlaySound() {
        KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0);
        String actionMapKey = "F9";
        Action action = BindKeysOnRootPane.runnableToAction(() -> vocableTester.playMp3());

        JRootPane rootPane = getRootPane();
        InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        inputMap.put(keyStroke, actionMapKey);
        rootPane.getActionMap().put(actionMapKey, action);
    }

    private Component createQuitButton() {
        quitButton.setText("Abbrechen");
        quitButton.addActionListener(e -> quit());
        return quitButton;
    }

    private void quit() {
        closeDialog();
    }

    private void showFirstVocable() {
        numberOfVocablesSelection.setText(NumberString.taupu(vocables.size()));

        numberOfDoneVocables = 0;
        numberOfCorrectDoneVocables = 0;
        setNumbersAndPercent();

        indexOfShownVocable = 0;
        showVocable(vocables.get(indexOfShownVocable));
    }

    public void setNumbersAndPercent() {
        numberOfDoneVocablesSelection.setText(
                NumberString.taupu(numberOfDoneVocables));
        numberOfCorrectDoneVocablesSelection.setText(
                NumberString.taupu(numberOfCorrectDoneVocables));
        if (numberOfDoneVocables == 0) {
            correctDonePercentVocablesSelection.setText("0.00 %");
        }
        else {
            correctDonePercentVocablesSelection.setText(
                    NumberString.percent(numberOfCorrectDoneVocables, numberOfDoneVocables) + " %");
        }
    }

    /**
     * Wird aufgerufen, wenn der Benutzer eine Übersetzung eines japanischen Begriffs eingegeben
     * hat.
     *
     * @param kana
     *            Die Darstellung in Kana der Vokabel welche abgefragt wird.
     * @param kanji
     *            Die Darstellung in Kanji der Vokabel welche abgefragt wird.
     * @param translationByUser
     *            Die vom Benutzer eingegebene Übersetzung.
     */
    @Override
    public void userEnteredJapaneseToGermanTranslation(String kana, String kanji,
            String translationByUser) {
        logic.userEnteredJapaneseToGermanTranslation(kana, kanji, translationByUser);
    }

    /**
     * Wird aufgerufen, wenn der Benutzer eine Übersetzung eines japanischen Begriffs eingegeben
     * hat.
     *
     * @param germanTerm
     *            Der abgefragte deutsche Begriff.
     * @param translationByUser
     *            Die vom Benutzer eingegebene Übersetzung.
     */
    @Override
    public void userEnteredGermanToJapaneseTranslation(String germanTerm, String translationByUser) {
        logic.userEnteredGermanToJapaneseTranslation(germanTerm, translationByUser);
    }

    public void increaseNumberOfDoneVocables() {
        ++numberOfDoneVocables;
    }

    public void increaseNumberOfCorrectDoneVocables() {
        ++numberOfCorrectDoneVocables;
    }

    public boolean informAboutTranslationSuccess(List<Vocable> matchingVocables, boolean correct,
            Vocable correctVocable) {
        List<Vocable> vocablesToShow = new ArrayList<>();
        if (correct) {
            vocablesToShow.add(correctVocable);
        }
        else {
            vocablesToShow.addAll(matchingVocables);
        }

        Options options = dataStructures.getOptions();
        TranslationEvaluationDialog dialog = new TranslationEvaluationDialog(vocablesToShow,
                correct, options.getTranslationDirection(), dataStructures, uiObjects,
                getLocation());
        dialog.setVisible(true);

        if (correct) {
            return false;
        }
        else {
            return dialog.wasOnlyTypingError();
        }
    }

    /** Schaltet zur nächsten Variablen weiter. */
    @Override
    public void switchToNextVocable() {
        if (indexOfShownVocable < vocables.size() - 1) {
            ++indexOfShownVocable;
            showVocable(vocables.get(indexOfShownVocable));
            if (indexOfShownVocable == vocables.size() - 1) {
                quitButton.setText("Beenden");
            }
        }
        else {
            vocableTester.disable();
            SwingUtilities.invokeLater(() -> quitButton.requestFocus());

            double lastTenTestsPercentAfter = VocabularyTrainerUiTools
                    .createLastTenTestsPercent(vocables, dataStructures);
            String originalDialogTitle = createDialogTitle(vocables, testTitle, dataStructures);
            Options options = dataStructures.getOptions();
            AllTranslationsEvaluationDialog dialog = new AllTranslationsEvaluationDialog(options,
                    numberOfDoneVocables, numberOfCorrectDoneVocables, getLocation(),
                    getProgramImage(), lastTenTestsPercentBefore, lastTenTestsPercentAfter,
                    originalDialogTitle);
            dialog.setVisible(true);

            if (indexOfShownVocable == vocables.size() - 1) {
                closeDialog();
            }
        }
    }

    private void showVocable(Vocable vocable) {
        boolean lastVocable = indexOfShownVocable + 1 == vocables.size();
        InternalDataRequester requester = dataStructures.getInternalDataRequester();
        InternalAdditionalVocableData data = requester.getInternalDataForVocable(vocable);
        vocableTester.showVocable(vocable, data, lastVocable);
        getRealDialog().pack();
    }

    /**
     * Lässt den Benutzer nach einer falschen Übersetzung,welche nur ein Tippfehler war, eine der
     * möglichen Vokabeln auswählen.
     *
     * @param translationByUser
     *            Die vom Benutzer eingegebene Übersetzung mit Tippfehler.
     * @param matchingVocables
     *            Die in Frage kommenden Vokabeln, diese passen zu den bei der Übersetzung
     *            angezeigten Informationen).
     * @return Die ausgewählte Vokabel oder, wenn der Benutzer den Vorgang abbricht, die erste der
     *         in Frage kommenden Vokabeln.
     */
    public Vocable letUserSelectMatchingVocableForHisTranslation(String translationByUser,
            List<Vocable> matchingVocables) {
        VocableAfterTypingErrorSelectorDialog dialog = new VocableAfterTypingErrorSelectorDialog(
                translationByUser, matchingVocables, dataStructures, uiObjects, getLocation());
        dialog.setVisible(true);
        if (dialog.hasUserSelected()) {
            return dialog.getCorrectVocable();
        }
        else {
            GuiTools.informUser(quitButton, "Abbruch bei der Auswahl", "Da die Auswahl "
                    + "abgebrochen wurde, schreiben wir die richige Übersetzung der ersten "
                    + "angezeigten Vokabel gut.");
            return matchingVocables.get(0);
        }
    }

    /** Gibt an, ob eine leere Übersetzung ausgewertet werden soll. */
    @Override
    public boolean doWeHaveToReactOnEmptyTranslation() {
        return logic.doWeHaveToReactOnEmptyTranslation();
    }

}
