package unileipzig.isys.neuronviz.kernel;

import java.util.Vector;

public class StreamParser implements DataListener
{
	protected Vector listeners = new Vector();
	protected String [] channelDescription = null;

	static private final int NEURALNET_STATE = 1;
	static private final int NEURON_STATE = 2;
	static private final int LAYER_STATE = 3;
	static private final int CONNECTION_STATE = 4;
	static private final int TERMINATED_STATE = 5;
	static private final int RECEIVE_VALUES_STATE = 6;
	
	private int actualState;
	
	public StreamParser()
	{
		actualState = 0;
	}
	
	/**
	 * is invoked when new data was received and a DataEvent was fired by DataStreamer
	 */	
	public void dataReceived(DataEvent e)
	{
		parseString(e.getDataLine());
	}
	
	/**
	 * parses the string s and fires a ParseEvent when reading a known command
	 */
	public void parseString(String s)
	{
		// is "s" a comment? 
		if(s.startsWith("#") && !s.startsWith("#C") && !s.startsWith("#N"))
		{
			// the string seems to be a comment
			System.out.println(s.substring(1).trim());
			return;
		}
		
		String originalString = s;
		
		// is "s" a command? 
		if(s.startsWith("#N"))
		{
			// the string seems to be a command
			s = s.substring(2).trim().toLowerCase();
		}
		// is "s" a channel description?
		else if(s.startsWith("#C"))
		{
			// the string seems to be a channel
			
			// proceed only if the state machine is in the correct state to receive a channel
			if (actualState == StreamParser.TERMINATED_STATE || actualState == StreamParser.RECEIVE_VALUES_STATE)
			{
				// the channel is at the correct position
				s = s.substring(2).trim().toLowerCase();
				channelDescription = s.split(" ");
				actualState = StreamParser.RECEIVE_VALUES_STATE;
				return;
			}
			else
			{
				// a channel is not allowed in here
				String [] arg = new String [] {originalString};
				ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
				event.setParseMessage("\"" + originalString + "\": A channel is not allowed here. \"#C\" is expected after the end of the net description (\"#N nn_end\").");
				notifyParse(event);
				return;
			}
		}
		else
		{
			// no command or channel detected
			
			// contains the received data line the values for a channel?
			// in that case the machine must be in the correct state
			if (actualState == StreamParser.RECEIVE_VALUES_STATE)
			{
				// channel values have been received
				// fire a ParseEvent
				String [] arg = s.trim().split(" ");
				ParseEvent event = new ParseEvent(this, arg, ParseEvent.RECEIVED_CHANNEL_TYPE);
				event.setParseMessage("channel parsed");
				event.setChannelDescription(channelDescription);
				notifyParse(event);
				return;
			}
			else
			{
				// the string/command is unknown
				// fire a ParseEvent
				String [] arg = new String [] {originalString};
				ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
				event.setParseMessage("\""+originalString+"\" - Can not interpret the received data line. A command should start with \"#N\" or \"#C\".");
				notifyParse(event);
				return;
			}
		}
		
		// parser behaves like a finite state machine
		
		if(s.startsWith("neural_net"))
		{
			// command "create neural net" detected
			
			// proceed only if the actual state is start state (0)
			if (actualState == 0)			
				processNeuralNetState(s.substring(10).trim(), originalString);
			else
			{
				// the command is not allowed in here
				String [] arg = new String [] {originalString};
				ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
				event.setParseMessage("\"" + originalString + "\": The command is not allowed here. \"neural_net\" is expected at the beginning of a description.");
				notifyParse(event);
				return;
			}

		}
		else if(s.startsWith("layer"))
		{
			// command "create layer" detected
			
			// proceed only if the actual state is "neural_net" or "neuron"
			if (actualState == StreamParser.NEURALNET_STATE || actualState == StreamParser.NEURON_STATE)
				processLayerState(s.substring(5).trim(), originalString);
			else
			{
				// the command is not allowed here
				String [] arg = new String [] {originalString};
				ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
				event.setParseMessage("\"" + originalString + "\": The command is not allowed here. \"neuron\" is expected after \"layer\".");
				notifyParse(event);
				return;
			}
		}
		else if (s.startsWith("neuron"))
		{
			// command "create neuron" detected
			
			// proceed only if the actual state is "layer" or "neuron"
			if (actualState == StreamParser.LAYER_STATE || actualState == StreamParser.NEURON_STATE)
				processNeuronState(s.substring(6).trim(), originalString);
			else
			{
				// the command is not allowed here
				String [] arg = new String [] {originalString};
				ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
				event.setParseMessage("\"" + originalString + "\": The command is not allowed here. \"neuron\" is expected after \"layer\" or \"neuron\".");
				notifyParse(event);
				return;
			}
		}
		else if (s.startsWith("connection"))
		{
			// command "create connection" detected
			
			// proceed only if the actual state is "neuron" or "connection"
			if (actualState == StreamParser.NEURON_STATE || actualState == StreamParser.CONNECTION_STATE)
				processConnectionState(s.substring(10).trim(), originalString);
			else
			{
				// the command is not allowed here
				String [] arg = new String [] {originalString};
				ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
				event.setParseMessage("\"" + originalString + "\": The command is not allowed here. \"connection\" is expected after \"neuron\".");
				notifyParse(event);
				return;
			}
		}
		else if (s.startsWith("nn_end"))
		{
			// command "nn_end" detected
			
			// proceed only if the actual state is "connection"
			if (actualState == StreamParser.CONNECTION_STATE)
				processTerminatedState(s.substring(6).trim(), originalString);
			else
			{
				// the command is not allowed here
				String [] arg = new String [] {originalString};
				ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
				event.setParseMessage("\"" + originalString + "\": The command is not allowed here. \"nn_end\" is expected after \"connection\".");
				notifyParse(event);
				return;
			}
		}
		else
		{
			// the string/command is unknown
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": \"" + s + "\" is a unkown command.");
			notifyParse(event);
			return;
		}
	}
	
	/**
	 * processes "NN_END"-command
	 * @param s
	 * @param originalString
	 */
	private void processTerminatedState(String s, String originalString)
	{
		// read the parameter
		if(!s.equals(""))
		{
			// too many parameter
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": Too many parameters. The command \"nn_end\" does not need parameters.");
			notifyParse(event);
			return;
		}
		else
		{
			// everything seems fine, fire ParseEvent
			actualState = StreamParser.TERMINATED_STATE;
			String [] arg = new String [] {s};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.TERMINATED_TYPE);
			event.setParseMessage("nn_end command parsed.");
			notifyParse(event);
			return;
		}

	}

	/**
	 * processes the "CONNECTION"-command
	 * @param s
	 * @param originalString
	 */
	private void processConnectionState(String s, String originalString)
	{
		// read the parameter
		if(s.equals(""))
		{
			// no parameter
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": No parameter. The command \"connection\" needs a parameter defining the name of the connection and the names of the connecting neurons.");
			notifyParse(event);
			return;
		}
		
		// separate the string at the dividing SPACE's
		String [] parameters = s.split(" ");
		
		if (parameters.length > 3)
		{
			// too many parameter
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": Too many parameters. The command \"connection\" needs only 3 parameters defining the name of the connection and the connecting neurons.");
			notifyParse(event);
			return;
		}
		else if (parameters.length < 3)
		{
			// too less parameter
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": Not enough parameters. The command \"connection\" needs 3 parameters defining the name of the connection and the connecting neurons.");
			notifyParse(event);
			return;
		}
		else
		{
			// test the parameters about containing unallowed characters
			for (int i=0; i<3; i++)
			{
				if(parameters[i].contains(",") || parameters[i].contains(";"))
				{
					// the parameter contains unallowed characters
					// fire a ParseEvent
					String [] arg = new String [] {originalString};
					ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
					event.setParseMessage("\"" + originalString + "\": \"" + parameters[i] + "\" - The parameter contains unallowed characters.");
					notifyParse(event);
					return;
				}
			}
			
			// everything seems fine, fire ParseEvent
			actualState = StreamParser.CONNECTION_STATE;
			String [] arg = new String [] {parameters[0], parameters[1], parameters[2]};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.CONNECTION_TYPE);
			event.setParseMessage("connection command parsed.");
			notifyParse(event);
			return;
		}
	}

	/**
	 * processes the "NEURON"-command
	 * @param s
	 * @param originalString
	 */
	private void processNeuronState(String s, String originalString)
	{
		// read the parameter
		if(s.equals(""))
		{
			// no parameter
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": No parameter. The command \"neuron\" needs a parameter defining the name of the neuron.");
			notifyParse(event);
			return;
		}
		if(s.contains(" ") || s.contains(",") || s.contains(";"))
		{
			// the parameter contains unallowed characters
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": \"" + s + "\" - The parameter contains unallowed characters.");
			notifyParse(event);
			return;
		}
		else
		{
			// everything seems fine, fire ParseEvent
			actualState = StreamParser.NEURON_STATE;
			String [] arg = new String [] {s};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.NEURON_TYPE);
			event.setParseMessage("neuron command parsed.");
			notifyParse(event);
			return;
		}

	}

	/**
	 * processes the "LAYER"-command
	 * @param s
	 * @param originalString
	 */
	private void processLayerState(String s, String originalString)
	{
		// read the parameter
		if(s.equals(""))
		{
			// no parameter
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": No parameter. The command \"layer\" needs a parameter defining the name of the layer.");
			notifyParse(event);
			return;
		}
		if(s.contains(" ") || s.contains(",") || s.contains(";"))
		{
			// the parameter contains unallowed characters
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": \"" + s + "\" - The parameter contains unallowed characters.");
			notifyParse(event);
			return;
		}
		else
		{
			// everything seems fine, fire ParseEvent
			actualState = StreamParser.LAYER_STATE;
			String [] arg = new String [] {s};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.LAYER_TYPE);
			event.setParseMessage("layer command parsed.");
			notifyParse(event);
			return;
		}

	}
	
	/**
	 * processes the "NEURAL_NET"-command
	 * @param s
	 * @param originalString
	 */
	private void processNeuralNetState(String s, String originalString)
	{
		// command "create neural net" detected

		// read the parameter
		if(s.equals(""))
		{
			// no parameter
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": No parameter. The command \"neural_net\" needs a parameter defining the name of the net.");
			notifyParse(event);
			return;
		}
		if(s.contains(" ") || s.contains(",") || s.contains(";"))
		{
			// the parameter contains unallowed characters
			// fire a ParseEvent
			String [] arg = new String [] {originalString};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
			event.setParseMessage("\"" + originalString + "\": \"" + s + "\" - The parameter contains unallowed characters.");
			notifyParse(event);
			return;
		}
		else
		{
			// everything seems fine, fire ParseEvent
			actualState = StreamParser.NEURALNET_STATE;
			String [] arg = new String [] {s};
			ParseEvent event = new ParseEvent(this, arg, ParseEvent.NEURALNET_TYPE);
			event.setParseMessage("neural_net command parsed");
			notifyParse(event);
			return;
		}
		
		
	}
	
	/**
	 * adds a ParseEvent listener
	 * @param listener
	 * @return
	 */
	public void addParseListener(ParseListener listener)
	{
		listeners.add(listener);
	}

	/**
	 * removes the event listener
	 * @param listener
	 * @return
	 */
	public void removeParseListener(ParseListener listener)
	{
		listeners.remove(listener);
	}

	/**
	 * notifies all listeners about a new ParseEvent
	 * @param event
	 * @return
	 */
	protected synchronized void notifyParse(ParseEvent event)
	{
		for (int i = 0; i < listeners.size(); i++)
		{
			if(event.getParseType() == ParseEvent.NEURALNET_TYPE)
				((ParseListener) (listeners.get(i))).createNeuralNet(event);
			if(event.getParseType() == ParseEvent.NEURON_TYPE)
				((ParseListener) (listeners.get(i))).createNeuron(event);
			if(event.getParseType() == ParseEvent.LAYER_TYPE)
				((ParseListener) (listeners.get(i))).createLayer(event);
			if(event.getParseType() == ParseEvent.CONNECTION_TYPE)
				((ParseListener) (listeners.get(i))).createConnection(event);
			if(event.getParseType() == ParseEvent.TERMINATED_TYPE)
				((ParseListener) (listeners.get(i))).descriptionTerminated(event);
			if(event.getParseType() == ParseEvent.UNKNOWN_TYPE)
				((ParseListener) (listeners.get(i))).unknownCommand(event);
			if(event.getParseType() == ParseEvent.RECEIVED_CHANNEL_TYPE)
				((ParseListener) (listeners.get(i))).channelReceived(event);
		}
	}
}
