package de.duehl.twosidecommander.ui.list;

import java.util.ArrayList;
import java.util.List;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Rectangle;

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

import de.duehl.basics.text.NumberString;
import de.duehl.swing.logic.LongTimeProcessInformer;
import de.duehl.swing.ui.GuiTools;
import de.duehl.swing.ui.layout.VerticalLayout;
import de.duehl.twosidecommander.ui.data.ListDisplayerClickReactor;
import de.duehl.twosidecommander.ui.list.data.ListElementMoveReactor;
import de.duehl.twosidecommander.ui.list.element.ListElementDisplayer;

/**
 * Diese Klasse stellt die abstrakte Basis für die grafische Oberfläche einer Liste 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 abstract class ListDisplayer implements ListElementMoveReactor {

    /** Die Hintergrundfarbe der Zeile am Fuß mit der Anzahl der Elemente. */
    private static final Color BOTTOM_LINE_BACKGROUND_COLOR = new Color(230, 230, 230);

    /** Der Abstand der Einträge in der Liste. */
    private static final int LIST_ELEMENT_DISTANCE = 0;

    /** Legt fest, wie viele Elemente man mit PageUp / PageDown weiter springt. */
    private static final int PAGE_DELTA = 25;


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

    /** Die Liste der dargestellten Elemente. */
    private final List<ListElementDisplayer> listElementDisplayers;

    /** Die ScrollPane um den Panel der Liste. */
    private final JScrollPane listScroll;

    /** Das aktive Element. */
    private ListElementDisplayer activeListElementDisplayer;

    /** Die Liste der ausgewählten Elemente. */
    private final List<ListElementDisplayer> selectedListElementDisplayer;

    /** Das aktive Element vor Veränderungen an der Liste. */
    private ListElementDisplayer storedActiveElementDisplayer;

    /** Der Index des aktiven ElementDisplayers vor Veränderungen an der Liste. */
    private int storedActiveElementDisplayerIndex;

    /** Der Wert der vertikalen ScrollBar vor Veränderungen an der Liste. */
    private int storedScrollValue;

    /** Das Objekt, das auf den Klick auf ein Element in der übergebenen Liste reagiert. */
    private ListDisplayerClickReactor listDisplayerClickReactor;

    /** Das Objekt das eine GlassPane bei längeren Operationen anzeigen kann. */
    private LongTimeProcessInformer longTimeProcessInformer;

    /** Gibt an, ob es sich um die aktive Seite handelt. */
    private boolean active;

    /** Der ListDisplayer auf der anderen Seite. */
    private ListDisplayer listDisplayerOnTheOtherSide;

    /** Gibt an, ob der ListDisplayer auf der anderen Seite gesetzt wurde. */
    private boolean listDisplayerOnTheOtherSideSet;

    /**
     * Das Label am Ende der Liste, welches anzeigt, wie viele Elemente in der Liste sind und
     * wieviele man ausgewählt hat.
     */
    private final JLabel bottomLineLabel;

    /** Konstruktor. */
    public ListDisplayer() {
        panel = new JPanel();
        listScroll = GuiTools.addNotHorizontalScrollingScrollPane(panel);

        bottomLineLabel = new JLabel();

        listElementDisplayers = new ArrayList<>();
        selectedListElementDisplayer = new ArrayList<>();
        active = false;

        init();
    }

    private void init() {
        initPanel();
        initScroll();
        initBottomLineLabel();
    }

    private void initPanel() {
        panel.setLayout(new VerticalLayout(LIST_ELEMENT_DISTANCE, VerticalLayout.BOTH));
        GuiTools.createTitle(panel);
        //GuiTools.ignoreTabulatorInComponent(panel);
        // Der verhindert hoch / runter / home /end
    }

    private void initScroll() {
        //GuiTools.ignoreTabulatorInComponent(displayScroll);
        // Der verhindert hoch / runter / home /end
    }

    private void initBottomLineLabel() {
        GuiTools.biggerFont(bottomLineLabel, 1);
        bottomLineLabel.setOpaque(true);
        bottomLineLabel.setBackground(BOTTOM_LINE_BACKGROUND_COLOR);
        bottomLineLabel.setBorder(BorderFactory.createEmptyBorder(2, 15, 2, 15));
    }

    /**
     * Setter für das Objekt, das auf den Klick auf ein Element in der übergebenen Liste reagiert.
     */
    public void setListDisplayerClickReactor(ListDisplayerClickReactor listDisplayerClickReactor) {
        this.listDisplayerClickReactor = listDisplayerClickReactor;
    }

    /** Setter für das Objekt, das eine GlassPane bei längeren Operationen anzeigen kann. */
    public void setLongTimeProcessInformer(LongTimeProcessInformer longTimeProcessInformer) {
        this.longTimeProcessInformer = longTimeProcessInformer;
    }

    /**
     * Wird aufgerufen, wenn der Benutzer im Selector eine neue Liste ausgewählt hat und zeigt
     * deren Inhalt, die Elemente der Liste, an.
     *
     * Dies wird hier ohne Aufruf eines eigenen Threads gemacht, da es sonst zu Komplikationen wie
     * mehrfach angezeigten Elementen und weiterlaufenden Threads kommt, da dies ja auf beiden
     * Seiten aufgerufen wird.
     */
    public synchronized final void readAndShowList() {
        longTimeProcessInformer.startLongTimeProcess("Lese Liste neu ein");

        selectedListElementDisplayer.clear();
        panel.removeAll();
        listElementDisplayers.clear();
        List<ListElementDisplayer> listElementDisplayers = createElementDisplyersToShow();

        for (ListElementDisplayer listElementDisplayer : listElementDisplayers) {
            addElementToPanel(listElementDisplayer);
        }

        //repaintPanel(); // nötig? Unten nochmal!
        scrollToTop();

        if (!listElementDisplayers.isEmpty()) {
            activeListElementDisplayer = listElementDisplayers.get(0);
        }
        adjustBottomLineText();

        longTimeProcessInformer.endLongTimeProcess();
        repaintPanel();
    }

    /**
     * Wird aufgerufen, wenn der Benutzer im Selector eine neue Liste ausgewählt hat. Man erzeugt
     * aus dem Inhalt der Liste, also ihren Elementen, ListElementDisplayer-Objekte und gibt diese
     * zurück. Im Anschluss werden sie angezeigt.
     */
    protected abstract List<ListElementDisplayer> createElementDisplyersToShow();

    /** Fügt ein Display eines Listenelements hinzu. */
    private void addElementToPanel(ListElementDisplayer listElementDisplayer) {
        listElementDisplayer.setListElementDisplayerClickReactor(
                aListElementDisplayer -> clickedOnListElement(aListElementDisplayer));
        listElementDisplayer.setListElementDisplayerShiftClickReactor(
                aListElementDisplayer -> shiftClickedOnListElement(aListElementDisplayer));
        panel.add(listElementDisplayer.getPanel());
        listElementDisplayers.add(listElementDisplayer);
    }

    private void adjustBottomLineText() {
        int numberOfElements = listElementDisplayers.size();

        int numberOfSelectedElements = selectedListElementDisplayer.size();

        String text;
        if (numberOfSelectedElements > 0) {
            if (numberOfElements == 1) {
                text = "ein Element von einem Element";
            }
            else {
                String selectedElemente = NumberString.germanPlural(numberOfSelectedElements,
                        "Elemente", "Element");

                text = NumberString.taupu(numberOfSelectedElements) + " " + selectedElemente
                        + " von " + NumberString.taupu(numberOfElements) + " Elementen";
            }
        }
        else {
            String elemente = NumberString.germanPlural(numberOfElements, "Elemente", "Element");
            text = NumberString.taupu(numberOfElements) + " " + elemente;
        }
        bottomLineLabel.setText(text);
    }

    /** Reagiert auf einen unmodifizierten Linksklick auf das übergebene Element. */
    private void clickedOnListElement(ListElementDisplayer listElementDisplayer) {
        activeListElementDisplayer = listElementDisplayer;

        boolean contained = selectedListElementDisplayer.contains(listElementDisplayer);
        selectedListElementDisplayer.clear();
        if (contained) {
            selectedListElementDisplayer.add(activeListElementDisplayer);
        }
        adjustBottomLineText();

        listDisplayerClickReactor.clickedOnList(this);
    }

    /**
     * Reagiert auf einen Linksklick mit gedrückter Shift-Taste auf das übergebene Element.
     *
     * Hier wird vom aktiven Element bis zum Element auf das shift-geklickt wurde, alles
     * ausgewählt.
     *
     * Waren diese bereits ausgewählt, wird alles bis auf das shift-geklickte entselektiert.
     */
    private void shiftClickedOnListElement(ListElementDisplayer listElementDisplayer) {
        int clickedIndex = listElementDisplayers.indexOf(listElementDisplayer);
        int activeIndex = listElementDisplayers.indexOf(activeListElementDisplayer);

        int fromIndex;
        int toIndex;
        if (clickedIndex <= activeIndex) {
            fromIndex = clickedIndex;
            toIndex = activeIndex;
        }
        else {
            fromIndex = activeIndex;
            toIndex = clickedIndex;
        }

        for (int index = fromIndex; index <= toIndex; ++index) {
            ListElementDisplayer elementDisplayer = listElementDisplayers.get(index);
            if (selectedListElementDisplayer.contains(elementDisplayer)) {
                if (!elementDisplayer.equals(activeListElementDisplayer)) {
                    selectedListElementDisplayer.remove(elementDisplayer);
                }
            }
            else {
                selectedListElementDisplayer.add(elementDisplayer);
            }
        }
        adjustBottomLineText();

        activeListElementDisplayer = listElementDisplayer;
        listDisplayerClickReactor.clickedOnList(this);
    }

    /** Reagiert darauf, dass der Benutzer die Eingabetaste gedrückt hat. */
    public void insertPressed() {
        if (selectedListElementDisplayer.contains(activeListElementDisplayer)) {
            selectedListElementDisplayer.remove(activeListElementDisplayer);
        }
        else {
            selectedListElementDisplayer.add(activeListElementDisplayer);
        }
        adjustBottomLineText();
        showCorrectHighlighting();
    }

    /** Selektiert alle Elemente. */
    public void selectAll() {
        selectedListElementDisplayer.clear();
        selectedListElementDisplayer.addAll(listElementDisplayers);
        adjustBottomLineText();
        showCorrectHighlighting();
    }

    /** Entfernt alle selektierten Elemente. */
    public void deselectAll() {
        selectedListElementDisplayer.clear();
        adjustBottomLineText();
        showCorrectHighlighting();
    }

    /** Zeichnet den Panel neu. */
    private void repaintPanel() {
        panel.repaint();
        panel.validate();
        panel.invalidate();

        listScroll.repaint();
        listScroll.validate();
        listScroll.invalidate();
    }

    /** Scrollt die ScrollPane nach oben. Wird später im EDT ausgeführt. */
    private void scrollToTop() {
        GuiTools.scrollScrollbarToMinimumLater(listScroll);
    }

    /** Getter für die Ui-Komponente, auf dem die Liste dargestellt wird. */
    public final Component getComponent() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());

        panel.add(listScroll, BorderLayout.CENTER);
        panel.add(createBottomLineLabelPart(), BorderLayout.SOUTH);

        return panel;
    }

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

        panel.add(bottomLineLabel, BorderLayout.CENTER);

        return panel;
    }

    /** Legt fest, ob es sich um die aktive Seite handelt. */
    public void setActive(boolean active) {
        this.active = active;
    }

    /** Stellt das Highlighting der Liste aktiv dar. */
    public final void showCorrectHighlighting() {
        if (active) {
            showActiveHighlighting();
        }
        else {
            showInactiveHighlighting();

        }
    }

    /** Stellt das Highlighting der Liste aktiv dar. */
    private final void showActiveHighlighting() {
        for (ListElementDisplayer listElementDisplayer : listElementDisplayers) {
            if (selectedListElementDisplayer.contains(listElementDisplayer)) {
                if (listElementDisplayer.equals(activeListElementDisplayer)) {
                    listElementDisplayer.showSelectedAndHighlighted();
                }
                else {
                    listElementDisplayer.showSelected();
                }
            }
            else if (listElementDisplayer.equals(activeListElementDisplayer)) {
                listElementDisplayer.showHighlighted();
            }
            else {
                listElementDisplayer.showNormal();
            }
        }

        repaintPanel();

        //panel.requestFocusInWindow();
        // Das verhindert hoch / runter / home /end
    }

    /** Stellt das Highlighting der Liste aktiv dar. */
    private final void showInactiveHighlighting() {
        /*
         * Falls man auch auf der inaktiven Seite die Selection anzeigen will, siehe
         * showActiveHighlighting()
         */
        for (ListElementDisplayer listElementDisplayer : listElementDisplayers) {
            listElementDisplayer.showNormal();
        }

        repaintPanel();
    }

    /** Bewegt die Hervorhebung (nicht das Element) eine Position nach oben. */
    public void up() {
        int index = listElementDisplayers.indexOf(activeListElementDisplayer);
        if (index > 0) {
            --index;
            activeListElementDisplayer = listElementDisplayers.get(index);
            showCorrectHighlighting();
            scrollToActiveListElement();
        }
    }

    /** Bewegt die Hervorhebung (nicht das Element) eine Position nach unten. */
    public void down() {
        int index = listElementDisplayers.indexOf(activeListElementDisplayer);
        if (index != -1 && index < listElementDisplayers.size() - 1) {
            ++index;
            activeListElementDisplayer = listElementDisplayers.get(index);
            showCorrectHighlighting();
            scrollToActiveListElement();
        }
    }

    /** Bewegt die Hervorhebung (nicht das Element) an den Anfang der Liste. */
    public void home() {
        activeListElementDisplayer = listElementDisplayers.get(0);
        showCorrectHighlighting();
        scrollToActiveListElement();
    }

    /** Bewegt die Hervorhebung (nicht das Element) an das Ende der Liste. */
    public void end() {
        activeListElementDisplayer = listElementDisplayers.get(listElementDisplayers.size() - 1);
        showCorrectHighlighting();
        scrollToActiveListElement();
    }

    /** Bewegt die Hervorhebung (nicht das Element) eine "Seite" nach oben. */
    public void pageUp() {
        int index = listElementDisplayers.indexOf(activeListElementDisplayer);
        if (index > 0) {
            index -= PAGE_DELTA;
            if (index < 0) {
                index = 0;
            }
            activeListElementDisplayer = listElementDisplayers.get(index);
            showCorrectHighlighting();
            scrollToActiveListElement();
        }
    }

    /** Bewegt die Hervorhebung (nicht das Element) eine "Seite" nach unten. */
    public void pageDown() {
        int index = listElementDisplayers.indexOf(activeListElementDisplayer);
        if (index != -1 && index < listElementDisplayers.size() - 1) {
            index += PAGE_DELTA;
            if (index > listElementDisplayers.size() - 1) {
                index = listElementDisplayers.size() - 1;
            }
            activeListElementDisplayer = listElementDisplayers.get(index);
            showCorrectHighlighting();
            scrollToActiveListElement();
        }
    }

    /**
     * Diese Methode wird aufgerufen, bevor Veränderungen an der Liste vorgenommen werden. Es
     * werden das aktive Element, der Index des aktiven Elements (nicht das Element, falls diese
     * verschoben oder gelöscht wird) und die Position der Scrollbar gespeichert, um diese nach
     * Modifikationen wieder herstellen zu können.
     */
    private void storeBeforeListMofications() {
        storedActiveElementDisplayer = activeListElementDisplayer;
        // null wenn Liste leer ist

        storedActiveElementDisplayerIndex = listElementDisplayers.indexOf(activeListElementDisplayer);
        // -1 wenn Liste leer ist

        storedScrollValue = listScroll.getVerticalScrollBar().getValue();
        //storedScrollVisibleRect = listScroll.getVerticalScrollBar().getVisibleRect();
    }

    /**
     * Diese Methode wird nach Veränderungen an der Liste aufgerufen und das aktive Element und die
     * Position der ScrollBar wird so gut es geht wieder hergestellt.
     */
    private void resetToStoredValuesAfterListModifications() {
        if (listElementDisplayers.contains(storedActiveElementDisplayer)) {
            activeListElementDisplayer = storedActiveElementDisplayer;
        }
        else if (storedActiveElementDisplayerIndex == -1) { // Liste war leer
            if (!listElementDisplayers.isEmpty()) {
                activeListElementDisplayer = listElementDisplayers.get(0);
            }
        }
        else if (listElementDisplayers.isEmpty()) { // Liste ist jetzt leer
            activeListElementDisplayer = null;
        }
        else {
            int maximumIndex = listElementDisplayers.size() - 1;
            int index = Math.min(storedActiveElementDisplayerIndex, maximumIndex);
            activeListElementDisplayer = listElementDisplayers.get(index);
        }

        int maximumScrollValue = listScroll.getVerticalScrollBar().getMaximum();
        int scrollValue = Math.min(storedScrollValue, maximumScrollValue);
        SwingUtilities.invokeLater(() -> {
               listScroll.getVerticalScrollBar().setValue(scrollValue);
               //listScroll.getVerticalScrollBar().scrollRectToVisible(storedScrollVisibleRect);
        });
    }

    /** Getter für das aktive Element. */
    public ListElementDisplayer getActiveElementDisplayer() {
        return activeListElementDisplayer;
    }

    /**
     * Getter für die Liste der ausgewählten Elemente.
     *
     * Die Liste der selektierten Elemente kann in beliebiger Reihenfolge sein, daher wird hier
     * eine Liste mit den gleichen Elementen in der Reihenfolge ihres Vorkommens aufgebaut
     */
    public List<ListElementDisplayer> getSelectedListElementDisplayer() {
        List<ListElementDisplayer> orderedList = new ArrayList<>();

        for (ListElementDisplayer listElementDisplayer : listElementDisplayers) {
            if (selectedListElementDisplayer.contains(listElementDisplayer)) {
                orderedList.add(listElementDisplayer);
            }
        }

        return orderedList;
    }

    /** Prüft, ob das übergebene Element als neues Listenelement hinzugefügt werden kann. */
    public abstract boolean canAppend(ListElementDisplayer newListElementDisplayer);

    /** Fügt ein neues Listenelement am Ende hinzu. */
    public void append(List<ListElementDisplayer> newListElementDisplayers) {
        storeBeforeListMofications();
        appendImplmentation(newListElementDisplayers);
        readListEtc();
    }

    /** Fügt die übergebenen neuen Listenelemente am Ende hinzu. */
    protected abstract void appendImplmentation(List<ListElementDisplayer> newListElementDisplayers);

    /** Löscht das übergebene Listenelement aus der Liste. */
    public void removeFromActiveList(List<ListElementDisplayer> listElementDisplayersToDelete) {
        storeBeforeListMofications();
        removeImplmentation(listElementDisplayersToDelete);
        readListEtc();
    }

    /** Löscht die übergebenen Listenelemente aus der Liste. */
    protected abstract void removeImplmentation(
            List<ListElementDisplayer> listElementDisplayersToDelete);

    /** Gibt eine Beschreibung der Liste zur Anzeige in Dialogen zurück. */
    public abstract String getListDescription();

    /** Zeigt die Buttons zum Verschieben an oder blendet wie aus. */
    public final void showMoveButtonsOnListElements(boolean showMoveButtonsOnListElements) {
        for (ListElementDisplayer listElementDisplayer : listElementDisplayers) {
            listElementDisplayer.showMoveButtonsOnListElements(showMoveButtonsOnListElements);
        }
    }

    /** Gibt an, ob das Element in der Liste nach oben bewegt werden kann. */
    @Override
    public boolean canListElementMoveUp(ListElementDisplayer displayer) {
        int index = listElementDisplayers.indexOf(displayer);
        return index > 0;
    }

    /** Gibt an, ob das Element in der Liste nach unten bewegt werden kann. */
    @Override
    public boolean canListElementMoveDown(ListElementDisplayer displayer) {
        int index = listElementDisplayers.indexOf(displayer);
        return index != -1 && index < listElementDisplayers.size() - 1;
    }

    /** Verschiebt das Element in der Liste an die erste Stelle. */
    @Override
    public void moveListElementToFirst(ListElementDisplayer displayer) {
        storeBeforeListMofications();
        moveListElementToFirstInImplementation(displayer);
        readListEtc();
        rereadInactiveListeWhenActiveListIsChangedAndTheSameListShown();
    }

    /** Verschiebt das Element in der Liste an die erste Stelle. */
    protected abstract void moveListElementToFirstInImplementation(ListElementDisplayer displayer);

    /** Verschiebt das Element in der Liste nach oben. */
    @Override
    public void moveListElementUp(ListElementDisplayer displayer) {
        storeBeforeListMofications();
        moveListElementUpInImplementation(displayer);
        readListEtc();
        rereadInactiveListeWhenActiveListIsChangedAndTheSameListShown();
    }

    /** Verschiebt das Element in der Liste nach oben. */
    protected abstract void moveListElementUpInImplementation(ListElementDisplayer displayer);

    /** Verschiebt das Element in der Liste nach unten. */
    @Override
    public void moveListElementDown(ListElementDisplayer displayer) {
        storeBeforeListMofications();
        moveListElementDownInImplementation(displayer);
        readListEtc();
        rereadInactiveListeWhenActiveListIsChangedAndTheSameListShown();
    }

    /** Verschiebt das Element in der Liste nach oben. */
    protected abstract void moveListElementDownInImplementation(ListElementDisplayer displayer);

    /** Verschiebt das Element in der Liste an die letzte Stelle. */
    @Override
    public void moveListElementToLast(ListElementDisplayer displayer) {
        storeBeforeListMofications();
        moveListElementToLastInImplementation(displayer);
        readListEtc();
        rereadInactiveListeWhenActiveListIsChangedAndTheSameListShown();
    }

    /** Verschiebt das Element in der Liste an die letzte Stelle. */
    protected abstract void moveListElementToLastInImplementation(ListElementDisplayer displayer);

    private void readListEtc() {
        boolean showMoveButtonsOnListElements = active
                && !listElementDisplayers.isEmpty()
                && listElementDisplayers.get(0).showsMoveButtons();
        readListEtc(showMoveButtonsOnListElements);
    }

    private void readListEtc(boolean showMoveButtonsOnListElements) {
        readAndShowList();
        showMoveButtonsOnListElements(showMoveButtonsOnListElements);
        initButtonColorsAndEnabled();
        resetToStoredValuesAfterListModifications();
        showCorrectHighlighting();
    }

    /** Setter für den ListDisplayer auf der anderen Seite. */
    public void setListDisplayerOnTheOtherSide(ListDisplayer listDisplayerOnTheOtherSide) {
        this.listDisplayerOnTheOtherSide = listDisplayerOnTheOtherSide;
        listDisplayerOnTheOtherSideSet = true;
    }

    /**
     * Liest die Liste neu ein, wenn beide Seiten die gleichen Listen anzeigen und die andere
     * geändert wurde.
     */
    private void rereadInactiveListeWhenActiveListIsChangedAndTheSameListShown() {
        if (active && listDisplayerOnTheOtherSideSet) {
            listDisplayerOnTheOtherSide.storeBeforeListMofications();
            listDisplayerOnTheOtherSide.readListEtc(false);
        }
    }

    /**
     * Setzt die Farben und Darstellung der Buttons aller Elemente abhängig davon, ob sie
     * verschoben werden können.
     */
    public void initButtonColorsAndEnabled() {
        for (ListElementDisplayer listElementDisplayer : listElementDisplayers) {
            listElementDisplayer.setUpAndDownButtonColorsAndEnabled();
        }
    }

    private void scrollToActiveListElement() {
        Rectangle rectangle = calculateActiveListElementRectangle();
        panel.scrollRectToVisible(rectangle);
        /*
         * Man muss den Panel scrollen, nicht die ScrollPane, auch wenn das seltsam scheint!
         */
        /*
         * Alternative:
            Rectangle r = listScroll.getVisibleRect();
            if (!r.contains(targetRectangle)) {
                panel.scrollRectToVisible(targetRectangle);
            }
            Aber das klappt auch so.
         */
    }

    private Rectangle calculateActiveListElementRectangle() {
        int indexOfActiveElement = listElementDisplayers.indexOf(activeListElementDisplayer);
        if (indexOfActiveElement != -1) {
            int startY = 0;
            for (int index = 0; index < indexOfActiveElement; ++index) {
                ListElementDisplayer element = listElementDisplayers.get(index);
                JPanel panel = element.getPanel();
                startY += panel.getHeight();
            }
            ListElementDisplayer activeElement = listElementDisplayers.get(indexOfActiveElement);
            JPanel panel = activeElement.getPanel();
            int width = panel.getWidth();
            int height = panel.getHeight();
            return new Rectangle(0, startY, width, height);
        }
        else {
            return new Rectangle(0, 0, 0, 0);
        }
    }

}
