package com.jwetherell.algorithms.data_structures;

import java.util.Arrays;

import com.jwetherell.algorithms.data_structures.interfaces.IList;

@SuppressWarnings("unchecked")
public abstract class List<T> implements IList<T> {

    /**
     * A dynamic array, growable array, resizable array, dynamic table, or array
     * list is a random access, variable-size list data structure that allows
     * elements to be added or removed.
     * 
     * http://en.wikipedia.org/wiki/Dynamic_array
     * 
     * @author Justin Wetherell <phishman3579@gmail.com>
     */
    public static class ArrayList<T> extends List<T> {

        private static final int MINIMUM_SIZE = 1024;

        private int size = 0;
        private T[] array = (T[]) new Object[MINIMUM_SIZE];

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean add(T value) {
            return add(size,value);
        }

        /**
         * Add value to list at index.
         * 
         * @param index to add value.
         * @param value to add to list.
         */
        public boolean add(int index, T value) {
            if (size >= array.length)
                grow();
            if (index==size) {
                array[size] = value;
            } else {
                // Shift the array down one spot
                System.arraycopy(array, index, array, index+1, size - index);
                array[index] = value;
            }
            size++;
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean remove(T value) {
            for (int i = 0; i < size; i++) {
                T obj = array[i];
                if (obj.equals(value)) {
                    if (remove(i)!=null) return true;
                    return false;
                }
            }
            return false;
        }

        /**
         * Remove value at index from list.
         * 
         * @param index of value to remove.
         * @return value at index.
         */
        public T remove(int index) {
            if (index<0 || index>=size) return null;

            T t = array[index];
            if (index != --size) {
                // Shift the array down one spot
                System.arraycopy(array, index + 1, array, index, size - index);
            }
            array[size] = null;

            int shrinkSize = array.length>>1;
            if (shrinkSize >= MINIMUM_SIZE && size < shrinkSize)
                shrink();

            return t;
        }

        // Grow the array by 50%
        private void grow() {
            int growSize = size + (size<<1);
            array = Arrays.copyOf(array, growSize);
        }

        // Shrink the array by 50%
        private void shrink() {
            int shrinkSize = array.length>>1;
            array = Arrays.copyOf(array, shrinkSize);
        }

        /**
         * Set value at index.
         * 
         * @param index of value to set.
         * @param value to set.
         * @return value previously at index.
         */
        public T set(int index, T value) {
            if (index<0 || index>=size) return null;
            T t = array[index];
            array[index] = value;
            return t;
        }

        /**
         * Get value at index.
         * 
         * @param index of value to get.
         * @return value at index.
         */
        public T get(int index) {
            if (index<0 || index>=size) return null;
            return array[index];
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void clear() {
            size = 0;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(T value) {
            for (int i = 0; i < size; i++) {
                T obj = array[i];
                if (obj.equals(value)) return true;
            }
            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int size() {
            return size;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean validate() {
            int localSize = 0;
            for (int i=0; i<array.length; i++) {
                T t = array[i];
                if (i<size) {
                    if (t==null) return false;
                    localSize++;
                } else {
                    if (t!=null) return false;
                }
            }
            return (localSize==size);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.List<T> toList() {
            return (new JavaCompatibleArrayList<T>(this));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.Collection<T> toCollection() {
            return (new JavaCompatibleArrayList<T>(this));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < size; i++) {
                builder.append(array[i]).append(", ");
            }
            return builder.toString();
        }
    }

    public static class JavaCompatibleArrayList<T> extends java.util.AbstractList<T> implements java.util.RandomAccess {

        private List.ArrayList<T> list = null;

        public JavaCompatibleArrayList(List.ArrayList<T> list) {
            this.list = list;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean add(T value) {
            return list.add(value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean remove(Object value) {
            return list.remove((T)value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(Object value) {
            return list.contains((T)value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int size() {
            return list.size;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void add(int index, T value) {
            list.add(index, value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T remove(int index) {
            return list.remove(index);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T get(int index) {
            T t = list.get(index);
            if (t!=null) 
                return t;
            throw new IndexOutOfBoundsException();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T set(int index, T value) {
            return list.set(index, value);
        }
    }

    /**
     * Linked List (Singly link). A linked list is a data structure consisting
     * of a group of nodes which together represent a sequence.
     * 
     * http://en.wikipedia.org/wiki/Linked_list
     * 
     * @author Justin Wetherell <phishman3579@gmail.com>
     */
    public static class SinglyLinkedList<T> extends List<T> {

        private int size = 0;
        private Node<T> head = null;
        private Node<T> tail = null;

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean add(T value) {
            return add(new Node<T>(value));
        }

        /**
         * Add node to list.
         * 
         * @param node
         *            to add to list.
         */
        private boolean add(Node<T> node) {
            if (head == null) {
                head = node;
                tail = node;
            } else {
                Node<T> prev = tail;
                prev.next = node;
                tail = node;
            }
            size++;
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean remove(T value) {
            // Find the node
            Node<T> prev = null;
            Node<T> node = head;
            while (node != null && (!node.value.equals(value))) {
                prev = node;
                node = node.next;
            }

            if (node == null)
                return false;

            // Update the tail, if needed
            if (node.equals(tail)) {
                tail = prev;
                if (prev != null)
                    prev.next = null;
            }

            Node<T> next = node.next;
            if (prev != null && next != null) {
                prev.next = next;
            } else if (prev != null && next == null) {
                prev.next = null;
            } else if (prev == null && next != null) {
                // Node is the head
                head = next;
            } else {
                // prev==null && next==null
                head = null;
            }

            size--;
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void clear() {
            head = null;
            size = 0;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(T value) {
            Node<T> node = head;
            while (node != null) {
                if (node.value.equals(value))
                    return true;
                node = node.next;
            }
            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int size() {
            return size;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean validate() {
            java.util.Set<T> keys = new java.util.HashSet<T>();
            Node<T> node = head;
            if (node != null) {
                keys.add(node.value);

                Node<T> child = node.next;
                while (child != null) {
                    if (!validate(child,keys)) 
                        return false;
                    child = child.next;
                }
            }
            return (keys.size()==size);
        }

        private boolean validate(Node<T> node, java.util.Set<T> keys) {
            if (node.value==null) 
                return false;

            keys.add(node.value);

            Node<T> child = node.next;
            if (child==null) {
                if (!node.equals(tail)) 
                    return false;
            }
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.List<T> toList() {
            return (new JavaCompatibleSinglyLinkedList<T>(this));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.Collection<T> toCollection() {
            return (new JavaCompatibleSinglyLinkedList<T>(this));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            Node<T> node = head;
            while (node != null) {
                builder.append(node.value).append(", ");
                node = node.next;
            }
            return builder.toString();
        }

        private static class Node<T> {

            private T value = null;
            private Node<T> next = null;

            private Node() { }

            private Node(T value) {
                this.value = value;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public String toString() {
                return "value=" + value + " next=" + ((next != null) ? next.value : "NULL");
            }
        }
    }

    /**
     * Linked List (singly link). A linked list is a data structure consisting
     * of a group of nodes which together represent a sequence.
     * 
     * http://en.wikipedia.org/wiki/Linked_list
     * 
     * @author Justin Wetherell <phishman3579@gmail.com>
     */
    public static class JavaCompatibleSinglyLinkedList<T> extends java.util.AbstractSequentialList<T> {

        private List.SinglyLinkedList<T> list = null;

        public JavaCompatibleSinglyLinkedList(List.SinglyLinkedList<T> list) {
            this.list = list;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean add(T value) {
            return list.add(value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean remove(Object value) {
            return list.remove((T)value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(Object value) {
            return list.contains((T)value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int size() {
            return list.size();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.ListIterator<T> listIterator(int index) {
            return (new SinglyLinkedListListIterator<T>(list));
        }

        private static class SinglyLinkedListListIterator<T> implements java.util.ListIterator<T> {

            private int index = 0;

            private SinglyLinkedList<T> list = null;
            private SinglyLinkedList.Node<T> prev = null;
            private SinglyLinkedList.Node<T> next = null;
            private SinglyLinkedList.Node<T> last = null;

            private SinglyLinkedListListIterator(SinglyLinkedList<T> list) {
                this.list = list;
                this.next = list.head;
                this.prev = null;
                this.last = null;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public void add(T value) {
                SinglyLinkedList.Node<T> node = new SinglyLinkedList.Node<T>(value);

                if (list.head == null) {
                    list.head = node;
                    list.tail = node;
                } else {
                    SinglyLinkedList.Node<T> p = null;
                    SinglyLinkedList.Node<T> n = list.head;
                    while (n!= null && !(n.equals(next))) {
                        p = node;
                        n = node.next;
                    }
                    if (p != null) {
                        p.next = node;
                    } else {
                        // replacing head
                        list.head = node;
                    }
                    node.next = n;
                }
                this.next = node;

                list.size++;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public void remove() {
                if (last == null) 
                    return;

                SinglyLinkedList.Node<T> p = null;
                SinglyLinkedList.Node<T> node = this.last;
                while (node!= null && !(node.equals(last))) {
                    p = node;
                    node = node.next;
                }

                SinglyLinkedList.Node<T> n = last.next;
                if (p != null) 
                    p.next = n;

                if (last.equals(list.head)) 
                    list.head = n;
                if (last.equals(list.tail)) 
                    list.tail = p;

                list.size--;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public void set(T value) {
                if (last != null) 
                    last.value = value;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public boolean hasNext() {
                return (next!=null);
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public boolean hasPrevious() {
                return (prev!=null);
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public int nextIndex() {
                return index;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public int previousIndex() {
                return index-1;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public T next() {
                if (next == null) 
                    throw new java.util.NoSuchElementException();

                index++;
                last = next;
                prev = next;
                next = next.next;

                return last.value;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public T previous() {
                if (prev == null) 
                    throw new java.util.NoSuchElementException();

                index--;
                last = prev;
                next = prev;

                SinglyLinkedList.Node<T> p = null;
                SinglyLinkedList.Node<T> node = this.list.head;
                while (node!= null && !(node.equals(prev))) {
                    p = node;
                    node = node.next;
                }
                prev = p;

                return last.value;
            }
        }
    }

    /**
     * Linked List (doubly link). A linked list is a data structure consisting
     * of a group of nodes which together represent a sequence.
     * 
     * http://en.wikipedia.org/wiki/Linked_list
     * 
     * @author Justin Wetherell <phishman3579@gmail.com>
     */
    public static class DoublyLinkedList<T> extends List<T> {

        private int size = 0;
        private Node<T> head = null;
        private Node<T> tail = null;

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean add(T value) {
            return add(new Node<T>(value));
        }

        /**
         * Add node to list.
         * 
         * @param node
         *            to add to list.
         */
        private boolean add(Node<T> node) {
            if (head == null) {
                head = node;
                tail = node;
            } else {
                Node<T> prev = tail;
                prev.next = node;
                node.prev = prev;
                tail = node;
            }
            size++;
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean remove(T value) {
            // Find the node
            Node<T> node = head;
            while (node != null && (!node.value.equals(value))) {
                node = node.next;
            }
            if (node == null)
                return false;

            // Update the tail, if needed
            if (node.equals(tail))
                tail = node.prev;

            Node<T> prev = node.prev;
            Node<T> next = node.next;
            if (prev != null && next != null) {
                prev.next = next;
                next.prev = prev;
            } else if (prev != null && next == null) {
                prev.next = null;
            } else if (prev == null && next != null) {
                // Node is the head
                next.prev = null;
                head = next;
            } else {
                // prev==null && next==null
                head = null;
            }
            size--;
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void clear() {
            head = null;
            size = 0;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(T value) {
            Node<T> node = head;
            while (node != null) {
                if (node.value.equals(value))
                    return true;
                node = node.next;
            }
            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int size() {
            return size;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean validate() {
            java.util.Set<T> keys = new java.util.HashSet<T>();
            Node<T> node = head;
            if (node!=null) {
                keys.add(node.value);
                if (node.prev!=null) 
                    return false;
                Node<T> child = node.next;
                while (child!=null) {
                    if (!validate(child,keys)) 
                        return false;
                    child = child.next;
                }
            }
            return (keys.size()==size);
        }

        private boolean validate(Node<T> node, java.util.Set<T> keys) {
            if (node.value==null) 
                return false;

            keys.add(node.value);

            Node<T> child = node.next;
            if (child!=null) {
                if (!child.prev.equals(node)) 
                    return false;
            } else {
                if (!node.equals(tail)) 
                    return false;
            }
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.List<T> toList() {
            return (new JavaCompatibleDoublyLinkedList<T>(this));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.Collection<T> toCollection() {
            return (new JavaCompatibleDoublyLinkedList<T>(this));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            Node<T> node = head;
            while (node != null) {
                builder.append(node.value).append(", ");
                node = node.next;
            }
            return builder.toString();
        }

        private static class Node<T> {

            private T value = null;
            private Node<T> prev = null;
            private Node<T> next = null;

            private Node() { }

            private Node(T value) {
                this.value = value;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public String toString() {
                return "value=" + value + " previous=" + ((prev != null) ? prev.value : "NULL")
                        + " next=" + ((next != null) ? next.value : "NULL");
            }
        }
    }

    public static class JavaCompatibleDoublyLinkedList<T> extends java.util.AbstractSequentialList<T> {

        private List.DoublyLinkedList<T> list = null;

        public JavaCompatibleDoublyLinkedList(List.DoublyLinkedList<T> list) {
            this.list = list;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean add(T value) {
            return list.add(value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean remove(Object value) {
            return list.remove((T)value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(Object value) {
            return list.contains((T)value);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int size() {
            return list.size();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.ListIterator<T> listIterator(int index) {
            return (new DoublyLinkedListListIterator<T>(list));
        }

        private static class DoublyLinkedListListIterator<T> implements java.util.ListIterator<T> {

            private int index = 0;

            private DoublyLinkedList<T> list = null;
            private DoublyLinkedList.Node<T> prev = null;
            private DoublyLinkedList.Node<T> next = null;
            private DoublyLinkedList.Node<T> last = null;

            private DoublyLinkedListListIterator(DoublyLinkedList<T> list) {
                this.list = list;
                this.next = list.head;
                this.prev = null;
                this.last = null;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public void add(T value) {
                DoublyLinkedList.Node<T> node = new DoublyLinkedList.Node<T>(value);
                DoublyLinkedList.Node<T> n = this.next;

                if (this.prev != null) 
                    this.prev.next = node;
                node.prev = this.prev;

                node.next = n;
                if (n != null) 
                    n.prev = node;

                this.next = node;
                if (this.prev == null) 
                    list.head = node; // new root
                list.size++;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public void remove() {
                if (last == null) 
                    return;

                DoublyLinkedList.Node<T> p = last.prev;
                DoublyLinkedList.Node<T> n = last.next;
                if (p != null) 
                    p.next = n;
                if (n != null) 
                    n.prev = p;
                if (last.equals(list.head)) 
                    list.head = n;
                if (last.equals(list.tail)) 
                    list.tail = p;

                list.size--;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public void set(T value) {
                if (last != null) 
                    last.value = value;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public boolean hasNext() {
                return (next!=null);
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public boolean hasPrevious() {
                return (prev!=null);
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public int nextIndex() {
                return index;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public int previousIndex() {
                return index-1;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public T next() {
                if (next == null) 
                    throw new java.util.NoSuchElementException();

                index++;
                last = next;
                prev = next;
                next = next.next;

                return last.value;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public T previous() {
                if (prev == null) 
                    throw new java.util.NoSuchElementException();

                index--;
                last = prev;
                next = prev;
                prev = next.prev;

                return last.value;
            }
        }
    }
}
