package de.duehl.swing.ui.dialogs.lists.logic;

/*
 * Copyright 2021 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.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import de.duehl.basics.collections.CollectionsHelper;

/**
 * Diese abstrakte Klasse steht für eine Liste von Datenelementen, welche in einem
 * EditableListDialog angezeigt, bearbeitet, gelöscht, umsortiert oder neu hinzugefügt werden
 * sollen. Solche Klassen müssen nicht diese Klasse erweitern, es reicht, wenn sie
 * LogicalEditableList implementieren.
 *
 * @param <Element> Klasse der Elemente der Liste.
 *
 * @version 1.02     2021-12-01
 * @author Christian Dühl
 */

public abstract class LogicalEditableListBase<Element extends LogicalEditableListElement>
    implements LogicalEditableList<Element>, Iterable<Element> {

    /** Liste der Elemente. */
    private final List<Element> list;

    /** Konstruktor für eine leere Liste. */
    public LogicalEditableListBase() {
        list = new ArrayList<>();
    }

    /** Damit man mit einer for-Schleife über die Liste iterieren kann. */
    @Override
    public final Iterator<Element> iterator() {
        return list.iterator();
    }

    /** Gibt an, ob die übergebene Aufgaben enthalten ist. */
    @Override
    public final boolean contains(Element element) {
        return list.contains(element);
    }

    /** Fügt das übergebene Element zur Liste hinzu. */
    @Override
    public final void add(Element element) {
        if (canWeAdd(element)) {
            list.add(element);
        }
        else {
            throw new IllegalArgumentException("Das Element\n\t" + element
                    + "\nkann nicht zur Liste hinzugefügt werden, bitte vorher mit "
                    + "canWeAdd(element) überprüfen!");
        }
    }

    /**
     * Prüft, ob das übergebene Element zur Liste hinzugefügt werden kann.
     *
     * Standardmäßig wird geprüft, ob das Element bereits in der Liste enthalten ist.
     * Kann für weitere oder auch weniger Prüfungen überschrieben werden.
     *
     * @param element
     *            Zu überprüfendes Element.
     * @return Wahrheitswert.
     */
    @Override
    public boolean canWeAdd(Element element) {
        return !contains(element);
    }

    /**
     * Entfernt das übergebene Element aus der Liste.
     *
     * Wirft eine Ausnahme, falls das Element nicht in der Liste enthalten ist.
     */
    @Override
    public final void remove(Element element) {
        checkIfElementIsContainedInList(element);
        list.remove(element);
    }

    /** Gibt an, ob das übergebene Element nach oben zu bewegen ist. */
    @Override
    public final boolean canMoveUp(Element element) {
        checkIfElementIsContainedInList(element);
        return list.indexOf(element) > 0;
    }

    /** Bewegt das übergebene Element einen Platz in der Liste aufwärts, falls möglich. */
    @Override
    public final void moveUp(Element element) {
        checkIfElementIsContainedInList(element);
        if (canMoveUp(element)) {
            int indexOfElement = list.indexOf(element);
            int indexBefore = indexOfElement - 1;
            Collections.swap(list, indexOfElement, indexBefore);
        }
    }

    /** Gibt an, ob das übergebene Element nach unten zu bewegen ist. */
    @Override
    public final boolean canMoveDown(Element element) {
        checkIfElementIsContainedInList(element);
        return list.indexOf(element) < list.size() - 1;
    }

    /** Bewegt das übergebene Element einen Platz in der Liste abwärts, falls möglich. */
    @Override
    public final void moveDown(Element element) {
        checkIfElementIsContainedInList(element);
        if (canMoveDown(element)) {
            int indexOfElement = list.indexOf(element);
            int indexBefore = indexOfElement + 1;
            Collections.swap(list, indexOfElement, indexBefore);
        }
    }

    private void checkIfElementIsContainedInList(Element element) {
        if (!list.contains(element)) {
            throw new IllegalArgumentException(
                    "Das übergebene Element " + element + " ist nicht in der Liste enthalten!");
        }
    }

    /** Gibt die Anzahl der Elemente zurück. */
    public final int size() {
        return list.size();
    }

    /**
     * Gibt das Element an dem übergebenen Index zurück.
     *
     * @throws IndexOutOfBoundsException
     *             falls der Index ungültig ist ({@code index < 0 || index >= size()})
     */
    public final Element get(int index) {
        return list.get(index);
    }

    /** Gibt die Liste zurück. */
    protected final List<Element> getList() {
        return list;
    }

    /** Gibt an, ob das übergebene Element mindestens zweimal in der Liste enthalten ist. */
    @Override
    public final boolean containedMoreThanOnce(Element element) {
        return CollectionsHelper.containedMoreThanOnce(list, element);
    }

    /** Leert die Liste. */
    @Override
    public void clear() {
        list.clear();
    }

    /** Gibt an, ob die Liste leer ist. */
    public boolean isEmpty() {
        return list.isEmpty();
    }

    @Override
    public String toString() {
        return "LogicalEditableListBase [list=" + list + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((list == null) ? 0 : list.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        LogicalEditableListBase<?> other = (LogicalEditableListBase<?>) obj;
        if (list == null) {
            if (other.list != null)
                return false;
        }
        else if (!list.equals(other.list))
            return false;
        return true;
    }

}
