/* Useful imports */

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.codec.*;
import edu.neu.ccs.console.*;
import edu.neu.ccs.filter.*;
import edu.neu.ccs.jpf.*;
import edu.neu.ccs.parser.*;
import edu.neu.ccs.pedagogy.*;
import edu.neu.ccs.quick.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
import java.util.*;
import java.math.*;
import java.beans.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.regex.*;
import java.text.ParseException;


public class SudokuHint
    implements Comparable
{
    // Static definitions //
    
    /** The bits for computing the hash. */
    private static int[] bits =
        { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 };
    
    /** The digits for toString(). */
    private static char[] digits =
    { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
    
    
    // Member definitions //
    
    /** The cached hash that maintains the hint state. */
    private int hash = 0;
    
    /** The number of hints that are true. */
    private int count = 0;
    
    
    // Constructors //
    
    /**
     * Default constructor.
     * 
     * All hint states are initially false.
     */
    public SudokuHint() { }
    
    
    /**
     * Cloning constructor.
     */
    public SudokuHint(SudokuHint h) {
        if (h == null)
            return;
        
        hash  = h.hash;
        count = h.count;
    }
    
    
    /**
     * The constructor that sets one data item to true
     * and all other data items to false.
     * 
     * @param data the single data item to set to true
     */
    public SudokuHint(int data) {
        setOneHint(data);
    }
    
    
    // Methods //
    
    /**
     * Gets the hint state for the given data value.
     */
    public boolean getHint(int data) {
        if ((data >= 1) && (data <= 9))
            return ((hash & bits[data]) != 0);
        
        return false;
    }
    
    
    /**
     * Sets the hint state for the given data value
     * to the given hint state.
     * 
     * Returns true if the hint was changed.
     */
    public boolean setHint(int data, boolean state) {
        if ((data >= 1) && (data <= 9))
            if (getHint(data) != state) {
                if (state) {
                    hash += bits[data];
                    count++;
                }
                else {
                    hash -= bits[data];
                    count--;
                }
                
                return true;
            }
        
        return false;
    }
    
    
    /**
     * Sets the hint state for the given data value
     * to false, that is, prunes that data value.
     * 
     * Returns true if the hint was pruned.
     */
    public boolean pruneHint(int data) {
        return setHint(data, false);
    }
    
    
    /**
     * Prunes this hint of all data values in the
     * given hint.
     * 
     * Returns true if the hint was pruned.
     */
    public boolean pruneHint(SudokuHint h) {
        if (h == null)
            return false;
        
        boolean haspruned = false;
        
        for (int data = 1; data<= 9; data++)
            if (h.getHint(data))
                haspruned |= pruneHint(data);
        
        
        return haspruned;
    }
    
    
    /**
     * Sets all hint states to the given state.
     * 
     * Returns true if the hint was changed.
     */
    public boolean setAll(boolean state) {
        int oldhash = hash;
        
        if (state) {
            hash  = 1022;
            count = 9;
        }
        else {
            hash  = 0;
            count = 0;
        }
        
        return (oldhash != hash);
    }
    
    
    /**
     * Sets the hint state for the given data value to true
     * and all other hint states to false.
     * 
     * Returns true if the hint was changed.
     */
    public boolean setOneHint(int data) {
        int oldhash = hash;
        
        if ((data >= 1) && (data <= 9)) {
            hash  = bits[data];
            count = 1;
        }
        
        return (oldhash != hash);
    }
    
    
    /**
     * Returns the count of indices for which the hint state
     * is true.
     */
    public int count() {
        return count;
    }
    
   
    /**
     * Returns true if this hint is equal to the given hint
     * in the sense that all corresponding hint states agree.
     */
    public boolean isEqualTo(SudokuHint h) {
        if (h == null)
            return false;
        
        return hash == h.hash;
    }
    
    
    /**
     * Returns true if o is a SudokuHint and isEqualTo this
     * hint.
     */
    public boolean equals(Object o) {
        if (o instanceof SudokuHint)
            return isEqualTo((SudokuHint) o);
        
        return false;
    }
    
    
    /**
     * Returns whether this hint contains
     * hint h as a subset.
     * 
     * Will also return true if this hint
     * equals h.
     */
    public boolean contains(SudokuHint h) {
        if (h == null)
            return false;
        
        int a = hash;
        int b = h.hash;
        
        return (b == (a & b));
    }
    
    
    /**
     * Returns the sum of 2-to-the-power-i for all i
     * such that getHint(i) is true.
     * 
     * Two SudokuHint objects are equal if and only if
     * they have the same hashCode.
     */
    public int hashCode() {
        return hash;
    }
    
    
    /**
     * If o is a SudokuHint h, returns the difference of
     * this hashCode minus that of h.
     * 
     * Throws ClassCastException if o is not a SudokuHint.
     */
    public int compareTo(Object o) {
        if (! (o instanceof SudokuHint))
            throw new ClassCastException
                ("Illegal argument in compareTo in SudokuHint");
        
        SudokuHint h = (SudokuHint) o;
        
        return hash - h.hash; 
    }
    
    
    /**
     * Returns a string with nine 1's and 0's corresponding
     * to the nine true-false states of the hint.
     */
    public String toString() {
        StringBuffer buffer = new StringBuffer(9);
        
        for (int i = 1; i <= 9; i++)
            buffer.append(getHint(i) ? digits[i] : '0');
        
        return buffer.toString();
    }
    
}
