package de.duehl.swing.ui.dialogs;

/*
 * Copyright 2020 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.CardLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;

import de.duehl.basics.collections.CollectionsHelper;
import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.dialogs.base.ModalDialogBase;
import de.duehl.swing.ui.elements.navigator.NavigatorPanel;
import de.duehl.swing.ui.layout.VerticalLayout;

/**
 * Diese Klasse öffnet einen Dialog zur Bestätigung, Ablehnung oder Modifikation einer Auswahl.
 *
 * @version 1.02     2020-01-17
 * @author Christian Dühl
 */

public class SelectionVerificationDialog extends ModalDialogBase {

    private static final Dimension DIALOG_DIMENSION = new Dimension(850, 600);
    private static final int MAX_LENGTH =
            (int) (DIALOG_DIMENSION.getWidth() - 50) / 10;
    private static final int NUMBER_OF_THINGS_PER_COLUMN =
            (int) (DIALOG_DIMENSION.getHeight() - 140) / 27;

    private static final int MAX_LENGTH_FOR_FOUR_COLUMNS = MAX_LENGTH / 4;
    private static final int MAX_LENGTH_FOR_THREE_COLUMNS = MAX_LENGTH / 3;
    private static final int MAX_LENGTH_FOR_TWO_COLUMNS = MAX_LENGTH / 2;


    /** Gibt an, ob der Benutzer den Dialog mit OK bestätigt hat. */
    private boolean userPressedOk;

    /** Liste mit der Auswahl, die man hier modifizieren kann. */
    private final List<String> thingsToSelect;

    /** Anzahl der Spalten mit angezeigten Dingen. */
    private int numberOfColumns;

    /** Anzahl der Dinge pro Seite. */
    private int numberOfThingsPerPage;

    /** Anzahl der angezeigten Seiten. */
    private int numberOfPages;

    /** Nummer der angezeigten Seite. */
    private int numberOfShownPage;

    /** Liste mit den anzuzeigenden Seiten. */
    private List<Component> pages;

    /** Liste mit den Checkboxen zur Auswahl der Dinge. */
    private List<JCheckBox> thingsWithSelection;

    /** Panel mit den Seiten. */
    private JPanel pagesPanel;

    /** Liste mit der vom Benutzer bestätigten Auswahl. */
    private List<String> selectedThings;
    private String bareTitle;

    /**
     * Konstruktor.
     *
     * @param thingsToSelect
     *            Liste mit der Auswahl, die man hier modifizieren kann.
     * @param title
     *            Titel des Dialogs.
     */
    public SelectionVerificationDialog(List<String> thingsToSelect, String title) {
        this(thingsToSelect, new Point(250, 100), title);
    }

    /**
     * Konstruktor.
     *
     * @param thingsToSelect
     *            Liste mit der Auswahl, die man hier modifizieren kann.
     * @param parentLocation
     *            Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird.
     * @param title
     *            Titel des Dialogs.
     */
    public SelectionVerificationDialog(List<String> thingsToSelect, Point parentLocation,
            String title) {
        this(thingsToSelect, parentLocation, null, title);
    }

    /**
     * Konstruktor.
     *
     * @param thingsToSelect
     *            Liste mit der Auswahl, die man hier modifizieren kann.
     * @param parentLocation
     *            Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird.
     * @param programImage
     *            Image für den Dialog.
     * @param title
     *            Titel des Dialogs.
     */
    public SelectionVerificationDialog(List<String> thingsToSelect, Point parentLocation,
            Image programImage, String title) {
        super(parentLocation, programImage, title, DIALOG_DIMENSION);
        this.thingsToSelect = thingsToSelect;
        bareTitle = title + " ";

        userPressedOk = false;

        init();
        fillDialog();
    }

    private void init() {
        determineNumberOfColumns();
        numberOfThingsPerPage = NUMBER_OF_THINGS_PER_COLUMN * numberOfColumns;
        int numberOfThings = thingsToSelect.size();
        numberOfPages = (int) Math.ceil(((double) numberOfThings) / numberOfThingsPerPage);

        createThingsWithSelection();
        createPages();
        createCardPanel();
    }

    private void determineNumberOfColumns() {
        int maxLength = determineMaximumLengthOfThings();
        if (maxLength <= MAX_LENGTH_FOR_FOUR_COLUMNS) {
            numberOfColumns = 4;
        }
        else if (maxLength <= MAX_LENGTH_FOR_THREE_COLUMNS) {
            numberOfColumns = 3;
        }
        else if (maxLength <= MAX_LENGTH_FOR_TWO_COLUMNS) {
            numberOfColumns = 2;
        }
        else {
            numberOfColumns = 1;
        }
    }

    private int determineMaximumLengthOfThings() {
        int length = 0;

        for (String thing : thingsToSelect) {
            int lengthOfThing = thing.length();
            if (length < lengthOfThing) {
                length = lengthOfThing;
            }
        }

        return length;
    }

    private void createThingsWithSelection() {
        thingsWithSelection = new ArrayList<>();

        for (String thing : thingsToSelect) {
            JCheckBox box = createCheckBox(thing);
            thingsWithSelection.add(box);
        }
    }

    private JCheckBox createCheckBox(String thing) {
        JCheckBox box = new JCheckBox(thing);
        box.setSelected(true);
        return box;
    }

    private void createPages() {
        List<JCheckBox> list = CollectionsHelper.copyList(thingsWithSelection);
        pages = new ArrayList<>();

        List<JCheckBox> boxesForPage = new ArrayList<>();

        while (!list.isEmpty()) {
            JCheckBox box = list.remove(0);
            boxesForPage.add(box);
            if (boxesForPage.size() >= numberOfThingsPerPage) {
                Component page = createPage(boxesForPage);
                pages.add(page);
                boxesForPage = new ArrayList<>();
            }
        }

        if (!boxesForPage.isEmpty()) {
            Component page = createPage(boxesForPage);
            pages.add(page);
        }
    }

    private Component createPage(List<JCheckBox> boxesForPage) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        GuiTools.createTitle(panel);

        panel.add(createPageButtonPart(boxesForPage), BorderLayout.SOUTH);
        panel.add(createPagePart(boxesForPage), BorderLayout.CENTER);

        return panel;
    }

    private Component createPageButtonPart(List<JCheckBox> boxesForPage) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        //GuiTools.createTitle(panel);

        panel.add(createSelectPageBoxesButton(boxesForPage), BorderLayout.WEST);
        panel.add(createDeselectPageBoxesButton(boxesForPage), BorderLayout.EAST);

        return panel;
    }

    private Component createSelectPageBoxesButton(List<JCheckBox> boxesForPage) {
        String title = doWeHaveToShowNavigation()
                ? "<html>Auf dieser Seite alles <b>aus</b>wählen</html>"
                : "<html>Alles <b>aus</b>wählen</html>";
        JButton button = new JButton(title);
        button.addActionListener(e -> selectAllBoxesOnPage(boxesForPage));

        return button;
    }

    private void selectAllBoxesOnPage(List<JCheckBox> boxesForPage) {
        for (JCheckBox box : boxesForPage) {
            box.setSelected(true);
        }
    }

    private Component createDeselectPageBoxesButton(List<JCheckBox> boxesForPage) {
        String title = doWeHaveToShowNavigation()
                ? "<html>Auf dieser Seite alles <b>ab</b>wählen</html>"
                : "<html>Alles <b>ab</b>wählen</html>";
        JButton button = new JButton(title);
        button.addActionListener(e -> deselectAllBoxesOnPage(boxesForPage));

        return button;
    }

    private void deselectAllBoxesOnPage(List<JCheckBox> boxesForPage) {
        for (JCheckBox box : boxesForPage) {
            box.setSelected(false);
        }
    }

    private Component createPagePart(List<JCheckBox> boxesForPage) {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(1, numberOfColumns));

        for (int columnIndex = 0; columnIndex < numberOfColumns; ++columnIndex) {
            List<JCheckBox> boxesForColumn = createBoxesForColumn(columnIndex, boxesForPage);
            Component column = createColumn(boxesForColumn);
            panel.add(column);
        }

        panel.setLayout(new GridLayout(1, numberOfColumns));

        return panel;
    }



    private List<JCheckBox> createBoxesForColumn(int columnIndex, List<JCheckBox> boxesForPage) {
        int fromIndex = columnIndex * NUMBER_OF_THINGS_PER_COLUMN;
        int toIndex = (columnIndex  + 1) * NUMBER_OF_THINGS_PER_COLUMN;

        if (fromIndex >= boxesForPage.size()) {
            return new ArrayList<>();
        }
        else if (toIndex > boxesForPage.size()) {
            return boxesForPage.subList(fromIndex, boxesForPage.size());
        }
        else {
            return boxesForPage.subList(fromIndex, toIndex);
        }
    }

    private Component createColumn(List<JCheckBox> boxesForColumn) {
        JPanel panel = new JPanel();
        panel.setLayout(new VerticalLayout(5, VerticalLayout.BOTH));

        for (JCheckBox box : boxesForColumn) {
            panel.add(box);
        }

        return panel;
    }

    private void createCardPanel() {
        pagesPanel = new JPanel(new CardLayout());
        GuiTools.createTitle(pagesPanel);

        for (int pageIndex = 0; pageIndex < numberOfPages; ++pageIndex) {
            Component page = pages.get(pageIndex);
            String pageName = buildPageName(pageIndex + 1);
            pagesPanel.add(page, pageName);
        }
    }

    private String buildPageName(int pageNumber) {
        return "PAGE " + pageNumber;
    }

    /** Baut die Gui auf. */
    @Override
    protected void populateDialog() {
        add(pagesPanel, BorderLayout.CENTER);
        add(createLowerPart(), BorderLayout.SOUTH);

        addEscapeBehaviour(); // sollte das nicht eher in den Konstruktor?

        first();
    }

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

        if (doWeHaveToShowNavigation()) {
            panel.add(createNavigation(), BorderLayout.WEST);
            panel.add(createButtonPart(), BorderLayout.EAST);
        }
        else {
            panel.add(createOkAndCancelButtons(), BorderLayout.CENTER);
        }

        return panel;
    }

    private boolean doWeHaveToShowNavigation() {
        return numberOfPages > 1;
    }

    private Component createNavigation() {
        NavigatorPanel navigator = new NavigatorPanel(new Dimension(50, 50));
        //GuiTools.createTitle(navigator);

        navigator.fillNavigator();
        navigator.addFirstActionListener(e -> first());
        navigator.addPreviousActionListener(e -> previous());
        navigator.addNextActionListener(e -> next());
        navigator.addLastActionListener(e -> last());

        JComponent centeredNavigation = navigator.center();
        GuiTools.createTitle(centeredNavigation);
        return centeredNavigation;
    }

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

    private void previous() {
        if (numberOfShownPage > 1) {
            switchToPage(numberOfShownPage - 1);
        }
    }

    private void next() {
        if (numberOfShownPage < numberOfPages) {
            switchToPage(numberOfShownPage + 1);
        }
    }

    private void last() {
        switchToPage(numberOfPages);
    }

    private void switchToPage(int pageNumber) {
        CardLayout cardLayout = (CardLayout) (pagesPanel.getLayout());
        String pageName = buildPageName(pageNumber);
        cardLayout.show(pagesPanel, pageName);

        numberOfShownPage = pageNumber;

        setTitle(bareTitle + "Seite " + numberOfShownPage + " / " + numberOfPages);
    }

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

        panel.add(createGlobalSelectionButtons(), BorderLayout.WEST);
        //panel.add(new JLabel("                "), BorderLayout.CENTER);
        panel.add(createOkAndCancelButtons(), BorderLayout.EAST);

        return panel;
    }

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

        panel.add(createSelectAllBoxesButton(), BorderLayout.WEST);
        panel.add(createDeselectAllBoxesButton(), BorderLayout.EAST);

        return panel;
    }

    private Component createSelectAllBoxesButton() {
        JButton button = new JButton("<html>Auf allen Seiten<br>alles <b>aus</b>wählen</html>");
        button.addActionListener(e -> selectAllBoxesOnAllPages());

        return button;
    }

    private void selectAllBoxesOnAllPages() {
        for (JCheckBox box : thingsWithSelection) {
            box.setSelected(true);
        }
    }

    private Component createDeselectAllBoxesButton() {
        JButton button = new JButton("<html>Auf allen Seiten<br>alles <b>ab</b>wählen</html>");
        button.addActionListener(e -> deselectAllBoxesOnAllPages());

        return button;
    }

    private void deselectAllBoxesOnAllPages() {
        for (JCheckBox box : thingsWithSelection) {
            box.setSelected(false);
        }
    }

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

        panel.add(createQuitButton(), BorderLayout.WEST);
        panel.add(new JLabel("                "), BorderLayout.CENTER);
        panel.add(createOKButton(), BorderLayout.EAST);

        return panel;
    }

    private Component createOKButton() {
        JButton button = new JButton("OK");
        button.setMargin(new Insets(5, 25, 5, 25));
        GuiTools.boldFont(button);
        button.addActionListener(e -> apply());

        return button;
    }

    private void apply() {
        userPressedOk = true;
        selectedThings = new ArrayList<>();
        for (JCheckBox box : thingsWithSelection) {
            if (box.isSelected()) {
                String thing = box.getText();
                selectedThings.add(thing);
            }
        }
        closeDialog();
    }

    private JButton createQuitButton() {
        JButton button = new JButton("Abbruch");
        button.addActionListener(e -> cancel());

        return button;
    }

    private void cancel() {
        userPressedOk = false;
        closeDialog();
    }

    /** Gibt an, ob der Benutzer den Dialog mit OK bestätigt hat. */
    public boolean userPressedOk() {
        return userPressedOk;
    }

    /** Getter für die Liste mit der vom Benutzer bestätigten Auswahl. */
    public List<String> getSelectedThings() {
        return selectedThings;
    }

}
