// Version 9/7/2001/OCL.

package inf101;

import java.io.*;
import java.util.*;
import java.net.*;

/** Denne klassen er laget ved Ifi, UiO og inneholder metoder
 *  for ? lese fra terminal og fil.  Klassen st?tter b?de innlesing
 *  linje for linje, tegn for tegn, og item for item (hvor et item
 *  kan v?re et heltall, et flyttall eller en tekststreng).  De tre
 *  typene innlesing kan kombineres.
 *
 *  Eksempler p? bruk:
 *  <pre>
 *     (1) Lese linje for linje fra fil:
 *
 *            String linje;
 *            Inn innfil = new Inn("tekstfil.txt");
 *            while (!innfil.endOfFile()) {
 *               linje = innfil.inStringLn();
 *               ...Gj?r noe med den leste linjen...
 *            }
 *
 *     (2) Lese fil med heltall (atskilt med blanke og/eller linjeskift):
 *
 *            int tall;
 *            Inn innfil = new Inn("fil.txt");
 *            while (!innfil.lastItem()) {
 *               tall = innfil.inInt();
 *               ...Gj?r noe med det innleste tallet...
 *            }
 *  </pre>
 *   Version 9/7/2001/OCL.
 **/

public class Inn {
  private final boolean FILE;
  private final int NEWTRIES;
  private boolean EOF;
  private String cur="";
  private BufferedReader in;
  private int lineNo = 0;

  /** Lager et Inn-objekt for lesing fra tastatur. **/
  public Inn() {
    in = new BufferedReader(new InputStreamReader(System.in));
    EOF = false;
    FILE = false;
    NEWTRIES = 3;
  }

  /** Lager et Inn-objekt for lesing fra spesifisert fil eller URL.
    * Dersom <CODE>name</CODE> starter med <CODE>"http://"</CODE>
    * tolkes <CODE>name</CODE>
    * som en URL (dvs som en web-adresse).  I alle andre tilfeller tolkes
    * <CODE>name</CODE> som et filnavn (med eller uten path).  Dersom det
    * ikke angis noen path antas det at filen ligger p? samme katalog som
    * Java-programmet ligger p?.  En path kan enten v?re en relativ path
    * eller en absolutt path.  En relativ path angir en katalog relativt
    * til katalogen som Java-programmet ligger p?, f.eks.
    * <pre>
    *    ../viktigbrev.txt
    *    obliger/Register.java
    * </pre>
    * En absolutt path angir en katalog i forhold til rotkatalogen, f.eks.
    * <pre>
    *    /ifi/ganglot/k01/inf101/LES_MEG
    * </pre>
    * Merk: forel?pig kan en ikke benytte linker (slik som ~ eller /hom/)
    * i pathen n?r en oppretter et Inn-objekt.
   **/
  public Inn (String name) {
    if (name.startsWith("http://")) {
      try {
	URL url = new URL(name);
	in = new BufferedReader(new InputStreamReader(url.openStream()));
      } catch (MalformedURLException e) {feil("Inn(String name): Feil i URL'en!");}
      catch (IOException e) {feil("Inn(String name): Klarte ikke ? ?pne fila!");}
    } else {
      try {
	in = new BufferedReader(new FileReader(name));
      } catch (IOException e) {feil("Inn(String name): Klarte ikke ? ?pne fila!");}
    }
    EOF = false;
    FILE = true;
    NEWTRIES = 0;
    newLine();
  }

 /** Lukker filen. **/
  public void close() {
    try {
      in.close();
    } catch (IOException e) {feil("close(): Klarte ikke ? lukke fila!");}
  }


  private void feil (String s) {
    System.out.println("\nFeil i " + s + "\n");
    System.exit(0);
  }

  private void melding (String s) {
    System.out.println("\nAdvarsel i " + s + "\n");
  }

  /** Returnerer true hvis EOF eller resten av linjen er tom. **/
  public boolean empty() {
    if (EOF || cur.equals("")) return true;
    else return false;
  }

  /** Returnerer true hvis EOF eller resten av linjen er blanke tegn. **/
  public boolean white() {
    if (EOF || cur.trim().equals("")) return true;
    else return false;
  }

  /** Les inn neste linje i buffer hvis ikke EOF.  Setter EOF=true
    * hvis slutt p? fila oppdages
   **/
  private void newLine() {
    String cur2 = null;
    if (!EOF) {
      try {
	cur2 = in.readLine();
      } catch (IOException e) {
	feil("newLine(): Feil ved innlesning!");
      }
      lineNo++;
      if (cur2 != null) cur = cur2;
      else EOF = true;
    }
  }

  /** Hopp fram til f?rste tegn.  Hopper over eventuelle
   *  linjeskift fram til f?rste tegn. **/
  public void skipEmpty() {
    while (!EOF && empty()) newLine();
  }

  /** Hopp fram til f?rste
   *  ikke-blanke tegn.  Hopper over blanke tegn (inkludert tabulatortegn)
   *  og linjeskift.
   */
  public void skipWhite() {
    while (!EOF && white()) newLine();
    if (!EOF) {
      while (Character.isWhitespace(cur.charAt(0))) cur = cur.substring(1);
    }
  }

  /** Hopp helt til slutten av linjen */
  public void flushLine() {
    cur = "";
  }

  /** Hopp fram til f?rste tegn som er forskjellig fra de spesifiserte
   *  separatortegnene.  Dersom f.eks. <CODE>sep</CODE> er tekststrengen
   *  <CODE>"&* "</CODE> s? vil en hoppe fram til f?rste tegn som ikke
   *  er '&', '*' eller ' '.  Kontrolltegn som starter med '\' kan ogs?
   *  angis, f.eks. <CODE>"\t"</CODE> for ? hoppe over tabulatortegn.

   *  For ? hoppe
   *  over tegnet '\' m? en angi "\\".  Eksempel: hvis <CODE>sep</CODE>
   *  er tekststrengen <CODE>" \t\\"</CODE> s? vil en hoppe fram til
   *  f?rste tegn som
   *  ikke er ' ', tabulator-tegn eller '\'.
   */
  public void skipSep(String sep) {
    StringTokenizer st = new StringTokenizer(cur, sep);
    while (!EOF && !st.hasMoreTokens()) {
      newLine();
      st = new StringTokenizer(cur, sep);
    }
    if (!EOF) {
      String s = st.nextToken();
      int i = cur.indexOf(s);
      cur = cur.substring(i, cur.length());
    }
  }

  /** Sjekk om alle tegn (og linjeskift) er lest.  Returnerer
   * <CODE>true</CODE> dersom alle tegn (inkludert blanke) og linjeskift
   * er lest, og returnerer <CODE>false</CODE> ellers.
   * Kun ved lesing fra fil eller URL. **/
  public boolean endOfFile() {
    return EOF;
  }

  /** Sjekk om siste ikke-blanke tegn er lest.  Hopper fram til
    * f?rste ikke-blanke tegn, eller til slutten av filen dersom
    * det bare er blanke tegn igjen.  I f?rste tilfelle returneres
    * verdien <CODE>true</CODE> og i siste tilfelle returneres verdien
    * <CODE>false</CODE>.  Kun ved lesing fra fil eller URL.
   **/
  public boolean lastItem() {
    skipWhite();
    return EOF;
  }

  /** Sjekk om siste ikke-separator tegn er lest, hvor separatortegn
    * er de tegn som er spesifisert med parameteren <CODE>sep</CODE>.
    * Dersom f.eks. <CODE>sep</CODE> er tekststrengen
    *  <CODE>"&* "</CODE> s? er separatortegnene
    *  '&', '*' og ' '.  Kontrolltegn som starter med '\' kan ogs?
    *  angis, f.eks. <CODE>"\t"</CODE> for tabulatortegn.
    *  For ? angi separatortegnet '\' m? en bruke "\\".  Eksempel:
    *  hvis <CODE>sep</CODE>
    *  er tekststrengen <CODE>" \t\\"</CODE> s? vil separatortegnene
    * v?re ' ', tabulator-tegn og '\'.
    * Hopper fram til f?rste ikke-separator tegn, eller til slutten av

    * filen dersom
    * det bare er separator-tegn igjen.  I f?rste tilfelle returneres
    * verdien <CODE>true</CODE> og i siste tilfelle returneres verdien
    * <CODE>false</CODE>.  Kun ved lesing fra fil eller URL.

   **/
  public boolean lastItem(String sep) {
    skipSep(sep);
    return EOF;
  }

  /** Les et tegn. Det hoppes over eventuelle innledende linjeskift. **/
  public char inChar() {
    char c='*';
    skipEmpty();
    if (!EOF) {
      c = cur.charAt(0);
      cur = cur.substring(1);
    } else {
      feil("inChar(): slutt p? filen!");
    }
    return c;
  }

  /** Les et tegn med spesifiserte tegn som separatortegn.
   *  Det hoppes over eventuelle innledende linjeskift og
   *  separatortegn spesifisert i <CODE>sep</CODE>.Separatortegn angis
   *  som en tekststreng, f.eks. "%&" for ? bruke '%' og '&' som separatortegn.
   **/
  public char inChar(String sep) {
    char c='*';
    skipSep(sep);
    if (!EOF) {
      c = cur.charAt(0);
      cur = cur.substring(1);
    } else {
      feil("inChar(): slutt p? filen!");
    }
    return c;
  }

  /**
   *   Les et heltall med blank som separatortegn.  Det hoppes
   *   over eventuelle innledende
   *   blanke tegn (inkludert tabulatortegn) og linjeskift. Det gis
   *   feilmelding dersom f?rste
   *   ikke-blanke tegn er noe annet enn et siffer.
   */
  public int inInt() {
    char c='*';
    String number = "";

    int i = 0;

    do {
      // Les tekst
      if (!lastItem()) {
	c = cur.charAt(0);
	if (c=='-') {
	  number = "-";
	  cur = cur.substring(1);
	  if (!empty()) c = cur.charAt(0);
	}
	while (!empty() && Character.isDigit(c)) {
	  number = number + c;
	  cur = cur.substring(1);
	  if (!empty()) c = cur.charAt(0);
	}
      } else {
	feil("inInt(): Slutt p? filen!");
      }

      // Konverter fra tekst til tall
      try {
	return Integer.parseInt(number);
      } catch (NumberFormatException e) {
	if (FILE) {
	  feil("inInt(): Forventet heltall i linje " + lineNo + ".");
	} else {
	  melding("inInt(): Forventet heltall - pr?v igjen!");
	  number = "";
	  newLine();
	}
      }

    } while (i++ < NEWTRIES); // do-while

    feil("inInt(): Forventet heltall");

    return 0;
  }

  /**
   *   Les et heltall med spesifiserte tegn som separatortegn.
   *   Det hoppes over eventuelle innledende linjeskift
   *   og separatortegn spesifisert i <CODE>sep</CODE>. Separatortegn angis
   *  som en tekststreng, f.eks. "%&" for ? bruke '%' og '&' som separatortegn. Det gis
   *   feilmelding dersom f?rste
   *   ikke-separator tegn er noe annet enn et siffer.
   */
  public int inInt(String sep) {
    char c='*';
    String number = "";

    int i = 0;
    do {
      // Les tekst
      if (!lastItem(sep)) {
	c = cur.charAt(0);
	if (c=='-') {
	  number = "-";
	  cur = cur.substring(1);
	  if (!empty()) c = cur.charAt(0);
	}
	while (!empty() && Character.isDigit(c)) {
	  number = number + c;
	  cur = cur.substring(1);
	  if (!empty()) c = cur.charAt(0);
	}
      } else {
	feil("inInt(String sep): slutt p? filen!");
      }

      // Konverter fra tekst til tall
      try {
	return Integer.parseInt(number);
      } catch (NumberFormatException e) {
	if (FILE) {
	  feil("inInt(String sep): Forventet heltall i linje " + lineNo + ".");
	} else {
	  melding("inInt(String sep): Forventet heltall - pr?v igjen!");
	  number = "";
	  newLine();
	}
      }
    } while (i++ < NEWTRIES); // do-while

    feil("inInt(String sep): Forventet heltall");

    return 0;
  }


  /** Les et flyttall med blanke som separatortegn.  Flyttallet m? ha en
   * av f?lgende formater:
   *  <pre>
   *       (1) xx.yy  (eksempel: 34.22)
   *       (2)   .yy  (eksempel:   .22)
   *       (3) xx     (eksempel: 34)
   *  </pre>
   *  Det hoppes over eventuelle innledende blanke tegn (inkludert
   *  tabulatortegn) og linjeskift.
   *  Det gis feilmelding dersom f?rste ikke-blanke tegn er noe annet enn et
   *  siffer.
   */
  public double inDouble() {
    String number = "";

    int i = 0;
    do {
      // Les tekst
      if (!lastItem()) {
	char c = cur.charAt(0);
	if (c=='-') {
	  number = "-";
	  cur = cur.substring(1);
	  if (!empty()) c = cur.charAt(0);
	}
	while (!empty() && Character.isDigit(c)) {
	  number = number + c;
	  cur = cur.substring(1);
	  if (!empty()) c = cur.charAt(0);
	}
	if (cur.length() > 1 && (c=='.' & Character.isDigit(cur.charAt(1)))) {
	  cur = cur.substring(1);
	  c = cur.charAt(0);
	  number = number + ".";
	  while (!empty() && Character.isDigit(c)) {
	    number = number + c;
	    cur = cur.substring(1);
	    if (!empty()) c = cur.charAt(0);
	  }
	}
      } else {
	feil("inDouble(): slutt p? filen!");
      }

      // Konverter fra tekst til tall
      try {
	return Double.valueOf(number).doubleValue();
      } catch (NumberFormatException e) {
	if (FILE) {
	  feil ("inDouble(): Forventet flyttall i linje " + lineNo + ".");
	} else {
	  melding("inDouble(): Forventet flyttall - pr?v igjen!");
	  number = "";
	  newLine();
	}
      }

    } while (i++ < NEWTRIES); // do-while

    feil("inDouble(): Forventet flyttall - programmet terminerer");
    return 0;
  }


  /** Les et flyttall med spesifiserte tegn som separatortegn.
   * Flyttallet m? ha en
   * av f?lgende formater:
   *  <pre>
   *       (1) xx.yy  (eksempel: 34.22)
   *       (2)   .yy  (eksempel:   .22)
   *       (3) xx     (eksempel: 34)
   *  </pre>
   *  Det hoppes over eventuelle innledende linjeskift og separatortegn
   *  spesifisert i <CODE>sep</CODE>.  Separatortegn angis
   *  som en tekststreng, f.eks. "%&" for ? bruke '%' og '&' som separatortegn.
   *  Det gis feilmelding dersom f?rste ikke-blanke tegn er noe annet enn et
   *  siffer.
   */
  public double inDouble(String sep) {
    String number = "";

    int i = 0;
    do {
      // Les tekst
      if (!lastItem(sep)) {
	char c = cur.charAt(0);
	if (c=='-') {
	  number = "-";
	  cur = cur.substring(1);
	  if (!empty()) c = cur.charAt(0);
	}
	while (!empty() && Character.isDigit(c)) {
	  number = number + c;
	  cur = cur.substring(1);
	  if (!empty()) c = cur.charAt(0);
	}
	if (cur.length() > 1 && (c=='.' & Character.isDigit(cur.charAt(1)))) {
	  cur = cur.substring(1);
	  c = cur.charAt(0);
	  number = number + ".";
	  while (!empty() && Character.isDigit(c)) {
	    number = number + c;
	    cur = cur.substring(1);
	    if (!empty()) c = cur.charAt(0);
	  }
	}
      } else {
	feil("inDouble(String sep): Slutt p? filen!");
      }

      // Konverter fra tekst til tall
      try {
	return Double.valueOf(number).doubleValue();
      } catch (NumberFormatException e) {
	if (FILE) {
	  feil("inDouble(String sep): Forventet flyttall i linje " + lineNo + ".");
	} else {
	  melding("inDouble(String sep): Forventet flyttall - pr?v igjen!");
	  number = "";
	  newLine();
	}
      }

    } while (i++ < NEWTRIES); // do-while

    feil("inDouble(String sep): Forventet flyttall - programmet terminerer");
    return 0;
  }


  /** Les en tekststreng med blank som separatortegn.  Hopper over
   *  innledende blanke tegn
   *  (inkludert tabulatortegn) og linjeskift, og leser deretter
   *  fram til f?rste blanke
   *  tegn (inkludert tabulatortegn) eller linjeskift (det som kommer f?rst).
  **/
  public String inString() {
    String s = "";
    char c = '*';
    if (!lastItem()) {
      skipWhite();
      c = cur.charAt(0);
      while (!empty() && !Character.isWhitespace(c)) {
	s = s + c;
	cur = cur.substring(1);
	if (!empty()) c = cur.charAt(0);
      }
    } else {
      feil("inString(): Slutt p? filen!");
    }
    return s;
  }

  /** Les en tekststreng med spesifiserte tegn som separatortegn. Hopper
   *  over innledende separatortegn
   *  og linjeskift, og leser deretter fram til f?rste separatortegn
   *  eller linjeskift (det som kommer f?rst).  Separatortegn angis
   *  som en tekststreng, f.eks. "%&" for ? bruke '%' og '&' som separatortegn.
   */
  public String inString (String sep) {
    String s = "";
    if (!lastItem(sep)) {
      StringTokenizer st = new StringTokenizer(cur, sep);
      s = st.nextToken();
      int i = cur.indexOf(s);
      cur = cur.substring(i+s.length(), cur.length());
    } else {
      feil("inString(String sep): Slutt p? filen!");
    }
    return s;
  }

  /** Les resten av linjen som en tekststreng (inkludert blanke tegn).
   */
  public String inStringLn() {
    if (FILE) {
      String cur0=cur;
      newLine();
      return cur0;
    } else {
      if (empty()) newLine();
      String cur0=cur;
      flushLine();
      return cur0;
    }
  }

}