/*---------------------------------------------------------------------------
File:         			FileRead.java
Package:                        JFLAP Version 3.0
Author:				Magda & Octavian Procopiuc V1.0 07/15/96
                                Eric Gramond V3.0 08/14/98

Description of Contents:	Contains class FileRead.
				
--------------------------------------------------------------------------*/

/* 
 * Susan H. Rodger, Magda Procopiuc, Octavian Procopiuc, Eric Gramond
 * Computer Science Department
 * Duke University
 * June 1998
 * Supported by National Science Foundation DUE-9596002 and DUE-9555084.
 *
 * Copyright (c) 1998
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the author.  The name of the author may not be used to
 * endorse or promote products derived from this software without
 * specific prior written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

package flap;

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

/**
 * This class is used to read and parse the data from a given
 * file, and to construct a machine using this data.
 *
 * @author	Octavian & Magda Procopiuc
 * @version	1.0 15 July 1996
 */
public class FileRead {

    public Machine	a = null;
    protected int		type;
    
    protected Desktop d;
    protected BufferedReader dis = null;
    protected String acceptance = PDA.FINAL_STATE;
	

  /**
   * Tries to read a machine from the given file.
   * @exception BadInputException if the input is badly formatted
   * @exception IOException if the file can't be read
   * @exception EOFException if the file ends unexpectedly 
   * @exception FileNotFoundException if the file can't be found
   */
  public void read(String fileName) throws FileNotFoundException, IOException, EOFException , BadInputException {

    //  FileInputStream	in = null;

    //    in = new FileInputStream(fileName);
    //  setStream(in);
    dis =  new BufferedReader(new FileReader(fileName));
    parseStream();
  }
  
  public void setStream(InputStream is) {
    dis = new BufferedReader(new InputStreamReader(is));
  }
  
  /**
   * Reads data from the current input stream
   * and parses it, building a new machine.
   * @exception BadInputException if the input is badly formatted
   * @exception IOException if the file cannot be read
   * @exception EOFException if the file ends unexpectedly
   */
  public void parseStream() throws IOException, EOFException, BadInputException{
    String	line;
    State       s;
    int idx;
    int count = 0;
    readTypeLine();

    line = dis.readLine();	// # of states.
    if (line == null)
      throw (new EOFException());

    try {
      count = Integer.parseInt(line);
    }
    catch(NumberFormatException exc) {
      throw(new BadInputException("\nInvalid number of states:\n" + line));
    }
    d = new Desktop(type);

    for (int i = 1; i <= count; i++)
      d.theStates.addElement(new State());

    line = dis.readLine();	// Input alphabet.
    if (line == null)
      throw (new EOFException());

    if (type == Machine.PDA) {
      line = dis.readLine();	// Stack alphabet.
      if (line == null)
        throw (new EOFException());
    } else if (type == Machine.TM1 || type == Machine.TM2) {
      line = dis.readLine();	// Input alphabet. (whatever...)
      if (line == null)
        throw (new EOFException());
    }

    line = dis.readLine();	// Initial state.
    if (line == null)
      throw (new EOFException());
    
    try {
      idx = Integer.parseInt(line);
    }
    catch(NumberFormatException exc) {
      throw(new BadInputException("\nInvalid initial state number:\n" + line));
    }
    if(idx < 0 || idx > count)
      throw(new BadInputException("\nInvalid initial state number:\n" + line));

    if(idx > 0) {
      s = (State) d.theStates.elementAt(idx - 1);
      d.initialState = s;
    }

    line = dis.readLine();	// Final states.
    if (line == null)
      throw (new EOFException());

    String token;
    StringTokenizer st = new StringTokenizer(line);

    while (st.hasMoreTokens()) {
      token = st.nextToken();
      try {
	idx = Integer.parseInt(token);
      }
      catch (NumberFormatException exc) {
	throw(new BadInputException("\nInvalid final state value:\n" + token));
      }
      if (idx != 0) {	// for whatever reason, this line ends with 0.
        s = (State) d.theStates.elementAt(idx - 1);
        s.isFinal = true;
      } else
        break;	// just in case.
    }

    line = dis.readLine();	// Transitions from first state.

    int count2;
    State s2;
    String label;
    
    for(count2 = 1; count2 <= count; count2++) {
	if(line == null) 
	    throw (new EOFException());
	processTransitions(line, count2);
	line = dis.readLine();
    }

    if (count2 != count +1)
	throw (new BadInputException("The number of transition lines("+count2+")\ndoesn't match the declared \nnumber of states ("+count+")"));
    
    // first interface line was read at the end of the above loop.

    int id;
    int maxid = 0;
    count2 = 0;
    String word = "";

    for(count2 = 1; count2 <= count; count2++) {
	if (line == null)
	    throw (new EOFException());
        st = new StringTokenizer(line);
	try {
	    id = Integer.parseInt((word = st.nextToken()).substring(1));
	}
	catch(NumberFormatException exc) {
	  throw(new BadInputException("\nInvalid state name:\n" + word));  
	}
	s = (State) d.theStates.elementAt(count2 - 1);
        if (id > maxid) maxid = id;
        s.id = id;
        s.p.x = Integer.parseInt(st.nextToken());
        s.p.y = Integer.parseInt(st.nextToken());
	//read in loop size. may not exist in old files.
	if(st.hasMoreTokens())
	    s.loopSize = Integer.parseInt(st.nextToken());

	//read in the label
	while(st.hasMoreTokens())
	{
	   s.label = s.label + " " + st.nextToken();
	}

        line = dis.readLine();
    }

    if (count2 != count+1)
      throw (new BadInputException("The number of interface data lines ("+count2+")\ndoesn't match the declared \nnumber of states (" + count + ")."));
    d.currentId = maxid + 1;


    //read in transition positions if exists (may not exist in old files)
    if(line != null) {
	for(count2 = 0; count2 < d.theTransitions.size(); count2++) {
	    ((Transition)d.theTransitions.elementAt(count2)).distance = Double.valueOf(line).doubleValue();
	    line = dis.readLine();
	}
    }

    dis.close();
    switch (type) {
      case Machine.FSA:
        a = new FSA(d);
        break;
      case Machine.PDA:
        a = new PDA(d);
	((PDA) a).acceptanceType = acceptance;
        break;
     case Machine.TM1:
        a = new TM1(d);
        break;
      case Machine.TM2:
        a = new TM2(d);
        break;
      default:
        a = null;
    }

  }	// end of method read.

  /**
   * Parses a line containing a transition.
   * @param line	the line to parse
   * @param pos		the number of the line, used to determine the from state.
   * @exception BadInputException if the input is badly formatted
   */
  protected void processTransitions(String line, int pos) throws BadInputException{
      String	pop = "";
      String	push = "";
      String	label = "";
      String	direction = "";
      int	idx;
      State	s2;
      String word = "";

    try {
      State s = (State) d.theStates.elementAt(pos - 1);
      StringTokenizer st = new StringTokenizer(line);
      while (st.hasMoreTokens()) {
        label = new String(st.nextToken());
        if (label.equalsIgnoreCase("null"))
          label = "";
        if (!label.equalsIgnoreCase("EOL")) {
          if (type == Machine.PDA) {
            pop = new String(st.nextToken().trim());	// pop string.
            if (pop.equalsIgnoreCase("null"))
              pop = "";
            st.nextToken();	// null.
          }
          idx = Integer.parseInt(word = st.nextToken());
          s2 = (State) d.theStates.elementAt(idx - 1);	// destination state
          if (type == Machine.FSA) {
            label = " " + label;
          } else if (type == Machine.PDA) {
            push = new String(st.nextToken().trim());	// push string.
            if (push.equalsIgnoreCase("null"))
              push = "";
            label = " " + label + ", " + pop + "; " + push;
          } else if (type == Machine.TM1) {
            push = new String(st.nextToken().trim());	// the write symbol.
            direction = new String(st.nextToken().trim());	// the direction.
            label = " " + label + "; " + push + ", " + direction;
          } else if (type == Machine.TM2) {
            push = new String(st.nextToken().trim());	// the write symbol.
            direction = new String(st.nextToken().trim());	// the direction.
            String label2 = new String(st.nextToken().trim());
            String push2 = new String(st.nextToken().trim());
            String direction2 = new String(st.nextToken().trim());
            label = " " + label  + "; " + push  + ", " + direction  + "|"
                  + " " + label2 + "; " + push2 + ", " + direction2;
          }   
	  d.theTransitions.addElement(new Transition(s, s2, label, d.fieldCounter, d.separator));
        } else
          break;  // just in case.
      }
    }
    catch(NoSuchElementException exc) {
	throw(new BadInputException("\nMissing data in transitions."));   
    }
    catch(NumberFormatException exc) {
      throw(new BadInputException("\nInvalid state number in transitions:\n" + word));
    }

  }	// end of method processTransitions.


  /**
   * Determines the type of the machine by looking at 
   * the first lines in the file.
   * @exception EOFException if the file ends too early
   * @exception IOException  if the file cant' be read
   * @exception BadInputException if the input is badly formatted
   */
  protected void readTypeLine() throws EOFException, IOException, BadInputException{
    String	line;

    line = dis.readLine();	// Machine type.
    if (line == null)
      throw (new EOFException());

    if (line.indexOf("FSA") != -1)
      type = Machine.FSA;
    else if (line.indexOf("PDA") != -1) {
      type = Machine.PDA;
      dis.mark(40);
      line = dis.readLine();
      if (line == null)
	  throw (new EOFException());
      line = line.trim();
      if(line.equals("FINAL")) 
	  ;
      else if(line.equals("EMPTY")) 
	  acceptance = PDA.EMPTY_STACK;
      else if(line.equals("FINAL+EMPTY")) 
	  acceptance = PDA.FINAL_AND_EMPTY;
      else 
	  dis.reset();
    }
    else if (line.indexOf("REGTM") != -1) {
      line = dis.readLine();
      if (line == null)
        throw (new EOFException());
      if (!line.trim().equals("TAPE"))
        throw new BadInputException("Invalid 'TAPE' line.");
      line = dis.readLine();
      if (line == null)
        throw (new EOFException());
      if (line.trim().equals("1"))
        type = Machine.TM1;
      else if (line.trim().equals("2"))
        type = Machine.TM2;
      else
        throw new BadInputException("Invalid number of tapes (should be 1 or 2)");
    } else
      throw (new BadInputException("Invalid first line"));

  }	// end of method readTypeLine.


}	// end of class FileReader.
