package de.duehl.swing.ui.elements.navigator.list;

/*
 * Copyright 2024 Christian Dühl. All rights reserved.
 *
 * This program is free software. You can redistribute it and/or
 * modify it under the same terms as perl:
 *
 * general:  http://dev.perl.org/licenses/
 * GPL:      http://dev.perl.org/licenses/gpl1.html
 * artistic: http://dev.perl.org/licenses/artistic.html
 */

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionListener;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JTextField;

import de.duehl.basics.text.NumberString;
import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.buttons.painted.FirstButton;
import de.duehl.swing.ui.buttons.painted.LastButton;
import de.duehl.swing.ui.buttons.painted.NextButton;
import de.duehl.swing.ui.buttons.painted.PreviousButton;

/**
 * Diese Klasse stellt eine Navigation mit vier Schaltern dar, um zum ersten, zum vorigen, zum
 * nächsten und zum letzten Element zu gelangen. Außerdem wird optional die Nummer des aktuell
 * angezeigten Elements als bearbeitbares Feld und die Anzahl der Elemente als nicht bearbeitbares
 * Feld angezeigt.
 *
 * @version 1.02     2024-02-15
 * @author Christian Dühl
 */

public class ListNavigator<DataType> {

    private static final int NO_NUMBER_OF_ELEMENTS_DETECTED = -2;

    private static final Dimension DEFAULT_BUTTON_SIZE = new Dimension(30, 40);
    private static final int HORIZONTAL_GAP = 5;
    private static final int VERTICAL_GAP = 0;


    /** Die Liste mit den anzuzeigenden Datenobjekten. */
    private List<DataType> dataList;

    /** Das Objekt das einen Datensatz anzeigen kann. */
    private ListNavigatorDataUser<DataType> dataUser;

    /**
     * Gibt die Richtung an, in die navigiert wird: true entspricht von links nach rechts, false
     * von rechts nach links.
     */
    private boolean fromLeftToRight;

    /**
     * Gibt an, ob die Nummer des aktuellen Datensatzes und die gesamte Anzahl an Datensätzen
     * angezeigt werden sollen.
     */
    private boolean showActualAndTotalNumberOfDatasets;

    /** Die aktuell angezeigte Elementnummer. */
    private int actualShownNumberOfElement;

    /** Der Panel auf dem die Navigation dargestellt wird. */
    private final JPanel panel;

    /** Die Größe der Schaltflächen zur Navigation. */
    private final Dimension buttonSize;

    /** Der Schalter um zum ersten Listenelement zu navigieren. */
    private final JButton firstButton;

    /** Der Schalter um zum vorigen Listenelement zu navigieren. */
    private final JButton previousButton;

    /** Der Schalter um zum nächsten Listenelement zu navigieren. */
    private final JButton nextButton;

    /** Der Schalter um zum letzten Listenelement zu navigieren. */
    private final JButton lastButton;

    /** Das Feld mit der Nummer des aktuellen Datensatzes. */
    private final JTextField goToNumberOfElementsIndexField;

    /** Das Feld mit der gesamten Anzahl der Datensätze. */
    private final JTextField totalNumberOfElementsIndexField;

    /** Konstruktor mit der Standard-Buttongröße. */
    public ListNavigator() {
        this(DEFAULT_BUTTON_SIZE);
    }

    /** Konstruktor mit individueller Buttongröße. */
    public ListNavigator(Dimension buttonSize) {
        this.buttonSize = buttonSize;

        fromLeftToRight = true;
        actualShownNumberOfElement = -1;

        showActualAndTotalNumberOfDatasets = true;

        panel = new JPanel();

        firstButton = new FirstButton();
        previousButton = new PreviousButton();
        nextButton = new NextButton();
        lastButton = new LastButton();

        goToNumberOfElementsIndexField = new JTextField("", 4);
        totalNumberOfElementsIndexField = new JTextField("", 4);

        initElelents();
        buildPanel();
    }

    /** Setter für die Liste mit den anzuzeigenden Datenobjekten. */
    public void setDataList(List<DataType> list) {
        setDataListWithoutShowingFirstElement(list);
        first();
    }

    /**
     * Setter für die Liste mit den anzuzeigenden Datenobjekten ohne die erste Seite anzuzeigen.
     * Der Aufrufer muss im Anschluss selbst dafür sorgen, etwa mit goToNumber(1).
     */
    public void setDataListWithoutShowingFirstElement(List<DataType> list) {
        this.dataList = list;
        totalNumberOfElementsIndexField.setText(Integer.toString(dataList.size()));
    }

    /** Getter für die aktuell angezeigte Elementnummer. */
    public int getActualShownNumberOfElement() {
        return actualShownNumberOfElement;
    }

    /** Gibt den aktuellen Datensatz zurück. */
    public DataType getActualDataSet() {
        return dataList.get(actualShownNumberOfElement - 1);
    }

    /** Setter für das Objekt, das einen Datensatz anzeigen kann. */
    public void setShowMethod(ListNavigatorDataUser<DataType> dataUser) {
        this.dataUser = dataUser;
    }

    /** Legt die Navigation auf von links nach rechts fest. */
    public void navigateLeftToRight() {
        fromLeftToRight = true;
        buildPanel();
    }

    /** Legt die Navigation auf von rechts nach links fest. */
    public void navigateRightToLeft() {
        fromLeftToRight = false;
        buildPanel();
    }

    /**
     * Legt fest, dass die Nummer des aktuellen Datensatzes und die gesamte Anzahl an Datensätzen
     * nicht angezeigt werden sollen.
     */
    public void hideActualAndTotalNumberOfDatasets() {
        showActualAndTotalNumberOfDatasets = false;
    }

    private void buildPanel() {
        panel.removeAll();
        setLayoutAndPreferredSize();
        addElements();
        setButtonActions();
    }

    private void setLayoutAndPreferredSize() {
        panel.setLayout(
                new FlowLayout(FlowLayout.CENTER, HORIZONTAL_GAP, VERTICAL_GAP));
        panel.setPreferredSize(calculatePreferredSize());
    }

    private Dimension calculatePreferredSize() {
        int width = calculatePreferredWidth();
        int height = calculatePreferredHeight();

        return new Dimension(width, height);
    }

    private int calculatePreferredWidth() {
        int numberOfHorizontalElements = 4;
        int elementWidth = buttonSize.width;
        int width = numberOfHorizontalElements * elementWidth
                + (numberOfHorizontalElements + 1) * HORIZONTAL_GAP;
                // + 1, falls das auch links und rechts angefügt wird, sonst -1
                // mit -1 ist es zu kurz, daher wird es wohl auch außen eingefügt.

        width += elementWidth; // reichte in manchen Anwendungen sonst nicht
                               // SelectionVerificationDialog

        int numberOfAdditionalHorizontalElements = 3;
        int estimatedFieldWidth = 50; // TODO
        int esitmatedSlashLabeWidth = 15; // TODO
        width += (numberOfAdditionalHorizontalElements + 1) * HORIZONTAL_GAP
                + 2 * estimatedFieldWidth
                + esitmatedSlashLabeWidth;
        /*
         * Die Gui-Elemente kann man ja leider erst nach der Größe fragen, wenn sie angezeigt sind.
         */

        //width += 100; // lastButton wurde nicht dargestellt...

        return width;
    }

    private int calculatePreferredHeight() {
        int numberOfVerticalElements = 1;
        int elementHeight = buttonSize.height;
        int height = numberOfVerticalElements * elementHeight
                + (numberOfVerticalElements + 1) * VERTICAL_GAP;
                // + 1, falls das auch oben und unten angefügt wird, sonst -1

        return height;
    }

    private void addElements() {
        addLeftNavigationElements();
        if (showActualAndTotalNumberOfDatasets) {
            addDocumentPositionElements();
        }
        addRightNavigationElements();
    }

    private final void addLeftNavigationElements() {
        panel.add(firstButton);
        panel.add(previousButton);
    }

    private void addDocumentPositionElements() {
        panel.add(goToNumberOfElementsIndexField);
        panel.add(new JLabel("/"));
        panel.add(totalNumberOfElementsIndexField);
    }

    private final void addRightNavigationElements() {
        panel.add(nextButton);
        panel.add(lastButton);
    }

    private void initElelents() {
        setButtonDimensions();
        setButtonToolTips();
        setNumbers();
    }

    private void setButtonDimensions() {
        firstButton.setPreferredSize(buttonSize);
        previousButton.setPreferredSize(buttonSize);
        nextButton.setPreferredSize(buttonSize);
        lastButton.setPreferredSize(buttonSize);
    }

    private void setButtonToolTips() {
        if (fromLeftToRight) {
            firstButton.setToolTipText("Zeige ersten Datensatz an.");
            previousButton.setToolTipText("Zeige vorigen Datensatz an.");
            nextButton.setToolTipText("Zeige nächsten Datensatz an.");
            lastButton.setToolTipText("Zeige letzten Datensatz an.");
        }
        else {
            firstButton.setToolTipText("Zeige letzten Datensatz an.");
            previousButton.setToolTipText("Zeige nächsten Datensatz an.");
            nextButton.setToolTipText("Zeige vorigen Datensatz an.");
            lastButton.setToolTipText("Zeige ersten Datensatz an.");
        }
    }

    private void setNumbers() {
        goToNumberOfElementsIndexField.addActionListener(e -> reactOnDocumentIndexInput());
        GuiTools.biggerFont(goToNumberOfElementsIndexField, 3);

        totalNumberOfElementsIndexField.setFocusable(false);
        totalNumberOfElementsIndexField.setEditable(false);
        GuiTools.biggerFont(totalNumberOfElementsIndexField, 3);
    }

    /** Reagiert auf die Eingabe einer Datensatznummer durch den Benutzer (1-basiert). */
    private void reactOnDocumentIndexInput() {
        String numberString = goToNumberOfElementsIndexField.getText().trim();
        int pageNumber = NumberString.parseIntIgnore(numberString, -1);
        goToNumber(pageNumber);
    }

    /** Geht zur Seite mit der übergebenen Seitennummer, wenn dies möglich ist. */
    public void goToNumber(int pageNumber) {
        int pageNumberToShow = determineShownNumberOfElements(pageNumber);
        if (pageNumberToShow != NO_NUMBER_OF_ELEMENTS_DETECTED) {
            setActualShownNumberOfElement(pageNumberToShow);
        }
    }

    private int determineShownNumberOfElements(int pageNumber) {
        if (pageNumber > 0 && pageNumber <= dataList.size()) {
            return pageNumber;
        }
        else {
            return NO_NUMBER_OF_ELEMENTS_DETECTED;
        }
    }

    /** Setzt die aktuell angezeigte Elementnummer. */
    private void setActualShownNumberOfElement(int actualShownNumberOfElement) {
        this.actualShownNumberOfElement = actualShownNumberOfElement;
        goToNumberOfElementsIndexField.setText(Integer.toString(actualShownNumberOfElement));
        showActualDataset();
    }

    private void showActualDataset() {
        DataType data = getActualDataSet();
        dataUser.showDatasetFromListNavigator(data);
    }

    private void setButtonActions() {
        removeAllActionListener(firstButton);
        removeAllActionListener(previousButton);
        removeAllActionListener(nextButton);
        removeAllActionListener(lastButton);

        if (fromLeftToRight) {
            firstButton.addActionListener(e -> first());
            previousButton.addActionListener(e -> previous());
            nextButton.addActionListener(e -> next());
            lastButton.addActionListener(e -> last());
        }
        else {
            firstButton.addActionListener(e -> last());
            previousButton.addActionListener(e -> next());
            nextButton.addActionListener(e -> previous());
            lastButton.addActionListener(e -> first());
        }
    }

    private void removeAllActionListener(JButton button) {
        for (ActionListener actionListener : button.getActionListeners()) {
            button.removeActionListener(actionListener);
        }
    }

    private void first() {
        setActualShownNumberOfElement(1);
    }

    private void previous() {
        if (actualShownNumberOfElement > 1) {
            setActualShownNumberOfElement(actualShownNumberOfElement - 1);
        }
    }

    private void next() {
        if (actualShownNumberOfElement < dataList.size()) {
            setActualShownNumberOfElement(actualShownNumberOfElement + 1);
        }
    }

    private void last() {
        setActualShownNumberOfElement(dataList.size());
    }

    /** Getter für den Panel auf dem die Navigation dargestellt wird. */
    public JPanel getPanel() {
        return panel;
    }

    /** Weist dem Button, der das erste Element anzeigt, einen Tooltip zu. */
    public void setFirstToolTip(String toolTip) {
        firstButton.setToolTipText(toolTip);
    }

    /** Weist dem Button, der das vorige Element anzeigt, einen Tooltip zu. */
    public void setPreviousToolTip(String toolTip) {
        previousButton.setToolTipText(toolTip);
    }

    /** Weist dem Button, der das nächste Element anzeigt, einen Tooltip zu. */
    public void setNextToolTip(String toolTip) {
        nextButton.setToolTipText(toolTip);
    }

    /** Weist dem Button, der das letzte Element anzeigt, einen Tooltip zu. */
    public void setLastToolTip(String toolTip) {
        lastButton.setToolTipText(toolTip);
    }

    /** Drückt den Button, der das erste Element anzeigt. */
    public void doClickOnFirstButton() {
        firstButton.doClick();
    }

    /** Drückt den Button, der das vorige Element anzeigt. */
    public void doClickOnPreviousButton() {
        previousButton.doClick();
    }

    /** Drückt den Button, der das nächste Element anzeigt. */
    public void doClickOnNextButton() {
        nextButton.doClick();
    }

    /** Drückt den Button, der das letzte Element anzeigt. */
    public void doClickOnLastButton() {
        lastButton.doClick();
    }

    /** Getter für die Liste mit den anzuzeigenden Datenobjekten. */
    public List<DataType> getAllDatasets() {
        return dataList;
    }

    /** Gibt die Anzahl der Datensätze (und damit die höchste anzeigbare Seitennummer) zurück. */
    public int getNumberOfDatasets() {
        return dataList.size();
    }

}
