package de.duehl.basics.history;

import java.util.ArrayList;
import java.util.List;

import de.duehl.basics.debug.DebugHelper;

/**
 * Diese Klasse hält verschiedene Versionen von Objekten während deren Bearbeitung
 * vor. Die hier vorgehaltenen Objekte werden von denen in der Bearbeitung streng
 * getrennt, da sichergestellt sein muss, dass die Objekte in der Historie nicht in
 * der Oberfäche verändert werden können.                                                <br><br><i>
 *
 * Achtung, die verwalteten Objekte müssen unbedingt equals() und hashCode()
 * implementieren, ansonsten funktioniert die Abfrage auf inhaltliche Gleichheit
 * nicht und es wird immer ein neues Objekt in der Historie abgelegt, auch ohne dass
 * sich wirklich etwas geändert hat!                                                            </i>
 *
 * @version 1.01     2017-12-19
 * @author Christian Dühl
 */

public class History<T> {

    private static final boolean DEBUG = false;

    /** Index des aktuellen Zustands. */
    private int index;

    /** Liste der Versionen. */
    private final List<T> list;

    /** Objekt das eine Kopie eines der hier verwalteten Objekte herstellen kann. */
    private HistoryObjectCopier<T> copier;

    /** Konstruktor */
    public History(HistoryObjectCopier<T> copier) {
        this.copier = copier;
        index = -1;
        list = new ArrayList<>();
    }

    /**
     * Fügt ein Objekt als neuen aktuellen Stand hinzu.
     *
     * @param t
     *            Hinzuzufügender Graph
     */
    public void add(T t) {
        say("Start");
        T copy = copier.copy(t);

        /*
         * TODO Refakturieren:
         * erst
         *
         * if (index < list.size() - 1) {
         *     for (int delete = list.size() - 1; delete > index; --delete) {
         *         list.remove(delete);
         *     }
         * }
         *
         * dann in jedem Fall
         *
         *     list.add(copy);
         *     ++index;
         *
         * Oder einfach in drei einzelen Methoden, dürfte verständlicher sein!
         */

        /* Bei leerer Liste wird das Element abgelegt. */
        if (list.isEmpty()) {
            list.add(copy);
            index = 0;
        }

        /*
         * Ist die Liste nicht leer und enthält nach dem Index keine weiteren Änderungen, so wird
         * der neue Stand eingefügt:
         */
        else if (index == list.size() - 1) {
            list.add(copy);
            ++index;
        }

        /*
         * Ist die Liste nicht leer und enthält nach dem index noch weitere Änderungen (nachdem der
         * Benutzer Dinge rückgängig gemacht hat und dann weitergearbeitet hat), so werden die
         * Änderungen hinter dem aktuellen Index entfernt und das neue Objekt dazugefügt:
         */
        else {
            /* Alle Einträge nach dem aktuellsten löschen: */
            for (int delete = list.size() - 1; delete > index; --delete) {
                list.remove(delete);
            }
            /* Neues Objekt hinzufügen: */
            list.add(copy);
            ++index;
        }

        say("Ende");
    }

    /**
     * Prüft, ob das aktuell vorgehaltene Objekt dem aktuellen in der Historie entspricht. Kann
     * helfen um Fehlern auf die Spur zu kommen, die von Änderungen am überprüften Objekt
     * herrühren, die vergessen wurden, der Historie mitzuteilen.
     *
     * @param actualObject
     *            Aktuelles Objekt.
     */
    public void checkActual(T actualObject) {
        if (!actualObject.equals(list.get(index))) {
            throw new RuntimeException("Logischer Fehler mit der Historie! Es wurde vergessen, "
                    + "Änderungen mitzuteilen, das aktuelle Objekt weicht ab!");
        }
    }

    /** Erfragt, ob es einen älteren Stand des Objekts gibt. */
    public boolean hasPrevious() {
        boolean hasPrevious = !list.isEmpty() && index > 0;
        say(hasPrevious ? "ja" : "nein");
        return hasPrevious;
    }

    /** Erfragt, ob es einen neueren Stand des Objekts gibt. */
    public boolean hasNext() {
        boolean hasNext = !list.isEmpty() && index + 1 < list.size();
        say(hasNext ? "ja" : "nein");
        return hasNext;
    }

    /** Gibt den vorigen Stand zurück. */
    public T getPrevious() {
        say("Start");
        if (!hasPrevious()) {
            throw new RuntimeException("Es gibt keine frühere Version!");
        }

        --index;
        T t = list.get(index);
        T copy = copier.copy(t);
        say("Ende");

        return copy;
    }

    /** Gibt den nächsten Stand zurück. */
    public T getNext() {
        say("Start");
        if (!hasNext()) {
            throw new RuntimeException("Es gibt keine spätere Version!");
        }

        ++index;
        T t = list.get(index);
        T copy = copier.copy(t);
        say("Ende");

        return copy;
    }

    /** Getter für die Anzahl der hinterlegten Versionen. */
    public int size() {
        return list.size();
    }

    /** Entfernt alle in der History hinterlegten Zustände. */
    public void clear() {
        index = -1;
        list.clear();
    }

    private void say(String message) {
        if (DEBUG) {
            String text = message + ": Anzahl hinterlegter Objekte = " + list.size() + ", Index = "
                    + index + ", Max Index = " + (list.size() - 1);
            if (index >= 0) {
                for (int i = 0; i < list.size(); ++i) {
                    text += "\n    " + i + ".: " + list.get(i);
                    if (index == i) {
                        text += "  <--------- aktueller Index";
                    }
                }
            }
            DebugHelper.sayWithClassAndMethod(text);
        }
    }

}
