package de.duehl.swing.ui.pages;

/*
 * 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.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.elements.navigator.list.ListNavigator;
import de.duehl.swing.ui.layout.VerticalLayout;

/**
 * Diese Klasse zeigt Daten auf Seiten, zwischen denen man blättern kann, dar.
 *
 * Alle bereits zu Datensätzen erzeugten UI-Komponente werden gecached, um das Zurückblättern und
 * das Blättern in bereits angezeigten Seiten zu beschleunigen.
 *
 * Man kann nicht nur im Konstruktor, sondern auch in der GUI entscheiden können, wie viele
 * Datensätze pro Seite angezeigt werden.
 *
 * Die Darstellung ist ebenfalls in mehreren Spalten möglich. Dies kann man wie die Anzahl der
 * Datensätze pro Seite im Konstruktor, aber auch in der GUI angeben.
 *
 * @version 1.01     2024-04-03
 * @author Christian Dühl
 */

public class DatasetsOnPages<DataType> {

    private static final List<Integer> POSSIBLE_DATASETS_ON_PAGE_VALUES =
            CollectionsHelper.buildListFrom(
                    10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100);

    private static final List<Integer> POSSIBLE_COLUMNS =
            CollectionsHelper.buildListFrom(
                    1, 2, 3, 4, 5);


    /** Die Liste mit den anzuzeigenden Datensätzen. */
    private List<DataType> datasets;

    /** Das Objekt, das aus einem Datensatz eine UI-Komponente erstellt. */
    private final PanelFromDataCreator<DataType> panelFromDatasetCreator;

    /** Die Anzahl an Datensätzen pro Seite. */
    private int numberOfDatasetsPerPage;

    /** Die Anzahl an Spalten mit den auf einer Seite angezeigten Daten. */
    private int numberOfColumns;

    /** Der Panel auf dem die Seiten dargestellt werden. */
    private final JPanel panel;

    /** Die Auswahlmöglichkeit der Anzahl an Datensätzen pro Seite. */
    private final JComboBox<Integer> numberOfDatasetsPerPageComboBox;

    /** Die Auswahlmöglichkeit der Anzahl an Spalten mit den auf einer Seite angezeigten Daten. */
    private final JComboBox<Integer> numberOfColumnsComboBox;

    /** Der Cache für bereits erzeugte UI-Komponenten. */
    private final Map<DataType, Component> createdComponentsCache;

    /** Der Navigator, der zwischen den Seiten wechselt. */
    private final ListNavigator<List<DataType>> navigator;

    /** Der Panel auf dem die Elemente der aktuellen Seite dargestellt werden. */
    private final JPanel pageContentPanel;

    /** Die Scrollpane um den Panel auf dem die Elemente der aktuellen Seite dargestellt werden. */
    private final JScrollPane pageContentScroll;

    /** Der Panel zum Hinzufügen eigener Elemente im oberen Bereich. */
    private JPanel ownExtensionPanel;

    /**
     * Konstruktor.
     *
     * @param datasets
     *            Die Liste mit den anzuzeigenden Datensätzen.
     * @param panelFromDatasetCreator
     *            Das Objekt, das aus einem Datensatz eine UI-Komponente erstellt.
     * @param numberOfDatasetsPerPage
     *            Die initiale Anzahl an Datensätzen pro Seite.
     * @param numberOfColumns
     *            Die initiale Anzahl an Spalten mit den auf einer Seite angezeigten Daten.
     */
    public DatasetsOnPages(List<DataType> datasets,
            PanelFromDataCreator<DataType> panelFromDatasetCreator, int numberOfDatasetsPerPage,
            int numberOfColumns) {
        this.datasets = datasets;
        this.panelFromDatasetCreator = panelFromDatasetCreator;
        this.numberOfDatasetsPerPage = numberOfDatasetsPerPage;
        this.numberOfColumns = numberOfColumns;

        panel = new JPanel();
        numberOfDatasetsPerPageComboBox = new JComboBox<>();
        numberOfColumnsComboBox = new JComboBox<>();
        createdComponentsCache = new HashMap<>();
        navigator = new ListNavigator<>();
        pageContentPanel = new JPanel();
        pageContentScroll = new JScrollPane(pageContentPanel);
        ownExtensionPanel = new JPanel();

        checkList();
        init();
        fillPanel();
        showFirstPage();
    }

    /** Wird aufgerufen, wenn sich die anzuzeigende Daten-Liste von außen ändert. */
    public void setOtherDatasets(List<DataType> datasets) {
        this.datasets = datasets;
        checkList();
        showFirstPage();
    }


    /** Fügt eigene Anzeigeelemente hinzu. */
    public void addOwnExtension(Component component) {
        ownExtensionPanel.add(component, BorderLayout.CENTER);
        panel.repaint();
    }

    private void checkList() {
        if (datasets.isEmpty()) {
            throw new RuntimeException("Die Liste mit den Datensätzen ist leer.");
        }
    }

    private void init() {
        initPanels();
        initPageContentPanel();
        initComboBoxes();
        initNavigator();
        initScroll();
    }

    private void initPanels() {
        panel.setLayout(new BorderLayout());
        ownExtensionPanel.setLayout(new BorderLayout());
    }

    private void initPageContentPanel() {
        pageContentPanel.setLayout(new GridLayout(1, 0, 5, 5));
        /*
         * Stellt die Spalten nebeneinander dar.
         */
    }

    private void initComboBoxes() {
        initNumberOfDatasetsPerSideComboBox();
        initNumberOfColumnsComboBox();
    }

    private void initNumberOfDatasetsPerSideComboBox() {
        numberOfDatasetsPerPageComboBox.setAlignmentX(JLabel.CENTER);
        numberOfDatasetsPerPageComboBox.setAlignmentY(JLabel.CENTER);
        ((JLabel) numberOfDatasetsPerPageComboBox.getRenderer()).setHorizontalAlignment(
                SwingConstants.RIGHT);

        List<Integer> possibleDatasetsOnPage = new ArrayList<>();
        possibleDatasetsOnPage.addAll(POSSIBLE_DATASETS_ON_PAGE_VALUES);
        if (!POSSIBLE_DATASETS_ON_PAGE_VALUES.contains(numberOfDatasetsPerPage)) {
            possibleDatasetsOnPage.add(numberOfDatasetsPerPage);
        }
        for (int value : possibleDatasetsOnPage) {
            numberOfDatasetsPerPageComboBox.addItem(value);
        }

        numberOfDatasetsPerPageComboBox.setSelectedItem(numberOfDatasetsPerPage);
        numberOfDatasetsPerPageComboBox.addActionListener(e -> rearrangePageAfterChangedNumbers());
    }

    private void initNumberOfColumnsComboBox() {
        numberOfColumnsComboBox.setAlignmentX(JLabel.CENTER);
        numberOfColumnsComboBox.setAlignmentY(JLabel.CENTER);
        ((JLabel) numberOfColumnsComboBox.getRenderer()).setHorizontalAlignment(
                SwingConstants.RIGHT);

        List<Integer> possibleColumns = new ArrayList<>();
        possibleColumns.addAll(POSSIBLE_COLUMNS);
        if (!POSSIBLE_COLUMNS.contains(numberOfColumns)) {
            possibleColumns.add(numberOfColumns);
        }
        for (int value : possibleColumns) {
            numberOfColumnsComboBox.addItem(value);
        }

        numberOfColumnsComboBox.setSelectedItem(numberOfColumns);
        numberOfColumnsComboBox.addActionListener(e -> rearrangePageAfterChangedNumbers());
    }

    private void rearrangePageAfterChangedNumbers() {
        numberOfDatasetsPerPage = (Integer) numberOfDatasetsPerPageComboBox.getSelectedItem();
        numberOfColumns = (Integer) numberOfColumnsComboBox.getSelectedItem();

        List<DataType> datasetsOnActualPage = navigator.getActualDataSet();
        int numberOfPageWithDataset;
        if (datasetsOnActualPage.isEmpty()) {
            numberOfPageWithDataset = 1;
        }
        else {
            DataType data = datasetsOnActualPage.get(0);

            navigator.setDataListWithoutShowingFirstElement(createPagesDatasetLists());
            List<List<DataType>> listOfDatasetsOnAllPages = navigator.getAllDatasets();

            int indexOfPageWithDataset = -1;
            for (int index = 0; index < listOfDatasetsOnAllPages.size(); ++index) {
                List<DataType> datasetsOnPage = listOfDatasetsOnAllPages.get(index);
                if (datasetsOnPage.contains(data)) {
                    indexOfPageWithDataset = index;
                    break;
                }
            }

            numberOfPageWithDataset = indexOfPageWithDataset + 1;
        }
        navigator.goToNumber(numberOfPageWithDataset);
    }

    private void initNavigator() {
        navigator.setShowMethod(datasetList -> showDatasetsOnPage(datasetList));
    }

    private void initScroll() {
        GuiTools.setVerticalScrollBarUnitIncrement(pageContentScroll, 30);
    }

    private void fillPanel() {
        panel.add(createUpperNumbersPart(), BorderLayout.NORTH);
        panel.add(pageContentScroll, BorderLayout.CENTER);
        panel.add(createNavigatorPart(), BorderLayout.SOUTH);
    }

    private Component createUpperNumbersPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        GuiTools.createTitle(panel);

        panel.add(createNumberOfDatasetsPerPageComboBoxWithTitelPart(), BorderLayout.WEST);
        panel.add(GuiTools.centerHorizontal(ownExtensionPanel), BorderLayout.CENTER);
        panel.add(createNumberOfColumnsComboBoxWithTitelPart(), BorderLayout.EAST);

        return panel;
    }

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

        panel.add(new JLabel("Anzahl Datensätze pro Seite "), BorderLayout.WEST);
        panel.add(numberOfDatasetsPerPageComboBox, BorderLayout.CENTER);

        return panel;
    }

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

        panel.add(new JLabel("Anzahl Spalten "), BorderLayout.WEST);
        panel.add(numberOfColumnsComboBox, BorderLayout.CENTER);

        return panel;
    }

    private Component createNavigatorPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        GuiTools.createTitle(panel);

        panel.add(createCenteredNavigatorPanel(), BorderLayout.CENTER);

        return panel;
    }

    private Component createCenteredNavigatorPanel() {
        JComponent centeredNavigation = GuiTools.center(navigator.getPanel());
        //GuiTools.createTitle(centeredNavigation);
        return centeredNavigation;
    }

    private void showFirstPage() {
        navigator.setDataList(createPagesDatasetLists());
    }

    /** Erzeugt die Liste mit den Listen der Datensätze pro Seite. */
    private List<List<DataType>> createPagesDatasetLists() {
        List<List<DataType>> datasetsOnPages = new ArrayList<>();

        int numberOfPages = determineNumberOfPages();
        for (int count = 1; count <= numberOfPages; ++count) {
            datasetsOnPages.add(new ArrayList<>());
        }

        for (int dataIndex = 0; dataIndex < datasets.size(); ++dataIndex) {
            DataType data = datasets.get(dataIndex);

            int index = dataIndex / numberOfDatasetsPerPage;
            List<DataType> list = datasetsOnPages.get(index);
            list.add(data);
        }

        return datasetsOnPages;
    }

    private int determineNumberOfPages() {
        int numberOfDatasets = datasets.size();
        int numberOfPages = numberOfDatasets / numberOfDatasetsPerPage;
        if (numberOfPages * numberOfDatasetsPerPage < numberOfDatasets) {
            ++numberOfPages;
        }
        return numberOfPages;
    }

    /** Zeigt die übergebenen Datensätze auf der Seite an. */
    private void showDatasetsOnPage(List<DataType> datasetsOnPage) {
        List<Component> componentsOnPage = createComponentsOnPage(datasetsOnPage);

        int datasetsPerColumn = componentsOnPage.size() / numberOfColumns;
        if (datasetsPerColumn * numberOfColumns < componentsOnPage.size()) {
            ++datasetsPerColumn;
        }

        List<List<Component>> componentsInColumns = new ArrayList<>();
        for (int count = 1; count <= numberOfColumns; ++count) {
            componentsInColumns.add(new ArrayList<>());
        }

        for (int componentIndex = 0; componentIndex < componentsOnPage.size(); ++componentIndex) {
            Component component = componentsOnPage.get(componentIndex);

            int index = componentIndex / datasetsPerColumn;
            List<Component> list = componentsInColumns.get(index);
            list.add(component);
        }

        pageContentPanel.removeAll();
        for (List<Component> components : componentsInColumns) {
            JPanel listPanel = new JPanel();
            listPanel.setLayout(new VerticalLayout(3, VerticalLayout.BOTH));

            for (Component component : components) {
                listPanel.add(component);
            }

            pageContentPanel.add(listPanel);
        }

        pageContentPanel.repaint();
        pageContentPanel.validate();
        pageContentPanel.invalidate();

        pageContentScroll.repaint();
        pageContentScroll.validate();
        pageContentScroll.invalidate();

        GuiTools.scrollScrollbarToMinimumLater(pageContentScroll);
    }

    private List<Component> createComponentsOnPage(List<DataType> datasetsOnPage) {
        List<Component> componentsOnPage = new ArrayList<>();

        for (DataType data : datasetsOnPage) {
            Component component;
            if (createdComponentsCache.containsKey(data)) {
                component = createdComponentsCache.get(data);
            }
            else {
                component = panelFromDatasetCreator.createDatasetUi(data);
                createdComponentsCache.put(data, component);
            }
            componentsOnPage.add(component);
        }

        return componentsOnPage;
    }

    /** Getter für den Panel auf dem die Seiten dargestellt werden. */
    public Component getPanel() {
        return panel;
    }

    /** Getter für die Anzahl an Datensätzen pro Seite. */
    public int getNumberOfDatasetsPerPage() {
        return numberOfDatasetsPerPage;
    }

    /** Getter für die Anzahl an Spalten mit den auf einer Seite angezeigten Daten. */
    public int getNumberOfColumns() {
        return numberOfColumns;
    }

    /** Gibt die auf der aktuellen Seite angezeigten Datensätze zurück. */
    public List<DataType> getDatasetsFromActualPage() {
        List<DataType> datasetsOnActualPage = navigator.getActualDataSet();
        return datasetsOnActualPage;
    }

    /** Gibt die angezeigte Seitennummer (1-basiert) zurück. */
    public int getActualPageNumber() {
        return navigator.getActualShownNumberOfElement();
    }

    /**
     * Zeigt die Seite mit der übergebenen Seitennummer an, falls diese vorhanden ist.
     *
     * @param pageNumber
     *            Die gewünschte Seitennummer (1-basiert).
     */
    public void showPage(int pageNumber) {
        int max = navigator.getNumberOfDatasets();
        if (pageNumber < 1) {
            navigator.goToNumber(1);
        }
        else  if (pageNumber > max) {
            navigator.goToNumber(max);
        }
        else {
            navigator.goToNumber(pageNumber);
        }
    }

}
