
package edu.unl.consystlab.sudoku;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;


public class Grid implements Griddable
{
	public final static int SZ = 9;

	// used for value and state
	public final static byte EMPTY = 0;
	// states:
	public final static byte LOCKED = 2;
	public final static byte UTRYING = 3; // user trying
	public final static byte STRYING = 4; // solver trying
	public final static byte SOLVED = 5;

	// data
	private byte[][] values = new byte[SZ][SZ];
	private byte[][] states = new byte[SZ][SZ];

	// statistics
	private byte rowUsed[][] = new byte[9][10];
	private byte colUsed[][]= new byte[9][10];
	private byte sqUsed[][]= new byte[9][10];

	public Grid()
	{
		clear();
	}

	public void clear(int row, int col)
	{
		setValue(row,col,Grid.EMPTY);
		setState(row,col,Grid.EMPTY);
	}
	
	public void clear()
	{
		// clear values & state
		for (int i = 0; i < SZ; i++)
		{
			for (int j = 0; j < SZ; j++)
			{
				clear(i,j);
			}
		}
		// clear statistics
		for (int x = 0; x < 9; x++)
		{
			for (byte val = 1; val < 10; val++) // NOTE: from 1 to 9 !!!
			{
				rowUsed[x][val] = 0;
				colUsed[x][val] = 0;
				sqUsed[x][val] = 0;
			}
		}
	} // clear()

	public byte getState(int row, int col)
	{
		return states[row][col];
	}

	public void setState(int row, int col, byte state)
	{
		states[row][col] = state;
	}

	public void setValue(int row, int col, byte val)
	{
		int sq = 3 * (row / 3) + col / 3;
		
		int oldval = values[row][col];		
		rowUsed[row][oldval]--;
		colUsed[col][oldval]--;		
		sqUsed[sq][oldval]--;
				
		values[row][col]=val;
				
		rowUsed[row][val]++;
		colUsed[col][val]++;		
		sqUsed[sq][val]++;
		
	} // setValue()
	
	public void lock(int row, int col, byte val)
	{
		setValue(row,col,val);
		if(val==Grid.EMPTY) // should have used clear!
			setState(row,col,Grid.EMPTY);
		else
			setState(row,col,Grid.LOCKED);
	}
	
	public byte getValue(int row, int col)
	{
		return values[row][col];
	}
	

	public int getEntropy(int row, int col)
	{
		if (states[row][col] == Grid.LOCKED)
			return 0; // nothing to choose
		int sq = 3 * (row / 3) + col / 3;
		int entropy = 0;
		for (byte v = 0; v < 9; v++)
		{
			if (isUsed(row, col, v) == false)
				entropy++;
		}
		return entropy;
	} // lower getEntropy() is better

	public boolean isUsed(int row, int col, byte val)
	{
		if (rowUsed[row][val] > 0)
			return true;
		if (colUsed[col][val] > 0)
			return true;
		int sq = 3 * (row / 3) + col / 3;
		if (sqUsed[sq][val] > 0)
			return true;
		return false;
	}

	public boolean isOk(int row, int col)
	{
		int val = getValue(row,col);
		if (rowUsed[row][val] > 1)
			return false;
		if (colUsed[col][val] > 1)
			return false;
		int sq = 3 * (row / 3) + col / 3;
		if (sqUsed[sq][val] > 1)
			return false;
		return true;
	} // isOk()
	
	public boolean isOk(int row, int col, int val)
	{
		if (rowUsed[row][val] > 1)
			return false;
		if (colUsed[col][val] > 1)
			return false;
		int sq = 3 * (row / 3) + col / 3;
		if (sqUsed[sq][val] > 1)
			return false;
		return true;
	} // isOk()
	
	public int getProgressPct()
	{
		int ok = 0;
		for (int i = 0; i < SZ; i++)
		{
			for (int j = 0; j < SZ; j++)
			{
				if(values[i][j]!=Grid.EMPTY)
					ok++;
			}
		}
		return ((ok*100)/(SZ*SZ));		
	} // getProgressPct
	
	
	public void lock(boolean mustLock)
	{
		for (int i = 0; i < SZ; i++)
		{
			for (int j = 0; j < SZ; j++)
			{
				if(getValue(i,j)==Grid.EMPTY)
					continue;
				if(mustLock)
				{
					if(isOk(i,j)==false)
						continue;
					setState(i,j,Grid.LOCKED);
				}
				else
					setState(i,j,Grid.UTRYING);				
			}
		}	
	} // lock();
	
	public void lock()
	{
		lock(true);
	}
	
	/*
	 * lockToggle switches state to all-locked or all-unlocked,
	 * depending on current state
	 */
	public void lockToggle() 
	{
		boolean mustLock = false;
		for (int i = 0; i < SZ; i++)
		{
			for (int j = 0; j < SZ; j++)
			{
				if(getValue(i,j)==Grid.EMPTY)
					continue;
				if(getState(i,j)!=Grid.LOCKED && isOk(i,j))
					mustLock = true;			
			}
		}		
		lock(mustLock);
	}
	
	public boolean save(String name,BufferedWriter bw)
	{
		try
		{
			bw.write(name+"\n");	
			for (int i = 0; i < SZ; i++)
			{
				for (int j = 0; j < SZ; j++)
				{
					bw.write(""+getValue(i,j));
				}
				bw.write("\n");
			}
			bw.write("States:\n");
			for (int i = 0; i < SZ; i++)
			{
				for (int j = 0; j < SZ; j++)
				{
					bw.write(""+getState(i,j));
				}
				bw.write("\n");
			}
			return true;
		}
		catch(IOException e)
		{
		}
		return false;	
	} // save()
	
	public boolean load(String name,BufferedReader br)
	{
		try
		{
			String line;
			line = br.readLine(); // skip name
			for (byte i = 0; i < SZ; i++)
			{
				line = br.readLine();
				for (byte j = 0; j < SZ; j++)
				{
					byte c = (byte)(line.charAt(j)-'0');
					lock(i,j,c);
				}
			}
			line = br.readLine(); // skip 'State' line
			for (byte i = 0; i < SZ; i++)
			{
				line = br.readLine();
				for (byte j = 0; j < SZ; j++)
				{
					byte c = (byte)(line.charAt(j)-'0');
					setState(i,j,c);
				}
			}
			return true;
		}
		catch(IOException e)
		{
		}
		return false;	
	} // load()
	
}
