/**
 * This models a Myopic Col position on a Binary Tree.  Myopic Col is just like Col, except that it's played on a directed graph and only outgoing edges "count" towards whether a vertex can't be painted.
 *
 * @author Kyle Burke <paithanq@gmail.com>
 * 
 */
 
import java.lang.*;
import java.io.*;
import java.util.*;
import java.awt.*;
import javax.swing.*;

public class TreeMyopicCol extends CombinatorialGame implements SwingDisplayable {
    
    //constants
    
    /**
     * Integer constant representing the "uncoloredness" of a vertex.
     */
    public static final int UNCOLORED = 2;

    //instance variables
    
    //Priority Queue of heap sizes
    private PureBinaryTree<Color> tree;
    
    //Map of integers to appropriate colors
    //initialize this outside of the constructor because it's static.
    private final Map<Integer, Color> intsToColors = new TreeMap<Integer, Color>();
    
    //Map of Colors to Integers
    //initialize this outside of the constructor because it's static
    private final Map<Color, Integer> colorsToInts = new HashMap<Color, Integer>();
    
    //Constructors
    
    //private constructor that sets the maps
    private TreeMyopicCol() {
        //set up the ints->Colors map
        this.intsToColors.put(CombinatorialGame.LEFT, Color.BLUE);
        this.intsToColors.put(CombinatorialGame.RIGHT, Color.RED);
        this.intsToColors.put(UNCOLORED, Color.WHITE);
        //set up the Colors -> ints Map
        this.colorsToInts.put(Color.BLUE, CombinatorialGame.LEFT);
        this.colorsToInts.put(Color.RED, CombinatorialGame.RIGHT);
        this.colorsToInts.put(Color.WHITE, UNCOLORED);
    }
    
    /**
     * Class constructor for a game that starts uncolored.
     *
     * @param levels  The number of levels 
     * @throws IllegalArgumentException  if one of the heap sizes is negative.
     */
    public TreeMyopicCol(int levels) throws IllegalArgumentException {
        this();
        if (levels < 1) {
            throw new IllegalArgumentException("Tried to use a non-positive number for the number of levels.");
        } else {
            this.tree = this.createEmptyTree(levels);
        }
    }
    
    /**
     * Class constructor.
     *
     * @param tree  A PureBinaryTree<Integer> to build our tree from.
     */
    public TreeMyopicCol(PureBinaryTree<Integer> tree) {
        this();
        this.tree = getColorTree(tree);
    }
    
    /**
     * Returns a string version of this.
     *
     * @return  String version of this.
     */
    public String toString() {
        return this.tree.toString();
    }
    
    /**
     * Returns the underlying tree.
     *
     * @return  A copy of the PureBinaryTree that holds the appropriate Integers.
     */
    public PureBinaryTree<Integer> getTree() {
        return this.getIntTree(this.tree);
    }
    
    //@override
    public CombinatorialGame clone() {
        return new TreeMyopicCol(this.getIntTree(this.tree));
    }
    
    //@override
    public boolean equals(CombinatorialGame object) {
        if (object == null) {
            return false;
        } else if (this.getClass() == object.getClass()) {
            TreeMyopicCol other = (TreeMyopicCol) object;
            return this.tree.equals(other.tree);
        } else {
            return false;
        }
    }
    
    //@override
    public Collection<CombinatorialGame> getOptions(int playerId) {
        PureBinaryTree<Integer> tree = this.getTree();
        return this.getOptions(playerId, tree, tree);
    }
    
    //private methods
    
    //gets a Color from the appropriate int
    public Color getColorFromInt(int colorAsInt) {
        return this.intsToColors.get(colorAsInt);
    }
    
    //gets an int from the appropriate Color
    public int getColorInteger(Color color) {
        return this.colorsToInts.get(color);
    }
    
    //Creates an empty tree with a given number of levels
    private PureBinaryTree<Color> createEmptyTree(int levels) {
        if (levels <= 0) {
            return null;
        } else {
            PureBinaryTree<Color> tree = new PureBinaryTree<Color>(this.getColorFromInt(UNCOLORED));
            tree.setLeftChild(this.createEmptyTree(levels - 1));
            tree.setRightChild(this.createEmptyTree(levels - 1));
        }
        return tree;
    }
    
    //copies a tree of ints to a tree using the appropriate Colors
    private PureBinaryTree<Color> getColorTree(PureBinaryTree<Integer> tree) {
        if (tree == null) {
            return null;
        } else {
            PureBinaryTree<Color> copy = new PureBinaryTree<Color>(this.getColorFromInt(tree.getValue()));
            copy.setLeftChild(this.getColorTree(tree.getLeftChild()));
            copy.setRightChild(this.getColorTree(tree.getRightChild()));
            return copy;
        }
    }
    
    //copies a tree of Colors to a tree using the appropriate Integers
    private PureBinaryTree<Integer> getIntTree(PureBinaryTree<Color> tree) {
        if (tree == null) {
            return null;
        } else {
            PureBinaryTree<Integer> copy = new PureBinaryTree<Integer>(this.getColorInteger(tree.getValue()));
            copy.setLeftChild(this.getIntTree(tree.getLeftChild()));
            copy.setRightChild(this.getIntTree(tree.getRightChild()));
            return copy;
        }
    }
    
    //copies a tree of integers
    private PureBinaryTree<Integer> copyIntTree(PureBinaryTree<Integer> tree) {
        return this.getIntTree(this.getColorTree(tree));
    }
    
    //recursive helper method for getOptions
    private Collection<CombinatorialGame> getOptions(int playerId, PureBinaryTree<Integer> root, PureBinaryTree<Integer> currentNode) {
        Collection<CombinatorialGame> options = new ArrayList<CombinatorialGame>();
        if (currentNode != null) {
            //see if we can make a move at the current node
            if (currentNode.getValue().equals(UNCOLORED)) {
                if ((currentNode.getRightChild() == null || !currentNode.getRightChild().getValue().equals(playerId)) &&
                    (currentNode.getLeftChild() == null || !currentNode.getLeftChild().getValue().equals(playerId))) {
                    //set the current node's color to the playerId
                    currentNode.setValue(playerId);
                    //copy the ENTIRE tree and add it
                    PureBinaryTree<Integer> copy = copyIntTree(root);
                    TreeMyopicCol option = new TreeMyopicCol(copy);
                    options.add(option);
                    //reset the current node to have no color
                    currentNode.setValue(UNCOLORED);
                    
                }
            }
            //add the options from the left subtree
            options.addAll(this.getOptions(playerId, root, currentNode.getLeftChild()));
            //ad all the options from the right subtree
            options.addAll(this.getOptions(playerId, root, currentNode.getRightChild()));
        }
        return options;
    }
    
    /**
     * Gets a Swing representation of this.
     *
     * @return  A JComponent containing a graphical display of the current position.
     */
    public JComponent toSwingComponent() {
        return new TreeMyopicColPanel(this);
    }
    
    
    /**
     * Represents a factory that creates instances of TreeMyopicCol.
     */
    public static class PositionBuilder implements PositionFactory<TreeMyopicCol> {
    
        //minimum depth of the tree
        private int minDepth;
        
        //maximum depth of the tree
        private int maxDepth;
        
        //probability a vertex will have each of the two children
        private double childProbability;
        
        //probability a vertex will be colored
        private double colorDensity;
        
        /**
         * Class constructor.
         *
         * @param minDepth  Minimum depth of a full tree.
         * @param childProbability  For each potential child of a node, probability that child will exist (past the minimum depth)
         * @param colorDensity  The probability a vertex will be colored.  (Color chosen uniformly at random.)
         */
        public PositionBuilder(int minDepth, int maxDepth, double childProbability, double colorDensity) {
            this.minDepth = minDepth;
            this.maxDepth = maxDepth;
            this.childProbability = childProbability;
            this.colorDensity = colorDensity;
        }
        
        //@override
        public TreeMyopicCol getPosition() {
            PureBinaryTree<Integer> tree = this.buildTree(0);
            return new TreeMyopicCol(tree);
        }
        
        //Helper function for getPosition.  Builds a tree, starting at the given depth
        private PureBinaryTree<Integer> buildTree(int depth) {
            Random randomGenerator = new Random();
            //choose the color of this node
            int color;
            double nextDouble = randomGenerator.nextDouble();
            if (nextDouble < (this.colorDensity/2)) {
                color = CombinatorialGame.LEFT;
            } else if (nextDouble < this.colorDensity) {
                color = CombinatorialGame.RIGHT;
            } else {
                color = UNCOLORED;
            }
            PureBinaryTree<Integer> tree = new PureBinaryTree<Integer>(color);
            if (depth < this.maxDepth) {
                //put together the left child, if it should exist
                if (depth < this.minDepth || randomGenerator.nextDouble() < this.childProbability) {
                    tree.setLeftChild(this.buildTree(depth + 1));
                }
                //put together the right child, if it should exist
                if (depth < this.minDepth || randomGenerator.nextDouble() < this.childProbability) {
                    tree.setRightChild(this.buildTree(depth + 1));
                }
                
            }
            return tree;
        }
        
    } //end of TreeMyopicColFactory
    
    //main method with the unit test
    public static void main(String[] args) {
        
        //test TreeMyopicColFactory
        PositionBuilder gameGenerator = new PositionBuilder(3, 6, .3, .2);
        System.out.println("A randomly-generated game: \n" + gameGenerator.getPosition());
        System.out.println("Another randomly-generated game: \n" + gameGenerator.getPosition());
        TreeMyopicCol position = gameGenerator.getPosition();
        System.out.println("Yet another randomly-generated game: \n" + position);
        
        JFrame frame = new JFrame("TreeMyopicCol");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(1000, 1000));
        frame.setContentPane(position.toSwingComponent());
        frame.pack();
        frame.setVisible(true);
    }
}  //end of TreeMyopicCol.java