/**
 * This models a Greedy Nim position.  Greedy Nim is just like Nim, except that players must choose to remove sticks from the biggest pile.
 *
 * @author Kyle Burke <paithanq@gmail.com>
 * 
 */
 
import java.lang.*;
import java.io.*;
import java.util.*;

public class GreedyNim extends CombinatorialGame {

    //instance variables
    
    //Priority Queue of pile sizes
    private PurePriorityQueue<Integer> piles;
    
    //Integer comparator that sorts in DESCENDING order
    private static final Comparator<Integer> MAX = new Comparator<Integer>() {
        public int compare(Integer left, Integer right) {
            return right.intValue() - left.intValue();
        }
    };

    /**
     * Class constructor.
     *
     * @param piles  A List of natural numbers used as the pile sizes.
     * @throws IllegalArgumentException  if one of the pile sizes is negative.
     */
    public GreedyNim(List<Integer> piles) throws IllegalArgumentException {
        this.piles = new PurePriorityQueue<Integer>(MAX);
        for (Integer pile : piles) {
            if (pile.intValue() > 0) {
                this.piles.add(pile);
            } else if (pile.intValue() < 0) {
                throw new IllegalArgumentException("Tried to use a negative pile size, " + pile.intValue() + ", in a GreedyNim position.");
            }
        }
    }
    
    /**
     * Returns a string version of this.
     *
     * @return  String version of this.
     */
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("GreedyNim position (max pile on left): ");
        for (Integer pile : this.getPilesAsList()) {
            buffer.append(pile + "  ");
        }
        return buffer.toString();
    }
    
    /**
     * Returns the piles of this.
     *
     * @return  A PurePriorityQueue of the pile sizes.
     */
    public PurePriorityQueue<Integer> getPiles() {
        ArrayList<Integer> pileList = this.getPilesAsList();
        
        //create the copy and put the piles into it.
        PurePriorityQueue<Integer> pilesCopy = new PurePriorityQueue<Integer>(MAX);
        for (Integer pile : pileList) {
            pilesCopy.add(pile);
        }
        return pilesCopy;
    }
    
    /**
     * Deep clones this.
     *
     * @return  A deep clone of this.
     */
    public CombinatorialGame clone() {
        return new GreedyNim(this.getPilesAsList());
    }
    
    //@override
    public boolean equals(CombinatorialGame other) {
        if (other == null) {
            return false;
        } else if (this.getClass() == other.getClass()) {
            GreedyNim otherGreedyNim;
            try {
                otherGreedyNim = (GreedyNim) other;
            } catch (ClassCastException cce) {
                //other is not the same type.  
                return false;
            }
            return this.piles.hasSameElementsAs(otherGreedyNim.piles);
        } else {
            return false;
        }
    }
    
    //@override
    public Collection<CombinatorialGame> getOptions(int playerId) {
        Collection<CombinatorialGame> options = new Vector<CombinatorialGame>();
        GreedyNim clone = (GreedyNim) this.clone();
        Integer biggest;
        try {
            biggest = clone.piles.remove();
        } catch (NoSuchElementException nsee) {
            //no options available
            return options;
        }
        //add the option where you take the entire max pile
        //(clone is still missing it's biggest pile)
        options.add(new GreedyNim(clone.getPilesAsList()));
        for (int i = 1; i < biggest.intValue(); i++) {
            GreedyNim copyOfClone = (GreedyNim) clone.clone();
            copyOfClone.piles.add(new Integer(i));
            options.add(copyOfClone);
        }
        return options;
    }
    
    //private methods
    
    //returns an ArrayList of the piles in this.
    private ArrayList<Integer> getPilesAsList() {
        ArrayList<Integer> pilesList = new ArrayList<Integer>();
        PurePriorityQueue<Integer> pilesCopy = new PurePriorityQueue<Integer>(MAX);
        while (true) {
            Integer nextPile;
            try {
                nextPile = this.piles.remove();
                pilesCopy.add(nextPile);
                pilesList.add(nextPile);
            } catch (NoSuchElementException nsee) {
                break; //exit the while loop
            }
        }
        
        //reset the piles and return
        this.piles = pilesCopy;
        return pilesList;
    }
    
    
    /**
     * Represents a factory that creates instances of GreedyNim.
     */
    public static class PositionBuilder implements PositionFactory<GreedyNim> {
    
        //maximum number of piles it will generate
        private int numPiles;
        
        //maximum piles size
        private int maxPileSize;
        
        /**
         * Class constructor.
         *
         * @param numPiles  The maximum number of piles.
         * @param maxPileSize  The maximum size of a pile.
         */
        public PositionBuilder(int numPiles, int maxPileSize) {
            this.numPiles = numPiles;
            this.maxPileSize = maxPileSize;
        }
        
        //@override
        public GreedyNim getPosition() {
            List<Integer> piles = new ArrayList<Integer>();
            Random randomGenerator = new Random();
            for (int i = 0; i < numPiles; i++) {
                int pileSize = randomGenerator.nextInt(this.maxPileSize + 1);
                if (pileSize > 0) {
                    piles.add(new Integer(pileSize));
                }
            }
            return new GreedyNim(piles);
        }
        
    } //end of GreedyNimFactory
    
    //main method with the unit test
    public static void main(String[] args) {
        ArrayList<Integer> piles = new ArrayList<Integer>();
        piles.add(new Integer(4));
        piles.add(new Integer(1));
        piles.add(new Integer(7));
        piles.add(new Integer(0)); //shouldn't get added :)
        piles.add(new Integer(5));
        //test constructor and toString
        GreedyNim nim = new GreedyNim(piles);
        System.out.println("Standard starting position: " + nim.toString());
        
        //test the exception throwing of the constructor
        piles.add(new Integer(-5)); //adding this pile will cause an exception.
        try {
            //should cause an exception
            nim = new GreedyNim(piles);
        } catch (IllegalArgumentException are) {
            System.out.println("Caught the exception caused by the negative pile size!");
        }
        
        //test getPiles (toString already tests this, but let's do it again for completeness)
        PurePriorityQueue<Integer> pilePQ = nim.getPiles();
        System.out.println("Piles in the position: ");
        while (true) {
            try {
                System.out.print(pilePQ.remove() + "  ");
            } catch (NoSuchElementException nsee) {
                break; //no more elements; quit the loop
            }
        }
        System.out.println();
        
        //test clone
        GreedyNim clone = nim;
        try {
            clone = (GreedyNim) nim.clone();
            System.out.println("Cloned game: " + clone.toString());
        } catch (ClassCastException cce) {
            System.out.println("ERROR!  clone() caused a ClassCastException!");
            cce.printStackTrace();
        }
        
        //test equals
        //easy tests
        System.out.println("nim equals clone?  Should be true: " + nim.equals(clone));
        System.out.println("clone equals nim?  Should be true: " + clone.equals(nim));
        //more difficult tests
        ArrayList<Integer> differentPiles = new ArrayList<Integer>();
        differentPiles.add(new Integer(7));
        differentPiles.add(new Integer(5));
        differentPiles.add(new Integer(4));
        GreedyNim differentNim = new GreedyNim(differentPiles);
        System.out.println("nim equals 7-5-4 position?  Should be false: " + nim.equals(differentNim));
        System.out.println("7-5-4 equals nim?  Should be false: " + differentNim.equals(nim));
        differentPiles.add(new Integer(4));
        differentPiles.add(new Integer(1));
        differentNim = new GreedyNim(differentPiles);
        System.out.println("nim equals 7-5-4-4-1 position?  Should be false: " + nim.equals(differentNim));
        System.out.println("7-5-4-4-1 equals nim?  Should be false: " + differentNim.equals(nim));
        //more difficult true test
        ArrayList<Integer> samePiles = differentPiles;
        samePiles.remove(3);
        GreedyNim sameNim = new GreedyNim(samePiles);
        System.out.println("nim equals 7-5-4-1 position?  Should be true: " + nim.equals(sameNim));
        System.out.println("7-5-4-1 equals nim?  Should be true: " + sameNim.equals(nim));
        
        //test getOptions
        System.out.println("Move options: ");
        for (CombinatorialGame position : nim.getOptions(0)) {
            System.out.println(position);
        }
        
        //test GreedyNimFactory
        PositionBuilder gameGenerator = new PositionBuilder(4, 7);
        System.out.println("A randomly-generated game: \n" + gameGenerator.getPosition());
        System.out.println("Another randomly-generated game: \n" + gameGenerator.getPosition());
        System.out.println("Yet another randomly-generated game: \n" + gameGenerator.getPosition());
        
    }
}  //end of GreedyNim.java