package com.conveyal.r5.analyst.broker;

import java.util.Iterator;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 *
 */
public class CircularList<T> implements Iterable<T> {

    Node<T> head = null;

    public class CircularListIterator implements Iterator<T> {

        Node<T> curr = head;

        @Override
        public boolean hasNext() {
            return curr != null;
        }

        @Override
        public T next() {
            T currElement = curr.element;
            curr = curr.next;
            if (curr == head) {
                curr = null; // No more elements
            }
            return currElement;
        }
    }

    @Override
    public Iterator<T> iterator() {
        return new CircularListIterator();
    }

    @Override
    public void forEach(Consumer<? super T> action) {
        Node<T> curr = head;
        do {
            action.accept(curr.element);
            curr = curr.next;
        } while (curr != head);
    }

    @Override
    public Spliterator<T> spliterator() {
        throw new UnsupportedOperationException();
    }

    private static class Node<T> {
        Node prev;
        Node next;
        T element;
    }

    /** Insert an element at the tail of the circular list (the position just previous to the head). */
    public void insertAtTail (T element) {
        Node<T> newNode = new Node<>();
        newNode.element = element;
        if (head == null) {
            newNode.next = newNode;
            newNode.prev = newNode;
            head = newNode;
        } else {
            newNode.prev = head.prev;
            newNode.next = head;
            head.prev.next = newNode;
            head.prev = newNode;
        }
    }


    /** Insert a new element at the head of the circular list. */
    public void insertAtHead (T element) {
        // Add at tail and then back the head up one element, wrapping around to the tail.
        insertAtTail(element);
        head = head.prev;
    }

    /** Get the element at the head of the list without removing it. */
    public T peek () {
        return head.element;
    }

    /** Take an element off the head of the list, removing it from the list. */
    public T pop () {
        if (head == null) {
            return null;
        }
        T element = head.element;
        if (head == head.next) {
            // Last node consumed. List is now empty.
            head = null;
        } else {
            head.prev.next = head.next;
            head.next.prev = head.prev;
            head = head.next;
        }
        return element;
    }

    /**
     * Advance the head of the circular list forward to the next element.
     * Return the element at the head of the list, leaving that element in the list.
     */
    public T advance() {
        if (head == null) {
            return null;
        }
        T headElement = head.element;
        head = head.next;
        return headElement;
    }

    /**
     * Advances through the circular list returning the first element for which the predicate evaluates to true.
     * If there is no such element, returns null leaving the head of the list back in the same place.
     */
    public T advanceToElement (Predicate<T> predicate) {
        Node<T> start = head;

        if (head == null)
            return null;

        do {
            T currElement = advance();
            if (predicate.test(currElement)) {
                return currElement;
            }
        } while (head != start);
        return null;
    }

    /**
     * Remove an item from this circular list.
     */
    public boolean remove (T obj) {
        // handle the zero-length and one-length case
        if (head == null) return false;

        if (head.prev == head) {
            if (head.element.equals(obj)) {
                head = null;
                return true;
            }
            else return false;
        }

        Node<T> start = head;
        Node<T> current = head;
        do {
            if (current.element.equals(obj)) {
                // remove from rotation
                current.prev.next = current.next;
                current.next.prev = current.prev;

                if (current == head)
                    head = current.next;

                return true;
            }
            current = current.next;
        } while (current != start);

        return false;
    }

}

@@ -872,24 +872,73 @@
 %3E action) %7B%0A
+        if (head == null) return; // empty list%0A%0A
         Node

fix NPE with empty circular lists.