/**
 *  TEDParser.java
 *
 *  This class will take the input from the GUI, which is passed through from the controller, 
 *  and parse it for correctness.  If all the input passes the correctness tests,
 *	the data is then loaded into a new Turing Machine. 
 *
 *  Copyright 2005 by Ben Pfistner and Jason Emmons
 *
 */
					
import java.util.*;
//import java.lang.*;
import javax.swing.*;

public class TEDParser
{
	//These variables are used during the parsing of the input.
	
	int endIndex;
	StringBuffer finalAlphabet;
	
	//These variables are all passed to the TM if and when it is created.
	
	boolean setInMiddle;
	StringBuffer finalTape;
	Vector finalRules;
	
	//Alphabet and Tape Errors
	private static final int EMPTY_ALPHABET = 0;
	private static final int EXPECTED_LP = 1;
	private static final int UNEXPECTED_RP = 2;
	private static final int NO_RP = 3;
	private static final int CHAR_NOT_IN_ALPHABET = 4;
	private static final int REPEATED_CHARACTER = 5;
	private static final int RESERVED_CHARACTER = 6;
	private static final int UNEXPECTED_EOA = 18;
	private static final int UNEXPECTED_EOT = 19;

	//Rule Errors
	private static final int EXPECTED_COMMA = 7;
	private static final int EXPECTED_LB = 8;
	private static final int INVALID_START_STATE = 9;
	private static final int R_EXPECTED_LP = 10;
	private static final int READ_CHAR_ERROR = 11;
	private static final int WRITE_CHAR_ERROR = 12;
	private static final int FINAL_STATE_ERROR = 13;
	private static final int EXPECTED_RP = 14;
	private static final int INVALID_DIRECTION = 15;
	private static final int UNEXPECTED_CHAR = 16;
	private static final int UNEXPECTED_EOF = 17;
	
	
	private static final String[] ERROR_MESSAGES = 
	{
		"There is no Alphabet for TED to parse.  Please \n" +
        	"enter an Alphabet and push the VALIDATE button again.\n",
        
        "The first character in the Alphabet must be the left \n" +
        	"parenthesis '('.  Please change the Alphabet and push the VALIDATE \n" +
        	"button again.",
        		
        "The right parenthesis ')' denotes the end of the alphabet.\n" +
        	"  This character has been encountered in an unexpected place.  Please\n" +
        	" alter your Alphabet and push the VALIDATE button again.\n",
        
        "The right parenthesis ')' denotes the end of the \n" +
        	"alphabet.  This character is missing.  Please alter your Alphabet \n" +
        	"and push the VALIDATE button again.\n",
        		
        "Every Character in the tape must also be in the alphabet.\n" + 
       		"  Please either remove the character from the tape or alter the alphabet.\n",
        
        "A character can only appear once in the alphabet.  Please remove the character from the alphabet.\n",
        		
        "This is a reserved character.  Please remove the character from the Alphabet.\n",
		
		"There was a comma expected.  Commas separate separate \n" + 
    	    "entries in a given input as well as individual rules.\n",
    		    
    	"The first character in a rule set is the left brace.  Please alter the tape and press the \n" +
    		"VALIDATE button again.\n",
        
        "The first item in a 5-tuple is the current state.  This character must be an integer.  Please change the \n" + 
        	"character from the tape or alter the alphabet and press the VALIDATE button again.\n",
        
        "The first character of a single Rule must be the left \n" +
        	"parenthesis '('.  Please change the appropriate rule and push the \n" + 
        	"VALIDATE button again.",
        
        "This character is not in your Alphabet.  TED can only \n" +
        	"read characters from the Alphabet.  Please either alter the Alphabet or \n" +
        	"the appropriate rule and the push the VALIDATE button again.\n",
        				
        "The character you are attempting to write is not in \n" +
        	"your alphabet.  Please either alter the Alphabet or the appropriate \n" +
        	"rule and the push the VALIDATE button again. \n",
        		
        "The Final State is not an integer  Please alter the appropriate rule and the push the \n" +
        	"VALIDATE button again.\n",
        		
        "Each rule must end with a right parenthesis.  Please either alter the Alphabet or the \n" +
        	"appropriate rule and the push the VALIDATE button again.",
        		
        "The directional part of the 5-tuple must be a capital L or R.  Please alter the " +
        	"appropriate rule and the push the VALIDATE button again.",
        	
        "The character found was not expected, please alter the Alphabet or the appropriate rule and push " +
        	"the VALIDATE button again.",
        	
        "The rules input has unexpectedly ended.  Please ensure that all of your input is entered into the text field.\n",
        
        "The alphabet input has unexpectedly ended.  Please ensure that all of your input is entered into the text field.\n",
        
        "The tape input has unexpectedly ended.  Please ensure that all of your input is entered into the text field.\n"
		};
	
	/**
	 *  Constructor
	 * 
	 *  @param alphabet: this is the alphabet of the Turing Machine
	 *  @param tapeInput: this is the tape of the Turing Machine    
	 *  @param rules: this is the rules of the Turing Machine
	 *  @param controller: this is the controller class of TED
	 *
	 */
	public boolean validate(StringBuffer alphabet, StringBuffer tapeInput, StringBuffer rules, 
						TEDController controller)
	{
		if(parseAlphabet(alphabet))
		{
			if(parseTapeInput(tapeInput))
			{
		    	if(parseRules(rules))
				{	
					TEDTM theTM = new TEDTM(setInMiddle, finalAlphabet, finalTape, finalRules, controller);
					controller.loadMachine(theTM);
					return true;
	    		}
			}
		}
		
		return false;
	}
	
//* * * Utility Methods * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *// 
	
	/**
	 *  getState
	 *
	 *  This method gets a state out of the input line.  It then returns that substring.
	 *
	 *  @param inputLine: the current working string
	 *
	 *  @return the substring which represents the state taken off of the inputLine. 
	 *
	 */
	public String getState(StringBuffer inputLine)
	{
	    endIndex = 0; //The end of the substring we want to chop off
        	   
	    //While we aren't at the end of the string nor the next character after the state.
	    while(endIndex < (inputLine.length()) &&
	          inputLine.charAt(endIndex) != ',' &&
	          inputLine.charAt(endIndex) != ')')
	    {
	       endIndex++;
	    }
	     
        String theState = inputLine.substring(0, endIndex);
        
        for(int i = 0; i < endIndex; i++)
        {
        	inputLine = inputLine.deleteCharAt(0);
    	}
    	
	    return theState;	
	}
    
	/**
	 *  parseError
	 *
	 *  This method prints a message to the system console when an error is encountered 
	 *  during the parsing of the input.
	 *
	 *  @param reason: the reason TED experienced an error.
	 *  @param fault:   the character which raised the exception.
	 *  @param location: the part of the program in which the error was encountered
	 *  @param errorCode: the constant value in the error message array for the appropriate error message
	 *
	 */    
    public void parseError(String reason, String fault, String location, int errorCode)
    {
       	StringBuffer errorMessage = new StringBuffer("");
       	
       	errorMessage.append("An Exception has been raised while: " + location + "\n\n");
       	
       	errorMessage.append("The type of exception raised is: " + reason + "\n");
       	if(!(fault == null))
       	{
       		errorMessage.append("TED stumbled over this character:" + fault + "\n\n");	
        }
        
        errorMessage.append(ERROR_MESSAGES[errorCode] + "\n");
        		
        JOptionPane.showMessageDialog(null, errorMessage);
    }
   
    /**
	 *  parseRuleError
	 *
	 *  This method prints a message to the system console when an error is encountered 
	 *  during the parsing of the rules.
	 *
	 *  @param reason: the reason TED experienced an error.
	 *  @param fault:   the character which raised the exception.
	 *  @param rule: number of the rule in which TED encountered the error
	 *  @param location: the part of the program in which the error was encountered
	 *  @param errorCode: the constant value in the error message array for the appropriate error message
	 *
	 */
    public void parseRuleError(String reason, String fault, int rule, String location, int errorCode)
    {
       	StringBuffer errorMessage = new StringBuffer("");
       	
       	errorMessage.append("An Exception has been raised while:" + location + "\n");
       	errorMessage.append("TED Faulted on rule number: " + rule + "\n");
       	errorMessage.append("The type of exception raised is: " + reason + "\n");
       	if(!(fault == null))
       	{
       		errorMessage.append("TED stumbled over this character:" + fault + "\n");
        }
        
        errorMessage.append(ERROR_MESSAGES[errorCode]);
        		  
        JOptionPane.showMessageDialog(null, errorMessage);
    }
   
    /**
     *  consumeChar
     *
     *  This method returns the first character of the current string.
     *
     *  @param currentString: this is the string we want to take a character from.
     *  
     *  @return the character consumed by the method
     *
     */
    public char consumeChar(StringBuffer currentString)
    {
    	char temp = currentString.charAt(0);
    	currentString.deleteCharAt(0);
    	return temp;
    }
	
	/** 
	 *	parseAlphabet
	 *
	 *  Parser for the alphabet 
	 *	Makes sure the alphabet is properly formated (x,x,x,x,x,x,x)
	 *	If it is not properly formatted an exception is thrown
	 *	Makes sure there are no doubles and none of TED's special chars
	 *	On either occurence an exception will be thrown
	 *
	 *  @param workingAlphabet: the current string from which we are parsing the alphabet
	 *
	 *  @return whether the method completed successfully
	 *
	 */	
	public boolean parseAlphabet(StringBuffer workingAlphabet)
	{
		//The current character we are looking at.
		char currentChar;
		
		//Characters which have been confirmed as being in the alphabet
	    finalAlphabet = new StringBuffer("");
	    		
		//Get first character and make sure something was typed into the text field	
		try
		{
			currentChar = consumeChar(workingAlphabet);
		}
		catch(Exception e)
		{
		    parseError("Empty Alphabet", null, "Parsing the Alphabet", EMPTY_ALPHABET);
		    return false;
	    }
	    
	    //the alphabet needs to begin with a (
		if(currentChar == '(')
		{
			try
			{
				//get next character
				currentChar = consumeChar(workingAlphabet);
			}
			catch(Exception e)
			{
				parseError("Unexpected End of Input", null, "Parsing the Alphabet", UNEXPECTED_EOA);
				return false;	
			}
	    }
		else
		{
			parseError("Expected (", ""+currentChar, "Parsing the Alphabet", EXPECTED_LP);
			return false;
	    }

	    //This is not the proper place for the ending )
		if(currentChar == ')')
		{
			parseError("Unexpected )", ")", "Parsing the Alphabet", UNEXPECTED_RP);
			return false;
	    }

	    //While we aren't at the end
		while(currentChar != ')')
	    {
			boolean nullChar = false;
			//If the character isn't reserved or already entered in put it in finalAlphabet
			if (currentChar != '@' && currentChar != '_' && 
				currentChar != '(' && currentChar != ')') 
			{
				if(finalAlphabet.indexOf(currentChar+"") == -1)
	        	{
	      			finalAlphabet.append(""+currentChar);
					try
					{
						//get next character
						currentChar = consumeChar(workingAlphabet);
			System.out.println("3, "+currentChar);
					}
					catch(Exception e)
					{
						break;	
					}
	        	}
	        	else
	        	{
	        		parseError("Repeated Character", "" + currentChar, "Parsing the Alphabet", REPEATED_CHARACTER);
					return false;	
	        	}
	     	}
	     	else
	     	{
	     		parseError("Reserved Character", "" + currentChar, "Parsing the Alphabet", RESERVED_CHARACTER);
				return false;	
	     	}

	        //if something is actually there.
			if(nullChar)
	        {
				parseError("No )", null, "Parsing the Alphabet", NO_RP);
				return false;
	        }
	        
			//we are either done or have a separator, anything else causes an error.
			if(currentChar != ',' && currentChar != ')')
			{
				parseError("Expected , or )", null, "Parsing the Alphabet", UNEXPECTED_CHAR);
				return false;
	        }
			
			//consume the separator and make sure it is there
			if(currentChar == ',')
	        {
				try
				{
					//get next character
					currentChar = consumeChar(workingAlphabet);
					if(currentChar == ')')
	        		{
	        			parseError("Unexpected Right Parenthese", null, "Parsing the Alphabet", UNEXPECTED_RP);
						return false;
					}
				}
				catch(Exception e)
				{
					parseError("Unexpected End of Input", null, "Parsing the Alphabet", UNEXPECTED_EOA);
					return false;	
				}
	        }
		}//End of While
		
		finalAlphabet.append('_');
		return true;
    }
    
	/** 
	 *	parseTapeInput
	 *	
	 *  Makes sure the tape is properly formatted "@abbba" or "abbba"
	 *	if it is not properly formatted an exception is thrown
	 *	Makes sure all the characters being put on the tape are members of the alphabet set.
	 *	If a character is not a member of alphabet an error will be raised.
	 *  
	 *
	 *  @param workingTapeInput: the input from the textField
	 *
	 *  @return whether or not we succeeded
	 *
	 */
	public boolean parseTapeInput(StringBuffer workingTapeInput)
	{
		//The current character off of the input
		char currentChar;
		
		//the final tape input
		finalTape = new StringBuffer();
			
		boolean whileControl = true;
		
		//Read the first character
		try
		{
			currentChar = consumeChar(workingTapeInput);
		}
		catch(Exception e)
		{
			parseError("No Input Tape", null, "Parsing Tape", UNEXPECTED_EOT);
		    return false;	
		}
		
		
			
		//This is a reserved character for TED.  It lets us know to start the input in the 
		//	middle of the tape.  Then we get the next character.
		if(currentChar == '@')
		{ 
			setInMiddle = true;
			
			try
			{
				currentChar = consumeChar(workingTapeInput);
			}
			catch(Exception e)
			{
				whileControl = false;	
			}
		}
		else
		{
			setInMiddle = false;	
		}
		
		//While there are still characters to parse, and they are part of the alphabet
		//or blanks, add them to what will go on the tape.
		while(whileControl)
		{
			if(finalAlphabet.indexOf("" + currentChar) == -1)
			{
				parseError("Character not in Alphabet", "" + currentChar, "Parsing Tape", CHAR_NOT_IN_ALPHABET);
		    	return false;
		    }
			else 
			{
				finalTape.append(currentChar);
			}
			
			//get next character
			try
			{
				currentChar = consumeChar(workingTapeInput);
			}
			catch(Exception e)
			{
				whileControl = false;	
			}
			
	    }//End of While
    
    	return true;
    }
	
	/**
	 *  parseRules
	 *
	 *	Parser for rule set
	 *	Makes sure the rule set is properly formated {(a,b,c,d,e),(a,b,c,d,e)}
	 *  and that the first char of a rule is "("
	 *	If it is not properly formatted an exception is thrown.
	 *
	 *  @param rules: the text input from the textfield.
	 *	@param finalAlphabet: the read/write characters can only come from the
	 *                                 Alphabet so we must check each read/write character.
	 *
	 *  @return boolean: whether the process completed successfully.
	 *
	 */
	public boolean parseRules(StringBuffer workingRules)
    {
    	    	
    	//the current character that we are working with
		char currentChar;
		
		//this vector will contain the list of 5Tuples
		finalRules = new Vector();
		
		//which specific rule we are parsing
		int rulenum = 0;
		
		try
		{
			currentChar = consumeChar(workingRules);
		}
		catch(Exception e)
		{
			parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
			return false;	
		}
		
		if(currentChar != '{')
		{
			parseRuleError("Expected {", "" + currentChar, rulenum, "Parsing Rules", EXPECTED_LB);
			return false;
		}

		try
		{
			currentChar = consumeChar(workingRules);
		}
		catch(Exception e)
		{
			parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
			return false;	
		}

		while(currentChar != '}')
		{	
		    //check for open parantheses	
			TED5tuple currentTuple = new TED5tuple();

			if(currentChar != '(')
			{
				parseError("Expected (", "" + currentChar, "Parsing the Rules", R_EXPECTED_LP);
				return false;
			}	
	    	
	    	
	    	//Set initial state
	    	int initialState;
	    	
	    	try
	    	{
	    		initialState = Integer.parseInt(getState(workingRules));
	    	}
	    	catch(Exception finalError)
	    	{
	    		parseRuleError("Invalid Initial State", null, rulenum, "Parsing the Rules", INVALID_START_STATE);
				return false;
			}

	    	//Get the initial state
			currentTuple.setInitialState("" + initialState);

			try
			{
				currentChar = consumeChar(workingRules);
			}
			catch(Exception e)
			{
				parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
				return false;	
			}
			
	    	//check to see if there is a comma
			if(currentChar != ',')
			{
				parseRuleError("Expected ,", "" + currentChar, rulenum, "Parsing the Rules", EXPECTED_COMMA);
				return false;
			}
	    
	    	try
			{
				currentChar = consumeChar(workingRules);
			}
			catch(Exception e)
			{
				parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
				return false;	
			}
	    
	    	//Check that the character that is being read is a member of alphabet
			if(finalAlphabet.indexOf("" + currentChar) != -1)
			{ 
				currentTuple.setReadChar(currentChar);
				
				try
				{
					currentChar = consumeChar(workingRules);
				}
				catch(Exception e)
				{
					parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
					return false;	
				}
			}
			else
			{
				parseRuleError("Read Character Error", "" + currentChar, rulenum, "Parsing the Rules", READ_CHAR_ERROR);
				return false;
			}
				
	    	//consume the comma
			if(currentChar == ',')
			{ 
				try
				{
					currentChar = consumeChar(workingRules);
				}
				catch(Exception e)
				{
					parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
					return false;	
				}
			}
			else
			{
				parseRuleError("Expected ,", "" + currentChar, rulenum, "Parsing the Rules", EXPECTED_COMMA);
				return false;
			}	
	
			//write character
			if(finalAlphabet.indexOf("" + currentChar) != -1)
			{ 
				currentTuple.setWriteChar(currentChar);
				
				try
				{
					currentChar = consumeChar(workingRules);
				}
				catch(Exception e)
				{
					parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
					return false;	
				}
			}
			else
			{
				parseRuleError("Write Character Error", "" + currentChar, rulenum, "Parsing the Rules", WRITE_CHAR_ERROR);
				return false;	
			}
	    
	    	//consume the comma
			if(currentChar == ',')
			{ 
				try
				{
					currentChar = Character.toUpperCase(consumeChar(workingRules));
				}
				catch(Exception e)
				{
					parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
					return false;		
				}
			}
			else
			{
				parseRuleError("Expected ,", "" + currentChar, rulenum, "Parsing the Rules", EXPECTED_COMMA);
				return false;
			}
	    
	    	//Consume direction character
			if((currentChar == 'L') || (currentChar == 'R'))
			{
				currentTuple.setDirection(currentChar);
				
				try
				{
					currentChar = consumeChar(workingRules);
				}
				catch(Exception e)
				{
					parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
					return false;	
				}
			}
			else
			{
				parseRuleError("Invalid Direction", "" + currentChar, rulenum, "Parsing the Rules", INVALID_DIRECTION);
				return false;
			}	
	
		    //Check to see if there was a comma
			if(currentChar != ',')	
			{
				parseRuleError("Expected ,", "" + currentChar, rulenum, "Parsing the Rules", EXPECTED_COMMA);
				return false;
			}
	    
	    	//Set final state
	    	int finalState;
	    	
	    	try
	    	{
	    		finalState = Integer.parseInt(getState(workingRules));
	    	}
	    	catch(Exception finalError)
	    	{
	    		parseRuleError("Invalid Final State", null, rulenum, "Parsing the Rules", FINAL_STATE_ERROR);
				
				return false;
			}
	    	
			currentTuple.setFinalState(""+ finalState);
		
			//get character after final state
			try
			{
				currentChar = consumeChar(workingRules);
			}
			catch(Exception e)
			{
				parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
				return false;	
			}
	
			if(currentChar != ')')
			{ 	
				parseRuleError("Expected )", "" + currentChar, rulenum, "Parsing the Rules", EXPECTED_RP);
				return false;
			}
			
			try
			{
				currentChar = consumeChar(workingRules);
			}
			catch(Exception e)
			{
				parseRuleError("Unexpected End of Input", "" + currentChar, rulenum, "Parsing the Rules", UNEXPECTED_EOF);
				return false;
			}
			
			//check the last character
			if(currentChar == ',')
			{  
				try
				{
					currentChar = consumeChar(workingRules);
				}
				catch(Exception e)
				{
					parseRuleError("Unexpected End of Input", null, rulenum, "Parsing Rules", UNEXPECTED_EOF);
					return false;	
				}
			}
			else if(currentChar == '}')
			{	
				finalRules.add(currentTuple);
				return true;	
			}
			else
			{
				parseRuleError("Unexpected Character", "" + currentChar, rulenum, "Parsing the Rules", UNEXPECTED_CHAR);
				return false;
			}
			
			finalRules.add(currentTuple);
						
			rulenum++;
		} //end of while
		
		
		
		return true;
    }
    
    public TEDParser()
    {}
    
    
     public static void main(String [] args) 
     { 
           
          //A = Alphabet  
          //B = Tape  
          //C = Rules 
           
          StringBuffer A = new StringBuffer().append("(1,0,2)"); 
          StringBuffer B = new StringBuffer().append("101_101"); 
          StringBuffer C = new StringBuffer().append("{(5,1,1,L,10),(6,0,0,R,2)}"); 
 
          //TEDController controller = new TEDController(); 
          TEDParser foo = new TEDParser(); 
          
          foo.parseAlphabet(A);
          foo.parseTapeInput(B);
          foo.parseRules(C);
     }
}	