
/*
    The file defines a class ConsolePanel.  Objects of type
    ConsolePanel can be used for simple input/output exchanges with
    the user.  Various routines are provided for reading and writing
    values of various types from the output.  (This class gives all
    the I/O behavior of another class, Console, that represents a
    separate window for doing console-style I/O.)

    This class is dependent on another class, ConsoleCanvas.

    Note that when the console has the input focus, it is outlined with
    a bright blue border.  If, in addition, the console is waiting for
    user input, then there will be a blinking cursor.  If the console
    is not outlined in light blue, the user has to click on it before
    any input will be accepted.


    Written by:  David Eck
                 Department of Mathematics and Computer Science
                 Hobart and William Smith Colleges
                 Geneva, NY 14456
                 Email:  eck@hws.edu
                 WWW:  http://math.hws.edu/eck/

    December 9, 1997

*/

   
import java.awt.*;

public class ConsolePanel extends Panel {

   // ***************************** Constructors *******************************
   
   public ConsolePanel() {   // default constructor just provides default window title and size
      setBackground(Color.white);
      setLayout(new BorderLayout(0,0));
      canvas = new ConsoleCanvas();
      add("Center",canvas);
   }
   
      
   public void clear() {  // clear all characters from the canvas
      canvas.clear();
   }

      
   // *************************** I/O Methods *********************************
   
         // Methods for writing the primitive types, plus type String,
         // to the console window, with no extra spaces.

   public void put(int x)     { put(x,0); }   // Note: also handles byte and short!
   public void put(long x)    { put(x,0); }
   public void put(float x)   { put(x,0); }
   public void put(double x)  { put(x,0); }
   public void put(char x)    { put(x,0); }
   public void put(boolean x) { put(x,0); }
   public void put(String x)  { put(x,0); }


         // Methods for writing the primitive types, plus type String,
         // to the console window,followed by a carriage return, with
         // no extra spaces.

   public void putln(int x)      { put(x,0); newLine(); }  // Note: also handles byte and short!
   public void putln(long x)     { put(x,0); newLine(); }
   public void putln(float x)    { put(x,0); newLine(); }
   public void putln(double x)   { put(x,0); newLine(); }
   public void putln(char x)     { put(x,0); newLine(); }
   public void putln(boolean x)  { put(x,0); newLine(); }
   public void putln(String x)   { put(x,0); newLine(); }
  

         // Methods for writing the primitive types, plus type String,
         // to the console window, with a minimum field width of w,
         // and followed by a carriage  return.
         // If outut value is less than w characters, it is padded
         // with extra spaces in front of the value.

   public void putln(int x, int w)     { put(x,w); newLine(); }   // Note: also handles byte and short!
   public void putln(long x, int w)    { put(x,w); newLine(); }
   public void putln(float x, int w)   { put(x,w); newLine(); }
   public void putln(double x, int w)  { put(x,w); newLine(); }
   public void putln(char x, int w)    { put(x,w); newLine(); }
   public void putln(boolean x, int w) { put(x,w); newLine(); }
   public void putln(String x, int w)  { put(x,w); newLine(); }


          // Method for outputting a carriage return

   public void putln() { newLine(); }
   

         // Methods for writing the primitive types, plus type String,
         // to the console window, with minimum field width w.
   
   public void put(int x, int w)     { dumpString(String.valueOf(x), w); }   // Note: also handles byte and short!
   public void put(long x, int w)    { dumpString(String.valueOf(x), w); }
   public void put(float x, int w)   { dumpString(String.valueOf(x), w); }
   public void put(double x, int w)  { dumpString(String.valueOf(x), w); }
   public void put(char x, int w)    { dumpString(String.valueOf(x), w); }
   public void put(boolean x, int w) { dumpString(String.valueOf(x), w); }
   public void put(String x, int w)  { dumpString(x, w); }
   
   
         // Methods for reading in the primitive types, plus "words" and "lines".
         // The "getln..." methods discard any extra input, up to and including
         //    the next carriage return.
         // A "word" read by getlnWord() is any sequence of non-blank characters.
         // A "line" read by getlnString() or getln() is everything up to next CR;
         //    the carriage return is not part of the returned value, but it is
         //    read and discarded.
         // Note that all input methods except those for char's and lines will
         //    skip past any blanks and carriage returns to find a non-blank value.
         // getln() can return an empty string; getChar() and getlnChar() can 
         //    return a space or a linefeed ('\n') character.
         // peek() allows you to look at the next character in input, without
         //    removing it from the input stream.  (Note that using this
         //    routine might force the user to enter a line, in order to
         //    check what the next character.)
         // Acceptable boolean values are the "words": true, false, t, f, yes,
         //    no, y, n, 0, or 1;  uppercase letters are OK.
         // None of these can produce an error; if an error is found in input,
         //    the user is forced to re-enter.
         // Available input routines are:
         //
         //            getByte()      getlnByte()    getShort()     getlnShort()
         //            getInt()       getlnInt()     getLong()      getlnLong()
         //            getFloat()     getlnFloat()   getDouble()    getlnDouble()
         //            getChar()      getlnChar()    peek()         getln()
         //            getWord()      getlnWord()    getString()    getlnString()
         //
         // (getlnString is the same as getln and is onlyprovided for consistency.)
   
   public byte getlnByte()       { byte x=getByte();       emptyBuffer();  return x; }
   public short getlnShort()     { short x=getShort();     emptyBuffer();  return x; }
   public int getlnInt()         { int x=getInt();         emptyBuffer();  return x; }
   public long getlnLong()       { long x=getLong();       emptyBuffer();  return x; }
   public float getlnFloat()     { float x=getFloat();     emptyBuffer();  return x; }
   public double getlnDouble()   { double x=getDouble();   emptyBuffer();  return x; }
   public char getlnChar()       { char x=getChar();       emptyBuffer();  return x; }
   public boolean getlnBoolean() { boolean x=getBoolean(); emptyBuffer();  return x; }
   public String getlnWord()     { String x=getWord();     emptyBuffer();  return x; }
   public String getlnString()   { return getln(); }  // same as getln()
   public String getln() {
      StringBuffer s = new StringBuffer(100);
      char ch = readChar();
      while (ch != '\n') {
         s.append(ch);
         ch = readChar();
      }
      return s.toString();
   }
   
   
   public byte getByte()   { return (byte)readInteger(-128L,127L); }
   public short getShort() { return (short)readInteger(-32768L,32767L); }   
   public int getInt()     { return (int)readInteger((long)Integer.MIN_VALUE, (long)Integer.MAX_VALUE); }
   public long getLong()   { return readInteger(Long.MIN_VALUE, Long.MAX_VALUE); }
   
   public char getChar()   { return readChar(); }
   public char peek()      { return lookChar(); }
   
   public float getFloat() {  // can return positive or negative infinity
      float x = 0.0F;
      while (true) {
         String str = readRealString();
         if (str.equals("")) {
             errorMessage("Illegal floating point input.",
                          "Real number in the range " + Float.MIN_VALUE + " to " + Float.MAX_VALUE);
         }
         else {
            Float f = null;
            try { f = Float.valueOf(str); }
            catch (NumberFormatException e) {
               errorMessage("Illegal floating point input.",
                            "Real number in the range " + Float.MIN_VALUE + " to " + Float.MAX_VALUE);
               continue;
            }
            if (f.isInfinite()) {
               errorMessage("Floating point input outside of legal range.",
                            "Real number in the range " + Float.MIN_VALUE + " to " + Float.MAX_VALUE);
               continue;
            }
            x = f.floatValue();
            break;
         }
      }
      return x;
   }
   
   public double getDouble() {
      double x = 0.0;
      while (true) {
         String str = readRealString();
         if (str.equals("")) {
             errorMessage("Illegal floating point input",
                          "Real number in the range " + Double.MIN_VALUE + " to " + Double.MAX_VALUE);
         }
         else {
            Double f = null;
            try { f = Double.valueOf(str); }
            catch (NumberFormatException e) {
               errorMessage("Illegal floating point input",
                            "Real number in the range " + Double.MIN_VALUE + " to " + Double.MAX_VALUE);
               continue;
            }
            if (f.isInfinite()) {
               errorMessage("Floating point input outside of legal range.",
                            "Real number in the range " + Double.MIN_VALUE + " to " + Double.MAX_VALUE);
               continue;
            }
            x = f.doubleValue();
            break;
         }
      }
      return x;
   }
   
   public String getWord() {
      char ch = lookChar();
      while (ch == ' ' || ch == '\n') {
         readChar();
         if (ch == '\n')
            dumpString("? ",0);
         ch = lookChar();
      }
      StringBuffer str = new StringBuffer(50);
      while (ch != ' ' && ch != '\n') {
         str.append(readChar());
         ch = lookChar();
      }
      return str.toString();
   }
   
   public boolean getBoolean() {
      boolean ans = false;
      while (true) {
         String s = getWord();
         if ( s.equalsIgnoreCase("true") || s.equalsIgnoreCase("t") ||
                 s.equalsIgnoreCase("yes")  || s.equalsIgnoreCase("y") ||
                 s.equals("1") ) {
              ans = true;
              break;
          }
          else if ( s.equalsIgnoreCase("false") || s.equalsIgnoreCase("f") ||
                 s.equalsIgnoreCase("no")  || s.equalsIgnoreCase("n") ||
                 s.equals("0") ) {
              ans = false;
              break;
          }
          else
             errorMessage("Illegal boolean input value.",
                          "one of:  true, false, t, f, yes, no, y, n, 0, or 1");
      }
      return ans;
   }
   
   // ***************** Everything beyond this point is private *******************
   
   // ********************** Utility routines for input/output ********************

   private ConsoleCanvas canvas;  // the canvas where I/O is displayed
   private String buffer = null;  // one line read from input
   private int pos = 0;           // position next char in input line that has
                                  //      not yet been processed


   public boolean gotFocus(Event evt, Object what) {
           // if the window gets the focus, pass it off to the canvas
      canvas.requestFocus();
      return true;
   }
      
   
   private String readRealString() {   // read chars from input following syntax of real numbers
      StringBuffer s=new StringBuffer(50);
      char ch=lookChar();
      while (ch == ' ' || ch == '\n') {
          readChar();
          if (ch == '\n')
             dumpString("? ",0);
          ch = lookChar();
      }
      if (ch == '-' || ch == '+') {
          s.append(readChar());
          ch = lookChar();
          while (ch == ' ') {
             readChar();
             ch = lookChar();
          }
      }
      while (ch >= '0' && ch <= '9') {
          s.append(readChar());
          ch = lookChar();
      }
      if (ch == '.') {
         s.append(readChar());
         ch = lookChar();
         while (ch >= '0' && ch <= '9') {
             s.append(readChar());
             ch = lookChar();
         }
      }
      if (ch == 'E' || ch == 'e') {
         s.append(readChar());
         ch = lookChar();
         if (ch == '-' || ch == '+') {
             s.append(readChar());
             ch = lookChar();
         }
         while (ch >= '0' && ch <= '9') {
             s.append(readChar());
             ch = lookChar();
         }
      }
      return s.toString();
   }

   private long readInteger(long min, long max) {  // read long integer, limited to specified range
      long x=0;
      while (true) {
         StringBuffer s=new StringBuffer(34);
         char ch=lookChar();
         while (ch == ' ' || ch == '\n') {
             readChar();
             if (ch == '\n');
                dumpString("? ",0);
             ch = lookChar();
         }
         if (ch == '-' || ch == '+') {
             s.append(readChar());
             ch = lookChar();
             while (ch == ' ') {
                readChar();
                ch = lookChar();
             }
         }
         while (ch >= '0' && ch <= '9') {
             s.append(readChar());
             ch = lookChar();
         }
         if (s.equals("")){
             errorMessage("Illegal integer input.",
                          "Integer in the range " + min + " to " + max);
         }
         else {
             String str = s.toString();
             try { 
                x = Long.parseLong(str);
             }
             catch (NumberFormatException e) {
                errorMessage("Illegal integer input.",
                             "Integer in the range " + min + " to " + max);
                continue;
             }
             if (x < min || x > max) {
                errorMessage("Integer input outside of legal range.",
                             "Integer in the range " + min + " to " + max);
                continue;
             }
             break;
         }
      }
      return x;
   }
   
   private void dumpString(String str, int w) {   // output string to console
      for (int i=str.length(); i<w; i++)
         canvas.addChar(' ');
      for (int i=0; i<str.length(); i++)
         if ((int)str.charAt(i) >= 0x20 && (int)str.charAt(i) != 0x7F)  // no control chars or delete
            canvas.addChar(str.charAt(i));
         else if (str.charAt(i) == '\n' || str.charAt(i) == '\r')
            newLine();
   }
   
   private void errorMessage(String message, String expecting) {
                  // inform user of error and force user to re-enter.
       newLine();
       dumpString("  *** Error in input: " + message + "\n", 0);
       dumpString("  *** Expecting: " + expecting + "\n", 0);
       dumpString("  *** Discarding Input: ", 0);
       if (lookChar() == '\n')
          dumpString("(end-of-line)\n\n",0);
       else {
          while (lookChar() != '\n')
             canvas.addChar(readChar());
          dumpString("\n\n",0);
       }
       dumpString("Please re-enter: ", 0);
       readChar();  // discard the end-of-line character
   }

   private char lookChar() {  // return next character from input
      if (buffer == null || pos > buffer.length())
         fillBuffer();
      if (pos == buffer.length())
         return '\n';
      return buffer.charAt(pos);
   }

   private char readChar() {  // return and discard next character from input
      char ch = lookChar();
      pos++;
      return ch;
   }

   private void newLine() {   // output a CR to console
      canvas.addCR();
   }

   private void fillBuffer() {    // Wait for user to type a line and press return,
                                  // and put the typed line into the buffer.
      buffer = canvas.readLine();
      pos = 0;
   }

   private void emptyBuffer() {   // discard the rest of the current line of input
      buffer = null;
   }

   public void clearBuffers() {   // I expect this will only be called by
                                  // CanvasApplet when a program ends.  It should
                                  // not be called in the middle of a running program.
      buffer = null;
      canvas.clearTypeAhead();
   }
   
   
} // end of class Console
