package de.duehl.vocabulary.japanese.ui.listcommander.selector;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

import de.duehl.swing.ui.buttons.painted.DownButton;
import de.duehl.swing.ui.dialogs.base.additional.LocationGetter;
import de.duehl.twosidecommander.ui.selector.ListSelector;
import de.duehl.vocabulary.japanese.common.persistence.data.HistoricalOwnListPersistanceDataList;
import de.duehl.vocabulary.japanese.common.persistence.data.OwnListPersistanceData;
import de.duehl.vocabulary.japanese.data.FumikoDataStructures;
import de.duehl.vocabulary.japanese.data.OwnList;
import de.duehl.vocabulary.japanese.data.Vocable;
import de.duehl.vocabulary.japanese.logic.ownlists.OwnLists;
import de.duehl.vocabulary.japanese.tools.VocabularyTools;
import de.duehl.vocabulary.japanese.ui.data.FumikoUiObjects;
import de.duehl.vocabulary.japanese.ui.listcommander.selector.history.OwnListHistoryDialog;

import static de.duehl.twosidecommander.ui.color.ListCommanderColors.*;

/**
 * Diese Klasse stellt ein Auswahl-Element für eigene Listen dar.
 *
 * @version 1.01     2025-11-29
 * @author Christian Dühl
 */

public class OwnListSelector extends ListSelector {

    private static final String ALL_CATEGORIES = "Alle Kategorien";
    private static final String ALL_SUBCATEGORIES = "Alle Unterkategorien";
    private static final String ALL_NAMES = "Alle eigenen Listen";

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

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

    /** Das Objekt, das über die Auswahl der Liste informiert informiert wird. */
    private final OwnListSelectionChangeInformer informer;

    /**
     * Die zuletzt verwendeten eigenen Listen, auf die neuste davon wird der Selektor gestellt,
     * wenn die Liste bekannt ist. Sie werden auch in der Historie angezeigt.
     */
    private final HistoricalOwnListPersistanceDataList historicalOwnLists;

    /**
     * Das Objekt, das die aktuelle Position des Rahmens der Oberfläche, vor der dieser Dialog
     * erzeugt wird, zurück geben kann.
     */
    private final LocationGetter locationGetter;

    /** Die Combobox für die Kategorie. */
    private final JComboBox<String> categoryFilterComboBox;

    /** Die Combobox für die Unterkategorie. */
    private final JComboBox<String> subCategoryFilterComboBox;

    /** Die Combobox für die Namen der eigenen Listen in Kategorie und Unterkategorie. */
    private final JComboBox<String> nameComboBox;

    /** Der Button für die Historie. */
    private final DownButton historicButton;

    /** Der Panel auf dem der Selector dargestellt wird. */
    private final JPanel panel;

    /** Gibt an, ob der Name bei Änderung durchgereicht werden soll. */
    private boolean actualiseSelectedName;

    /** Gibt an, ob die ComboBoxen auch die Auswahl aller Elemente erlauben sollen. */
    private boolean selectAll;

    /**
     * Gibt an, ob die dem OwnListSelectionChangeInformer übergebene eigene Liste konstruiert
     * wurde, weil mindestens eine der Auswahlen 'alle' umfasst hat. Anderenfalls ist es eine der
     * in ownLists vorhandenen eigenen Listen.
     */
    private boolean ownListIsConstructedWithAllSelection;

    /**
     * Konstruktor.
     *
     * @param dataStructures
     *            Die Datenstrukturen des Vokabeltrainers.
     * @param uiObjects
     *            Die häufig verwendeten Funktionen der grafischen Oberfläche des Vokabeltrainers.
     * @param historicalOwnLists
     *            Die zuletzt verwendeten eigenen Listen, auf die neuste davon wird der Selektor
     *            gestellt, wenn die Liste bekannt ist. Sie werden auch in der Historie angezeigt.
     * @param informer
     *            Das Objekt, das über die Auswahl der Liste informiert informiert wird.
     * @param locationGetter
     *            Das Objekt, das die aktuelle Position des Rahmens der Oberfläche, vor der dieser Dialog
     * erzeugt wird, zurück geben kann.
     */
    public OwnListSelector(FumikoDataStructures dataStructures, FumikoUiObjects uiObjects,
            HistoricalOwnListPersistanceDataList historicalOwnLists,
            OwnListSelectionChangeInformer informer, LocationGetter locationGetter) {
        this.dataStructures = dataStructures;
        this.uiObjects = uiObjects;
        this.historicalOwnLists = historicalOwnLists;
        this.informer = informer;
        this.locationGetter = locationGetter;

        categoryFilterComboBox = new JComboBox<>();
        subCategoryFilterComboBox = new JComboBox<>();
        nameComboBox = new JComboBox<>();

        historicButton = new DownButton();

        panel = new JPanel();

        actualiseSelectedName = true;
        selectAll = false;
    }

    /** Wird aufgerufen, wenn die ComboBoxen auch die Auswahl aller Elemente erlauben sollen. */
    public void allowSelectAll() {
        selectAll = true;
    }

    /** Wird aufgerufen, wenn alle Optionen gesetzt wurden. */
    public void createSelector() {
        init();
        fillPanel();
    }

    private void init() {
        initComboBoxes();
        fillComboBoxes();
        selectLastUsedOwnListInComboBoxes();
        isUsed();
        addComboboxesActionListeners();
        initHistoricButton();
    }

    private void initComboBoxes() {
        initComboBox(categoryFilterComboBox);
        initComboBox(subCategoryFilterComboBox);
        initComboBox(nameComboBox);
    }

    private static void initComboBox(JComboBox<String> comboBox) {
        comboBox.setAlignmentX(JLabel.CENTER);
        comboBox.setAlignmentY(JLabel.CENTER);
        ((JLabel) comboBox.getRenderer()).setHorizontalAlignment(SwingConstants.CENTER);
        comboBox.setFocusable(false);
    }

    private void fillComboBoxes() {
        fillCategoryComboBox();
        fillSubCategoryComboBox();
        fillNameComboBox();
    }

    private void fillCategoryComboBox() {
        categoryFilterComboBox.removeAllItems();

        if (selectAll) {
            categoryFilterComboBox.addItem(ALL_CATEGORIES);
        }

        OwnLists ownLists = dataStructures.getOwnLists();
        for (String category : VocabularyTools.determineCategories(ownLists .getOwnLists())) {
            categoryFilterComboBox.addItem(category);
        }
    }

    /** Befüllt die ComboBox mit der Unterkategorie abhängig von der Auswahl der Kategorie. */
    private void fillSubCategoryComboBox() {
        subCategoryFilterComboBox.removeAllItems();

        if (selectAll) {
            subCategoryFilterComboBox.addItem(ALL_SUBCATEGORIES);
        }

        String selectedCategory = (String) categoryFilterComboBox.getSelectedItem();
        if (selectedCategory == null) {
            return;
        }

        if (!selectedCategory.equals(ALL_CATEGORIES)) {
            OwnLists ownLists = dataStructures.getOwnLists();
            List<String> subCategories = VocabularyTools.determineSubCategoriesOfCategory(
                        ownLists.getOwnLists(), selectedCategory);

            for (String category : subCategories) {
                subCategoryFilterComboBox.addItem(category);
            }
        }
    }

    private void fillNameComboBox() {
        nameComboBox.removeAllItems();

        if (selectAll) {
            nameComboBox.addItem(ALL_NAMES);
        }

        String selectedCategory = (String) categoryFilterComboBox.getSelectedItem();
        if (selectedCategory == null) {
            return;
        }

        String selectedSubCategory = (String) subCategoryFilterComboBox.getSelectedItem();
        if (selectedSubCategory == null) {
            return;
        }

        if (!selectedCategory.equals(ALL_CATEGORIES)
                && !selectedSubCategory.equals(ALL_SUBCATEGORIES)) {
            OwnLists ownLists = dataStructures.getOwnLists();
            List<OwnList> ownListsWithCategoryAndSubCategory =
                    ownLists.findOwnListsWithCategoryAndSubCategory(selectedCategory,
                            selectedSubCategory);
                    // Nicht in VocabularyTools, da eine Liste mit echten eigenen Listen erzeugt wird!

            OwnLists.sortListsByName(ownListsWithCategoryAndSubCategory);

            for (OwnList ownList : ownListsWithCategoryAndSubCategory) {
                String name = ownList.getName();
                nameComboBox.addItem(name);
            }
        }
    }

    private void selectLastUsedOwnListInComboBoxes() {
        List<OwnListPersistanceData> historicalOwnListDataSets =
                historicalOwnLists.getHistoricalOwnLists();
        if (!historicalOwnListDataSets.isEmpty()) {
            OwnListPersistanceData lastUsedOwnList =
                    historicalOwnListDataSets.get(historicalOwnListDataSets.size() - 1);
            OwnLists ownLists = dataStructures.getOwnLists();
            if (lastUsedOwnList.isInitialised() && ownLists.containsOwnList(lastUsedOwnList)) {
                String category = lastUsedOwnList.getCategory();
                String subCategory = lastUsedOwnList.getSubCategory();
                String name = lastUsedOwnList.getName();

                categoryFilterComboBox.setSelectedItem(category);
                fillSubCategoryComboBox();

                subCategoryFilterComboBox.setSelectedItem(subCategory);
                fillNameComboBox();

                nameComboBox.setSelectedItem(name);

                OwnList ownList = ownLists.findByName(name); // gleiche Namen werden verhindert!
                if (!ownList.equals(OwnLists.NO_OWN_LIST_FOUND)) {
                    informer.ownListSelected(ownList);
                }
            }
        }
    }

    private void addComboboxesActionListeners() {
        categoryFilterComboBox.addActionListener(e -> categorySelectionChanged());
        subCategoryFilterComboBox.addActionListener(e -> subCategorySelectionChanged());
        nameComboBox.addActionListener(e -> nameSelectionChanged());
    }

    private void categorySelectionChanged() {
        if (actualiseSelectedName) {
            fillSubCategoryComboBox();
            fillNameComboBox();
            isUsed();
        }
    }

    private void subCategorySelectionChanged() {
        if (actualiseSelectedName) {
            fillNameComboBox();
            isUsed();
        }
    }

    private void nameSelectionChanged() {
        if (actualiseSelectedName) {
            String name = (String) nameComboBox.getSelectedItem();
            if (null == name) {
                return;
            }

            /*
             * Wenn eine der anderen ComboBoxen auf einem ALL-Wert steht, dann auch diese! Daher
             * teste ich nur 'name':
             */
            OwnList ownList;
            if (name == ALL_NAMES) {
                ownList = createOwnListWithAllWantedVocables();
                ownListIsConstructedWithAllSelection = true;
            }
            else {
                OwnLists ownLists = dataStructures.getOwnLists();
                ownList = ownLists.findByName(name); // gleiche Namen werden verhindert!
                ownListIsConstructedWithAllSelection = false;
            }
            if (!ownList.equals(OwnLists.NO_OWN_LIST_FOUND)) {
                informer.ownListSelected(ownList); // nur wenn vernünftig!
            }
            isUsed();
        }
    }

    private OwnList createOwnListWithAllWantedVocables() {
        String selectedCategory = (String) categoryFilterComboBox.getSelectedItem();
        if (selectedCategory == null) {
            return OwnLists.NO_OWN_LIST_FOUND;
        }

        String selectedSubCategory = (String) subCategoryFilterComboBox.getSelectedItem();
        if (selectedSubCategory == null) {
            return OwnLists.NO_OWN_LIST_FOUND;
        }

        String selectedName = (String) nameComboBox.getSelectedItem();
        if (selectedName == null) {
            return OwnLists.NO_OWN_LIST_FOUND;
        }

        List<Vocable> vocables = new ArrayList<>();
        OwnLists ownLists = dataStructures.getOwnLists();
        for (OwnList ownList : ownLists.getOwnLists()) {
            String ownListCategory = ownList.getCategory();
            String ownListSubCategory = ownList.getSubCategory();
            String ownListName = ownList.getName();

            boolean categoryOk = selectedCategory.equals(ALL_CATEGORIES)
                    || selectedCategory.equals(ownListCategory);
            boolean subCategoryOk = selectedSubCategory.equals(ALL_SUBCATEGORIES)
                    || selectedSubCategory.equals(ownListSubCategory);
            boolean nameOk = selectedName.equals(ALL_NAMES)
                    || selectedName.equals(ownListName);

            if (categoryOk && subCategoryOk && nameOk) {
                for (Vocable vocable : ownList.getVocables()) {
                    if (!vocables.contains(vocable)) {
                        vocables.add(vocable);
                    }
                }
            }
        }

        return new OwnList(selectedName, selectedSubCategory, selectedCategory, vocables);
    }

    private void initHistoricButton() {
        historicButton.forceSquare();
        historicButton.setPreferredSize(new Dimension(35, 1));
        historicButton.addActionListener(e -> showHistory());
    }

    private void showHistory() {
        if (!historicalOwnLists.isEmpty()) {
            Point panelLocation = panel.getLocation();
            Point buttonLocation = historicButton.getLocation();
            Point parentLocation = locationGetter.getLocation();
            Point location = new Point(
                    parentLocation .x + panelLocation.x + buttonLocation.x + 150,
                    parentLocation.y + panelLocation.y + buttonLocation.y + 100);

            OwnListHistoryDialog dialog = new OwnListHistoryDialog(historicalOwnLists, location,
                    uiObjects.getProgramImage());
            dialog.setVisible(true);
            if (dialog.isValidSelection()) {
                OwnListPersistanceData selectedPersistentOwnList =
                        dialog.getSelectedPersistentOwnList();
                historicalOwnLists.addOwnListData(selectedPersistentOwnList);
                actualiseSelectedName = false;
                selectLastUsedOwnListInComboBoxes();
                actualiseSelectedName = true;
            }
        }
        isUsed();
    }

    private void fillPanel() {
        panel.setLayout(new BorderLayout());

        panel.add(createComboBoxesPart(), BorderLayout.CENTER);
        panel.add(historicButton, BorderLayout.EAST);
    }

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

        panel.add(createCategoryComboBoxesPart(), BorderLayout.NORTH);
        panel.add(nameComboBox, BorderLayout.SOUTH);

        return panel;
    }

    private Component createCategoryComboBoxesPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(0, 2, 2, 2));

        panel.add(categoryFilterComboBox);
        panel.add(subCategoryFilterComboBox);

        return panel;
    }

    /** Getter für den Panel, auf dem der Selector dargestellt wird. */
    @Override
    public final JPanel getPanel() {
        return panel;
    }

    /** Stellt den Selector aktiv dar. */
    @Override
    public final void showActive() {
        setComboBoxBackgroundColors(ACTIVE_COMBOBOX_BACKGROUND_COLOR);
    }

    /** Stellt den Selector inaktiv dar. */
    @Override
    public final void showInactive() {
        setComboBoxBackgroundColors(INACTIVE_COMBOBOX_BACKGROUND_COLOR);
    }

    private void setComboBoxBackgroundColors(Color color) {
        categoryFilterComboBox.setBackground(color);
        subCategoryFilterComboBox.setBackground(color);
        nameComboBox.setBackground(color);
    }

    /** Füllt die ComboBoxes neu, z.B. wenn man eine neue Liste angelegt hat. */
    public void refillComboboxes() {
        removeComboboxesActionListeners();
        fillComboBoxes();
        selectLastUsedOwnListInComboBoxes();
        nameSelectionChanged(); // erst nach selectLastUsedOwnListInComboBoxes() !
        addComboboxesActionListeners();
    }

    private void removeComboboxesActionListeners() {
        removeAllActionListeners(categoryFilterComboBox);
        removeAllActionListeners(subCategoryFilterComboBox);
        removeAllActionListeners(nameComboBox);
    }

    private void removeAllActionListeners(JComboBox<String> comboBox) {
        for (ActionListener listener : comboBox.getActionListeners()) {
            comboBox.removeActionListener(listener);
        }
    }

    /**
     * Gibt an, ob die dem OwnListSelectionChangeInformer übergebene eigene Liste konstruiert
     * wurde, weil mindestens eine der Auswahlen 'alle' umfasst hat. Anderenfalls ist es eine der
     * in ownLists vorhandenen eigenen Listen.
     */
    public boolean isOwnListIsConstructedWithAllSelection() {
        return ownListIsConstructedWithAllSelection;
    }

}
