/*---------------------------------------------------------------------------
File:         			State.java
Package:                        JFLAP Version 3.1
Author:				Magda & Octavian Procopiuc V1.0 07/15/96
                                Eric Gramond V3.0 08/14/98
				Ted Hung V3.1 7/10/99
 
Description of Contents:	Contains class State.
				
--------------------------------------------------------------------------*/

/* 
 * 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.util.*;
import	java.awt.*;
import  java.awt.event.*;

/**
 * This class implements a state of a machine.
 *
 * The painting of a state is done in the Desktop class.
 *
 * @see		flap.Desktop
 * @author	Magda & Octavian Procopiuc
 * @version	1.0 15 July 1996
 */
public class State implements BinaryPredicate {
  public int	  id;
  public boolean  isFinal;

  boolean	  isMouseOver = false;
  Point	p;	  // the position relative to the desktop
  int		  ss;	// NORMAL or FOCUSED 
  Vector 	  transitions;
  Vector	  stateList;  // list of corresponding states for NFA-DFA
  String	  label;      // label for NFA-DFA states
  //public String   stringLabel;
  Point		  labelStart;
  Point		  labelEnd;
  int		  checkMark;  // for NFA-DFA checking algorithm
  int		  selectedSublabel = 0;
  int		  loopSize = 0;
  
  final static int NORMAL = 0;
  final static int FOCUSED = 1;
  final static int HIGHLIGHTED = 2;
    
    
    /**
     * Constructs a new State with the specified label, isFinal flag,
     * and position.
     */
  public State (int id, boolean isFinal, Point p) {
    this.id = id;
    this.isFinal = isFinal;
    this.p = p;
    ss = State.NORMAL;
    transitions = new Vector(0); 
    stateList = new Vector(0);
    label = "";
    //stringLabel = "";
    labelStart = new Point();
    labelEnd = new Point();
  }
  
  /**
   * Constructs a new State with the specified label, isFinal flag,
   * and position.
   */
  public State (int id, boolean isFinal, int x, int y) {
    this.id = id;
    this.isFinal = isFinal;
    ss = State.NORMAL;
    transitions = new Vector(0);
    stateList = new Vector(0);
    p = new Point(x, y);
    label = "";
    //stringLabel = "";
    labelStart = new Point();
    labelEnd = new Point();
  }

  /**
   * Construct a new, uninitialized, state.
   * Used when reading a machine from a file.
   */
  public State() {
    this(-1, false, -1, -1);
  }

  void move(int x, int y, Dimension d) {
    p.move(x, y);
    p.x = Math.min(p.x, d.width);
    p.x = Math.max(0, p.x);
    p.y = Math.min(p.y, d.height);
    p.y = Math.max(0, p.y);
  }

  /**
    * copy constructor
    */
   public State(State aState)
   {
    this.id = aState.id;
    this.isFinal = aState.isFinal;
    this.p = new Point(aState.p);
    transitions = new Vector(0);
    stateList = new Vector(0);
    labelStart = new Point(aState.labelStart);
    labelEnd = new Point(aState.labelEnd);
    label = new String(aState.label);
    //stringLabel = new String(aState.stringLabel);
    for(Enumeration e = aState.stateList.elements(); e.hasMoreElements(); ) 
      stateList.addElement( (State) e.nextElement());
    
    transitions = new Vector();
    if(aState.transitions != null) {
      for(Enumeration e2 = aState.transitions.elements(); e2.hasMoreElements(); ) 
	transitions.addElement(e2.nextElement());
    }
    checkMark = aState.checkMark;
    selectedSublabel = aState.selectedSublabel;
    loopSize = aState.loopSize;
  }

   /** used for sorting the vectors of States with the JGL VectorArray Sorting
    */
  public boolean execute(Object a, Object b) {
    State a1, b1;
    a1 = (State) a;
    b1 = (State) b;
    return (a1.id < b1.id);  }

  /** returns a tokenised version of the Dfa label stored in a Vector of Strings */
  public Vector getTokenizedLabel() {
    Vector result = new Vector();
    String temp = "";

    for(int i= 0; i<label.length(); i++) {
      if(label.charAt(i) == ',') {
	result.addElement(temp);
	temp = "";
      }
      else temp += label.charAt(i);
    }
    result.addElement(temp);
    return result;    
  }

  /** sets the label to the String corresponding to the Vector of Strings passed */
  public void setLabel(Vector aVector) {
    String aString;

    label = "";

    for(Enumeration e = aVector.elements(); e.hasMoreElements(); ) {
      aString = (String) e.nextElement();
      label+=aString;
      if(e.hasMoreElements())
	 label+=',';
    }
  }
  /** turns the label into a list of the corresponding States in the passed nfa
    * @return the integer corresponding to any nonexisting states in the nfa. 
    *         or -1 if the label is the empty String or -2 if the operation succeeded
    */
  public int labelToStateList(Desktop nfa) throws NumberFormatException
  {
    Vector result = new Vector();
    String temp = "";
    int stateId;
    State aState;
    int count = 0;
    stateList = new Vector();

    for(int i= 0; i<label.length(); i++) {
      if(label.charAt(i) == ',') {
	if(temp.equals("")) {
	  selectedSublabel = count;
	  return -1;
	}
	stateId = Integer.parseInt(temp);
	aState = nfa.getStateWithId(stateId);
	if(aState == null)
	  return stateId;
	result.addElement(aState);
	temp = "";
	count++;
      }
      else temp += label.charAt(i);
    }
    if(temp.equals("")) {
      selectedSublabel = count;
      return -1;
    }
    stateId = Integer.parseInt(temp);
    aState = nfa.getStateWithId(stateId);
    if(aState == null)
      return stateId;
    result.addElement(aState);
    
    VectorArray arr = new VectorArray(result);
    Sorting.sort( arr, this );
    stateList = new Vector();
    for ( Enumeration e3 = arr.elements(); e3.hasMoreElements(); ) {
      stateList.addElement( (State) e3.nextElement());
    }

    return -2;
  }

  /** turns a list of States in the corresponding nfa into a String and stores 
    * it as the label */
  public void stateListToLabel() {
    State aState;
    label = "";
    for(Enumeration e = stateList.elements(); e.hasMoreElements(); ) {
      aState = (State) e.nextElement();
      label += aState.id;
      if(e.hasMoreElements())
	label+=',';
    }
  }

  /** Compares the label of this State with that of the passed State.
    * Precondition: both States already have properly set (and sorted) stateLists.
    * @return true if the two Labels are the same
    */
  public boolean compareLabel(State aState) {
    
    Enumeration e2 = stateList.elements();
    Enumeration e3 = aState.stateList.elements();

    State aState2, aState3;

    while(e2.hasMoreElements() && e3.hasMoreElements()) {
      aState2 = (State) e2.nextElement();
      aState3 = (State) e3.nextElement();
      if(aState2 != aState3) {
	return false;
      }
    }
    if((e2.hasMoreElements()) || (e3.hasMoreElements())) 
      return false;

    return true;
  }

  /**
   * Paints a state. 
   */
  public void paint(Graphics g, boolean showDfaLabels) {
    int diameter = 2 * Params._s_radius;
    int off = 2;

    Color sc = (ss == State.NORMAL) ? Params._s_normalcolor: ((ss == State.HIGHLIGHTED) ? Params._s_highlightedcolor : Params._s_focusedcolor);
    g.setColor(sc);
    g.fillOval(p.x - Params._s_radius, p.y - Params._s_radius, diameter, diameter);
    g.setColor(Params._s_forecolor);
    g.drawOval(p.x - Params._s_radius, p.y - Params._s_radius, diameter, diameter);

    g.setFont(Params._s_font);
    FontMetrics fm = g.getFontMetrics();
    String str = "q" + Integer.toString(id);
    int l = fm.stringWidth(str);
    int h = fm.getAscent();
    if(!showDfaLabels)
      g.drawString(str, p.x - ((int) l/2), p.y + ((int) h/2));

    if (isFinal) {
      g.drawOval(p.x - Params._s_radius - off, p.y - Params._s_radius - off, 
		 diameter + 2 * off, diameter + 2 * off);
    }
  }

  /**
   * Paints the label in normal mode.
   */
  public void paintNormalLabel(Graphics g, State selectedState)
  {
    int emptyDfaLabelWidth = Params.emptyDfaLabelWidth;
    int	   cx;
    FontMetrics fm = g.getFontMetrics();
    int promptLength = (this == selectedState) ? fm.stringWidth("_") : 0;
    Color tc = ((this == selectedState) ? Params._t_selectedcolor: Params._t_normalcolor);
    String str = ((this == selectedState) ? label: getLabel());

    Point pl = new Point(p); 
    
    int h = fm.getAscent();
    int yStrBase = (int) (pl.y - 2 + (Params._t_height + h)/2);

    pl.x -=  (int) ((fm.stringWidth(str) + (2*emptyDfaLabelWidth) + promptLength)/2);
    labelStart = new Point(pl);
    labelEnd = new Point(pl.x+fm.stringWidth(str) 
		   + (2*emptyDfaLabelWidth) +
		   promptLength , pl.y+ Params._t_height);
    g.setColor(Params._t_interiorcolor);
    g.fillRect(pl.x, pl.y, labelEnd.x - pl.x, labelEnd.y - pl.y);   
    g.setColor(tc);
    g.drawRect(pl.x, pl.y, labelEnd.x- pl.x, labelEnd.y - pl.y);

    if(this == selectedState)
       g.drawString(str + "_", pl.x+ emptyDfaLabelWidth, yStrBase);
    else
       g.drawString(str, pl.x+ emptyDfaLabelWidth, yStrBase);
  }

  /**
   * Paints the label of a State in DFA mode.
   */
  public void paintDfaLabel(Graphics g, State selectedDfaState)
  {
    int emptyDfaLabelWidth = Params.emptyDfaLabelWidth;
    int	   cx;
    Vector sublabels = getTokenizedLabel();
    FontMetrics fm = g.getFontMetrics();
    int promptLength = (this == selectedDfaState) ? fm.stringWidth("_") : 0;
    Color tc = ((this == selectedDfaState) ? Params._t_selectedcolor: Params._t_normalcolor);

    Point pl = new Point(p); 
    
    int h = fm.getAscent();
    int yStrBase = (int) (pl.y - 2 + (Params._t_height + h)/2);
    pl.x -=  (int) ((fm.stringWidth(label) + (2*emptyDfaLabelWidth) + promptLength)/2);
    labelStart = new Point(pl);

    labelEnd = new Point(pl.x+fm.stringWidth(label) 
		   + (2*emptyDfaLabelWidth) +
		   promptLength , pl.y+ Params._t_height);
    g.setColor(Params._t_interiorcolor);
    g.fillRect(pl.x, pl.y, labelEnd.x - pl.x, labelEnd.y - pl.y);   
    g.setColor(tc);
    g.drawRect(pl.x, pl.y, labelEnd.x- pl.x, labelEnd.y - pl.y);
    if (this != selectedDfaState)
    {
        g.drawString(label, pl.x+ emptyDfaLabelWidth, yStrBase); 
    }
    else
    {
        cx = pl.x + emptyDfaLabelWidth;
        promptLength = 0;
        g.setColor(Params._t_normalcolor);
        for (int i=0; i< sublabels.size(); i++) {
          if (selectedSublabel == i)  {
            promptLength = fm.stringWidth("_"); 
            g.setColor(Params._t_selectedcolor);
            g.drawString(( (String) sublabels.elementAt(i))+"_", cx, yStrBase);
            cx += fm.stringWidth(( (String) sublabels.elementAt(i)) + "_");
            g.setColor(Params._t_normalcolor);
          } else {
            g.drawString( ((String) sublabels.elementAt(i)), cx, yStrBase);
            cx += fm.stringWidth((String) sublabels.elementAt(i));
          }
	  if(i < sublabels.size() -1) {
	    g.drawString(",", cx, yStrBase);
	    cx += fm.stringWidth(",");
	  }
        }
    }
  }

  /**
   * Paints the label of a State in reg to FSA mode.
   */
  public void paintStringLabel(Graphics g, State selectedDfaState)
  {
    
    int emptyDfaLabelWidth = Params.emptyDfaLabelWidth;
    int	   cx;
    FontMetrics fm = g.getFontMetrics();
    
    Color tc = ((this == selectedDfaState) ? Params._t_selectedcolor: 
                Params._t_normalcolor);

    Point pl = new Point(p); 
    pl.x -= (int) ((fm.stringWidth(label) + (2*emptyDfaLabelWidth))/2);
    labelStart = new Point(pl);
    labelEnd = new Point(pl.x+fm.stringWidth(label) + (2*emptyDfaLabelWidth)
			  , pl.y+ Params._t_height);

    g.setColor(Params._t_interiorcolor);
    g.fillRect(pl.x, pl.y, labelEnd.x - pl.x, labelEnd.y - pl.y);   
    g.setColor(tc);
    g.drawRect(pl.x, pl.y, labelEnd.x- pl.x, labelEnd.y - pl.y);
        
    int h = fm.getAscent();
    int yStrBase = (int) (pl.y - 2 + (Params._t_height + h)/2);
    g.drawString(label, pl.x+ emptyDfaLabelWidth, yStrBase); 
  }

  /**
   * Paints the triangle that represents the initial state.
   */
  public void paintInitial(Graphics g, Desktop d) {
    int up = 0;
    int right = 0;
    int down = 0;
    int left = 0;
    int r = Params._s_radius;
    Transition aTransition;
    g.setColor(Params._s_forecolor);
  

    if(p.x <= 2*r)
      left += 5;
    if(p.y <= 2*r)
      up+= 5;
    if(p.x >= d.getSize().width - 2*r)
      right += 5;
    if(p.y >= d.getSize().height - 2*r)
      down+=5;

    for(Enumeration e = transitions.elements(); e.hasMoreElements(); ) {
      aTransition = (Transition) e.nextElement();
      if(aTransition.to == this) {
	up++;
	continue;
      }

      if(aTransition.to.p.x > p.x) {
	if(aTransition.to.p.y > p.y) {
	  if(aTransition.to.p.x - p.x > aTransition.to.p.y - p.y)
	    right++;
	  else 
	    down++;
	} else {
	  if(aTransition.to.p.x - p.x > p.y - aTransition.to.p.y)  
	    right++;
	    else
	      up++;
	  }
      } else {
	if(aTransition.to.p.y > p.y) {
	  if(p.x  - aTransition.to.p.x > aTransition.to.p.y - p.y)
	    left++;
	  else
	    down++;
	} else {
	  if(p.x - aTransition.to.p.x > p.y - aTransition.to.p.y)
	    left++;
	  else
	    up++;
	}
      }
    }
    
    if(left <= right && left <= up && left <= down) {
      int xPoints[] = {p.x - r, p.x - 2*r, p.x - 2*r, p.x - r};
      int yPoints[] = {p.y, p.y - r, p.y + r, p.y};
      g.drawPolygon(xPoints, yPoints, 4);    
    } else if (right <= up && right <= down) {
      int xPoints[] = {p.x+r, p.x+2*r, p.x + 2*r, p.x +r};
      int yPoints[] = {p.y, p.y - r, p.y + r, p.y};
      g.drawPolygon(xPoints, yPoints, 4);    
    } else if (up <= down) {
      int yPoints[] = {p.y-r, p.y-2*r, p.y - 2*r, p.y -r};
      int xPoints[] = {p.x, p.x - r, p.x + r, p.x};
      g.drawPolygon(xPoints, yPoints, 4);    
    } else {
      int yPoints[] = {p.y+r, p.y+2*r, p.y + 2*r, p.y +r};
      int xPoints[] = {p.x, p.x - r, p.x + r, p.x};
      g.drawPolygon(xPoints, yPoints, 4);    
    }
  } 

  /** 
   * handles keyEvents on normal labels for the Desktop
   */
  public boolean processKey(int key, KeyEvent e)
  {
    String aString;
    boolean modified = false;

    if ((!e.isActionKey()) && key >= (int) '0' && key <= (int) '9') {
      label += (char) key;
      modified = true;
    }
    else if (key == 8 || key == 127||
                e.getKeyCode() == KeyEvent.VK_DELETE ||
		e.getKeyCode() == KeyEvent.VK_BACK_SPACE /*||
                key == 65288 || key == 65535*/)
    {	 //Backspace or Delete
      if(label.length() > 0)
      {
	label = label.substring(0, label.length()-1);
	modified = true;
      }
    }
    else if(!e.isActionKey() && key >= 32 && key <= 126) {
      label += (char) key;
      modified = true;
    }
    return modified;
  }

  /** 
    * handles keyEvents on StateDfalabels for the Desktop
    */
  public boolean processDfaKey(int key, KeyEvent e) {
    
    Vector sublabels =  getTokenizedLabel();
    String aString;
    boolean modified = false;


    if (selectedSublabel > sublabels.size()-1)
      selectedSublabel = 0;

    aString = (String) sublabels.elementAt(selectedSublabel);

    if ((!e.isActionKey()) && key >= (int) '0' && key <= (int) '9') {
      aString += (char) key;
      sublabels.setElementAt(aString, selectedSublabel);
      modified = true;
    }
    else if (key == 8 || key == 127||
                e.getKeyCode() == KeyEvent.VK_DELETE ||
		e.getKeyCode() == KeyEvent.VK_BACK_SPACE /*||
                key == 65288 || key == 65535*/) {	 //Backspace or Delete
      if(aString.length() > 0) {
	aString = aString.substring(0, aString.length()-1);
	sublabels.setElementAt(aString, selectedSublabel);
	modified = true;
      }
      else if(sublabels.size() > 1){
	sublabels.removeElement(aString);
	if(selectedSublabel > 0) 
	  selectedSublabel--;
      }
      
    } else if (key == KeyEvent.VK_RIGHT || (key == '\t' && e.getModifiers() == 0) &&
	       (sublabels.size() > 1)) {
      selectedSublabel = (++selectedSublabel) % (sublabels.size());
    } else if ((sublabels.size() > 1) && (key == KeyEvent.VK_LEFT || 
	       (key == '\t' && e.getModifiers() == InputEvent.SHIFT_MASK))) {
      selectedSublabel = (--selectedSublabel + sublabels.size()) % (sublabels.size());
    } else if(key == (int) ',') {
      sublabels.addElement("");
      selectedSublabel = (sublabels.size()-1);
      modified = true;
    }
    else if(!e.isActionKey() && key >= 32 && key <= 126 && 
	 key != ((int) ';') && key != (int) ',') {
      aString += (char) key;
      sublabels.setElementAt(aString, selectedSublabel);
      modified = true;
    }
    setLabel(sublabels);
    return modified;
 }


  /** 
    * handles keyEvents on StateStringLabelsabels for the Desktop
    */
  public boolean processStringKey(int key, KeyEvent e) {
    
    String aString;
    boolean modified = false;

    if ((!e.isActionKey()) && key >= (int) 'A' && key <= (int) 'Z') {
      label = new String (""+(char) key);
      modified = true;
    } 
    return modified;
 }

 /**
   * sets the flag saying whether the mouse is over the state
   * allows for label expansion when mouse moves over state
   */
 public void setMouseOver(boolean flag)
 {    
    isMouseOver = flag;
 }

 /**
   * returns "qx" of the state if the label is null, otherwise it returns
   * the label
   */
 private String getLabel()
 {
   //abbreviate name
   if(label.length() > 10 && !isMouseOver)
   {
      return new String(label.substring(0, 7) + "...");
   }
   return new String(label);
 }
 
}
