package com.googlecode.totallylazy.collections;
import com.googlecode.totallylazy.functions.Function1;
import com.googlecode.totallylazy.functions.Function2;
import com.googlecode.totallylazy.Option;
import com.googlecode.totallylazy.predicates.Predicate;
import com.googlecode.totallylazy.Segment;
import com.googlecode.totallylazy.iterators.SegmentIterator;
import java.util.Iterator;
import java.util.NoSuchElementException;
import static com.googlecode.totallylazy.Callers.call;
import static com.googlecode.totallylazy.None.none;
import static com.googlecode.totallylazy.Option.some;
import static com.googlecode.totallylazy.predicates.Predicates.in;
import static com.googlecode.totallylazy.predicates.Predicates.not;
import static com.googlecode.totallylazy.Sequences.sequence;
import static com.googlecode.totallylazy.Sets.set;
import static com.googlecode.totallylazy.Unchecked.cast;
public abstract class LinkedList<T> extends AbstractList<T> implements PersistentList<T> {
static final Empty EMPTY = new Empty();
static <T> LinkedList<T> emptyList() {
return cast(EMPTY);
}
static <T> LinkedList<T> cons(T head, PersistentList<T> tail) {
return Node.node(head, tail);
}
@Override
public PersistentList<T> empty() {
return emptyList();
}
@Override
public PersistentList<T> cons(T head) {
return cons(head, this);
}
@Override
public PersistentList<T> append(T value) {
return PersistentList.constructors.list(toSequence().append(value));
}
@Override
public Iterator<T> iterator() {
return SegmentIterator.iterator(this);
}
private static class Empty<T> extends LinkedList<T> {
private Empty() {
}
@Override
public T head() {
throw new NoSuchElementException();
}
@Override
public Option<T> headOption() {
return none();
}
@Override
public PersistentList<T> tail() {
throw new NoSuchElementException();
}
@Override
public <C extends Segment<T>> C joinTo(C rest) {
return rest;
}
@Override
public PersistentList<T> delete(T value) {
return this;
}
@Override
public int size() {
return 0;
}
@Override
public boolean contains(Object other) {
return false;
}
@Override
public boolean exists(Predicate<? super T> predicate) {
return false;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public String toString() {
return "[]";
}
@Override
public T get(int i) throws IndexOutOfBoundsException {
throw new IndexOutOfBoundsException();
}
@Override
public int indexOf(Object t) {
return -1;
}
}
private static class Node<T> extends LinkedList<T> {
private final T head;
private final PersistentList<T> tail;
private final int size;
private Node(T head, PersistentList<T> tail) {
this.head = head;
this.tail = tail;
size = 1 + tail.size();
}
private static <T> Node<T> node(T head, PersistentList<T> tail) {
return new Node<T>(head, tail);
}
@Override
public T head() {
return head;
}
@Override
public Option<T> headOption() {
return some(head);
}
@Override
public PersistentList<T> tail() {
return tail;
}
@Override
public <C extends Segment<T>> C joinTo(C rest) {
return cast(tail.joinTo(rest).cons(head));
}
@Override
public PersistentList<T> delete(T value) {
if(useRecursion()){
if(head.equals(value)) return tail;
return cons(head, tail.delete(value));
}
ListZipper<T> zipper = ListZipper.zipper(this);
while (!zipper.isBottom()){
if(zipper.current().equals(value)) return zipper.delete().toList();
zipper = zipper.next();
}
return this;
}
@Override
public int size() {
return size;
}
protected boolean useRecursion() {
return size < 1024;
}
@Override
public boolean contains(Object other) {
return toSequence().contains(other);
}
@Override
public boolean exists(Predicate<? super T> predicate) {
return toSequence().exists(predicate);
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public String toString() {
return toSequence().toString("::");
}
@Override
public int hashCode() {
return toSequence().hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof Node && ((Node) obj).head.equals(head) && ((Node) obj).tail.equals(tail);
}
@Override
public T get(int i) throws IndexOutOfBoundsException {
if(i == 0) return head;
return tail().get(i - 1);
}
@Override
public int indexOf(Object t) {
if(t.equals(head)) return 0;
return 1 + tail.indexOf(t);
}
}
}