/**
 * @(#)Converter 
 * A class that converts ProfessorJ beginner and intermediate files
 * to files that run in Java 1.5 with the tester package
 * 
 * @since 5 March 2008
 * @author Viera K. Proulx
 */

import java.io.BufferedReader;
import java.io.File;
import javax.swing.filechooser.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

import javax.swing.JFileChooser;
import javax.swing.JFrame;
import java.util.*;


/** Program that reads in a ProfessorJ file and converts it so
 * it can run in Java 1.5 using the tester package. */
public class Converter{
  
  /*-------------------------------------------------------- 
   Member data
   *------------------------------------------------------*/
  
  /** the input reader */
  protected BufferedReader buffer;
  
  /** the file writer */
  protected FileWriter filewriter;
  
  /** one line of input at a time */  protected String line;
  
  /** an array of all lines in the file */
  protected ArrayList<String> lines = new ArrayList<String>();
  
  /** determines whether the general dialog has been closed */
  protected boolean closed = true;
  
  /** the JFrame that holds the chooser */
  protected JFrame parent = new JFrame("File to Convert");

  /** the JFrame that holds the chooser */
  protected JFrame parent2 = new JFrame("File to Save");

  /*-------------------------------------------------------- 
   Constructor
   *------------------------------------------------------*/  
  /**
   * Build a file chooser for the input file, select the input
   * ProfessorJ file, build a file chooser for the output file 
   * (must end in .java) and open the file for writing; 
   * then read the input ProfessorJ file 
   * and write the converted .java file.
   */
  public Converter() {
    
    /* Select the input file, quit if the user canceled the choice */
    
    JFileChooser chooser = new JFileChooser();
    JavaFileFilter filter = new JavaFileFilter();
    chooser.setApproveButtonText("Select ProfessorJ File");
    int returnVal = chooser.showOpenDialog(parent);
    if(returnVal == JFileChooser.APPROVE_OPTION) {
       //System.out.println("You chose to open this file: " +
       //     chooser.getSelectedFile().getName());
       closed = false;
    }   
    else {    
      /* see if file was selected - quit if user canceled */
      closed = true;
      return;
    }
    try{
      buffer = new BufferedReader(
          new FileReader(chooser.getSelectedFile()));
    }
    catch(FileNotFoundException e){
      System.out.println("File not found exception: " + e);
      closed = true;        
    }
    
    /* Select the output file, quit if the user canceled the choice */
   boolean nofileyet = true;
    while (nofileyet){

    	JFileChooser chooser2 = new JFileChooser();
    	//chooser2.setFileFilter(filter);
    	returnVal = chooser2.showDialog(parent2, "Save as Java 1.5 File");

    	if(returnVal == JFileChooser.APPROVE_OPTION) {
    		//System.out.println("You chose to write to this file: " +
    		//     chooser2.getSelectedFile().getName());
    		closed = false;
    	}   
    	else {    
    		/** see if file was selected - quit if user canceled */
    		closed = true;
    		return;
    	}
    	try{

    		File outf = chooser2.getSelectedFile();
    		if (outf.getName().endsWith(".java")){
    			filewriter = new FileWriter(outf);
    			nofileyet = false;
    		}

    	}
    	catch(FileNotFoundException e){
    		System.out.println("File not found exception: " + e);
    		closed = true;        
    	}
    	catch(IOException e){
    		System.out.println("File IO exception: " + e);
    		closed = true;  
    	}
    	System.out.println("File name must end with .java");
    }

    /** read all input lines from the selected file */
    /** convert the lines and write them to the output file */
    readLines();
    writeLines();
  }
  
  /**
   * Read all lines into a buffer --- until the end of the file
   */
  public void readLines() {
    
    if (!closed)
      try{
        while (buffer.ready()){
          
          line = new String(buffer.readLine());
          if ((line == null)){     
            buffer.close();
            closed = true;
          }
          else{ 
            lines.add(line);
          }            
        }
        // finished reading the data
        buffer.close();
        closed = true;
        
      }
    catch(Exception e) {
      System.out.println("Error in reading line exception: " + e);
      closed = true;
    }
  }

  /**
   * <p>Process every line in the list of lines:</P> 
   * <p>First look for test case headers and replace them with test
   * method call with the <code>Tester t</code> argument.</p> 
   * <p>Then look for check - expect clauses and replace them with
   * <code>t.checkExpect(</code> - and place a comma between the
   * check and expect clauses.
   */
  public void convertTests(){
    // converting the test calls
    for (int i = 0; i < lines.size(); i++){
      String oneLine = lines.get(i);
      if (oneLine.length() > 0 && isTestHeader(oneLine))
          lines.set(i, this.convertTestCall(oneLine, i));
    }
    
    // converting the check-expect expressions
    for (int i = 0; i < lines.size(); i++){
      String oneLine = lines.get(i);
      if (oneLine.length() > 0) 
        lines.set(i, this.convertCheckExpect(oneLine, i)); 
    } 
  }

  /**
   * Is this line a method header for a test method or a 
   * definition of a test case value?
   * @param aline the line that could be a test method header or 
   * a test case definition
   * @return true if the line defines a test method or a test case
   */
  public boolean isTestHeader(String aline){
    StringTokenizer st = new StringTokenizer(aline, " (),;{}");
    if (st.hasMoreTokens()){
      String bool = st.nextToken();
      if (bool.equals("public"))
        bool = st.nextToken();
      if (!bool.equals("boolean"))
        return false;
      String methodName = st.nextToken();
      if (methodName.startsWith("test"))
        return true;
      else
        return false;
    }
    else
      return false;
  }

  /**
   * <p>Convert the method header for a test method by adding the
   * <code>Tester t</code> argument</p>
   * <p>Convert a test case definition to a method call with the
   * <code>Testert t</code> argument, add <code>}</code> after the
   * test case definition (could be several lines later)</p>
   * 
   * @param aline the line that is a test method header 
   * or a test case definition
   * @return modified line with 'Tester t" parameter included
   * @effect if this is a test case definition, the line that ends
   * the test case has <code>}</code> addes after the <code>;</code>
   */
  public String convertTestCall(String aline, int lineNo){
    int n = aline.indexOf("()");
    // the line starts: public boolean testMethodname() ...
    if (n > 0){   
      String newline = aline.substring(0, n).
      concat("(Tester t)").
      concat(aline.substring(n + 2, aline.length()));
      return newline;
    }
    
    else{
      n = aline.indexOf(" =");
      if (n > 0){
        // the line starts: public boolean testCaseName = ...
        String newline = aline.substring(0, n).
        concat("(Tester t){\n    return").
        concat(aline.substring(n + 2, aline.length()));
        if (newline.endsWith(";"))
          newline = newline.concat("}");
        else
          // look for the end of the test case and add '}" after the ';'
          for (int i = lineNo; i < lines.size(); i++){
            if (lines.get(i).endsWith(";")){
              lines.set(i, lines.get(i).concat("}"));
              i = lines.size();  // exit the loop
            }
          }
        return newline;
      }
      else
        return aline;
    }
  }

 
  /**
   * Convert any occurrence of '(check' to 'checkExpect(',
   * any occurrence of ' expect' to ', ' - accounting for the option
   * of splitting the check-expect clause over two lines
   * 
   * @param aline the line with possibly 'check' or 'expect' in it
   * @return new line with either or both clauses replaced
   */
  public String convertCheckExpect(String aline, int index){
    if (aline.length() == 0)
      return aline;
    
    int checkIndexP = aline.indexOf("(check ");
    int checkIndex = aline.indexOf(" check ");
    int expectIndex = aline.indexOf(" expect ");
    if (checkIndexP == -1 && checkIndex == -1 && expectIndex == -1)
      return aline;

    String newline = "";

    // expect starts on a new line - there is no 'check' in this line
    if ((checkIndexP == -1 && checkIndex == -1) && expectIndex > 0){
      newline = aline.substring(0, expectIndex).
      concat(aline.substring(expectIndex + 7, aline.length()));
      return newline;
    }

    // replace '(check' with 't.checkExpect(' 
    // replace 'expect' keyword with a comma
    if (checkIndexP > 0 && checkIndex == -1){
      newline = aline.substring(0, checkIndexP);

      if (expectIndex > 0){
        newline = newline.concat("t.checkExpect(").
        concat(aline.substring(checkIndexP + 7, expectIndex)).
        concat(", ").
        concat(aline.substring(expectIndex + 7, aline.length()));
        return newline;
      }

      // if no 'expect' clause in this line, just end it with a comma
      else{
        newline = newline.concat("t.checkExpect(").
        concat(aline.substring(checkIndexP + 7, aline.length())).
        concat(", ");
        return newline;
      }
    }
  
    // replace ' check' with 't.checkExpect(' 
    // replace 'expect' keyword with a comma
    else // checkIndexP == -1 && checkIndex > 0
      if (expectIndex > 0){
        int semiIndex = aline.indexOf(";");
        newline = aline.substring(0, checkIndex).concat(" t.checkExpect(").
        concat(aline.substring(checkIndex + 7, expectIndex)).
        concat(", ").  // insert the comma
        concat(aline.substring(expectIndex + 7, semiIndex)).
        concat(")").   // add the closing parenthesis
        concat(aline.substring(semiIndex, aline.length()));
        return newline;
      }

      // if no 'expect' clause in this line, just end it with a comma
      // but find the end of the expect clause 
      // and add a closing parentheses there.
      else{
        newline = aline.substring(0, checkIndex).concat(" t.checkExpect(").
        concat(aline.substring(checkIndex + 7, aline.length())).
        concat(", ");

        // look for the end of the test case and add ')' before the ';'
        for (int i = index; i < lines.size(); i++){
          int semiIndex = lines.get(i).indexOf(";");
          if (semiIndex > 0){
            String line = lines.get(i);
            lines.set(i, line.substring(0, semiIndex).
                concat(")").
                concat(line.substring(semiIndex, line.length())));
            i = lines.size();  // exit the loop
          }
        }
        return newline;
      }
    }
 

  /**
   * Add the import statement at the beginning
   * Find the name of the 'Examples' class
   * 
   * @return the name of the 'Examples' class that defines the tests
   */
  public String findExamples(){
    lines.add(0, "import tester.*;");
    lines.add(1, "  ");
    String className = "";
    for (int i = 0; i < lines.size(); i++){
      String oneLine = lines.get(i);
      
      if (this.isClassDef(oneLine))
        className = this.classDefName(oneLine);
      if (isTestHeader(oneLine))
        return className;
    } 
    return "";
  }
  
  /**
   * Does this line contain a class definition?
   * @param aline a line of code that could be a class definition
   * @return true if it is a class definition
   */
  public boolean isClassDef(String aline){
    if (aline.length() == 0)
      return false;
    StringTokenizer st = new StringTokenizer(aline, " {(),;}");
    if (st.hasMoreTokens()){
      String first = st.nextToken();
      // possibly a visibility modifier (ignore abstract - cannot be Examples)
      if (first.equals("public") ||
          first.equals("protected") ||
          first.equals("private"))
        first = st.nextToken();

      // now we have a class def  
      if (first.equals("class"))
        return true;
      else
        return false;
    }
    else
      return false;
  }
  
  /**
   * Produce the name of the class defined on this line
   * c
   * @param aline a line of code that contains a class definition
   * @return the name of the class defined here
   */
  public String classDefName(String aline){
    if (aline.length() == 0)
      return "";
    StringTokenizer st = new StringTokenizer(aline, " {");
    String first = st.nextToken();
    // possibly a visibility modifier (ignore abstract - cannot be Examples)
    if (first.equals("public") ||
        first.equals("protected") ||
        first.equals("private"))
      first = st.nextToken();

    // now we have a class def  
    if (first.equals("class")){
      first = st.nextToken();
      return first;
    }
    else
      return "";
  }
 
  /**
   * Print all lines in this file
   */
  public void printLines(){
    for (String line: lines)
      System.out.println(line); 
  }
  
  /**
   * Write all lines into a file
   */
  public void writeLines(){
    String testClassName = this.findExamples();
    this.convertTests();
    
    try{
    for (String line: lines)
      filewriter.write(line + "\n"); 
    
    filewriter.close();   
    }
    catch(IOException e){
      System.out.println("Error when writing file: " + e.getMessage());
    } 
  }
 
  /*--------------------------------------------------------
  Run the converter
  *------------------------------------------------------*/
  public static void main(String argv[]){
  
    Converter convert = new Converter();
 
    String testClassName = convert.findExamples();
    convert.convertTests();
    convert.printLines();
    
    System.out.println("\n\nTest class name: " + testClassName);
    convert.parent.removeNotify();
    convert.parent2.removeNotify();
  }
}


/**
 * Class used to assure that only file names with the suffix .java are
 * seen in the FileChooser dialog.
 * 
 * @author Viera K. Proulx
 * @since 7 March 2008
 *
 */
class JavaFileFilter extends FileFilter{
  
  public boolean accept(File f){
    // accept if file name ends in .java
    return f.getName().endsWith(".java");
  }
  
  public String getDescription(){
    return "Java files";
  }
  
}


