import java.util.ArrayList;
import java.util.Comparator;

/**
 * Example priority queue.
 * Written for CS211, Oct 2006.
 * 
 * @author Paul Chew
 * 
 * This version has just two operations: put and get.  It uses an ArrayList to hold the
 * heap.  This simplifies coding, since ArrayList already does table doubling when
 * necessary (if you add to the ArrayList via the add method).
 */
public class PQ<T> {
    /* This could have been made into a subclass of, for instance, AbstractQueue in order
     * integrate this class with the Java Collections Framework, but I haven't tried to
     * do such integration here.
     * 
     * The type parameter here could have been <? extends Comparable<T>>, but this would
     * restrict us to using the natural ordering.  It's likely that clients will sometimes 
     * want to use a different ordering.
     */
    
    private int size = 0;                            // Current number of elements in PQ
    private Comparator<? super T> comparator = null; // Null indicates natural ordering
    private ArrayList<T> heap = new ArrayList<T>();  // Array holding the heap
    /* An ArrayList works nicely because it automatically does "table-doubling".
     * The heap is stored in positions heap[1] to heap[size]; heap[0] is not used
     * as part of the heap, although it does act as a sentinal for bubbleUp operations.
     */
    
    /**
     * Constructor for PQ based on natural ordering.
     */
    public PQ () {
        heap.add(null);  // Use up the zero-space
    }
    
    /**
     * Constructor for PQ based on a Comparator.
     */
    public PQ (Comparator<? super T> comparator) {
        this.comparator = comparator;
        heap.add(null);  // Use up the zero-space
    }
    
    /**
     * Put an item into the PQ.
     */
    public void put (T item) {
        heap.add(item);  // Does table doubling, when necessary
        bubbleUp(++size);
    }
    
    /**
     * Get the head item of the PQ (i.e. the one that comes first in the ordering).
     */
    public T get () {
        T result = heap.get(1);
        heap.set(1, heap.get(size));
        heap.remove(size--);
        bubbleDown(1);
        return result;
    }
    
    /**
     * Bubble-up, adjusting the heap property as we go.
     */
    private void bubbleUp (int index) {
        heap.set(0, heap.get(index));  // Acts as sentinal so we don't fall off the array
        int parent = index / 2;
        while (compare(heap.get(parent), heap.get(index)) > 0) {
            T temp = heap.get(parent);
            heap.set(parent, heap.get(index));
            heap.set(index, temp);
            index = parent;
            parent = index / 2;
        }
    }
    
    /**
     * Bubble-down, adjusting the heap property as we go.
     */
    private void bubbleDown (int index) {
        while (true) {
            int left = 2 * index;
            if (left > size) break;
            int right = left + 1;
            int min = right;
            if (right > size || compare(heap.get(left), heap.get(right)) <= 0) min = left;
            if (compare(heap.get(index), heap.get(min)) <= 0) break;
            T temp = heap.get(index);
            heap.set(index, heap.get(min));
            heap.set(min, temp);
            index = min;
        }
    }
    
    /**
     * Compare two items using the appropriate comparator.
     * @return -1 for a < b; 0 for a = b; +1 for a > b
     */
    private int compare (T a, T b) {
        // The next line generates a compiler warning; I don't know how to prevent this.
        if (comparator == null) return ((Comparable<T>) a).compareTo(b);
        return this.comparator.compare(a, b);
    }
    
    /**
     * Main program (for testing).
     */
    public static void main (String[] args) {
        PQ<String> pq = new PQ<String>();
        PQ<String> other = new PQ<String>(java.util.Collections.reverseOrder());
        String[] months = {"jan", "feb", "mar", "apr", "may", "jun", 
            "jul", "aug", "sep", "oct", "nov", "dec"};
        for (String m: months) {
            pq.put(m); 
            other.put(m);
        }
        for (int i = 0; i < 12; i++) System.out.print(pq.get() + " ");
        System.out.println();
         for (int i = 0; i < 12; i++) System.out.print(other.get() + " ");
        System.out.println();       
    }
}