/**
 *  TEDTM.java
 *  
 *  This class will represent the actual Turing Machine object.  It is in this class that
 *  the actual running fo the machine takes place.
 *
 *  (c)2005 by Ben Pfistner and Jason Emmons
 *
 */

//import java.lang.*;
import java.io.*;
import java.util.*;
import javax.swing.*;

public class TEDTM implements Runnable
{
	//This marks where the tape head is
	int tapehead;

	//This is the tape
	char theTape[] = new char[4096];
	
	//Ths map stores all the states associated with this Turing Machine
	TreeMap stateMap;
	
	//next value added by Brinton on 11/26/2005
	int step;
	
	//Whether or not the user has pushed the halt button
	boolean halt;

	//The stateMap key to the current state
	String currentStateKey;
	
	//The current tuple being executed by TED
	TED5tuple currentTuple;
	
	//This is the controller for our program
	TEDController controller;

	StringBuffer alphabet;

	/**
	 *  Constructor
	 *
	 *  This makes a new Turing Machine.
	 *
	 *  @param setInMiddle whether or not the tape should start in the middle of the tape.
	 *  @param finalTape the parsed and validated input that was given to be the tape.
	 *  @param finalRules
	 *
	 */
	public TEDTM(boolean setInMiddle, StringBuffer alphabet, StringBuffer finalTape, Vector finalRules, TEDController controller)
	{
		
		this.controller = controller;
	
		this.alphabet = alphabet;
		
		//If we need to, set the tapehead to the middle of the tape
		if(setInMiddle)
		{
			tapehead = 2048;
		}
		else
		{
			tapehead = 0;	
		}
		
		//initialize the tape
		initializeTape(finalTape);		
		
		//initialize the rules
		initializeRules(finalRules);
	}
	
	/**
	 *  initializeTape
	 *
	 *  Sets up the tape for a new Turing Machine.
	 *
	 *  @param startingTape the input that was given to us in the constructor
	 *
	 */
	public void initializeTape(StringBuffer startingTape)
	{
		//Start the tape with all blanks
		for(int j = 0; j < 4096; j++)
		{
			theTape[j] = '_';	
		}
		
		//Fill the tape with any initial input
		for(int i = tapehead; i <= (startingTape.length() - 1 + tapehead); i++)
		{
			theTape[i] = startingTape.charAt(i - tapehead);
		}
	}
	
	/**
	 *  initializeRules
	 *
	 *  Sets up the rules for a new Turing Machine. 
	 *
	 *  @param startingTape the input that was given to us in the constructor
	 *
	 */
	public void initializeRules(Vector finalRules)
	{
		//Initialize the stateMap
		stateMap = new TreeMap();
		
		//This gets the starting state.
		currentStateKey = ((TED5tuple)finalRules.get(0)).getInitialState();
		
		//While we still have 5tuples to get
		for(int i = 0; i < finalRules.size(); i++)
		{
			//Get the current 5tuple
			TED5tuple currentTuple = (TED5tuple)(finalRules.get(i));

			//If there isn't a state for it create one
			//Else just add it to the appropriate state
			if(stateMap.get(currentTuple.getInitialState()) == null)
			{
				TEDState newState = new TEDState();
				stateMap.put(currentTuple.getInitialState(), newState);
				newState.addTuple(currentTuple);
			}
			else
			{
				TEDState theState = (TEDState)(stateMap.get(currentTuple.getInitialState()));
				theState.addTuple(currentTuple);
			}
			
			//Check to make sure there is a state for the final state
			if(stateMap.get(currentTuple.getFinalState()) == null)
			{
				TEDState endState = new TEDState();
				stateMap.put(currentTuple.getFinalState(), endState);
			}
		}//end for
	}
	
	/**
	 *  getTapeHeadPos
	 *
	 *  Returns the current position of the tapehead.
	 *
	 *  @return the value of the tapehead.
	 */
	public int getTapeHeadPos()
	{
		return tapehead;
	}
	
	boolean stepping;
	boolean running;
	
	/**
	 *  stepSpeed
	 *
	 *  Runs the Turing Machine at step speed, only one transition.
	 *
	 */
	public void stepSpeed()
	{
		controller.gui.uncarat(tapehead);
		stepping = true;
		runTransition();
		stepping = false;		
		if(currentTuple != null)
		{
			controller.gui.carat(tapehead);
			controller.updateRuleDisplay(currentTuple.toString());
			controller.updateStepDisplay(step);
			controller.updateStatusBox(currentTuple);
		}
	}
	
	/**
	 *  run
	 *
	 *  Runs the Turing Machine at run speed, about 1 transition 
	 *  per second until we halt, or the user pushes the halt button.
	 *
	 */
	public void run()
	{
		halt = false;
		step = 0;
		while(!halt)
		{
			step++;
			
			controller.gui.uncarat(tapehead);
			running = true;
			runTransition();
			running = false;
			if(currentTuple != null)
			{
				controller.gui.carat(tapehead);
				controller.updateRuleDisplay(currentTuple.toString());
				controller.updateStepDisplay(step);
				controller.updateStatusBox(currentTuple);
			}
			else
			{
				break;	
			}
			
			try
			{
				Thread.sleep(1000);
			}
			catch(Exception e)
			{
				
			}	
		}
	}
	
	/**
	 *  ludicrousSpeed
	 *
	 *  Runs the Turing Machine at ludicrous speed.  If the Turing Machine executes 10000 
	 *  transitions or the user halts, then the Turing Machine will stop execution.
	 *
	 */
	public void ludicrousSpeed(PrintWriter printOut) throws IOException
	{
		halt = false;
		int numberOfTransitions = 0;
		
		printOut.println("The Initial State of the Tape");
		printOut.println("");
		printOut.println("===============================================================");
		printOut.println("");
		
		for(int i = 0; i < 4096; i++)
		{
			printOut.print("" + theTape[i]);
		}
		
		printOut.println("");
		
		while(!halt && (numberOfTransitions < 10000))
		{
			runTransition();
			
			if(currentTuple != null)
			{	
				numberOfTransitions++;
				printOut.println("Transition Number:"+ numberOfTransitions + 
					": " + currentTuple.toString());
			}
			else
			{
				break;	
			}
		}
		
		printOut.println("");
		printOut.println("===============================================================");
		printOut.println("");
		printOut.println("The Final State of the Tape");
		printOut.println("");
		printOut.println("===============================================================");
		printOut.println("");
		
		for(int i = 0; i < 4096; i++)
		{
			printOut.print("" + theTape[i]);
		}
		
		printOut.println("");
		printOut.println("===============================================================");
		printOut.println("");
		
		printOut.close();
		
		JOptionPane.showMessageDialog(null, "Ludicrous Speed Report Complete (LSRC)");
	}
	
	/**
	 *  runTransition
	 *
	 *  This will execute one transition on the current Turing Machine and 
	 *  set itself up to run the next transition.  
	 *
	 */
	public void runTransition()
	{
		char readChar = theTape[tapehead];
		
		TEDState currentState = (TEDState)(stateMap.get(currentStateKey));
		
		//Get the appropriate 5tuple based on the current state and the 
		//  character read at the tape head.
		currentTuple = currentState.getTuple(readChar);
	
		//If there is an appropriate 5tuple then execute the transition;
		//Else halt.
		if(currentTuple != null)
		{
			//Write the appropriate character to the tape
			theTape[tapehead] = currentTuple.getWriteChar();

			if(stepping || running)
			{
				controller.updateTape(tapehead);
			}	
			
			//Move the tapehead
			if(currentTuple.getDirection() == 'L')
			{
				if(!((tapehead-1) < 0))
				{
					tapehead--;	
				}
				else
				{
					halt = true;
					JOptionPane.showMessageDialog(null, "We ran off the left end of the tape.");
					return;	
				}
			}
			else
			{
				if(!((tapehead+1) > 4095))
				{
					tapehead++;	
				}
				else
				{
					halt = true;
					JOptionPane.showMessageDialog(null, "We ran off the right end of the tape.");	
					return;
				}
			}
			
			//Get the next state
			currentStateKey = currentTuple.getFinalState();
		}
		else
		{
			JOptionPane.showMessageDialog(null, "We have halted or crashed.");
			return;
		}
	}
	
	/**
	 *  halt
	 *
	 *  Makes the Turing Machine halt.
	 *
	 */
	public void halt()
	{
		halt = true;	
	}
	
	/**
	 *  getTapeCel
	 *
	 *  @param x: the cell of the tape which we want to get
	 *
	 *  @return the String at the cell[x] of the tape padded with 3 spaces
	 */
	public String getTapeCel(int x)
	{
		String padded = theTape[x] + "    ";
		
		return padded;	
	}
	
	public StringBuffer makeSerialNumber()
	{
		StringBuffer serialNumber = new StringBuffer();
		serialNumber.append("111");
		
		Iterator keyIter = stateMap.keySet().iterator();
			
		
		while(keyIter.hasNext())
		{	
			TEDState currentState = (TEDState)stateMap.get(keyIter.next());
			
			ArrayList tupleList = currentState.getArray();
			Iterator tupleIter = tupleList.iterator();
			
			while(tupleIter.hasNext())
			{			
				TED5tuple tuple = (TED5tuple)(tupleIter.next());
				
				for(int j = 0; j < Integer.parseInt(tuple.getInitialState()); j++)
				{
					serialNumber.append("0");	
				}
				
				serialNumber.append("1");
				
				//the readChar
				for(int k = -1; k < alphabet.indexOf(""+tuple.getReadChar()); k++)
				{
					serialNumber.append("0");
				}
				
				serialNumber.append("1");
				
				//the writeChar
				for(int l = -1; l < alphabet.indexOf(""+tuple.getWriteChar()); l++)
				{
					serialNumber.append("0");	
				}
				
				serialNumber.append("1");
				
				//the direction
				if(tuple.getDirection() == 'L')
				{
					serialNumber.append("0");	
				}
				else
				{
					serialNumber.append("00");	
				}
				
				serialNumber.append("1");
				
				for(int m = 0; m < Integer.parseInt(tuple.getFinalState()); m++)
				{
					serialNumber.append("0");	
				}			
				
				serialNumber.append("11");
			}//end tuple iteration
		}//end state iteration
	
		
		serialNumber.append("1");
		
		return serialNumber;
	}
	
	public TEDTM()
	{}

	public TEDTM copy()
	{
		TEDTM theCopy = new TEDTM();
		
		theCopy.tapehead = this.tapehead;
		theCopy.theTape = new char[4096];
		for(int k = 0; k < 4096; k++)
		{
			theCopy.theTape[k] = this.theTape[k];	
		}
		theCopy.stateMap = this.stateMap;
		theCopy.currentStateKey = this.currentStateKey;
		theCopy.alphabet = this.alphabet;
		theCopy.controller = this.controller;
		
		return theCopy;
	}
}