/* 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
{
    
    /** The bits for computing the hashCode. */
    private static int[] bits =
        { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 };
    
    /** The current hint state. */
    private boolean[] hint = new boolean[10];
    
    /** The number of hints that are true. */
    private int count = 0;
    
    /** Whether or not the hashCode is up-to-date. */
    private boolean hasHash = true;
    
    /** The cached hash. */
    private int hash = 0;
    
    /** The digits for toString(). */
    private static char[] digits =
    { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
    
    
    /**
     * Default constructor.
     * 
     * All hint states are initially false.
     */
    public SudokuHint() { }
    
    
    /**
     * Cloning constructor.
     */
    public SudokuHint(SudokuHint h) {
        if (h == null)
            return;
        
        for (int i = 1; i <= 9; i++)
            setHint(i, h.getHint(i));
    }
    
    
    /**
     * Gets the hint state for the given data value.
     */
    public boolean getHint(int data) {
        if ((data >= 1) && (data <= 9))
            return hint[data];
        
        return false;
    }
    
    
    /**
     * Sets the hint state for the given data value
     * to the given hint state.
     * 
     * Returns true if the hint state was changed.
     */
    public boolean setHint(int data, boolean state) {
        if ((data >= 1) && (data <= 9))
            if (hint[data] != state) {
                hint[data] = state;
                
                if (state)
                    count++;
                else
                    count--;
                
                hasHash = false;
                
                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 state was pruned.
     */
    public boolean pruneHint(int data) {
        return setHint(data, false);
    }
    
    
    /**
     * Prunes this hint of all data values in the
     * given hint.
     */
    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. */
    public void setAll(boolean state) {
        for (int i = 1; i <= 9; i++)
            hint[i] = state;
        
        if (state) {
            count = 9;
            hash = 1022;
        }
        else {
            count = 0;
            hash = 0;
        }
        
        hasHash = true;
    }
    
    
    /**
     * 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 hashCode() == h.hashCode();
    }
    
    
    /**
     * 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 = hashCode();
        int b = h.hashCode();
        
        return (b == (a & b));
    }
    
    
    /**
     * Returns the sum of 2-to-the-power-i for all i
     * such that getHint(i) is true.
     * 
     * In particular, 2 SudokuHint objects are equal
     * if and only if they have the same hashCode.
     */
    public int hashCode() {
        if (hasHash)
            return hash;
        
        hash = 0;
        
        for (int i = 1; i <= 9; i++)
            if (hint[i])
                hash += bits[i];
        
        hasHash = true;
        
        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 hashCode() - h.hashCode(); 
    }
    
    
    /**
     * 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(hint[i] ? digits[i] : '0');
        
        return buffer.toString();
    }
    
}

