package rebelsky.io;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.NumberFormat;
import java.text.ParsePosition;

/**
 * A simple input system for beginning programmers.  Permits reading
 * of basic data types without delving into the workings of the
 * various Java libraries.  Does not throw exceptions.
 * <p>
 * If something goes wrong with one of the read methods, an error
 * value is returned.  You can determine what that value is with
 * <code>getErrorXXX</code> (e.g., <code>getErrorInt</code>) and 
 * set the value with <code>setErrorXXX</code>).  You can also use
 * <code>checkError</code> to determine whether a value you received 
 * is intended to mark an error.
 * <p>
 * Copyright (c) 1998 Samuel A. Rebelsky.  All rights reserved.
 * 
 * @author Samuel A. Rebelsky
 * @version 1.1 of January 1998
 */
public class SimpleInput {

  // +-------+-------------------------------------------------------------
  // | Notes |
  // +-------+

/*
  The current line of text is kept in a buffer (called line) and we
  do all of the work within that buffer.  This makes it easier to
  do unreading, peeking, error recovery, and other similar manipulations.
  It also allows us to take advantage of the NumberFormat.parse()
  routine which can be used to parse numbers.

  We use a variable to keep track of whether or not we're at the end
  of the file.  When readLine() throws an exception, we mark the
  end of the file.
 */

  
  // +--------+------------------------------------------------------------
  // | Fields |
  // +--------+

  /**
   * Are we testing this puppy?
   */
  private static final boolean TESTING = false;

  /**
   * The base input.
   */
  protected BufferedReader base;

  /**
   * Do we need to close our input?
   */ 
  protected boolean needs_closing;

  /**
   * The current line.
   */
  protected String buffer;

  /**
   * The position on the current line.
   */
  protected ParsePosition pos;

  /**
   * A tool for parsing real numbers (floats and doubles).
   */
  protected NumberFormat real_parser;

  /**
   * A tool for parsing integers (and longs).
   */
  protected NumberFormat int_parser;

  /**
   * Are we at the end of the file?
   */
  protected boolean at_eof;

  /**
   * The default double value used when errors occur.
   */
  protected double DOUBLE_ERROR = Double.NaN;

  /**
   * The string used when errors occur.
   */
  protected String STRING_ERROR = "";

  /**
   * The default integer value used when errors occur.
   */
  protected int INT_ERROR = -9999;

  /**
   * The default character value used when errors occur.
   */
  protected char CHAR_ERROR = (char) -1;

  /**
   * Did we just encounter an error?
   */
  protected boolean error = false;


  // +--------------+------------------------------------------------------
  // | Constructors |
  // +--------------+

  /**
   * Create a new object to read from standard input.
   * <br>pre: standard input has not been modified.
   * <br>post: the object will read from standard input.
   */
  public SimpleInput() {
    base = new BufferedReader(new InputStreamReader(System.in));
    real_parser = NumberFormat.getNumberInstance();
    int_parser = NumberFormat.getNumberInstance();
    int_parser.setParseIntegerOnly(true);
    needs_closing = false;
    buffer = null;
    pos = null;
    at_eof = false;
  } // SimpleInput

 
  // +------------------+--------------------------------------------------
  // | Exported Methods |
  // +------------------+

  /** 
   * See if the last input operation we did returned an error.
   * <br>pre: We've completed an input operation.
   * <br>post: Returns true if the last operation failed.
   *           Returns false otherwise.
   */
  public boolean checkError() {
    return error;
  } // checkError

  /**
   * See if we're at the end of the file.
   * <br>pre: (none)
   * <br>post: if no input has been read, input may be read
   * <br>post: returns true if we've reached the end of the file
   */
  public boolean eof() {
    // Make sure that the buffer is full.
    fillBuffer();
    // See if we've hit the end of file.
    return at_eof;
  } // eof

  /**
   * See if we're at the end of the line.
   * <br>pre: (none)
   * <br>post: if no input has been read, input may be read
   * <br>post: returns true if we're currently at the end of the line
   */
  public boolean eoln() {
    // Make sure that the buffer is full
    fillBuffer();
    // If the index is off of the string, then we're at the end of line.
    return (pos.getIndex() >= buffer.length());
  } // eoln()

  /** 
   * Get the double used to report errors.
   * <br>pre: (none)
   * <br>post: The double currently used to report errors is returned.
   */
  public double getErrorDouble() {
    return DOUBLE_ERROR;
  } // getErrorDouble

  /** 
   * Get the integer used to report errors.
   * <br>pre: (none)
   * <br>post: The integer currently used to report errors is returned.
   */
  public int getErrorInt() {
    return INT_ERROR;
  } // getErrorInt

  /**
   * Get the string used to report errors.
   * <br>pre: (none)
   * <br>post: The string currently used to report errors is returned.
   */
  public String getErrorString() {
    return STRING_ERROR;
  } // getErrorString

  /**
   * Look at the next character in the buffer.
   * <br>pre: we haven't reached the end of the buffer.
   * <br>post: the next character to be read is returned.
   * <br>post: one line of input may be read.
   */
  public char peek() {
    // Make sure there's stuff in the buffer
    fillBuffer();
    // Are we at the end of file?  If so, return an error.
    if (at_eof) {
      return CHAR_ERROR;
    }
    // Are we at the end of the line?  If so, return the end-of-line
    // character (currently Unix-specific).
    else if (eoln()) {
      return '\n';
    }
    // Return the appropriate character
    return buffer.charAt(pos.getIndex());
  } // peek

  /**
   * Read the next character (including whitespace) from the given
   * input.  If you are at the end of the line, returns the end
   * of line character.
   * <br>pre: (none)
   * <br>post: input may be read
   */
  public char readChar() {
    // Get the current character
    char ch = peek();
    // Advance the position
    pos.setIndex(pos.getIndex()+1);
    // Return the character
    return ch;
  } // readChar()

  /**
   * Read a double from the given input.  Skips any whitespace up
   * to the double .  If the next thing in the input is not a double
   * sets the cursor at the beginning of that thing, but does not
   * read beyond it and returns the default double.
   * <br>pre: the next input characters are whitespace and those 
   *          that make up an double.
   * <br>post: the cursor is after those digits.
   */
  public double readDouble() {
    // Assume no errors.
    error = false;

    // Make sure the buffer is full
    fillBuffer();

    // If we're at the end of the file, report an error
    if (at_eof) {
      error = true;
      return INT_ERROR;
    }

    // Skip any whitespace
    skipWhite();

    // Get and return the next integer
    try {
      return real_parser.parse(buffer, pos).doubleValue();
    }
    catch (Exception e) {
      error = true;
      return DOUBLE_ERROR;
    } // exception
  } // readDouble() 

  /**
   * Read an integer from the given input.  Skips any whitespace up
   * to the integer.  If the next thing in the input is not an integer,
   * sets the cursor at the beginning of that thing, but does not
   * read beyond it and returns the default integer.
   * <br>pre: the next input characters are whitespace and those 
   *          that make up an integer (optional sign and some number
   *          of digits).
   * <br>post: the cursor is after those digits.
   */
  public int readInt() {
    // Assume no errors.
    error = false;

    // Make sure the buffer is full
    fillBuffer();

    // If we're at the end of the file, report an error
    if (at_eof) {
      error = true;
      return INT_ERROR;
    }

    // Skip any whitespace
    skipWhite();

    // Get and return the next integer
    try {
      return int_parser.parse(buffer, pos).intValue();
    }
    catch (Exception e) {
      error = true;
      return INT_ERROR;
    } // exception
  } // readInt

  /**
   * Read a long integer from the given input.  Skips any whitespace up
   * to the integer.  If the next thing in the input is not an integer,
   * sets the cursor at the beginning of that thing, but does not
   * read beyond it and returns the default integer.
   * <br>pre: the next input characters are whitespace and those 
   *          that make up an integer (optional sign and some number
   *          of digits).
   * <br>post: the cursor is after those digits.
   */
  public long readLong() {
    // Assume no errors.
    error = false;

    // Make sure the buffer is full
    fillBuffer();

    // If we're at the end of the file, report an error
    if (at_eof) {
      error = true;
      return INT_ERROR;
    }

    // Skip any whitespace
    skipWhite();

    // Get and return the next integer
    try {
      return int_parser.parse(buffer, pos).longValue();
    }
    catch (Exception e) {
      error = true;
      return INT_ERROR;
    } // exception
  } // readLong

  /**
   * Read one line from the given input and return that line without
   * any carriage return or other line-terminating character.  If
   * we're at the end of file, return the empty string.
   * <br>pre: we haven't reached the end of the input.
   * <br>post: the next line (or partial line, if we're already on a
   *           line) is returned.
   */
  public String readLine() {
    // Assume no errors
    error = false;
    // Make sure the buffer is full
    fillBuffer();
    // If we're at the end of the file, return the error string 
    if ((at_eof) || (buffer == null)) {
     error = true;
     return STRING_ERROR;
    }
    // Get the rest of the current line
    String rest = buffer.substring(pos.getIndex());
    // Clear the buffer in preparation for reading future lines.
    clearBuffer();
    // Return the appropriate string
    return rest;
  } // readLine

  /**
   * Read a double from the current line and then skip to the end
   * of the line.
   * <br>pre: Not yet at end of file.
   * <br>pre: The next thing in the input is a double.
   * <br>post: who knows
   */
  public double readLineDouble() 
  {
    double tmp = readDouble();
    readLine();
    return tmp;
  } // readLineDouble()

  /**
   * Read the next nonempty string, skipping any intervening whitespace 
   * (including carriage returns).
   * <br>pre: (none)
   * <br>post: The next nonempty string is returned.
   */
  public String readString() {
    // The starting position of string on this line
    int start;
    // The ending position of the string on this line
    int end;
    // Assume no errors
    error = false;
    // Make sure the buffer is full
    fillBuffer();
    // Skip over whitespace
    skipWhite();
    // If we're at the end of the file, return the error string 
    if ((at_eof) || (buffer == null)) {
     error = true;
     return STRING_ERROR;
    }
    // Determine the starting point
    start = pos.getIndex();
    // Keep reading until we hit the end of the line or reach a 
    // space.
    while (!eoln() && !Character.isWhitespace(buffer.charAt(pos.getIndex()))) {
      // Advance the position
      pos.setIndex(pos.getIndex() + 1);
    } // while
    // Back up one
    end = pos.getIndex();
    // Return the appropriate substring
    return buffer.substring(start,end);
  } // readString

  /**
   * Set the double used to report errors.
   * <br>pre: (none)
   * <br>post: the parameter is returned whenever a double should
   *           be returned and an error occurs.
   */
  public void setErrorDouble(double error) {
    DOUBLE_ERROR = error;
  } // setErrorInt

  /**
   * Set the integer used to report errors.
   * <br>pre: (none)
   * <br>post: the parameter is returned whenever an integer should
   *           be returned and an error occurs.
   */
  public void setErrorInt(int error) {
    INT_ERROR = error;
  } // setErrorInt

  /**
   * Set the error string.
   * <br>pre: (none)
   * <br>post: the error string is now equal to the parameter.
   */
  public void setErrorString(String error) {
     STRING_ERROR = error;
  } // setErrorString

  /**
   * Skip over any whitespace until we reach a non-whitespace
   * character or the end of file.
   * <br>pre: none
   * <br>post: the cursor is at the next non-whitespace character
   *           of we've reached the end of file.
   */
  public void skipWhite() {
    // Keep going until we hit a character or end of file.  The
    // exit for a character happens within the while loop.
    while(!eof()) {
      // If we're at the end of the line, refill the buffer
      if (eoln()) {
        clearBuffer();
        fillBuffer();
      }
      // Otherwise, check the next character
      else {
        // If it's not whitespace, we're done
        if (!Character.isWhitespace(peek())) {
          return;
        }
        // Otherwise, advance in the buffer
        else {
          pos.setIndex(1+pos.getIndex());
        }
      } // check the next character
    } // while
  } // skipWhite()


  // +-----------------+---------------------------------------------------
  // | Local Utilities |
  // +-----------------+

  /**
   * Make a comment.
   */
  protected void COMMENT(String note) {
    if (TESTING) {
      System.err.println("*** " + note);
    }
  } // COMMENT

  /**
   * Make sure the buffer is set.
   * <br>pre: (none)
   * <br>post: buffer is non-null
   * <br>post: pos is set to 0
   */
  protected void fillBuffer() {
    // If the buffer is null, then it needs to be filled.
    if ((!at_eof) && (buffer == null)) {
      // Get the next line, obersving end of files
      try {
        buffer = base.readLine();
      }
      catch (EOFException e) {
        at_eof = true;
      } // catch   
      catch (IOException e) {
        // Some error, ignore it
        COMMENT("Caught the exception " + e.toString());
      }
      // Update the position
      pos = new ParsePosition(0);
      // If we got a null, then we're at the end of the file (I would
      // have thought an error would be thrown, but ...)
      if (buffer == null) {
        at_eof = true;
      }
    }
  } // fillBuffer

  /**
   * Clear the buffer.
   * <br>pre: (none)
   * <br>post: buffer is null
   * <br>post: pos is null
   */
  protected void clearBuffer() {
    buffer = null;
    pos = null;
  } // clearBuffer
} // SimpleInput

