package com.jwetherell.algorithms.data_structures;

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

@SuppressWarnings("unchecked")
public interface Queue<T> extends IQueue<T> {

    /**
     * This queue implementation is backed by an array.
     * 
     * @author Justin Wetherell <phishman3579@gmail.com>
     */
    public static class ArrayQueue<T> implements Queue<T> {

        private static final int MINIMUM_SIZE = 1024;

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

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean offer(T value) {
            if (size() >= array.length)
                grow(size());

            array[lastIndex % array.length] = value;
            lastIndex++;
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T poll() {
            int size = lastIndex - firstIndex;
            if (size < 0) return null;

            T t = array[firstIndex % array.length];
            array[firstIndex % array.length] = null;
            firstIndex++;

            size = lastIndex - firstIndex;
            if (size <= 0) {
                // Removed last element
                lastIndex = 0;
                firstIndex = 0;
            }

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

            return t;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T peek() {
            return array[firstIndex % array.length];
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean remove(T value) {
            for (int i=0; i < array.length; i++) {
                T obj = array[i];
                // if obj is null, it should return false (not NPE)
                if (value.equals(obj)) return remove(i);
            }
            return false;
        }

        private boolean remove(int index) {
            if (index<0 || index >= array.length) return false;
            if (index==firstIndex) return (poll()!=null);

            int adjIndex = index % array.length;
            int adjLastIndex = (lastIndex-1) % array.length;
            if (adjIndex != adjLastIndex) {
                // Shift the array down one spot
                System.arraycopy(array, index+1, array, index, (array.length - (index+1)));
                if (adjLastIndex < firstIndex) {
                	//Wrapped around array
                	array[array.length-1] = array[0];
                	System.arraycopy(array, 1, array, 0, firstIndex-1);
                }
            }
            array[adjLastIndex] = null;

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

            lastIndex--;
            return true;
        }

        // Grow the array by 50% and rearrange to make sequential
        private void grow(int size) {
            int growSize = (size + (size<<1));
            T[] temp = (T[]) new Object[growSize];
            // Since the array can wrap around, make sure you grab the first chunk 
            int adjLast = lastIndex % array.length;
            if (adjLast < firstIndex) {
                System.arraycopy(array, 0, temp, array.length-adjLast, adjLast+1);
            }
            // Copy the remaining
            System.arraycopy(array, firstIndex, temp, 0, array.length-firstIndex);
            array = null;
            array = temp;
            lastIndex = (lastIndex - firstIndex);
            firstIndex = 0;
        }

        // Shrink the array by 50% and rearrange to make sequential
        private void shrink() {
            int shrinkSize = array.length>>1;
            T[] temp = (T[]) new Object[shrinkSize];
            // Since the array can wrap around, make sure you grab the first chunk 
            int adjLast = lastIndex % array.length;
            int endIndex = (lastIndex>array.length)?array.length:lastIndex;
            if (adjLast <= firstIndex) {
                System.arraycopy(array, 0, temp, array.length-firstIndex, adjLast);
            }
            // Copy the remaining
            System.arraycopy(array, firstIndex, temp, 0, endIndex-firstIndex);
            array = null;
            array = temp;
            lastIndex = (lastIndex - firstIndex);
            firstIndex = 0;
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean contains(T value) {
            for (int i=0; i < array.length; i++) {
                T obj = array[i];
                // if obj is null, it should return false (not NPE)
                if (value.equals(obj)) return true;
            }
            return false;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean validate() {
            if (size()==0) return true;

            int localSize = 0;
            int realFirst = firstIndex;
            if (firstIndex>array.length) 
                realFirst = firstIndex%array.length;
            int realLast = lastIndex;
            if (lastIndex>array.length) 
                realLast = lastIndex%array.length;
            for (int i=0; i<array.length; i++) {
                T t = array[i];
                if ((realFirst==realLast) || 
                    (realFirst<realLast && (i>=realFirst && i<realLast)) || 
                    (realLast<realFirst && (i<realLast || i>=realFirst))
                ) {
                    if (t==null)
                        return false;
                    localSize++;
                } else {
                    if (t!=null)
                        return false;
                }
            }
            return (localSize==size());
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.Queue<T> toQueue() {
            return (new JavaCompatibleArrayQueue<T>(this));
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            for (int i = lastIndex - 1; i >= firstIndex; i--) {
                builder.append(array[i%array.length]).append(", ");
            }
            return builder.toString();
        }
    }

    /**
     * This queue implementation is backed by a linked list.
     * 
     * @author Justin Wetherell <phishman3579@gmail.com>
     */
    public static class LinkedQueue<T> implements Queue<T> {

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

        public LinkedQueue() {
            head = null;
            tail = null;
            size = 0;
        }

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

        /**
         * Enqueue the node in the queue.
         * 
         * @param node
         *            to enqueue.
         */
        private boolean add(Node<T> node) {
            if (head == null) {
                head = node;
                tail = node;
            } else {
                Node<T> oldHead = head;
                head = node;
                node.next = oldHead;
                oldHead.prev = node;
            }
            size++;
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T poll() {
            T result = null;
            if (tail != null) {
                result = tail.value;

                Node<T> prev = tail.prev;
                if (prev != null) {
                    prev.next = null;
                    tail = prev;
                } else {
                    head = null;
                    tail = null;
                }
                size--;
            }
            return result;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T peek() {
            return (tail!=null)?tail.value:null;
        }

        /**
         * {@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;
            return remove(node);
        }

        private boolean remove(Node<T> node) {
            // 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) {
            if (head == null)
                return false;

            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;
                if (!validate(child,keys)) return false;
            } else {
                if (!node.equals(tail)) return false;
            }
            return true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.Queue<T> toQueue() {
            return (new JavaCompatibleLinkedQueue<T>(this));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.Collection<T> toCollection() {
            return (new JavaCompatibleLinkedQueue<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(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 JavaCompatibleArrayQueue<T> extends java.util.AbstractQueue<T> {

        private ArrayQueue<T> queue = null;

        public JavaCompatibleArrayQueue(ArrayQueue<T> queue) {
            this.queue = queue;
        }

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

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

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

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

        /**
         * {@inheritDoc}
         */
        @Override
        public T peek() {
            return queue.peek();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T poll() {
            return queue.poll();
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.Iterator<T> iterator() {
            return (new ArrayQueueIterator<T>(queue));
        }

        private static class ArrayQueueIterator<T> implements java.util.Iterator<T> {

            private ArrayQueue<T> queue = null;
            private int last = -1;
            private int index = 0; //offset from first

            private ArrayQueueIterator(ArrayQueue<T> queue) {
                this.queue = queue;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public boolean hasNext() {
                return ((queue.firstIndex+index) < queue.lastIndex);
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public T next() {
                if (queue.firstIndex+index < queue.lastIndex) {
                    last = queue.firstIndex+index;
                    return queue.array[queue.firstIndex+index++];
                }
                return null;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public void remove() {
                queue.remove(last);
            }
        }
    }

    public static class JavaCompatibleLinkedQueue<T> extends java.util.AbstractQueue<T> {

        private LinkedQueue<T> queue = null;

        public JavaCompatibleLinkedQueue(LinkedQueue<T> queue) {
            this.queue = queue;
        }

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

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

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

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

        /**
         * {@inheritDoc}
         */
        @Override
        public T peek() {
            return queue.peek();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public T poll() {
            return queue.poll();
        }

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

        /**
         * {@inheritDoc}
         */
        @Override
        public java.util.Iterator<T> iterator() {
            return (new LinkedQueueIterator<T>(queue));
        }

        private static class LinkedQueueIterator<T> implements java.util.Iterator<T> {

            private LinkedQueue<T> queue = null;
            private LinkedQueue.Node<T> lastNode = null;
            private LinkedQueue.Node<T> nextNode = null;

            private LinkedQueueIterator(LinkedQueue<T> queue) {
                this.queue = queue;
                this.nextNode = queue.tail;
            }

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

            /**
             * {@inheritDoc}
             */
            @Override
            public T next() {
                LinkedQueue.Node<T> current = nextNode;
                lastNode = current;
                if (current!=null) {
                    nextNode = current.prev;
                    return current.value;
                }
                return null;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            public void remove() {
                queue.remove(lastNode);
            }
        }
    }
}
