/*
 * File: BlankClass.java
 * ---------------------
 * This class is a blank one that you can change at will. Remember, if you
 * change the class name, you'll need to change the filename so that it matches.
 */

import java.awt.event.*;
import java.util.Arrays;

import javax.swing.JButton;

import acm.graphics.*;
import acm.program.*;
import acm.util.RandomGenerator;

public class SudokuCheckerSoln extends ConsoleProgram {

	// CONSTANTS AND INSTANCE FIELDS GO HERE!
	public static final int BOARD_SIZE = 9;
	public static final int SECTOR_SIZE = 3;

	/**
	 * This program will eventually check whether a given Sudoku
	 * board is a valid Sudoku solution or not.
	 */
	public void run() {

		// Validates incorrect boards five times.
		for (int i = 0; i < 5; i++) {
			println("Checking INCORRECT sudoku board number " + i + "...");
			SudokuBoard board = new SudokuBoard(false, BOARD_SIZE);
			println("The board looks like:\n" + board);
			boolean result = checkRows(board) && checkCols(board) && checkSectors(board, SECTOR_SIZE);
			println("Result: " + result + "\n");
			if (result) {
				println("Check failed!");
			} else {
				println("Check passed!");
			}
		}

		// Validates single correct board once.
		println("Checking CORRECT sudoku board...");
		SudokuBoard board = new SudokuBoard(true, BOARD_SIZE);
		println("The board looks like:\n" + board);
		boolean result = checkRows(board) && checkCols(board) && checkSectors(board, SECTOR_SIZE);
		println("Result: " + result);
		if (!result) {
			println("Check failed!");
		} else {
			println("Check passed!");
		}
	}


	/**
	 * Checks all rows for 1-9 consistency.
	 * @param arr
	 * @return
	 */
	private boolean checkRows(SudokuBoard board) {
		int [][] boardArr = board.getBoard();
		for (int row = 0; row < boardArr.length; row++) {
			if (!checkConsistency(boardArr[row])) {
				return false;
			}
		}
		return true;
	}

	
	/**
	 * The foundational checker method for this entire program!
	 * Checks if the passed-in array has any duplicate or invalid
	 * numbers.
	 * @param board
	 * @return
	 */
	private boolean checkConsistency(int[] arr) {
		for (int i = 0; i < arr.length; i++) {
			if (arr[i] > BOARD_SIZE || arr[i] <= 0) {
				return false;
			}
			for (int j = i + 1; j < arr.length; j++) {
				if (arr[i] == arr[j]) {
					return false;
				}
			}
		}
		return true;
	}


	/**
	 * Checks all columns for 1-9 consistency :)
	 * @param board
	 * @return
	 */
	private boolean checkCols(SudokuBoard board) {
		int [][] boardArr = board.getBoard();
		for (int col = 0; col < boardArr[0].length; col++) {
			int[] column = new int[boardArr[0].length];
			for (int row = 0; row < boardArr.length; row++) {
				column[row] = boardArr[row][col];
			}
			if (!checkConsistency(column)) {
				return false;
			}
		}
		return true;
	}


	/**
	 * This is NOT one of the ways in which a Sudoku board must
	 * be consistent (i.e. have the numbers 1-9), but is a good
	 * exercise :)
	 * @param board
	 * @return
	 */
	private boolean checkDiagonals(SudokuBoard board) {
		int [][] boardArr = board.getBoard();

		// First check major diagonal
		int[] diagonal = new int[boardArr.length];
		for (int idx = 0; idx < boardArr.length; idx++) {
			diagonal[idx] = boardArr[idx][idx];
		}
		if (!checkConsistency(diagonal)) {
			return false;
		}

		// Then check minor diagonal
		diagonal = new int[boardArr.length];
		for (int idx = 0; idx < boardArr.length; idx++) {
			diagonal[idx] = boardArr[boardArr.length - 1 - idx][idx];
		}
		if (!checkConsistency(diagonal)) {
			return false;
		}

		return false;
	}


	/**
	 * Iterating through all sectors, creating an int[] of numbers
	 * present in that sector, and then checking sector consistency.
	 * @param board
	 * @param sectorSize
	 * @return
	 */
	private boolean checkSectors(SudokuBoard board, int sectorSize) {
		int [][] boardArr = board.getBoard();
		for (int rowSector = 0; rowSector < boardArr.length; rowSector += sectorSize) {
			int[] sector = new int[boardArr.length];
			for (int colSector = 0; colSector < boardArr[rowSector].length; colSector += sectorSize) {
				constructSector(boardArr, rowSector, colSector, sector, sectorSize);
			}
			if (!checkConsistency(sector)) {
				return false;
			}
		}
		return true;
	}
	
	
	/**
	 * Given sector bounds, constructs a 1-D "flattened" version of the sector
	 * to check consistency.
	 * @param boardArr
	 * @param rowSector
	 * @param colSector
	 * @param sector
	 * @param sectorSize
	 */
	private void constructSector(int[][] boardArr, int rowSector, int colSector, int[] sector, int sectorSize) {
		for (int row = 0; row < sectorSize; row++) {
			for (int col = 0; col < sectorSize; col++) {
				sector[row * sectorSize + col] = boardArr[row + rowSector][col + colSector];
			}
		}
	}


	/**
	 * A simple wrapper class for a Sudoku Board representation.
	 */
	private class SudokuBoard {

		private int boardSize;
		private int[][] boardRepr;
		private RandomGenerator rgen;

		private SudokuBoard(boolean correct, int boardSize) {
			this.boardSize = boardSize;
			this.boardRepr = new int[boardSize][boardSize];
			this.rgen = RandomGenerator.getInstance();
			if (correct) {
				generateCorrectBoard();
			} else {
				generateRandomIncorrectBoard();
			}
		}


		/**
		 * A hard-coded valid Sudoku solution.
		 */
		private void generateCorrectBoard() {
			int[][] temp = {
					{7, 3, 5, 6, 1, 4, 8, 9, 2},
					{8, 4, 2, 9, 7, 3, 5, 6, 1},
					{9, 6, 1, 2, 8, 5, 3, 7, 4},
					{2, 8, 6, 3, 4, 9, 1, 5, 7},
					{4, 1, 3, 8, 5, 7, 9, 2, 6},
					{5, 7, 9, 1, 2, 6, 4, 3, 8},
					{1, 5, 7, 4, 9, 2, 6, 8, 3},
					{6, 9, 4, 7, 3, 8, 2, 1, 5},
					{3, 2, 8, 5, 6, 1, 7, 4, 9}
			};
			this.boardRepr = temp;
		}


		/**
		 * Always generates a board with at least one duplicate pair somewhere.
		 */
		private void generateRandomIncorrectBoard() {

			// Fill entire board randomly
			for (int row = 0; row < this.boardRepr.length; ++row) {
				for (int col = 0; col < this.boardRepr[row].length; ++col) {
					this.boardRepr[row][col] = rgen.nextInt(1, boardSize);
				}
			}

			// A lot of random numbers
			int duplicate = rgen.nextInt(1, boardSize);
			int where = rgen.nextInt(boardSize - 1);
			int loc1 = rgen.nextInt(boardSize - 1);
			int loc2 = rgen.nextInt(boardSize - 1);

			// Whether to choose same row or column
			boolean which = rgen.nextBoolean();
			if (which) {
				this.boardRepr[where][loc1] = duplicate;
				this.boardRepr[where][loc2] = duplicate;
			} else {
				this.boardRepr[loc1][where] = duplicate;
				this.boardRepr[loc2][where] = duplicate;
			}
		}


		/**
		 * Safely returns the int[][] board representation.
		 */
		public int[][] getBoard() {
			return this.boardRepr.clone();
		}
		
		
		/**
		 * Returns a nice representation of the SudokuBoard. 
		 */
		public String toString() {
			String ret = "";
			for (int i = 0; i < this.boardRepr.length; i++) {
				ret += Arrays.toString(this.boardRepr[i]) + "\n";
			}
			ret = ret.substring(0, ret.length() - 1);
			return ret;
		}
	}
}

