// Class ObstCourseDataFileHandler
//
// Author: Alyce Brady
//
// This class is based on the College Board's MBSDataFileHandler class,
// as allowed by the GNU General Public License.  MBSDataFileHandler
// is a black-box class within the AP(r) CS Marine Biology Simulation
// case study (see www.collegeboard.com/ap/students/compsci).
//
// License Information:
//   This class is free software; you can redistribute it and/or modify
//   it under the terms of the GNU General Public License as published by
//   the Free Software Foundation.
//
//   This class is distributed in the hope that it will be useful,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   GNU General Public License for more details.

import java.io.File;
import java.io.FileReader;
import java.io.LineNumberReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.StringTokenizer;

import edu.kzoo.grid.Grid;
import edu.kzoo.grid.GridObject;
import edu.kzoo.grid.Location;
import edu.kzoo.grid.gui.GridDataFileHandler;

/**
 *  Obstacle Course Program:<br>
 *
 *  A <code>ObstCourseDataFileHandler</code> object reads and writes
 *  information about an Obstacle Course grid to and from a data
 *  file, including the grid's dimensions and the locations of
 *  the obstacles.
 *  file, including the grid's dimensions, the number of racers,
 *  and the locations of the obstacles.
 *
 *  <p>
 *  The first line of the input file should contain the dimensions of
 *  the obstacle course.  Subsequent lines should specify the locations
 *  of the obstacles.  The file format is:
 *  obstacle course and the number of racers.  Subsequent lines should
 *  specify the locations of the obstacles.  The file format is:
 *  <pre>
 *     rows columns
 *     rows columns numRacers
 *     row-pos col-pos
 *     row-pos col-pos
 *     ...
 *     row-pos col-pos
 *  </pre>
 *  where <code>rows</code> and <code>columns</code> are integers
 *  indicating the number of rows and columns in the grid,
 *  <code>numRacers</code> is an integer indicating the number of racers,
 *  and <code>row-pos</code> and <code>col-pos</code> are integers
 *  indicating the row and column position of an obstacle.
 *
 *  @author Alyce Brady (based on MBSDataFileHandler)
 *  @version 29 February 2004
 **/
public class ObstCourseDataFileHandler implements GridDataFileHandler
{
    // Encapsulated data used to read/write obstacle course info from a file
    private LineNumberReader inputReader;  	// buffered input w/ line number
    private StringTokenizer tokenizer;   	// parses tokens from a line


    /** Reads information about an obstacle course from an initial configuration
     *  data file.
     *  @param  file       java.io.File object from which to read
     *  @throws java.io.FileNotFoundException if file cannot be opened
     *  @throws RuntimeException   if invalid information is read from file
     **/
    public Grid readGrid(File file)
        throws java.io.FileNotFoundException
    {
        // Open the file for reading.
        inputReader = new LineNumberReader(new FileReader(file));

        // Read obstacle course dimensions and construct it.
        Grid grid = buildGrid();

        // Read the number of racers.
//        int numRacers = getNumRacers(grid);

        // Read obstacle locations.
        while ( readObstacle(grid) == true )
            ;

        // Place the racers.
/*        for ( int r = 0; r < numRacers; r++ )
            if ( ! grid.placeRacer() )
                throw new RuntimeException("Error placing racers - not enough columns "
                                   + "(line " 
                                   + inputReader.getLineNumber() + ")");
*/

        return grid;
    }

    /** Read obstacle course dimensions and construct it.
     *  @throws RuntimeException  if invalid grid information is read
     **/
    private ObstacleCourse buildGrid()
    {
        int gridRows, gridCols;

        try
        {
            // Read the grid dimensions.
            gridRows = readInt();
            gridCols = readInt();
        }
        catch (Exception e)
        { 
            throw new RuntimeException("Error reading grid dimensions "
                                   + "(line " 
                                   + inputReader.getLineNumber() + ")");
        }

        // Validate grid dimensions.
        if ( gridRows <= 0 || gridCols <= 0 )
            throw new RuntimeException("Grid dimensions must be positive (line "
                                   + inputReader.getLineNumber() + ")");

        // Construct the appropriate bounded grid.
        ObstacleCourse newGrid = new ObstacleCourse(gridRows, gridCols);
        return newGrid;
    }

    /** Reads the number of racers to place in the grid.
     *  @param grid  the grid
     *  @throws RuntimeException  if invalid grid information is read
     **/
/*    private int getNumRacers(Grid grid)
    {
        int numRacers;

        try
        {
            // Read the number of racers.
            numRacers = readInt();
        }
        catch (Exception e)
        { 
            throw new RuntimeException("Error reading the number of racers "
                                   + "(line " 
                                   + inputReader.getLineNumber() + ")");
        }

        // Validate the number of racers.
        if ( numRacers < 0 || numRacers > grid.numCols() )
            throw new RuntimeException("Number of racers must be positive but not "
                                   + "greater than number of columns (line "
                                   + inputReader.getLineNumber() + ")");

        return numRacers;
    }

    /** Reads location information for the next obstacle and constructs it.
     *  The obstacle adds itself to the grid as it is constructed.
     *  @param grid   the grid in which obstacle should be created
     *  @return <code>true</code> if obstacle was successfully read,
     *          <code>false</code> at EOF
     *  @throws RuntimeExceptions   if invalid location information is read
     **/
    private boolean readObstacle(Grid grid)
    {
        // Read an obstacle's location (if there is another one in the file).
        Location loc = readLocation(grid);
        if ( loc == null )
            return false;

        // Construct the obstacle in the grid.
        new Obstacle(grid, loc);
        return true;
    }

    /** Reads in a Location object (must be a valid location in
     *  <code>grid</code>).
     *  @param grid      grid in which location must be valid
     *  @return  the newly created Location object
     *  @throws MBSException   if invalid location information is read
     **/
    private Location readLocation(Grid grid)
    {
        int row, col;
        try
        {
            // Read the location.
            row = readInt();
            col = readInt();
        }
        catch (java.io.EOFException e)
        {
            // Reached end of file
            return null;
        }
        catch (Exception e)
        {
            // Convert reading exceptions to MBSException.
            throw new RuntimeException("Error reading location (line " 
                                   + inputReader.getLineNumber() + ")"); 
        }
        Location loc = new Location(row, col);
        
        // Verify that location is valid in this grid.
        if ( grid.isValid(loc) )
            return loc;
        else
            throw new RuntimeException("Location " +  loc +
                                   " is not valid in this grid (line " 
                                   + inputReader.getLineNumber() + ")"); 
    }

    /** Returns the next token in the file as an integer.
     *  @return    an int containing the next number in the file
     *  @throws    java.io.EOFException if EOF
     *  @throws    java.lang.NumberFormatException if next token is not an int
     *  @throws    java.io.IOException if another type of input error occurs
     **/
    private int readInt()
        throws java.io.IOException
    {
        // Read in number as string, then convert to integer.
        String token = readString();
        if ( token == null )
            throw new java.io.EOFException();
        return Integer.parseInt(token);
    }

    /** Returns the next token in the file as a string.
     *  @return     a String containing the next token in the file; or null
     *              if end of file is encountered
     *  @throws     java.io.IOException if an input error occurs
     **/
    private String readString()
        throws java.io.IOException
    {
        // Read in a new line if there are no more tokens in current line.
        while ( tokenizer == null || ! tokenizer.hasMoreTokens() )
        {
            String line = inputReader.readLine();

            // Did we encounter end of file?
            if ( line == null )
                return null;
            tokenizer = new StringTokenizer(line);
        }

        // Return next token.
        return tokenizer.nextToken();
    }

    /** Writes information about a grid into a data file,
     *  including the grid dimensions and the locations of
     *  the obstacles.
     *  @param  grid   grid to write to file
     *  @param  file   java.io.File object to which to write
     *  @throws java.io.FileNotFoundException if file cannot be opened
     *  @throws java.io.IOException if error writing to file
     **/
    public void writeGrid(Grid grid, File file)
        throws java.io.IOException
    {
        PrintWriter out = new PrintWriter(new FileWriter(file));

        // Save grid dimensions.
        out.println(grid.numRows() + " " + grid.numCols());

        // Save the number of racers.
//        out.println(grid.numRows() + " " + grid.numCols());
            
        // Save the locations of all the obstacles.
        GridObject[] objList = grid.allObjects();
        for ( int index = 0; index < objList.length; index++ )
        {
            if ( objList[index] instanceof Obstacle )
            {
                int row = objList[index].location().row();
                int col = objList[index].location().col();
                out.println(row + " " + col);
            }
        }
        out.close();
    }

}
