import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A list of massive objects implemented as a linked list.
 * The number of elements of the list is not limited.
 */
public class MassiveLinkedList implements MassiveIterable {
    private int size = 0;
    private Item first;
    private Item last;

    /**
     * Initializes 'this' as an empty list.
     */
    public MassiveLinkedList() {
        first = null;
        last = null;
    }

    /**
     * Initializes 'this' as an independent copy of the specified list 'list'.
     * Calling methods of this list will not affect the specified list 'list'
     * and vice versa.
     * Precondition: list != null.
     */
    public MassiveLinkedList(BodyLinkedList list) {
        this.size = 0;
        for (Body b : list) {
            this.addLast(b);
        }
    }

    /**
     * Inserts the specified element 'body' at the beginning of this list.
     */
    public void addFirst(Massive body) {
        if (first == null) {
            first = new Item(body);
            last = first;
        } else {
            first.setPrev(new Item(body));
            first = first.prev;
        }
        size++;
    }

    /**
     * Appends the specified element 'body' to the end of this list.
     */
    public void addLast(Massive body) {
        if (last == null) {
            last = new Item(body);
            first = last;
        } else {
            last.setNext(new Item(body));
            last = last.next;
        }
        size++;
    }

    /**
     * Returns the last element in this list.
     * Returns 'null' if the list is empty.
     */
    public Massive getLast() {
        return (last != null) ? last.body : null;
    }

    /**
     * Returns the first element in this list.
     * Returns 'null' if the list is empty.
     */
    public Massive getFirst() {
        return (first != null) ? first.body : null;
    }

    /**
     * Retrieves and removes the first element in this list.
     * Returns 'null' if the list is empty.
     */
    public Massive pollFirst() {
        if (first == null) {
            return null;
        }
        Massive m = first.body;
        first = first.next;
        if (first != null) first.setPrev(null);
        size--;
        return m;
    }

    /**
     * Retrieves and removes the last element in this list.
     * Returns 'null' if the list is empty.
     */
    public Massive pollLast() {
        if (last == null) {
            return null;
        }
        Massive m = last.body;
        last = last.prev;
        if (last != null) last.setNext(null);
        size--;
        return m;
    }

    /**
     * Inserts the specified element at the specified position in this list.
     * Precondition: i >= 0 && i <= size().
     */
    public void add(int i, Massive m) {
        if (first == null || i == 0) {
            addFirst(m);
            return;
        } else if (i == size) {
            addLast(m);
            return;
        }

        Item item = first;
        for (int j = 0; j < i; j++) {
            item = item.next;
        }

        item.prev.setNext(new Item(m));
        item.setPrev(item.prev.next);
        size++;
    }

    /**
     * Returns the element at the specified position in this list.
     * Precondition: i >= 0 && i < size().
     */
    public Massive get(int i) {
        Item item;
        if (i < size / 2) {
            item = first;
            for (int j = 0; j < i; j++) {
                item = item.next;
            }
        } else {
            item = last;
            for (int j = size - 1; j > i; j--) {
                item = item.prev;
            }
        }
        return item.body;
    }

    /**
     * Returns the index of the first occurrence of the specified element in this list, or -1 if
     * this list does not contain the element.
     */
    public int indexOf(Massive m) {
        if (first == null) {
            return -1;
        }

        Item item = first;
        for (int i = 0; i < size; i++, item = item.next) {
            if (item.body.equals(m)) return i;
        }

        return -1;
    }

    /**
     * Returns the number of elements in this list.
     */
    public int size() {
        return size;
    }

    @Override
    public MassiveIterator iterator() {
        return new MassiveIterator() {
            Item ptr = first;
            boolean yieldedFirst = false;

            @Override
            public boolean hasNext() {
                return ptr != null && (!yieldedFirst || ptr.next != null);
            }

            @Override
            public Massive next() {
                if (!hasNext()) throw new NoSuchElementException();
                if (!yieldedFirst) {
                    yieldedFirst = true;
                } else {
                    ptr = ptr.next;
                }
                return ptr.body;
            }
        };
    }

    private static class Item {
        private final Massive body;
        private Item prev;
        private Item next;

        public Item(Massive body) {
            this.body = body;
            this.prev = null;
            this.next = null;
        }

        public void setPrev(Item prev) {
            this.prev = prev;
            if (prev != null) prev.next = this;
        }

        public void setNext(Item next) {
            this.next = next;
            if (next != null) next.prev = this;
        }
    }
}
