package de.duehl.twosidecommander.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

import de.duehl.basics.text.html.generation.SwingHtmlBuilder;
import de.duehl.basics.text.html.generation.tools.Utf8MetaExchanger;
import de.duehl.swing.logic.LongTimeProcessInformer;
import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.dialogs.base.ModalDialogBase;
import de.duehl.swing.ui.text.html.HtmlDialog;
import de.duehl.twosidecommander.ui.data.Side;
import de.duehl.twosidecommander.ui.list.ListDisplayer;
import de.duehl.twosidecommander.ui.list.element.ListElementDisplayer;
import de.duehl.twosidecommander.ui.selector.ListSelector;

/**
 * Diese Klasse stellt die grafische Oberfläche des Listen-Commanders dar, mit dem sich generisch
 * zwischen zwei Listen von Dingen Listenelemente kopieren, verschieben oder löschen lassen.
 *
 * @version 1.01     2025-08-15
 * @author Christian Dühl
 */

public class TwoSideCommander extends ModalDialogBase {

    private static final Dimension DIALOG_DIMENSION = new Dimension(1500, 1000);


    /** Der Selector der linken Liste. */
    private ListSelector leftListSelector;

    /** Der Selector der rechten Liste. */
    private ListSelector rightListSelector;

    /** Der Displayer der linken Liste. */
    private ListDisplayer leftListDisplayer;

    /** Der Displayer der rechten Liste. */
    private ListDisplayer rightListDisplayer;

    /** Die aktive Seite. */
    private Side activeSide;

    /**
     * Der von der Implementation gesetzte Panel mit weiteren Buttons, die unten zentriert
     * angeordnet werden.
     */
    private final JPanel middleButtonPanel;

    /** Der Button zum Anzeigen oder Verstecken der Buttons zum Bewegen der Bars den Listen. */
    private final JButton toggleMoveButtonsButton;

    /** Gibt an, ob die Buttons zum Verschieben auf den Elementen den Listen angezeigt werden. */
    private boolean showMoveButtonsOnListElements;

    /**
     * Konstruktor.
     *
     * @param title
     *            Der Titel des Dialoges.
     * @param parentLocation
     *            Die Position des Rahmens der Oberfläche, vor der dieser Dialog erzeugt wird.
     * @param programImage
     *            Das Icon für das Programm.
     */
    public TwoSideCommander(String title, Point parentLocation, Image programImage) {
        super(parentLocation, programImage, title, DIALOG_DIMENSION);

        activeSide = Side.LEFT;
        middleButtonPanel = new JPanel();
        toggleMoveButtonsButton = new JButton();
        showMoveButtonsOnListElements = false;

        init();
    }

    private void init() {
        initMiddleButtonPanel();
        setToggleMoveButtonsButtonShowAndHideText();
        initToggleMoveButtonsButton();
    }

    private void initMiddleButtonPanel() {
        middleButtonPanel.setLayout(new GridLayout(1, 0, 2, 2));
    }

    private void initToggleMoveButtonsButton() {
        toggleMoveButtonsButton.addActionListener(e -> toggleMoveButtons());
    }

    private void toggleMoveButtons() {
        showMoveButtonsOnListElements = !showMoveButtonsOnListElements;
        setToggleMoveButtonsButtonShowAndHideText();
        activateAndDeactivateMoveButtonsOnBothSides();
    }

    private void activateAndDeactivateMoveButtonsOnBothSides() {
        if (showMoveButtonsOnListElements) {
            ListDisplayer activeDisplayer = getActiveListDisplayer();
            ListDisplayer inactiveDisplayer = getInactiveListDisplayer();

            activeDisplayer.showMoveButtonsOnListElements(true);
            activeDisplayer.initButtonColorsAndEnabled();

            //inactiveDisplayer.showMoveButtonsOnListElements(true); // Zur Initialisierung...
            inactiveDisplayer.showMoveButtonsOnListElements(false);
            inactiveDisplayer.initButtonColorsAndEnabled();
        }
        else {
            leftListDisplayer.showMoveButtonsOnListElements(false);
            rightListDisplayer.showMoveButtonsOnListElements(false);
        }
        repaint();
        validate();
        invalidate();
    }

    /** Setter für den Displayer der linken Liste. */
    public final void setLeftListDisplayer(ListDisplayer leftListDisplayer) {
        this.leftListDisplayer = leftListDisplayer;
        leftListDisplayer.setListDisplayerClickReactor(
                listDisplay -> clickedOnListElementOfList(listDisplay));
        leftListDisplayer.setLongTimeProcessInformer((LongTimeProcessInformer) this);
        leftListDisplayer.setActive(true);
    }

    /** Setter für den Displayer der rechten Liste. */
    public final void setRightListDisplayer(ListDisplayer rightListDisplayer) {
        this.rightListDisplayer = rightListDisplayer;
        rightListDisplayer.setListDisplayerClickReactor(
                listDisplay -> clickedOnListElementOfList(listDisplay));
        rightListDisplayer.setLongTimeProcessInformer((LongTimeProcessInformer) this);
        rightListDisplayer.setActive(false);
    }

    private void clickedOnListElementOfList(ListDisplayer listDisplay) {
        ListDisplayer activeDisplayer = getActiveListDisplayer();
        boolean clickedListIsActive = activeDisplayer == listDisplay; // Ja mit == !

        listDisplay.showCorrectHighlighting();

        if (!clickedListIsActive) {
            if (listDisplay == leftListDisplayer) {
                activateLeftSide();
            }
            else {
                activateRightSide();
            }
        }
    }

    /** Setter für den Selector der linken Liste. */
    public final void setLeftListSelector(ListSelector leftListSelector) {
        this.leftListSelector = leftListSelector;
        leftListSelector.setIsUsedCaller(() -> activateLeftSide());
    }

    private void activateLeftSide() {
        activeSide = Side.LEFT;
        showActiveSide();
    }

    /** Setter für den Selector der rechten Liste. */
    public final void setRightListSelector(ListSelector rightListSelector) {
        this.rightListSelector = rightListSelector;
        rightListSelector.setIsUsedCaller(() -> activateRightSide());
    }

    private void activateRightSide() {
        activeSide = Side.RIGHT;
        showActiveSide();
    }

    /**
     * Legt fest, dass es einen Schalter gibt, mit dem man in den Listenelementen Buttons zum
     * verschieben ein- und ausblenden kann.
     */
    public final void enableToggableMovingButtons() {
        middleButtonPanel.add(toggleMoveButtonsButton);
        setKeyBindingF2(() -> toggleMoveButtons());
    }

    private void setToggleMoveButtonsButtonShowAndHideText() {
        String show = "einblenden";
        String hide = "ausblenden";
        String moveShowOrHide = showMoveButtonsOnListElements ? hide : show;

        toggleMoveButtonsButton.setText("<html><center>"
                + "F2 - Die Buttons zum Verschieben"
                + "<br>"
                + moveShowOrHide
                + "</center></html>");
    }

    /** Setzt einen weiteren, zentriert angeordneten Panel mit Buttons. */
    public final void setMiddleButtonBar(Component middleButtonBar) {
        middleButtonPanel.add(middleButtonBar);
    }

    /** Erzeugt die Oberfläche. */
    public final void createGui() {
        checkIfObjectsAreInitialised();
        setOtherLists();
        fillDialog();
        setKeyBindings();
        showActiveSide();
    }

    private void checkIfObjectsAreInitialised() {
        if (null == leftListDisplayer) {
            throw new RuntimeException("Der Displayer für die linke Liste wurde nicht gesetzt.\n"
                    + "Bitte die Methode setLeftListDisplayer() verwenden\n.");
        }
        if (null == rightListDisplayer) {
            throw new RuntimeException("Der Displayer für die rechte Liste wurde nicht gesetzt.\n"
                    + "Bitte die Methode setRightListDisplayer() verwenden\n.");
        }

        if (null == leftListSelector) {
            throw new RuntimeException("Der Selector für die linke Liste wurde nicht gesetzt.\n"
                    + "Bitte die Methode setLeftListSelector() verwenden\n.");
        }
        if (null == rightListSelector) {
            throw new RuntimeException("Der Selector für die rechte Liste wurde nicht gesetzt.\n"
                    + "Bitte die Methode setRightListSelector() verwenden\n.");
        }
    }

    private void setOtherLists() {
        leftListDisplayer.setListDisplayerOnTheOtherSide(rightListDisplayer);
        rightListDisplayer.setListDisplayerOnTheOtherSide(leftListDisplayer);
    }

    @Override
    protected final void populateDialog() {
        add(createHeadPart(), BorderLayout.NORTH);
        add(createTwoListsPart(), BorderLayout.CENTER);
        add(createButtonBar(), BorderLayout.SOUTH);
    }

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

        panel.add(leftListSelector.getPanel());
        panel.add(rightListSelector.getPanel());

        return panel;
    }

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

        panel.add(leftListDisplayer.getComponent());
        panel.add(rightListDisplayer.getComponent());

        return panel;
    }

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

        panel.add(createLeftButtons(), BorderLayout.WEST);
        panel.add(GuiTools.centerHorizontal(middleButtonPanel), BorderLayout.CENTER);
        panel.add(createRightButtons(), BorderLayout.EAST);

        return panel;
    }

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

        panel.add(createKeyUsageButton());
        panel.add(createCopyButton());
        panel.add(createMoveButton());
        panel.add(createDeleteButton());

        return panel;
    }

    private Component createKeyUsageButton() {
        JButton button = new JButton("F1 - Tastenbelegung");
        button.addActionListener(e -> keyUsage());
        return button;
    }

    private Component createCopyButton() {
        JButton button = new JButton("F5 - Kopieren");
        button.addActionListener(e -> copy());
        return button;
    }

    private Component createMoveButton() {
        JButton button = new JButton("F6 - verschieben");
        button.addActionListener(e -> move());
        return button;
    }

    private Component createDeleteButton() {
        JButton button = new JButton("F8 - löschen");
        button.addActionListener(e -> delete());
        return button;
    }

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

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

        return panel;
    }

    private Component createQuitButton() {
        JButton button = new JButton("Beenden");
        button.addActionListener(e -> closeDialog());
        return button;
    }

    /** Erstellt Tastenkombinationen. */
    private void setKeyBindings() {
        setKeyBindingF1(() -> keyUsage());
        setKeyBindingF5(() -> copy());
        setKeyBindingF6(() -> move());
        setKeyBindingF8(() -> delete());
        setKeyBindingDelete(() -> delete());

        GuiTools.setFocusTraversalKeysToEmptySet(getWindowAsComponent()); // nötig für die Bindung
        setKeyBindingTabulator(() -> toOtherSide());                      // an die Tabulator-Taste

        setKeyBindingUp(() -> up());
        setKeyBindingDown(() -> down());
        setKeyBindingHome(() -> home());
        setKeyBindingEnd(() -> end());
        setKeyBindingPageUp(() -> pageUp());
        setKeyBindingPageDown(() -> pageDown());
        setKeyBindingInsert(() -> insert());
        setKeyBindingNumpadPlus(() -> numpadPlus());
        setKeyBindingNumpadMinus(() -> numpadMinus());

        // Neue Belegungen in die Methode keyUsage() eintragen!
    }

    private void keyUsage() {
        String title = "Tastenbelegung im Listen-Commander";
        SwingHtmlBuilder html = new SwingHtmlBuilder()
                .hideTopLinks()
                .appendHtml5HeadWithOwnExtendedCssUtf8(title,
                        SwingHtmlBuilder.createBodyFontSizeInPointCssExtension(16));
        html.appendH1(title);
        html.appendOpeningTableWithBorderWidth(2);

        html.appendOpeningTr();
        html.appendLeftAlignedTh("Taste");
        html.appendLeftAlignedTh("Belegung");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>F1</tt>");
        html.appendTd("Zeigt diese Tastenbelegung an");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>F5</tt>");
        html.appendTd("Kopiert die selektierten Elemente, wenn keine selektiert sind, wird das "
                + "aktive Element kopiert.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>F6</tt>");
        html.appendTd("Verschiebt die selektierten Elemente, wenn keine selektiert sind, wird das "
                + "aktive Element verschoben.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>F8</tt> oder <tt>Entf</tt>");
        html.appendTd("Löscht die selektierten Elemente, wenn keine selektiert sind, wird das "
                + "aktive Element gelöscht.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Tabulator</tt>");
        html.appendTd("Wechselt zur anderen Seite, also zur rechten, wenn die linke aktiv war "
                + "und andersherum.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Cursor hoch</tt>");
        html.appendTd("Bewegt die Markierung des aktiven Elements nach oben.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Cursor runter</tt>");
        html.appendTd("Bewegt die Markierung des aktiven Elements nach unten.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Pos 1</tt>");
        html.appendTd("Bewegt die Markierung des aktiven Elements nach ganz oben.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Ende</tt>");
        html.appendTd("Bewegt die Markierung des aktiven Elements nach ganz unten.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Bild hoch</tt>");
        html.appendTd("Bewegt die Markierung des aktiven Elements einen Abschnitt nach oben.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Bild runter</tt>");
        html.appendTd("Bewegt die Markierung des aktiven Elements einen Abschnitt nach unten.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Einfg</tt>");
        html.appendTd("Selektiert das aktive Element.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Nummernblock-Plus</tt>");
        html.appendTd("Selektiert alle Elemente.");
        html.appendClosingTr();

        html.appendOpeningTr();
        html.appendTd("<tt>Nummernblock-Minus</tt>");
        html.appendTd("Leert die Selektion.");
        html.appendClosingTr();

        html.appendClosingTable();
        html.appendFoot();
        String htmlAsString = html.toString();
        htmlAsString = Utf8MetaExchanger.replaceLongToShort(htmlAsString);

        HtmlDialog dialog = new HtmlDialog(title, getProgramImage(), getLocation());
        dialog.setText(htmlAsString);
        dialog.scrollScrollbarToMinimumLater();
        dialog.setVisible(true);
    }

    private void copy() {
        startLongTimeProcess("Kopiere");
        new Thread(() -> copyInOwnThread()).start();
    }

    private void copyInOwnThread() {
        List<ListElementDisplayer> selectedElements = determineSelectedOrActiveElementDisplayers();
        List<ListElementDisplayer> elementsToCopy = new ArrayList<>();

        ListDisplayer activeDisplayer = getActiveListDisplayer();
        ListDisplayer inactiveDisplayer = getInactiveListDisplayer();

        for (ListElementDisplayer selectedElement : selectedElements) {
            if (inactiveDisplayer.canAppend(selectedElement)) {
                elementsToCopy.add(selectedElement);
            }
            else {
                GuiTools.informUserInEdt(getWindowAsComponent(), "Kopieren nicht möglich",
                        "Das Element '" + selectedElement.getElementDescription()
                                + "' kann nicht in die Liste '"
                                + inactiveDisplayer.getListDescription() + "' kopiert werden.");
            }
        }
        inactiveDisplayer.append(elementsToCopy);

        SwingUtilities.invokeLater(() -> endCopyMoveOrDeleteInEdt(activeDisplayer));
    }

    private void endCopyMoveOrDeleteInEdt(ListDisplayer activeDisplayer) {
        endLongTimeProcess();
        activeDisplayer.deselectAll();
    }

    private void move() {
        startLongTimeProcess("Verschiebe");
        new Thread(() -> moveInOwnThread()).start();
    }

    private void moveInOwnThread() {
        List<ListElementDisplayer> selectedElements = determineSelectedOrActiveElementDisplayers();
        List<ListElementDisplayer> elementsToMove = new ArrayList<>();

        ListDisplayer activeDisplayer = getActiveListDisplayer();
        ListDisplayer inactiveDisplayer = getInactiveListDisplayer();

        for (ListElementDisplayer selectedElement : selectedElements) {
            if (inactiveDisplayer.canAppend(selectedElement)) {
                elementsToMove.add(selectedElement);
            }
            else {
                GuiTools.informUserInEdt(getWindowAsComponent(), "Verschieben nicht möglich",
                        "Das Element '" + selectedElement.getElementDescription()
                                + "' kann nicht in die Liste '"
                                + inactiveDisplayer.getListDescription() + "' verschoben werden.");
            }
        }
        inactiveDisplayer.append(elementsToMove);
        activeDisplayer.removeFromActiveList(elementsToMove);

        SwingUtilities.invokeLater(() -> endCopyMoveOrDeleteInEdt(activeDisplayer));
    }

    private void delete() {
        startLongTimeProcess("Lösche");
        new Thread(() -> deleteInOwnThread()).start();
    }

    private void deleteInOwnThread() {
        List<ListElementDisplayer> elementsToDelete = determineSelectedOrActiveElementDisplayers();
        ListDisplayer activeDisplayer = getActiveListDisplayer();

        activeDisplayer.removeFromActiveList(elementsToDelete);

        SwingUtilities.invokeLater(() -> endCopyMoveOrDeleteInEdt(activeDisplayer));
    }

    private List<ListElementDisplayer> determineSelectedOrActiveElementDisplayers() {
        ListDisplayer activeDisplayer = getActiveListDisplayer();
        ListElementDisplayer activeElementDisplayer = activeDisplayer.getActiveElementDisplayer();
        List<ListElementDisplayer> selectedListElementDisplayer =
                activeDisplayer.getSelectedListElementDisplayer();

        List<ListElementDisplayer> elementsToHandle = new ArrayList<>();
        if (selectedListElementDisplayer.isEmpty()) {
            elementsToHandle.add(activeElementDisplayer);
        }
        else {
            elementsToHandle.addAll(selectedListElementDisplayer);
        }

        return elementsToHandle;
    }

    private void toOtherSide() {
        switch (activeSide) {
            case LEFT:
                activeSide = Side.RIGHT;
                break;
            case RIGHT:
                activeSide = Side.LEFT;
                break;
            default:
                throw new RuntimeException("Unbekannte Seite '" + activeSide + "'");

        }
        showActiveSide();
    }

    private void showActiveSide() {
        switch (activeSide) {
            case LEFT:
                showLeftSideAsActive();
                break;
            case RIGHT:
                showRightSideAsActive();
                break;
            default:
                throw new RuntimeException("Unbekannte Seite '" + activeSide + "'");
        }
    }

    private void showLeftSideAsActive() {
        leftListSelector.showActive();
        rightListSelector.showInactive();

        leftListDisplayer.setActive(true);
        leftListDisplayer.showCorrectHighlighting();
        rightListDisplayer.setActive(false);
        rightListDisplayer.showCorrectHighlighting();

        activateAndDeactivateMoveButtonsOnBothSides();
    }

    private void showRightSideAsActive() {
        rightListSelector.showActive();
        leftListSelector.showInactive();

        rightListDisplayer.setActive(true);
        rightListDisplayer.showCorrectHighlighting();
        leftListDisplayer.setActive(false);
        leftListDisplayer.showCorrectHighlighting();

        activateAndDeactivateMoveButtonsOnBothSides();
    }

    private void up() {
        getActiveListDisplayer().up();
    }

    private void down() {
        getActiveListDisplayer().down();
    }

    private void home() {
        getActiveListDisplayer().home();
    }

    private void end() {
        getActiveListDisplayer().end();
    }

    private void pageUp() {
        getActiveListDisplayer().pageUp();
    }

    private void pageDown() {
        getActiveListDisplayer().pageDown();
    }

    private void insert() {
        getActiveListDisplayer().insertPressed();
        down();
    }

    private void numpadPlus() {
        getActiveListDisplayer().selectAll();
    }

    private void numpadMinus() {
        getActiveListDisplayer().deselectAll();
    }

    /** Gibt den aktiven ListDisplayer zurück. */
    public final ListDisplayer getActiveListDisplayer() {
        switch (activeSide) {
            case LEFT:
                return leftListDisplayer;
            case RIGHT:
                return rightListDisplayer;
            default:
                throw new RuntimeException("Unbekannte Seite '" + activeSide + "'");
        }
    }

    private ListDisplayer getInactiveListDisplayer() {
        switch (activeSide) {
            case LEFT:
                return rightListDisplayer;
            case RIGHT:
                return leftListDisplayer;
            default:
                throw new RuntimeException("Unbekannte Seite '" + activeSide + "'");
        }
    }

}
