
/* 
 * TuringTapeArea: Draws and animates the ticker tape
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.event.*;

public class TuringTapeArea extends JPanel  {
    boolean machineIsRunning = false;
    boolean tapeInit = false;
    int cellWidth = 40;
    double newLeftCellWidth = 0;
    double newRightCellWidth = 0;
    int cellHeight = 40;
    int fontSize = 20;
    int shift, filler;
    int leftMostCell = 0, rightMostCell = 0;
    Dimension areaSize;

    int origin = 0;

    boolean grow = false;
    boolean newLeftCell = false, newRightCell = false;
    
    double animationInc;
    Tape tape;
    
    Color currentTextColor = Color.black;
    
    final int LEFT_SHIFT = 0;
    final int RIGHT_SHIFT = 1;
  
    final int ADD_DELAY = 10;
  
    final int FONT_STYLE = Font.BOLD;
    final String FONT_NAME = "Monotype";
    final Color BG_COLOR = new Color(200,200,200);
    final Color DEFAULT_CELL_COLOR = Color.white;
    final Color CURRENT_CELL_COLOR = Color.orange;
    final Color CELL_INDEX_COLOR = new Color(0, 130, 0);

    final double beginTab = 0.0;
    final double INITIAL_OFFSET = -1.5;
    double offset = INITIAL_OFFSET; 
    public final Color DEFAULT_TEXT_COLOR = Color.black;
    
    public void setTape(Tape t) {
	tape = t;
    }

    public void resetColors() {
	currentTextColor = Color.black;
    }

    public void removePartialCells() {
	newLeftCellWidth = 0;
	newRightCellWidth = 0;
    }
    
    public void paintComponent(Graphics g) {
	int i;
	int yAlign;     //used for vertical centering of tape
	int tapeLength;  //length in cells, not pixels
	int tapeEndX[] = new int[7];
	int tapeEndY[] = new int[7];
	int fontScalingFactor;
	int stringWidth, stringHeight;
	int index = 0, startAt, cellsToDraw, currentPosition;
	int drawPos;
	String symbol;
	
	FontRenderContext DEFAULT_FONT_RENDER_CONTEXT =
	    new FontRenderContext(null, false, false);
	
	Rectangle2D charBounds;
	
	super.paintComponent(g);
	
	if(tape == null) {
	    return;
	}

	g.setColor(BG_COLOR);
	g.fillRect(0,0,getWidth(), getHeight());

	tapeLength = tape.getSize();
	
	if(areaSize != null) {
	    if(areaSize.width != getSize().width || areaSize.height != getSize().height) { 
		tapeInit = false;
	    }
	}

	areaSize = getSize();
	yAlign = areaSize.height/2;

	cellWidth = getHeight() - 2;
	if(cellWidth > 40) {
	    cellWidth = 40;
	}
	cellHeight = cellWidth;
	fontSize = cellWidth / 2;
	
	if(newLeftCell) {
	    startAt = 1;
	    cellsToDraw = tapeLength - 1;
	    currentPosition = 0;
	} else if(newRightCell) {
	    startAt = 0;
	    cellsToDraw = tapeLength - 1;
	    currentPosition = tapeLength - 2;
	} else {
	    startAt = 0;
	    cellsToDraw = tapeLength;
	    currentPosition = tape.getCurrentPosition();
	}
	
	currentPosition = tape.getCurrentPosition();
	
	if(!tapeInit) {
	    for(filler = 0; cellWidth * (1 + 2*filler) <= areaSize.width; filler++)
	       ;
	    filler += 1;
	    leftMostCell = currentPosition - filler;
	    rightMostCell = leftMostCell + 2*filler;
	    while(-leftMostCell < rightMostCell - tapeLength - 1 && 0 < rightMostCell - tapeLength - 1) {
		leftMostCell--;
		rightMostCell--;
	    }
	    while(-leftMostCell > rightMostCell - tapeLength + 1 && leftMostCell < 0) {
		leftMostCell++;
		rightMostCell++;
	    }
	}


	if(grow) {
	    if(currentPosition - 3 < leftMostCell) {
		while(currentPosition < leftMostCell + 3) {
		    leftMostCell--;
		    rightMostCell--;
		}
	    } else {
		while(currentPosition > rightMostCell - 3) {
		    leftMostCell++;
		    rightMostCell++;
		}
	    }
	    grow = false;
	}
	

	if(origin != tape.getOrigin()) {
	    leftMostCell++;
	    rightMostCell++;
	}
	
	origin = tape.getOrigin();

	tapeInit = true;

	//draw the tape cells

	for(drawPos = 0, i = leftMostCell; i <= rightMostCell; i++, drawPos++) {

	    if(i >= 0 && i < tapeLength)
		symbol = (String)tape.getSymbolAt(i);
	    else
		symbol = tape.getFillSymbol();
	    
	    //indicate current tape cell by coloring it
	    
	    if(i == currentPosition) {
		g.setColor(CURRENT_CELL_COLOR);
	    } else {
		g.setColor(DEFAULT_CELL_COLOR);
	    }
	    
	    g.fillRect((int)((newLeftCellWidth + drawPos + offset) * cellWidth), yAlign - cellHeight/2, 
		       cellWidth, cellHeight);
	    
	    g.setColor(Color.black);
	    g.drawRect((int)((newLeftCellWidth + drawPos + offset) * cellWidth), yAlign - cellHeight/2, 
	           cellWidth, cellHeight);			
	    
	    
	    //determine font size and position of symbol
	    
	    charBounds = g.getFont().getStringBounds(symbol, 
						     DEFAULT_FONT_RENDER_CONTEXT);
	    
	    stringWidth  = (int)Math.ceil(charBounds.getWidth());
	    stringHeight = (int)Math.ceil(charBounds.getHeight());
	    

	    //if symbol is multi-character, font may be adjusted so it will fit
	    fontScalingFactor = (int)Math.ceil((double)stringWidth/(double)cellWidth);
	    g.setFont(new Font(FONT_NAME, FONT_STYLE, fontSize/fontScalingFactor));
	    
	    charBounds = g.getFont().getStringBounds(symbol, 
						     DEFAULT_FONT_RENDER_CONTEXT);
	    
	    stringWidth  = (int)Math.ceil(charBounds.getWidth());
	    stringHeight = (int)Math.ceil(charBounds.getHeight());
	    
	    if(i == tape.getCurrentPosition()) {
		g.setColor(currentTextColor);
	    }
	    
	    
	    //draw the symbol in the tape cell
	    g.drawString(symbol, 
	     (int)((newLeftCellWidth + drawPos + 0.5 + offset) * cellWidth - (stringWidth/2)), 
	    	 (int)(yAlign + (stringHeight/4)));

	    g.setFont(new Font(FONT_NAME, Font.PLAIN, fontSize/(2 * fontScalingFactor)));
	    g.setColor(CELL_INDEX_COLOR);

	    g.drawString(Integer.toString(i - origin), 
	    	 (int)((newLeftCellWidth + drawPos + 0.3 + offset) * cellWidth - (stringWidth/2)), 
	    	 (int)(yAlign + 0.45 * cellHeight));
	   
	    index++;
	}
	
	//draw new cell on right if a cell is currently being added
	g.setColor(DEFAULT_CELL_COLOR);
	g.fillRect((int)((newLeftCellWidth + drawPos + offset) * cellWidth), yAlign - cellHeight/2, 
	   (int)(newRightCellWidth * cellWidth), cellHeight);
	g.setColor(Color.black);
	//g.drawRect((int)((newLeftCellWidth + i + beginTab) * cellWidth), yAlign - cellHeight/2, 
	//   (int)(newRightCellWidth * cellWidth), cellHeight);	
	
	if(newRightCell) {
	    charBounds = g.getFont().getStringBounds(Tape.getFillSymbol(), 
						     DEFAULT_FONT_RENDER_CONTEXT);
	    
	    stringWidth  = (int)Math.ceil(charBounds.getWidth());
	    stringHeight = (int)Math.ceil(charBounds.getHeight());
	    
	    fontScalingFactor = (int)Math.ceil((double)stringWidth/(double)cellWidth);
	    g.setFont(new Font(FONT_NAME, FONT_STYLE, fontSize/fontScalingFactor));
	    
	    charBounds = g.getFont().getStringBounds(Tape.getFillSymbol(), 
						     DEFAULT_FONT_RENDER_CONTEXT);
	    
	    stringWidth  = (int)Math.ceil(charBounds.getWidth());
	    stringHeight = (int)Math.ceil(charBounds.getHeight());
	    
	    //draw the symbol in the tape cell
	    g.drawString(Tape.getFillSymbol(), 
			 (int)((newLeftCellWidth + i + 0.5 + beginTab) * cellWidth - (stringWidth/2)), 
			 (int)(yAlign + (stringHeight/4)));

	    g.setFont(new Font(FONT_NAME, Font.PLAIN, fontSize/(2 * fontScalingFactor)));
	    g.setColor(CELL_INDEX_COLOR);

	    g.drawString(Integer.toString(index), 
	    	 (int)((newLeftCellWidth + i + 0.3 + beginTab) * cellWidth - (stringWidth/2)), 
	    	 (int)(yAlign + 0.45 * cellHeight));
	}
    }
}

