package de.duehl.swing.ui.components;

import java.awt.Color;

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

import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;

import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.colors.Colorizer;
import de.duehl.swing.ui.layout.VerticalLayout;

/**
 * Dieses Panel verteilt eine Anzahl gleichförmiger Elemente so in einem Gitter, dass diese
 * möglichst ansprechend und übersichtlich dargestellt werden.
 *
 * @version 1.01     2018-01-04
 * @author Christian Dühl
 */

public class MultipleElementsPanel<C extends Component> extends JPanel {

    private static final long serialVersionUID = 1L;

    private static final int MAXIMAL_NUMBER_OF_COLUMNNS = 10;
    private static final int MAXIMAL_NUMBER_OF_ELEMENTS_PER_COLUMNN = 30;

    /** Liste mit den darzustellenden Elementen. */
    private final List<C> elements;

    /** Anzahl der Spalten. */
    private int columns;

    /** Maximale Anzahl der Spalten. */
    private final int maximalNumberOfColumns;

    /** Anzahl der Elemente pro Spalte, falls möglich. */
    private final int wantedMaximalNumberOfElementsPerColumn;

    /** Anzahl der Elemente pro Spalte, falls möglich. */
    private int maximalNumberOfElementsPerColumn;

    /** Liste der Panels mit jeder Spalte. */
    private final List<JPanel> columnPanels;

    /**
     * Konstruktor für bislang leere Liste mit höchstens 10 Spalten und höchstens 30 Elementen pro
     * Spalte, falls möglich.
     */
    public MultipleElementsPanel() {
        this(new ArrayList<>(), MAXIMAL_NUMBER_OF_COLUMNNS, MAXIMAL_NUMBER_OF_ELEMENTS_PER_COLUMNN);
    }

    /**
     * Konstruktor mit höchstens 10 Spalten und höchstens 30 Elementen pro Spalte, falls möglich.
     *
     * @param elements
     *            Liste mit den darzustellenden Elementen (darf nicht leer sein).
     */
    public MultipleElementsPanel(List<C> elements) {
        this(elements, MAXIMAL_NUMBER_OF_COLUMNNS, MAXIMAL_NUMBER_OF_ELEMENTS_PER_COLUMNN);
    }

    /**
     * Konstruktor für bislang leere Liste.
     *
     * @param maximalNumberOfColumns
     *            Maximale Anzahl an Spalten (hat Vorrang).
     * @param maximalNumberOfElementsPerColumn
     *            Maximale Anzahl an Elementen pro Spalte (falls möglich).
     */
    public MultipleElementsPanel(int maximalNumberOfColumns, int maximalNumberOfElementsPerColumn) {
        this(new ArrayList<>(), maximalNumberOfColumns, maximalNumberOfElementsPerColumn);
    }

    /**
     * Konstruktor.
     *
     * @param elements
     *            Liste mit den darzustellenden Elementen (darf nicht leer sein).
     * @param maximalNumberOfColumns
     *            Maximale Anzahl an Spalten (hat Vorrang).
     * @param maximalNumberOfElementsPerColumn
     *            Maximale Anzahl an Elementen pro Spalte (falls möglich).
     */
    public MultipleElementsPanel(List<C> elements, int maximalNumberOfColumns,
            int maximalNumberOfElementsPerColumn) {
        super();

        this.elements = new ArrayList<>();
        this.elements.addAll(elements);

        this.maximalNumberOfColumns = maximalNumberOfColumns;
        this.wantedMaximalNumberOfElementsPerColumn = maximalNumberOfElementsPerColumn;

        columnPanels = new ArrayList<>();

        fillPanel();
    }

    private void fillPanel() {
        removeAll();
        columnPanels.clear();

        maximalNumberOfElementsPerColumn = wantedMaximalNumberOfElementsPerColumn;
        columns = determineNumberOfColumns();
        // setLayout(new GridLayout(0, columns, 5, 2)); // damit tauchen seltsame Artefakte bei
                                                        // drei, vier und fünf Spalten auf...
        setLayout(new GridLayout(0, columns));

        createColumnPanels();
        addColumnPanels();
        distributeElements();

        validate();
    }

    /**
     * Bestimmt die Anzahl der Spalten abhängig von der Anzahl der anzuzeigenden Elemente.
     *
     * Dabei wird zunächst versucht, in jeder Spalte nicht mehr als
     * maximalNumberOfElementsPerColumn Elemente unterzubringen.
     *
     * Wenn dabei aber mehr als maximalNumberOfColumns Spalten entstehen, wird deren Anzahl auf
     * maximalNumberOfColumns begrenzt, auch wenn dann mehr als maximalNumberOfElementsPerColumn
     * Elemente in einer Spalte sind.
     * Hierbei wird dann maximalNumberOfElementsPerColumn erhöht!
     */
    private int determineNumberOfColumns() {
        int numberOfElements = elements.size();

        int columns = numberOfElements / maximalNumberOfElementsPerColumn;
        if (columns * maximalNumberOfElementsPerColumn < numberOfElements) {
            ++columns;
        }

        if (columns == 0) {
            return 1;
        }

        if (columns > maximalNumberOfColumns) {
            ++maximalNumberOfElementsPerColumn;
            return determineNumberOfColumns();
        }
        else {
            return columns;
        }
    }

    private void createColumnPanels() {
        for (int column = 0; column < columns; ++column) {
            columnPanels.add(createColumnPanel());
        }
    }

    private JPanel createColumnPanel() {
        JPanel columnPanel = new JPanel();
        columnPanel.setLayout(new VerticalLayout(3, VerticalLayout.BOTH));
        return columnPanel;
    }

    private void addColumnPanels() {
        for (Component columnPanel : columnPanels) {
            add(columnPanel);
        }
    }

    private void distributeElements() {
        for (int index = 0; index < elements.size(); ++index) {
            Component element = elements.get(index);

            int column = determineColumn(index);
            JPanel columnPanel = columnPanels.get(column);
            columnPanel.add(element);
        }
    }

    /**
     * Bestimmt die richtige Spalte für ein Element.
     *
     * Hier steht zweimal "+ 1", da es sich um Indizes statt Nummern handelt.
     *
     * @param index
     *            Index des zu verteilenden Elements.
     * @return Index der richtigen Spalte.
     */
    private int determineColumn(int index) {
        int column = 0;
        while ((index + 1) > (column + 1) * maximalNumberOfElementsPerColumn) {
            ++column;
        }
        if (column >= columns ) {
            throw new RuntimeException("Fehler in der Berechnung!"
                    + "\n\t" + "column = " + column
                    + "\n\t" + "columns = " + columns
                    + "\n\t" + "index = " + index
                    + "\n\t" + "elements = " + elements);
        }
        return column;
    }

    /**
     * Färbt alle Komponenten ein: Die übergebenen Elemente, den Panel selbst und die Spaltenpanel.
     */
    public void colorize(Colorizer colorizer) {
        colorizer.setColors(this);
        for (Component element : elements) {
            colorizer.setColors(element);
        }
        for (Component panel : columnPanels) {
            colorizer.setColors(panel);
        }
    }

    public void addElement(C component) {
        elements.add(component);
        fillPanel();
    }

    public void removeElement(C component) {
        elements.remove(component);
        fillPanel();
    }

    public List<C> getElements() {
        return elements;
    }

    /**
     * Erzeugt einen MultipleElementsPanel aus einer Liste von Texten mit höchstens 10 Spalten und
     * höchstens 30 Elementen pro Spalte, falls möglich.
     *
     * @param texts
     *            Liste mit den darzustellenden Texten (darf nicht leer sein).
     */
    public static MultipleElementsPanel<JLabel> createFromTexts(List<String> texts) {
        return createFromTexts(texts, MAXIMAL_NUMBER_OF_COLUMNNS,
                MAXIMAL_NUMBER_OF_ELEMENTS_PER_COLUMNN);
    }

    /**
     * Erzeugt einen MultipleElementsPanel aus einer Liste von Texten mit höchstens
     * maximalNumberOfColumns Spalten und höchstens maximalNumberOfElementsPerColumn Elementen pro
     * Spalte, falls möglich.
     *
     * @param texts
     *            Liste mit den darzustellenden Texten (darf nicht leer sein).
     * @param maximalNumberOfColumns
     *            Maximale Anzahl an Spalten (hat Vorrang).
     * @param maximalNumberOfElementsPerColumn
     *            Maximale Anzahl an Elementen pro Spalte (falls möglich).
     */
    public static MultipleElementsPanel<JLabel> createFromTexts(List<String> texts,
            int maximalNumberOfColumnns, int maximalNumberOfElementsPerColumnn) {
        List<JLabel> labels = new ArrayList<>();

        for (String text : texts) {
            JLabel label = createTextLabel(text);
            labels.add(label);
        }

        return new MultipleElementsPanel<JLabel>(labels, maximalNumberOfColumnns,
                maximalNumberOfElementsPerColumnn);
    }

    public static JLabel createTextLabel(String text) {
        JLabel label = new JLabel(text);
        GuiTools.biggerFont(label, 2);
        label.setBorder(BorderFactory.createLineBorder(Color.BLUE, 1));
        return label;
    }

}
