package de.duehl.basics.collections;

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

import de.duehl.basics.exceptions.EmptyStackException;

/**
 * Eine Klasse für Stapel von Dingen.
 *
 * Was sich zuletzt "anstellt", kommt zuerst dran.
 *
 * @version 1.01     2020-01-09
 * @author Christian Dühl
 */

public class Stack<E> {

    /** Liste mit den aufgestapelten Dingen. */
    private final List<E> stackElements;

    /** Konstruktor, erzeugt einen leeren Stapel. */
    public Stack() {
        stackElements = new ArrayList<>();
    }

    /** Prüft, ob der Stapel leer ist. */
    public boolean isEmpty() {
        return stackElements.isEmpty();
    }

    /** Prüft, ob der Stapel Elemente enthält, also nicht leer ist. */
    public boolean hasElements() {
        return !isEmpty();
    }

    /** Leert den Stapel. */
    public void clear() {
        stackElements.clear();
    }

    /**
     * Legt etwas oben auf dem Stapel ab.
     *
     * @param element
     *            Abzulegendes Ding.
     */
    public void push(E element) {
        stackElements.add(element);
    }

    /**
     * Nimmt das oberste Ding vom Stapel und gibt es zurück.
     *
     * @return Oberstes Ding vom Stapel.
     * @throws EmptyStackException
     *             Falls der Stapel vorher bereits leer war.
     */
    public E pop() {
        if (isEmpty()) {
            throw new EmptyStackException("Der Stapel ist leer!");
        }
        else {
            int lastIndex = stackElements.size() - 1;
            E lastElement = stackElements.get(lastIndex);
            stackElements.remove(lastIndex);
            return lastElement;
        }
    }

    /** Gibt die Anzahl der Elemente zurück, die auf dem Stapel liegen. */
    public int size() {
        return stackElements.size();
    }

    /** Gibt eine flache Kopie der Liste der Elemente auf dem Stapel zurück. */
    public List<E> getElementsAsList() {
        List<E> list = new ArrayList<>();
        list.addAll(stackElements);
        return list;
    }

    /** Entnimmt alle Elemente des übergebenen Stapels und fügt sie dem eigenen Stapel hinzu. */
    public void takeAllElementsFrom(Stack<E> that) {
        that.reverse();
        takeAllReverseElementsFrom(that);
    }

    /**
     * Entnimmt alle Elemente des übergebenen Stapels in umgedrehter Reihenfolge und fügt sie dem
     * eigenen Stapel hinzu.
     */
    public void takeAllReverseElementsFrom(Stack<E> that) {
        while (that.hasElements()) {
            push(that.pop());
        }
    }

    /**
     * Dreht die Elemente auf dem Stapel um. Dies widerspricht dem Sinn eines Stapels, daher nicht
     * öffentlich zugänglich.
     */
    void reverse() {
        Collections.reverse(stackElements);
    }

    /** Erzeugt eine Stringdarstellung des Stapels zu Debugzwecken. */
    @Override
    public String toString() {
        return "Stack [stackElements=" + stackElements + "]";
    }

}
