import java.math.BigInteger;

/**
 * Ein Stack basierend auf der Cantorschen Paarungsfunktion
 * 
 * @author Rainer Helfrich
 * @version Oktober 2020
 */
public class CantorStack extends Stack<Integer>
{
    /**
     * Die Zahl, die den Stack repräsentiert
     */
    private BigInteger stack;
    
    /**
     * Erzeugt einen neuen leeren Stack
     */
    public CantorStack()
    {
        stack = BigInteger.TWO;
    }
    
    /**
     * Gibt zurück, ob der Stack leer ist
     * @return true, wenn der Stack leer ist; false sonst
     */
    public boolean isEmpty()
    {
        return stack.equals(BigInteger.TWO);
    }
    
    /**
     * Legt ein neues Element auf den Stack
     * @param x Das neue Element
     */
    public void push(Integer wert)
    {
        BigInteger y = BigInteger.valueOf(wert);
        BigInteger x = stack;
        BigInteger t1 = x.add(y);
        BigInteger t2 = t1.add(BigInteger.ONE);
        BigInteger t3 = t1.multiply(t2).shiftRight(1);
        System.out.print("Push: (" + stack + " x " + wert + ") -> ");
        stack = y.add(t3);
        System.out.println(stack);
    }
    
    /**
     * Gibt das oberste Element des Stacks zurück (falls der Stack nicht leer ist)
     * @return Das oberste Element
     */
    public Integer top()
    {
        if (isEmpty())
        {
            return null;
        }
        BigInteger top = topInt();
        System.out.println("Top: " + top);
        return top.intValue();
    }
    
    private BigInteger topInt()
    {
        return stack.subtract(f(q(stack)));
    }
    
    /**
     * Entfernt das oberste Element vom Stack (falls der Stack nicht leer ist) und gibt es zurück
     * @return Das bisherige oberste Element
     */
    public Integer pop()
    {
        if (isEmpty())
        {
            return null;
        }
        BigInteger top = topInt();
        System.out.print("Pop: " + stack + " -> (");
        stack = q(stack).subtract(top);
        System.out.println(stack + "," + top +")");
        return top.intValue();
    }
    
    private BigInteger f(BigInteger w)
    {
        return w.multiply(w.add(BigInteger.ONE)).shiftRight(1);
    }
    
    private BigInteger q(BigInteger z)
    {
        return z.shiftLeft(3).add(BigInteger.ONE).sqrt().subtract(BigInteger.ONE).shiftRight(1);
    }
    
    /**
     * Gibt den Stack in String-Form aus.
     * @return Die String-Repräsentation des Stacks
     */
    @Override
    public String toString()
    {
        String ergebnis = " <";
        CantorStack s = new CantorStack();
        while(!this.isEmpty())
        {
            Integer top = pop();
            s.push(top);
            ergebnis = top + ergebnis;
            if (!isEmpty())
            {
                ergebnis = ", " + ergebnis;
            }
        }
        while(!s.isEmpty())
        {
            push(s.pop());
        }
        return "[ " + ergebnis;
    }
}
