package de.renew.unify;

import java.util.Iterator;

/**
 * A custom linked list implementation used in the unification process.
 * <p>
 * Each list node contains a head and a tail, where the tail is either another
 * List instance or a terminal value.
 */
public final class List extends Aggregate {
    /**
     * A shared, immutable instance representing the empty list.
     */
    public static final List NULL = new List(0);

    /**
     * Creates a new list of the given arity.
     *
     * @param arity number of elements in the list,
     *              has to be either 0 (empty list) or 2 (head and tail)
     * @throws RuntimeException if arity is not 0 or 2
     */
    public List(int arity) {
        super(arity);
        if (arity != 2 && arity != 0) {
            throw new RuntimeException("Illegal list element size.");
        }
    }

    /**
     * Creates a new list using the given initial values.
     *
     * @param head the head of the list
     * @param tail the tail of the list, another List or a terminal value
     * @param recorder the {@link IStateRecorder}
     */
    public List(Object head, Object tail, IStateRecorder recorder) {
        super(new Object[] { head, tail }, recorder);
    }

    /**
     * Checks whether the list is empty.
     *
     * @return {@code true} if the list has no elements, otherwise {@code false}.
     */
    public boolean isNull() {
        return getReferences().length == 0;
    }

    /**
     * Returns the head of the list.
     *
     * @return the value of the first element of the list.
     */
    public Object head() {
        return getReferences()[0].getValue();
    }

    /**
     * Returns the tail of the list.
     *
     * @return the value of the last element of the list
     */
    public Object tail() {
        return getReferences()[1].getValue();
    }

    @Override
    public Iterator<Object> iterator() {
        return new ListIterator(this);
    }

    /**
     * Computes the length of the list.
     *
     * @return the number of elements in the list
     */
    public int length() {
        int length = 0;
        List current = this;
        while (!current.isNull()) {
            ++length;
            current = (List) current.tail();

            // Throws ClassCastException if tail is not a list.
            // We should better convert this Exception to CorruptedList or something.
        }
        return length;
    }

    /**
     * Appends another list to the end of this list.
     *
     * @param that the list to append
     * @return a new list containing this list's elements followed by {@code that}
     */
    public List append(List that) {
        if (this.isNull()) {
            return that;
        }
        if (tail() instanceof List tail) {
            return new List(head(), tail.append(that), null);
        } else {
            // Ignore remainder of ill-formed list.
            return new List(head(), that, null);
        }
    }

    /**
     * Creates a deep copy of this list using the given {@code Copier}.
     *
     * @param copier the copier used to copy each element
     * @return a new {@code List} with copied elements
     */
    public List copy(Copier copier) {
        Reference[] references = getReferences();

        if (references.length == 0) {
            return NULL;
        } else {
            return new List(
                copier.copy(references[0].getValue()), copier.copy(references[1].getValue()), null);
        }
    }

    @Override
    public int hashCode() {
        if (getReferences().length == 0) {
            return 73;
        } else {
            return refHash(0) + 43 * refHash(1);
        }
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof List && matches((Aggregate) obj);
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        result.append('{');

        List listCopy = this;
        boolean prependComma = false;
        while (listCopy.getReferences().length != 0) {
            if (listCopy.getReferences().length == 1) {
                // This should not happen. Somehow the getReferences()
                // reduction did not work.
                throw new RuntimeException("Somebody took the hashcode of a delegator.");
            }

            if (prependComma) {
                result.append(',');
            } else {
                prependComma = true;
            }

            Object headValue = listCopy.getReferences()[0].getValue();
            if (headValue == null) {
                result.append("null");
            } else {
                result.append(headValue);
            }

            Object tailValue = listCopy.getReferences()[1].getValue();
            if (tailValue instanceof List) {
                listCopy = (List) tailValue;
            } else {
                result.append(':');
                if (tailValue == null) {
                    result.append("null");
                } else {
                    result.append(tailValue);
                }
                listCopy = NULL;
            }
        }
        result.append('}');
        return result.toString();
    }
}