/*
 * 2006 Utah High School Programming Contest, University of Utah
 * Take-Home Problem
 *
 * BoardIO.java
 *
 * This file contains the implementation of the BoardIO class.
 */

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;

/**
 * This class contains methods for initializing <code>Board</code> objects from
 * files and other input sources, and for printing <code>Board</code> objects.
 * Placing these I/O methods in a separate class helps to keep the
 * <code>Board</code> class simple.
 *
 * @see Board
 */
public class BoardIO {
    /**
     * Initializes a <code>Board</code> using data from a file.  The input data
     * format is described in the documentation for
     * {@link #readBoardData(Reader)}.  The process of initializing the board
     * will cause notifications to be sent to a <code>Solver</code>.
     *
     * @param board         the <code>Board</code> to be initialized
     * @param filename      the name of the input data file
     * @param solver        the <code>Solver</code> for the board
     * @throws IOException  if a problem occurs while reading the input file
     */
    public static void initBoardFromFile(Board board, String filename,
                                         Solver solver)
    throws IOException {
        FileReader in = new FileReader(filename);
        try {
            initBoardFromReader(board, in, solver);
        } finally {
            in.close();
        }
    }

    /**
     * Initializes a <code>Board</code> using data from a <code>Reader</code>.
     * The input data format is described in the documentation for
     * {@link #readBoardData(Reader)}.  The process of initializing the board
     * will cause notifications to be sent to a <code>Solver</code>.
     *
     * @param board         the <code>Board</code> to be initialized
     * @param reader        the <code>Reader</code> to provide board data
     * @param solver        the <code>Solver</code> for the board
     * @throws IOException  if a problem occurs while reading data
     */
    public static void initBoardFromReader(Board board, Reader reader,
                                           Solver solver)
    throws IOException {
        int[][] data = readBoardData(reader);
        initBoardFromArray(board, data, solver);
    }

    /**
     * Initializes a <code>Board</code> using data from an array of integers.
     * The array should have the same dimensions as the Sudoku board: that is,
     * <code>Board.BOARD_SIZE</code> rows and the same number of columns.  A
     * zero in the data array represents a square with an unspecified value.  A
     * non-zero value in the data array represents "given" square in the
     * puzzle.  The value of a given square must be between
     * <code>Square.MIN_SQ_VALUE</code> and <code>Square.MAX_SQ_VALUE</code>,
     * inclusive.
     *
     * <p>The process of initializing the board will cause notifications to be
     * sent to a <code>Solver</code>.
     *
     * @param board         the <code>Board</code> to be initialized
     * @param data          the array of input data
     * @param solver        the <code>Solver</code> for the board
     * @throws IOException  if the data array contains an invalid value
     */
    public static void initBoardFromArray(Board board, int[][] data,
                                          Solver solver)
    throws IOException {
        for (int i = 0; i < Board.BOARD_SIZE; ++i) {
            for (int j = 0; j < Board.BOARD_SIZE; ++j) {
                int value = data[i][j];
                if (value == 0) {
                    // An unspecified square.  Do nothing.
                } else if ((value >= Square.MIN_SQ_VALUE)
                           && (value <= Square.MAX_SQ_VALUE)) {
                    Square sq = board.getSquare(i, j);
                    sq.setTo(value, solver);
                } else {
                    throw new IOException(
                        "invalid square value " + value
                        + " at [" + i + "," + j + "]");
                }
            }
        }
    }

    /**
     * Reads data describing a Sudoku board from the given <code>Reader</code>.
     *
     * <p>The input must contain exactly <code>Board.BOARD_SIZE</code> lines,
     * and each line must contain exactly <code>Board.BOARD_SIZE</code>
     * "digits" and spaces.  (Line separator characters are ignored.)
     *
     * <p>A "digit" in this context is a single character that represents a
     * non-zero value in the number base that is
     * <code>Board.BOARD_SIZE+1</code>.  For example, if the board size is 9,
     * then digits are the characters '1' through '9'.  If the board size is
     * 16, then digits are '1' through '9' and the letters 'a' through 'g'.
     * Letter case is insignificant.
     *
     * <p>A digit in the input represents the value of a "given" square in the
     * puzzle: in other words, a square that is specified as part of the
     * puzzle.  These squares are represented by non-zero values in the
     * returned array.  A space in the input represents an unspecified square,
     * which is represented by a zero in the returned array.
     *
     * <p>The first line of input describes the topmost row of the board (row 0
     * in the returned array).  Similarly, the first characters on each line
     * describe the leftmost column of the board (column 0).  The returned
     * array is indexed by [row,column].
     *
     * @param reader        the <code>Reader</code> to provide board data
     * @return              the parsed board data
     * @throws IOException  if an error occurs while reading data
     */
    public static int[][] readBoardData(Reader reader) throws IOException {
        BufferedReader in = new BufferedReader(reader);
        try {
            int lineno = 1;
            int data[][] = new int[Board.BOARD_SIZE][Board.BOARD_SIZE];

            for (int i = 0; i < Board.BOARD_SIZE; ++i, ++lineno) {
                String line = in.readLine();
                if (line == null) {
                    throw new EOFException(
                        "unexpected end of input at line " + lineno);
                }
                if (line.length() != Board.BOARD_SIZE) {
                    throw new IOException(
                        "input line " + lineno + " is too "
                        + (line.length() < Board.BOARD_SIZE ?
                           "short" : "long"));
                }
                for (int j = 0; j < Board.BOARD_SIZE; ++j) {
                    char c = line.charAt(j);
                    if (c == ' ') {
                        data[i][j] = 0;
                        continue;
                    }
                    int val = Character.digit(c, Square.MAX_SQ_VALUE+1);
                    if ((val >= Square.MIN_SQ_VALUE)
                        && (val <= Square.MAX_SQ_VALUE)) {
                        data[i][j] = val;
                    } else {
                        throw new IOException(
                            "invalid input character '" + c + "' on line "
                            + lineno);
                    }
                }
            }
            String line = in.readLine();
            if (line != null) {
                throw new IOException(
                    "input has more than " + Board.BOARD_SIZE + " lines");
            }
            return data;
        } finally {
            in.close();
        }
    }

    /**
     * Prints a <code>Board</code>.  The output format mirrors the input format
     * described in the documentation for {@link #readBoardData(Reader)}.  In
     * summary, known square values are printed as single-character digits, and
     * unknown square values are printed as spaces.
     *
     * @param board  the <code>Board</code> to be printed
     * @param out    the output <code>PrintStream</code>
     */
    public static void print(Board board, PrintStream out) {
        for (int i = 0; i < Board.BOARD_SIZE; ++i) {
            for (int j = 0; j < Board.BOARD_SIZE; ++j) {
                Square sq = board.getSquare(i, j);
                if (sq.isKnown()) {
                    int value = sq.getValue();
                    out.print(Integer.toString(value, Square.MAX_SQ_VALUE+1));
                } else {
                    out.print(' ');
                }
            }
            out.println();
        }
    }
}

// End of file.

