package com.jwetherell.algorithms.data_structures;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

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

/**
 * A Treap is a self-balancing binary search tree that uses randomization to maintain 
 * a low height. In this version, it is used emulate the operations of an array and linked list.
 * 
 * Time Complexity: Assuming the join/merge functions have constant complexity.
 * add(value), add(index,value), remove(index), set(index,value), get(index) all have O(log N).
 * remove(value), get(value), contains(value) all have O(N).
 * 
 * Space Complexity: O(N)
 * 
 * Note: This implementation is 0-based, meaning that all
 * indices from 0 to size() - 1, inclusive, are accessible.
 * 
 * http://en.wikipedia.org/wiki/Treap
 * 
 * @author Justin Wetherell <phishman3579@gmail.com>
 */
@SuppressWarnings("unchecked")
public class ImplicitKeyTreap<T> implements IList<T> {

    private static final Random RANDOM = new Random();

    private static int randomSeed = Integer.MAX_VALUE; // This should be at least twice the number of Nodes

    protected Node<T> root = null;
    protected int size = 0;

    /**
     * Default constructor.
     */
    public ImplicitKeyTreap() { }

    /**
     * Constructor with a random seed.
     * 
     * @param randomSeed to use.
     */
    public ImplicitKeyTreap(int randomSeed) {
        this();
        ImplicitKeyTreap.randomSeed = randomSeed;
    }

    /**
     * {@inheritDoc}
     * 
     * Note: adds to the end of the treap (rightmost node)
     */
    @Override
    public boolean add(T value) {
        final T t = add(size, value);
        return (t!=null);
    }

    /**
     * Insert value at index
     * 
     * @param index to insert value
     * @param value to insert
     */
    public T add(int index, T value) {
        addAtIndexAndUpdate(index, value);

        // Check to make sure value was added
        final Node<T> n = getNodeByIndex(index);
        if (n == null)
            return null;
        return n.value;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean remove(T value) {
        // See if value already exists
        final int idx = getIndexByValue(value);
        if (idx < 0)
            return false;

        final T t = removeAtIndex(idx);
        return (t!=null);
    }

    /**
     * Remove value at index
     * 
     * @param index to remove value
     * @return value or null if not found
     */
    public T removeAtIndex(int index) {
        // See if index already exists
        Node<T> n = getNodeByIndex(index);
        if (n == null)
            return null;

        removeAtIndexAndUpdate(index);
        return n.value;
    }

    /**
     * Set value at index
     * 
     * @param index to remove value
     * @return value or null if not found
     */
    public T set(int index, T value) {
        // See if index already exists
        final Node<T> n = getNodeByIndex(index);
        if (n == null)
            return null;

        n.value = value;
        return n.value;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean contains(T value) {
        final Node<T> n = getNodeByValue(value);
        return (n!=null);
    }

    /**
     * Get value at index
     * 
     * @param index to remove value
     */
    public T getAtIndex(int index) {
        final Node<T> n = getNodeByIndex(index);
        if (n == null)
            return null;
        return n.value;
    }

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

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

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean validate() {
        if (root == null) 
            return true;
        return validateNode(root);
    }

    private boolean validateNode(Node<T> node) {
        final Node<T> left = node.left;
        final Node<T> right = node.right;

        if (left != null) {
            if (node.priority < left.priority)
                return validateNode(left);
            return false;
        }
        if (right != null) {
            if (node.priority < right.priority)
                return validateNode(right);
            return false;
        }

        return true;
    }

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

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

    /**
     * Split the treap at index
     * 
     * @param index to split at
     * 
     * @return Pair which contains root of both trees
     */
    public Pair<T> split(int index) {
        final Pair<T> p = split((Node<T>)root, index);
        if (p.left != null)
            p.left.parent = null;
        if (p.right != null)
            p.right.parent = null;
        return p;
    }

    public void addAtIndexAndUpdate(int index, T value) {
        root = insert(((Node<T>)root), index, value);
        if (root == null)
            size = 0;
        else
            size = (((Node<T>)root).size);
    }

    private void removeAtIndexAndUpdate(int index) {
        root = remove(((Node<T>)root), index);
        if (root == null)
            size = 0;
        else
            size = (((Node<T>)root).size);
    }

    private Node<T> getNodeByValue(T value) {
        return getNodeByValue(root,value);
    }

    private Node<T> getNodeByIndex(int index) {
        if (root == null)
            return null;

        final Node<T> l = (Node<T>)root.left;
        final Node<T> r = (Node<T>)root.right;
        final int leftSize = ((l!=null)?l.size:0);
        final int idx = leftSize;

        if (idx == index) {
            return root;
        } else if (index < leftSize) {
            return getNodeByIndex(l, idx, index);
        } else {
            return getNodeByIndex(r, idx, index);
        }
    }

    private int getIndexByValue(T value) {
        final Node<T> node = (Node<T>)root;
        if (value == null || node == null)
            return Integer.MIN_VALUE;

        final Node<T> l = (Node<T>)node.left;
        final Node<T> r = (Node<T>)node.right;
        final int leftSize = ((l!=null)?l.size:0);
        final int idx = leftSize;

        if (value.equals(node.value)) 
            return idx;

        int i = getIndexByValue(l, idx, value);
        if (i >= 0)
            return i;

        i = getIndexByValue(r, idx, value);
        return i;
    }

    /**
     * Split the treap rooted at node at given index
     * 
     * @param node which represents root
     * @param index in treap to split
     * 
     * @return Pair which contains root of both trees
     */
    public static <T> Pair<T> split(Node<T> node, int index) {
        if (node == null)
            return new Pair<T>(null, null);

        final int leftSize = (node.left!=null?
                                  ((Node<T>)node.left).size
                              :
                                  0);
        if (index <= leftSize) {
            final Pair<T> sub = split((Node<T>)node.left, index);
            node.left = sub.right;
            if (node.left != null)
                node.left.parent = node;
            sub.right = node;
            node.update();
            return sub;
        }
        // else
        final Pair<T> sub = split((Node<T>)node.right, (index - leftSize - 1));
        node.right = sub.left;
        if (node.right != null)
            node.right.parent = node;
        sub.left = node;
        node.update();
        return sub;
    }

    /**
     * Merge treaps from given left and right nodes
     * 
     * @param left node which represents root of left treap
     * @param right node which represents root of great treap
     * 
     * @return treap from merged treaps
     */
    public static <T> Node<T> merge(Node<T> left, Node<T> right) {
        if (left == null)
            return right;

        if (right == null)
            return left;

        if (left.priority < right.priority) {
            left.right = merge((Node<T>)left.right, right);
            if (left.right != null) 
                left.right.parent = left;
            left.update();
            return left;
        }
        // else
        right.left = merge(left, (Node<T>)right.left);
        if (right.left != null) 
            right.left.parent = right;
        right.update();
        return right;
    }

    private static <T> Node<T> insert(Node<T> root, int index, T value) {
        final Pair<T> p = split(root, index);
        return merge(merge((Node<T>)p.left, new Node<T>(value)), (Node<T>)p.right);
    }

    private static <T> Node<T> remove(Node<T> root, int index) {
        final Pair<T> p = split(root, index);
        final int leftSize = (p.left!=null?
                                      ((Node<T>)p.left).size
                                  :
                                      0);
        return merge(p.left, (split(p.right, (index + 1 - leftSize))).right);
    }

    private static <T> Node<T> getNodeByValue(Node<T> node, T value) {
        if (node == null)
            return null;

        if (node.value.equals(value))
            return node;

        Node<T> n = getNodeByValue(node.left, value);
        if (n == null)
            n = getNodeByValue(node.right, value);
        return n;
    }

    private static <T> Node<T> getNodeByIndex(Node<T> node, int parentIndex, int index) {
        if (node == null)
            return null;

        final Node<T> p = (Node<T>)node.parent;
        final Node<T> l = (Node<T>)node.left;
        final Node<T> r = (Node<T>)node.right;
        final int leftSize = ((l!=null)?l.size:0);
        final int rightSize = ((r!=null)?r.size:0);

        int idx = Integer.MIN_VALUE;
        if (p!=null && node.equals(p.left)) {
            // left
            idx = parentIndex - rightSize - 1;
        } else if (p!=null && node.equals(p.right)) {
            // right
            idx = leftSize + parentIndex + 1;
        } else {
            throw new RuntimeException("I do not have a parent :-(");
        }

        if (idx == index)
            return node;

        if (index <= idx) {
            return getNodeByIndex(l, idx, index);
        } else {
            return getNodeByIndex(r, idx, index);
        }
    }

    private static <T> int getIndexByValue(Node<T> node, int parentIndex, T value) {
        if (node == null)
            return Integer.MIN_VALUE;

        final Node<T> p = (Node<T>)node.parent;
        final Node<T> l = (Node<T>)node.left;
        final Node<T> r = (Node<T>)node.right;
        final int leftSize = ((l!=null)?l.size:0);
        final int rightSize = ((r!=null)?r.size:0);

        int idx = Integer.MIN_VALUE;
        if (p!=null && node.equals(p.left)) {
            // left
            idx = parentIndex - rightSize - 1;
        } else if (p!=null && node.equals(p.right)) {
            // right
            idx = leftSize + parentIndex + 1;
        } else {
            throw new RuntimeException("I do not have a parent :-(");
        }

        if (value.equals(node.value)) 
            return idx;

        int i = getIndexByValue(l, idx, value);
        if (i >= 0)
            return i;

        i = getIndexByValue(r, idx, value);
        return i;
    }

    public T[] inOrder() {
        return inOrder(root,size);
    }

    public static <T> T[] inOrder(Node<T> node, int size) {
        T[] data = (T[]) new Object[size];
        if (node == null)
            return data;

        inOrder(node, data, 0);
        return data;
    }

    private static <T> int inOrder(Node<T> node, T[] data, int idx) {
        if (node == null)
            return idx;

        idx = inOrder(node.left, data, idx);
        data[idx++] = node.value;
        idx = inOrder(node.right, data, idx);
        return idx;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return TreePrinter.getString(this);
    }

    public static class Node<T> {

        private T value = null;
        private int priority;
        private int size;
        private Node<T> parent = null;
        private Node<T> left = null;
        private Node<T> right = null;

        private Node(T id) {
            this(null, id);
        }

        private Node(Node<T> parent, T id) {
            this.parent = parent;
            this.value = id;
            this.size = 1;
            this.priority = RANDOM.nextInt(randomSeed);
        }

        public int getSize() {
            return size;
        }

        private void update() {
            size = 1 + (left!=null?
                                    ((Node<T>)left).size
                                :   
                                    0) 
                       + (right!=null?
                                   ((Node<T>)right).size
                               :
                                   0);
        }

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

    public static class Pair<T>  {

        private Node<T> left;
        private Node<T> right;

        private Pair(Node<T> left, Node<T> right) {
            this.left = left;
            this.right = right;
        }

        public Node<T> getLesser() {
            return left;
        }

        public Node<T> getGreater() {
            return right;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString() {
            return "left={"+left.toString()+"} right={"+right.toString()+"}";
        }
    }

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

        private ImplicitKeyTreap<T> list = null;

        public JavaCompatibleArrayList(ImplicitKeyTreap<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.removeAtIndex(index);
        }

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

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

    protected static class TreePrinter {

        public static <T> String getString(ImplicitKeyTreap<T> tree) {
            if (tree.root == null)
                return "Tree has no nodes.";
            return getString(tree.root, "", true);
        }

        private static <T> String getString(Node<T> node, String prefix, boolean isTail) {
            StringBuilder builder = new StringBuilder();

            if (node.parent != null) {
                String side = "left";
                if (node.equals(node.parent.right))
                    side = "right";
                builder.append(prefix + (isTail ? "└── " : "├── ") + "(" + side + ") " + node.value + "\n");
            } else {
                builder.append(prefix + (isTail ? "└── " : "├── ") + node.value + "\n");
            }
            List<Node<T>> children = null;
            if (node.left != null || node.right != null) {
                children = new ArrayList<Node<T>>(2);
                if (node.left != null)
                    children.add(node.left);
                if (node.right != null)
                    children.add(node.right);
            }
            if (children != null) {
                for (int i = 0; i < children.size() - 1; i++) {
                    builder.append(getString(children.get(i), prefix + (isTail ? "    " : "│   "), false));
                }
                if (children.size() >= 1) {
                    builder.append(getString(children.get(children.size() - 1), prefix + (isTail ? "    " : "│   "), true));
                }
            }

            return builder.toString();
        }
    }
}

