/*---------------------------------------------------------------------------
File:         			Configuration.java
Package:                        JFLAP Version 1.0
Author:				Magda & Octavian Procopiuc V1.0 07/15/96
                                
Description of Contents:	Contains class Configuration.
				
--------------------------------------------------------------------------*/

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


/**
 * Implements a configuration of a machine.
 * It is designed to be used as a configuration for all 4 types
 * of machines implemented in this release: FSA, PDA, TM1, TM2.
 *
 * @see		flap.Machine
 * @author	Magda & Octavian Procopiuc
 * @version	1.0 15 July 1996
 */
public class Configuration {
  /**
   * The machine to which this config. belongs.
   */
  public Machine	a;
  /**
   * The state of the config.
   */
  public State		theState;
  /**
   * The remaining input string.
   */
  public String		theString;
  /**
   * The stack (for PDA) or the part of the input string
   * at the right of the reading head (for Turing machines).
   */
  public String		theStack;
  /**
   * secondString is used for the second
   * tape (for 2-tape TM).
   */
  public String		secondString;
  /**
   * secondStack is used for the second
   * tape (for 2-tape TM).
   */
  public String		secondStack;
  /**
   * This is used for the derivation tree.
   */
  public Configuration	father;
  /**
   * This is for the appearance on the step run window.
   */
  public boolean	isFrozen = false;
  /**
   * This is for the appearance on the step run window.
   */
  public boolean	isValid = true;
  /**
   * This is for the appearance on the step run window.
   */
  public boolean	isSelected = false;

  final static String ACCEPTED_STR = "Input Accepted";

  static int 	w = Params._c_width;
  static int	h = Params._c_height;
  /**
   * The radius of the state when painted in the config.
   */
  static int 	r = 11;
  /**
   * The x offset of the Cross image.
   */
  static int	xCrossPad = 3;
  /**
   * The y offset of the Cross image.
   */
  static int	yCrossPad = 4;
  
  /**
   * The most general constructor. Actually used for the TM2.
   */
  public Configuration (State theState, String theString, String theStack, 
  		String secondString, String secondStack, 
  		Configuration father, Machine a) {
    this.theState = theState;
    this.theString = theString;
    this.theStack = theStack;
    this.secondString = secondString;
    this.secondStack = secondStack;
    this.father = father;
    this.a = a;
  }

  /**
   * This constructor is used for FA.
   */
  public Configuration (State theState, String theString, 
  		Configuration father, Machine a) {
    this(theState, theString, null, null, null, father, a);
  }

  /**
   * This constructor is used for TM1 and PDA.
   */
  public Configuration (State theState, String theString, 
  		String theStack, Configuration father, Machine a) {
    this(theState, theString, theStack, null, null, father, a);
  }

  /**
   * Returns true if the configuration is an acceptance config.
   * @return	true if this config. is an acceptance one.
   */
  public boolean isAccept() {
    boolean answer = "".equals(theString);

    if (a instanceof FSA)
      answer = answer && theState.isFinal;
    else if (a instanceof PDA) {
      if (((PDA) a).acceptanceType.equals(PDA.FINAL_STATE))
        answer = answer && theState.isFinal;
      else if (((PDA) a).acceptanceType.equals(PDA.EMPTY_STACK))
        answer = answer && "".equals(theStack);
      else if (((PDA) a).acceptanceType.equals(PDA.FINAL_AND_EMPTY))
        answer = answer && theState.isFinal && "".equals(theStack);
    } else if ((a instanceof TM1) || (a instanceof TM2))
      answer = theState.isFinal;  
    return answer;
  }

  /**
   * Change the width, height and radius, because we are using
   * the painting methods for both the step run window and the trace window.
   * @param w1	the new width
   * @param h1	the new height
   * @param r1	the new radius
   */
  public static void setParams(int w1, int h1, int r1) {
    w = w1;
    h = h1;
    r = r1;
  }

  /**
   * Paints the configuration at position x, y, in the 
   * context g.
   * @param g	the graphics context
   * @param x	the x coordinate of the upper left corner
   * @param y	the y coordinate of the upper left corner
   */
  public void paint(Graphics g, int x, int y) {
    int			hPad = 2*r + 15;
    int			vPad = 0;    
    FontMetrics	        fm = null;

    switch (a.type) {
      case Machine.FSA:
        vPad = (int) (h/2);
        break;
      case Machine.PDA:
      case Machine.TM1: 
        vPad = (int) (h/2 - r);
        break;
      case Machine.TM2:
        vPad = (int) (h/3 - r - 2);
        break;
    }  
    try {
      fm = g.getFontMetrics();
    }
    catch (Exception e) {
      g.setFont(new Font("System", Font.PLAIN, 12));
      fm = g.getFontMetrics();
    }

    g.setColor(Params._c_confcolor);
    g.fill3DRect(x, y, w, h, !isSelected);

    g.setColor(Params._s_normalcolor);
    g.fillOval(x + hPad - r, y + vPad - r, 2*r, 2*r);
    g.setColor(Params._c_forecolor);
    g.drawOval(x + hPad - r, y + vPad - r, 2*r, 2*r);

    if (theState == a.d.initialState) {
      int[] xpoints = {x+hPad-2*r, x+hPad-2*r, x+hPad-r, x+hPad-2*r};
      int[] ypoints = {y+vPad-r, y+vPad+r, y+vPad, y+vPad-r};
      g.drawPolygon(xpoints, ypoints, 4);
    }
    if (theState.isFinal) 
      g.drawOval(x + hPad - r - 2, y + vPad - r - 2, 2*(r+2), 2*(r+2));

    String str = "q" + Integer.toString(theState.id);
    int l = fm.stringWidth(str);
    int hh = fm.getAscent();
    g.drawString(str, x + hPad - ((int) l/2) + 1, y + vPad + ((int) hh/2));

    if (!isValid) {
      if (Params._img_cross != null)
        g.drawImage(Params._img_cross, x + xCrossPad, y + yCrossPad, null);
      else {
       g.drawLine(x+2, y+2, x+16, y+h-2);
       g.drawLine(x+16, y+2, x+2, y+h-2);
      }
    } else if (isFrozen) {
      if (Params._img_flake != null)
        g.drawImage(Params._img_flake, x + xCrossPad, y + yCrossPad, null);
      else {
        g.drawLine(x+2, y+2, x+2, y+h-2);
        g.drawLine(x+16, y+2, x+2, y+h-2);
      }
    }   

   switch (a.type) {
     case Machine.FSA:
     case Machine.PDA:   
       if (isAccept() && isValid)
         str = ACCEPTED_STR;
       else
         str = theString;
       paintStringForAutomaton(g, str, x+hPad+r+8, y+vPad-r, w-hPad-r-15, 2*r-1);
       if (a.type == Machine.PDA) {
         paintStringForAutomaton(g, theStack, x+hPad-r, y+h/2+6, w-hPad+r-7, 2*r-1);
       }
       break;
     case Machine.TM1:
     case Machine.TM2:
       if (isAccept() && isValid) {
         g.setColor(Color.red);
         g.drawString(ACCEPTED_STR, x+hPad+r+8, y+vPad+fm.getAscent()/2);
       }
       if (a.type == Machine.TM1) 
         paintStringForTM(g, theStack, theString, x+hPad-r, y+h/2+6, w-hPad+r-7, 2*r-2);
       else {
         paintStringForTM(g, theStack, theString, x+hPad-r, y+vPad+r+6, w-hPad+r-7, 2*r-3);
         paintStringForTM(g, secondStack, secondString, x+hPad-r, y+vPad+3*r+10, w-hPad+r-7, 2*r-3);
       }  
       break;
   }
 }	// end of method paint

  /**
   * Paints a given string in a given rectangle.
   * It is called from paint.
   */
  private void paintStringForAutomaton(Graphics g,
  		 String str, int x, int y, int width, int height) {
    FontMetrics	fm = null;
    int		pad = 3;
    int		l = str.length();

    try {
      fm = g.getFontMetrics();
    }
    catch (Exception e) {
      g.setFont(new Font("System", Font.PLAIN, 12));
      fm = g.getFontMetrics();
    }
    int		nChars = (int) ((width - pad -2) / fm.charWidth('Z'));

    if (l <= nChars) {
      g.setColor(Params._c_backcolor);
      g.fillRect(x, y, width, height);
      g.setColor(Params._c_forecolor);
      g.drawRect(x, y, width, height);
    } else {
      int[] xpoints = {x, x+width, x+width-2, x+width, x+width-2, x+width, x, x};
      int[] ypoints = {y, y, y+height/4, y+height/2, y+3*height/4, y+height, y+height, y};
      g.setColor(Params._c_backcolor);
      g.fillPolygon(xpoints, ypoints, 8);
      g.setColor(Params._c_forecolor);
      g.drawPolygon(xpoints, ypoints, 8);
    } 

    String whatToPrint;

    if (ACCEPTED_STR.equals(str)) {
      g.setColor(Color.red);
      g.drawString(ACCEPTED_STR, x+pad, y+height/2+fm.getAscent()/2);
    } else {
      if (l > nChars)
        whatToPrint = str.substring(0, nChars);
      else
        whatToPrint = str;
      g.drawString(whatToPrint, x+pad, y+height/2+fm.getAscent()/2);
    }
  }  // end of method paintStringForAutomaton

  /**
   * Paints a given string in a given rectangle.
   * It is called from paint.
   */
  private void paintStringForTM(Graphics g,  String left,
  		 String right, int x, int y, int width, int height) {
    FontMetrics	fm = null;
    String	l = "...BB"+left;
    String	r = right+"BB...";

    int		middle = x + (int) (width / 2);
    int		l1 = l.length();
    int		l2 = r.length();

    try {
      fm = g.getFontMetrics();
    }
    catch (Exception e) {
      g.setFont(new Font("System", Font.PLAIN, 12));
      fm = g.getFontMetrics();
    }
    int		nChars = (int) ((width/2 - 2) / fm.charWidth('Z'));
   
    int[] xpoints = {x, x+width, x+width-2, x+width, x+width-2, x+width, x, x+2, x, x+2, x};
    int[] ypoints = {y, y, y+height/4, y+height/2, y+3*height/4, y+height, y+height, y+3*height/4, y+height/2, y+height/4, y};
    g.setColor(Params._c_backcolor);
    g.fillPolygon(xpoints, ypoints, 11);
    g.setColor(Params._c_forecolor);
    g.drawPolygon(xpoints, ypoints, 11);
    drawArrow(g, middle+2, y, height);
   
    String whatToPrint;

    if (l1 > nChars)
        whatToPrint = l.substring(l1-nChars);
      else
        whatToPrint = l;
    int offset = width/2 - fm.stringWidth(whatToPrint);
    g.drawString(whatToPrint, x+offset, y+height/2+fm.getAscent()/2);
    
    if (l2 > nChars)
       whatToPrint = r.substring(0, nChars);
     else
       whatToPrint = r;
     g.drawString(whatToPrint, middle, y+height/2+fm.getAscent()/2);  
  }     // end of method paintStringForTM

  /**
   * Paints the read/write head for Turing machines.
   */
  protected void drawArrow(Graphics g, int x, int y, int distance) {
    g.drawLine(x, y-5, x, y);
    g.drawLine(x-2, y-2, x, y);
    g.drawLine(x+2, y-2, x, y); 
    g.drawLine(x, y+distance+5, x, y+distance);
    g.drawLine(x-2, y+distance+2, x, y+distance);
    g.drawLine(x+2, y+distance+2, x, y+distance);
  }

}	// end of class Configuration.
