/*---------------------------------------------------------------------------
File:         			FSAtoRegExpWindow.java
Package:                        JFLAP Version 3.1
Author:			        Ted Hung
                                
Description of Contents:	Contains class FSAtoRegExpWindow.
				
--------------------------------------------------------------------------*/

/* 
 * Susan H. Rodger, Magda Procopiuc, Octavian Procopiuc, Eric Gramond, Ted Hung
 * Computer Science Department
 * Duke University
 * August 1999
 * Supported by National Science Foundation DUE-9596002, DUE-9555084,
 *  and DUE-9752583
 *
 * Copyright (c) 1999
 * 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.awt.*;
import java.awt.event.*;
import java.util.*;


//
//
// FSAtoRegExpWindow
//
//

public class FSAtoRegExpWindow extends SourceWindow 
   implements ActionListener, ItemListener
{
   /**
    * List that displays the nodes in the Regular Expression
    */
   private java.awt.List nodes;
   
   /**
    * used to handle the input for certain nodes
    */
   private Panel     inputpanel;
   private Panel     mainpanel;
   private Label     inputlabel;
   private TextField inputfield;
   private Label     displabel;
   private Label     combinedlabel;
   private Button    nextbutton;
   private Button    tellbutton;
   private Button    editbutton;
   private Button    exitbutton;
   private Button    checkbutton;
   private Button    showbutton;
   private Button    showlevelbutton;
   private Desktop   d;
   private Vector    exps;
   private int       curterm = 0;
   private int       numselected = 0;

   /**
    * used to determine width of window.
    */
   int maxterm = 0;

   /**
    * the nodes of the vector at the current level
    */
   private Vector    levelnodes;
   private int       levelnum;
   private boolean   reverse = false;

   /**
    * the current node and tree that is being analyzed
    */
   private RegExpNode curtree;
   private RegExpNode curnode;
   final static String divider = "--------------------------------------------";
   final static String indent  = "           ";
   
   public FSAtoRegExpWindow(MainMenu mainmenu, Desktop desk)
      throws BadInputException
   {
      super(mainmenu, mainmenu.isApplet);
      setLayout(new BorderLayout(0, 0));
      setTitle("Convert FSA to Regular Expression");

      //add the controls
      Panel p = new Panel(new GridLayout(5,1));
      p.add(nextbutton = new Button("Next Expansion"));
      p.add(showlevelbutton = new Button("Show Current Level"));
      p.add(tellbutton = new Button("Show All Answers"));
      p.add(editbutton = new Button("Edit Regular Expression"));
      p.add(exitbutton = new Button("Exit"));
      Panel southPanel = new Panel(new BorderLayout());
      southPanel.add(p, "Center");
      nextbutton.addActionListener(this);
      showlevelbutton.addActionListener(this);
      tellbutton.addActionListener(this);
      editbutton.addActionListener(this);
      exitbutton.addActionListener(this);
      
      add(nodes = new java.awt.List(15, true), "Center");
      nodes.setBackground(Params._d_backcolor);

      mainpanel = new Panel(new BorderLayout(5, 5));
      
      inputpanel = new Panel(new BorderLayout(0,0));
      inputpanel.add(inputlabel = new Label("R( , , ) = "), "West");
      inputpanel.add(inputfield = new TextField("", 10), "Center");
      Panel temp = new Panel(new FlowLayout(FlowLayout.CENTER, 0, 0));
      temp.add(checkbutton = new Button("Check Answer"));
      temp.add(showbutton = new Button("Show Answer"));
      inputlabel.setAlignment(Label.RIGHT);
      inputpanel.add(temp, "East");

      //add the items in the tree expression to the list
      d = desk;
      exps = FSAToRegExp.createTree(d);
      if(exps == null || exps.isEmpty())
      {
	 throw new BadInputException("No FSA defined");
      }

      mainpanel.add(inputpanel, "Center");
      mainpanel.add(displabel = new Label(" R( , , ) = "));
      displabel.setAlignment(Label.LEFT);
      
      add(combinedlabel = new Label(""), "North");
      
      //add the elements to a string for the combined label
      String combinedexp = "Final Expression = ";
      for(Enumeration e = exps.elements(); e.hasMoreElements(); )
      {
	 combinedexp = combinedexp + 
	    ((RegExpNode)e.nextElement()).toString() + " + ";
      }
      combinedexp = combinedexp.substring(0, combinedexp.length() - 3);
      combinedlabel.setText(combinedexp);

      combinedlabel.setAlignment(Label.CENTER);
      combinedlabel.setBackground(Color.white);
      combinedlabel.setForeground(Color.blue);
      
      southPanel.add(mainpanel, "North");
      add(southPanel, "South");
      
      checkbutton.addActionListener(this);
      showbutton.addActionListener(this);

      //add the rest of the tree
      addTreesToList(exps);
      
      //start with the first tree and first node
      if(exps.elements().hasMoreElements())
      {
	 curtree = (RegExpNode) exps.elements().nextElement();
      }
      curnode = curtree;

      this.pack();
      inputpanel.setVisible(false);
      

      //resize the window
      FontMetrics fm = getGraphics().getFontMetrics();
      for(int x = 0; x < nodes.getItemCount(); ++x)
      {
	 if(fm.stringWidth(nodes.getItem(x)) > maxterm &&
	    !(nodes.getItem(x).equals(divider)) )
	 {
	    maxterm = fm.stringWidth(nodes.getItem(x));
	 }
      }

      this.setSize(2 * (4 * maxterm + fm.stringWidth(" = ") +
		   fm.stringWidth(" + ") + fm.stringWidth("* ") + 25),
		   500);
      this.validate();

      //initialize breadth first search
      levelnodes = new Vector();
      levelnum = curtree.k;
      getLevel(curnode, levelnodes, levelnum);

      //add the listeners
      nodes.addItemListener(this);
      this.addWindowListener(
	 new WindowAdapter()
	 {
	    public void windowClosing(WindowEvent e)
	    {
	       FSAtoRegExpWindow.this.startThread("quit");
	    }
	 }
      );

      //update the display
      displabel.setText(curnode.toString() + " = " +
			 curnode.getUnexpandedExp());
      validate();
      setListValue(curnode, true);
      highlightNodes();
   }
   
   /**
    * adds the terms in the tree to the listbox
    */
   public void addTreesToList(Vector trees)
   {      
      if(trees == null || trees.isEmpty())
	 return;

      //a vector of vectors to hold each level's nodes
      Vector listitems = new Vector();
      RegExpNode addnode;

      //add the all of the trees to the vector of vectors of nodes
      for(Enumeration e = trees.elements(); e.hasMoreElements(); )
      {
	 addnode = (RegExpNode) e.nextElement();

	 if(addnode != null)
	 {
	    addTreeToVector(addnode, listitems);
	 }
      }

      //add the nodes in the vector of vectors to the list
      //in reverse order
      Vector temp;      
      for(int x = listitems.size() - 1; x >= 0; --x)
      {
	 temp = (Vector) listitems.elementAt(x);
	 for(Enumeration enum = temp.elements(); enum.hasMoreElements(); )
	 {
	    addnode = (RegExpNode) enum.nextElement();
	    nodes.add(addnode.toString());
	 }
	 
	 nodes.add(divider);
      }

      //remove the last divider
      nodes.remove(nodes.getItemCount() - 1);
   }

   /**
    * adds the terms in the tree to the vector of vectors of RegExpNodes
    * depending on each nodes' level in the expression
    */
   public void addTreeToVector(RegExpNode tree, Vector levels)
   {      
      if(tree == null || levels == null)
      {
	 return;
      }

      //expand the size of the vector and add new vectors
      //if it is too small.
      while(levels.size() < tree.k + 2)
      {
	 levels.addElement(new Vector());
      }

      //add the element if it wasn't found in the list already
      if(!((Vector)levels.elementAt(tree.k)).contains(tree))
      {
	 ((Vector)levels.elementAt(tree.k)).addElement(tree);
      }

      addTreeToVector(tree.R1, levels);
      addTreeToVector(tree.R2, levels);
      addTreeToVector(tree.R3, levels);
      addTreeToVector(tree.R4, levels);
   }

   public void itemStateChanged(ItemEvent e)
   {
      highlightNodes();
   }

   public void paint(Graphics g)
   {
   }


   /**
    * when step or check answer is pressed
    */
   public void actionPerformed(ActionEvent h)
   {
      if(h.getSource() == exitbutton)
      {
	 startThread("quit");
      }
      else if(h.getSource() == editbutton)
      {
	 try
	 {
	    RegExpInputWindow regwin = new RegExpInputWindow(mainmenu);
	    regwin.setRegExp(curtree.getSimplifiedExp());
	    regwin.setLocation(
	       (Toolkit.getDefaultToolkit().getScreenSize().width - regwin.getSize().width) / 2,
	       (Toolkit.getDefaultToolkit().getScreenSize().height - regwin.getSize().height) / 2);
	    regwin.show();
	 }
	 catch(BadInputException e)
	 {
	    OKDialog dg = new OKDialog(
	       e.getMessage(),
	       this, "Regular Expression Error");
	    dg.show();
	 }
      }

      if(curnode == null)
	 return;

      if(h.getSource() == nextbutton)
      {
	 //replace the item in the list with the
	 //correct value if next button pressed
	 int index = findNode(curnode);
	 if(curnode.k <= 0)
	 {
	    if(checkExpEqual(inputfield.getText(), curnode.getSimplifiedExp()))
	    {
	       nodes.replaceItem(curnode.toString() + " = " +
		  curnode.getSimplifiedExp(), index);
	    }
	    else
	    {
	       OKDialog dg = new OKDialog(
		     "The expression is INCORRECT.",
		     this, "Regular Expression Error");
		  dg.show();
		  return;
	    }
	 }
	 else if(reverse)
	 {
	    if(checkExpEqual(inputfield.getText(), curnode.getSimplifiedExp()))
	    {
	       nodes.replaceItem(indent + " = " +
		  curnode.getSimplifiedExp(), index + 2);
	    }
	    else
	    {
	       OKDialog dg = new OKDialog(
		  "The simplified expression is INCORRECT.",
		  this, "Regular Expression Error");
	       dg.show();
	       return;
	    }
	 }

	 //repeat until the term is one that has not yet
	 //been replaced
	 do
	 {
	    stepNextNodeBreadth();
	 }
	 while(curnode != null && !setListValue(curnode, true));

	 if(curnode == null)
	 {
	    if(!nextExpTree())
	       return;
	 }

	 //if it's the first level, prompt for
	 //user input of the correct answer
	 if(reverse || curnode.k <= 0)
	 {
	    displabel.setVisible(false);
	    inputpanel.setVisible(true);
            mainpanel.add(inputpanel, "Center");
	    inputpanel.setSize(this.getBounds().width,
	       inputpanel.getBounds().height);
	    inputfield.setText("");
	    inputlabel.setText(curnode.toString() + " = ");
	    inputpanel.validate();
	 }
	 else
	 {
	    displabel.setVisible(true);
	    inputpanel.setVisible(false);
	    displabel.setText(curnode.toString() + " = " +
			      curnode.getUnexpandedExp());
	 }
	 
	 this.validate();
	 highlightNodes();
      }
      else if(h.getSource() == showlevelbutton)
      { 
	 int index;
	 RegExpNode prevnode = null;

	 index = findNode(curnode);
	 if(reverse)
	 {
	    setListValue(curnode, false);
	    nodes.replaceItem(indent + " = " +
	       curnode.getSimplifiedExp(), index + 2);
	 }
	 else if(curnode.k <= 0)
	 {
	    nodes.replaceItem(curnode.toString() + " = " +
	       curnode.getSimplifiedExp(), index);
	 }	 
	 
	 //repeat until the term is one that has not yet
	 //been replaced
	 do
	 {
	    stepNextNodeBreadth();
	 }
	 while(curnode != null && !setListValue(curnode, true));
	 int curlevel = levelnum;

	 //finish off the current iteration
	 while(curnode != null && curnode.k == curlevel)
	 {
	    setListValue(curnode, false);
	    index = findNode(curnode);
	    if(curnode.k <= 0)
	    {
	       nodes.replaceItem(curnode.toString() + " = " +
		  curnode.getSimplifiedExp(), index);
	    }
	    else if(reverse)
	    {
	       nodes.replaceItem(indent + " = " +
		  curnode.getSimplifiedExp(), index + 2);
	    }
	    prevnode = curnode;
	    stepNextNodeBreadth();
	 }
	 if(curnode != null)
	 {
	    if(reverse || curnode.k <= 0)
	    {
	       setListValue(curnode, true);
	       index = findNode(curnode);
	       displabel.setVisible(false);
	       inputpanel.setVisible(true);
	       mainpanel.add(inputpanel, "Center");
	       inputpanel.setSize(this.getBounds().width,
		  inputpanel.getBounds().height);
	       inputfield.setText("");
	       inputlabel.setText(curnode.toString() + " = ");
	       inputpanel.validate();
	    }
	    else
	    {
	       //set it to the previous value so nextnode won't screw up
	       curnode = prevnode;
	       levelnum = curnode.k;
	       getLevel(curnode, levelnodes, levelnum);
	       displabel.setVisible(true);
	       inputpanel.setVisible(false);
	       displabel.setText(curnode.toString() + " = " +
		  curnode.getUnexpandedExp());
	    }
	 }
	 else //if it is at the top of the tree
	 {
	    if(!nextExpTree())
	       return;
	 }
	 this.validate();
	 highlightNodes();
      }
      else if(h.getSource() == tellbutton)
      {
	 nextbutton.setEnabled(false);
	 tellbutton.setEnabled(false);
	 showlevelbutton.setEnabled(false);
	 int index;
	 
	 //finish off the current iteration
	 while(curnode != null)
	 {
	    setListValue(curnode, false);
	    index = findNode(curnode);
	    if(curnode.k <= 0)
	    {
	       nodes.replaceItem(curnode.toString() + " = " +
		  curnode.getSimplifiedExp(), index);
	    }
	    else if(reverse)
	    {
	       nodes.replaceItem(indent + " = " +
		  curnode.getSimplifiedExp(), index + 2);
	    }
	    stepNextNodeBreadth();
	 }

	 Enumeration e = exps.elements();
	 
	 //finish off the rest of the trees
	 while(nextExpTree())
	 {
	    index = findNode(curnode);
	    getLevel(curnode, levelnodes, levelnum);
	    reverse = false;

	    while(curnode != null)
	    {
	       setListValue(curnode, false);
	       index = findNode(curnode);
	       if(curnode.k <= 0)
	       {
		  nodes.replaceItem(curnode.toString() + " = " +
		     curnode.getSimplifiedExp(), index);
	       }
	       else if(reverse)
	       {
		  nodes.replaceItem(indent + " = " +
		     curnode.getSimplifiedExp(), index + 2);
	       }
	       stepNextNodeBreadth();
	    }
	 }
	 
	 reverse = true;
	 curnode = null;
      }
      else if(h.getSource() == checkbutton)
      {
	 if(checkExpEqual(inputfield.getText(), curnode.getSimplifiedExp()))
	 {
	    OKDialog dg = new OKDialog(
	       "Your simplification is CORRECT",
	       this, "Regular Expression Simplification");
	    dg.show();
	 }
	 else
	 {
	    OKDialog dg = new OKDialog(
	       "Your simplification is INCORRECT",
	       this, "Regular Expression Simplification");
	    dg.show();
	 }
      }
      else if(h.getSource() == showbutton)
      {
	 inputfield.setText(curnode.getSimplifiedExp());
      }
   }

   /**
    * steps to the next expression tree in the
    * vector of expressions (corresponding to each final state)
    */
   public boolean nextExpTree()
   {
      int index;
      displabel.setVisible(true);
      inputpanel.setVisible(false);
      displabel.setText(curtree.toString() + " = " +
			curtree.getSimplifiedExp());

      //find the node's position
      Enumeration e;
      RegExpNode atree;
      String temp = "Final Expression = ";
      for(e = exps.elements(); e.hasMoreElements(); )
      {
	 atree = (RegExpNode) e.nextElement();
	 temp = temp + atree.getSimplifiedExp() + " + ";

	 if(atree == curtree)
	 {
	    break;
	 }
      }

      if(e.hasMoreElements())
      {
	 curtree = (RegExpNode) e.nextElement();

	 temp = temp + curtree.toString() + " + ";
	 while(e.hasMoreElements())
	 {
	    temp = temp + ((RegExpNode)e.nextElement()).toString() +
	       " + ";
	 }
	 temp = temp.substring(0, temp.length() - 3);
	 combinedlabel.setText(temp);

	 curnode = curtree;
	 reverse = false;
	 index = findNode(curnode);

	 nodes.replaceItem(curnode.toString() + " = " +
	    curnode.getUnexpandedExp(), index);
	 levelnum = curtree.k;
	 getLevel(curnode, levelnodes, levelnum);	       
      }
      else  //if this is the last tree to iterate
      {
	 temp = temp.substring(0, temp.length() - 3);
	 combinedlabel.setText(temp);

	 nextbutton.setEnabled(false);
	 tellbutton.setEnabled(false);
	 showlevelbutton.setEnabled(false);

	 highlightNodes();
	 return false;
      }

      return true;
   }

   /**
    * steps to the next node from the current node
    * BREADTH FIRST
    */
   public void stepNextNodeBreadth()
   {
      if(curnode == null || levelnodes == null)
	 return;

      if((curnode == levelnodes.lastElement() && !reverse) ||
	 (curnode == levelnodes.firstElement() && reverse))
      {
	 levelnodes.removeAllElements();
	 if(reverse)
	 {
	    ++levelnum;
	    if(levelnum > curtree.k)
	    {
	       curnode = null;
	       return;
	    }
	    getLevel(curtree, levelnodes, levelnum);
	    curnode = (RegExpNode)levelnodes.elementAt(
	       levelnodes.lastIndexOf(levelnodes.lastElement()));
	 }
	 else
	 {
	    --levelnum;
	    if(levelnum < 0)
	    {
	       reverse = true;
	       levelnum = 1;
	       getLevel(curtree, levelnodes, levelnum);
	       curnode = (RegExpNode) levelnodes.elementAt(
	           levelnodes.lastIndexOf(levelnodes.lastElement()));
	       return;
	    }
	    getLevel(curtree, levelnodes, levelnum);
	    curnode = (RegExpNode)levelnodes.elementAt(0);
	 }
      }
      else
      {
	 int x = 0;
	 while(levelnodes.elementAt(x) != curnode)
	 {
	    ++x;
	 }

	 if(reverse)
	    curnode = (RegExpNode) levelnodes.elementAt(x - 1);
	 else
	    curnode = (RegExpNode) levelnodes.elementAt(x + 1);
      }
   }

   /**
    * returns a vector containing all the nodes at a specified
    * level in the tree
    */
   public void getLevel(RegExpNode tree, Vector vect, int level)
   {
      if(tree != null && tree.k == level)
      {
	 boolean foundinvect = false;
	 RegExpNode reg;
	 for(Enumeration e = vect.elements(); e.hasMoreElements(); )
	 {
	    reg = (RegExpNode) e.nextElement();
	    if(reg.toString().equals(tree.toString()))
	    {
	       foundinvect = true;
	    }
	 }
	 if(!foundinvect)
	    vect.addElement(tree);
	 return;
      }
      getLevel(tree.R1, vect, level);
      getLevel(tree.R2, vect, level);
      getLevel(tree.R3, vect, level);
      getLevel(tree.R4, vect, level);
      return;
   }

   /**
    * Sets the value in the listbox for the specified node
    * returns true if it was found, returns false if it wasn't
    * The askQuestion flag specifies whether to leave the simplified
    * expression as a question mark to deal with the user inputting
    * the correct answer
    */
   private boolean setListValue(RegExpNode setnode, boolean askQuestion) 
   {
      //find the index of the node
      String items[] = nodes.getItems();
      String temp;
      int index = nodes.getItemCount() - 1;

      //replace equations with values if reverse, otherwise
      //show equations
      if(reverse)
	 temp = setnode.toString() + " = " + setnode.getUnexpandedExp();
      else
	 temp = setnode.toString();

      //find the index
      while(index >= 0 && !items[index].equals(temp.toString()))
      {
	 --index;
      }

      //replace the item in the listbox
      if(index >= 0)
      {
	 if(reverse)
	 {
	    //add a node that will be
	    nodes.add(indent + " = " + 
	       setnode.getExpandedExp(), index + 1);
	    if(askQuestion)
	       nodes.add(indent + " = ?", index + 2);
	    else
	       nodes.add(indent + " = " + setnode.getSimplifiedExp(), index + 2);
	    //use the space to ensure it doesn't get replaced again
	    temp = setnode.toString() + " = " + 
	       setnode.getUnexpandedExp() + " ";
	 }
	 else if(setnode.k <= 0)
	 {
	    if(askQuestion)
	       temp = setnode.toString() + " = ?";
	    else
	       temp = setnode.toString() + " = " + setnode.expression;
	 }
	 else
	 {
	    temp = setnode.toString() + " = " + setnode.getUnexpandedExp();
	 }

	 nodes.replaceItem(temp, index);

	 return true;
      }
      return false;
   }

   /**
    * highlights the nodes corresponding to current node
    */
   private void highlightNodes()
   {
      //deselect all of the nodes
      for(int x = 0; x < nodes.getItemCount(); ++x)
      {
	 nodes.deselect(x);
      }

      if(curnode == null)
	 return;

      if(curnode.R1 == null ||
	 curnode.R2 == null ||
	 curnode.R3 == null ||
	 curnode.R4 == null)
      {
	 nodes.select(findNode(curnode));
	 return;
      }

      nodes.select(findNode(curnode.R4));
      nodes.select(findNode(curnode.R3));
      nodes.select(findNode(curnode.R2));
      nodes.select(findNode(curnode.R1));
      nodes.select(findNode(curnode));
   }

   /**
    * finds the index of the node in the listbox
    */
   private int findNode(RegExpNode r)
   {
      String temp;

      if(r == null)
	 return -1;

      for(int x = 0; x < nodes.getItemCount(); ++x)
      {
	 temp = nodes.getItem(x);
	 temp = temp.substring(0, temp.indexOf(')') + 1);
	 if(temp.equals(r.toString()))
	 {
	    return x;
	 }
      }

      return -1;
   }

   /**
    * checks whether the two regular expressions are
    * equal
    */
   protected boolean checkExpEqual(String exp1, String exp2)
   {
      String temp1 = "";
      String temp2 = "";

      //remove the whitespaces from expression 1
      for(int x = 0; x < exp1.length(); ++x)
      {
	 if(exp1.charAt(x) != ' ')
	 {
	    temp1 = temp1 + exp1.substring(x, x + 1);
	 }
      }

      //remove the whitespaces from expression 2
      for(int x = 0; x < exp2.length(); ++x)
      {
	 if(exp2.charAt(x) != ' ')
	 {
	    temp2 = temp2 + exp2.substring(x, x + 1);
	 }
      }

      temp1 = RegExpUtilities.minimizeParenthesis(temp1);
      temp1 = RegExpUtilities.sortExpressions(temp1);
      temp2 = RegExpUtilities.minimizeParenthesis(temp2);
      temp2 = RegExpUtilities.sortExpressions(temp2);
      //System.out.println(temp1 + " " + temp2);

      return temp1.equals(temp2);
   }


    /* 
     * starts a functionThread with the given function inside
     */
   public void  startThread(String functionName) {
	FunctionThread ft;
	Class source;
       
	try {
	    source = this.getClass();
	    ft = new FunctionThread(source.getMethod(functionName, new Class[0]), this, new Object[0]);
	    ft.start();
	}
	catch(NoSuchMethodException h) {
	    System.out.println("no " + functionName + " method found.");
	}
   }
}

  
