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;
    private int actualLine = 0;

    private int streamingMode = 0;
	
    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(), e);
	actualLine++;
    }

    /**
     * is invoked when new data was received and a DataEvent was fired by DataStreamer
     */	
    public void streamingMode(int mode)
    {
	this.streamingMode = mode;
    }


	
    /**
     * parses the string s and fires a ParseEvent when reading a known command
     */
    public void parseString(String s, DataEvent e)
    {
	// is "s" a comment? 
	if(s.startsWith("#") 
	   && !s.startsWith("#C") && !s.startsWith("#N") && !s.startsWith("#Q"))
	    {
		// the string seems to be a comment
		// System.out.println(s.substring(1).trim());
		return;
	    }
		
	String originalString = s;
	String [] arg = new String [] {originalString};
		
	// is "s" a network 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
			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;
		    }
	    }		
	// is "s" a quit command? 
	else if(s.startsWith("#Q"))
	    {
		if(streamingMode != DataStreamer.FILE_MODE){
		    ParseEvent event = new ParseEvent(this, arg, ParseEvent.QUIT_TYPE);
		    event.setParseMessage("Quit received");
		    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
			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
			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
			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
			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
			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
			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);
			if (e.getSource() instanceof DataStreamer)
			    {
				((DataStreamer)(e.getSource())).setEndOfDescription(this.actualLine+1);
			    }
		    }
		else
		    {
			// the command is not allowed here
			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
		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)
    {
	String [] arg = new String [] {originalString};
	// read the parameter
	if(!s.equals(""))
	    {
		// too many parameter
		// fire a ParseEvent
		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;
		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)
    {
	String [] arg = new String [] {originalString};
	// read the parameter
	if(s.equals(""))
	    {
		// no parameter
		// fire a ParseEvent
		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
		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
		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
				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;
		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)
    {
	String [] arg = new String [] {originalString};
	// read the parameter
	if(s.equals(""))
	    {
		// no parameter
		// fire a ParseEvent
		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;
	    }
		
	// separate the string at the dividing SPACE's
	String [] parameters = s.split(" ");

	if (parameters.length > 2)
	    {
		// too many parameter
		// fire a ParseEvent
		ParseEvent event = new ParseEvent(this, arg, ParseEvent.UNKNOWN_TYPE);
		event.setParseMessage("\"" + originalString + "\": Too many parameters. The command \"neuron\" needs maximal 2 parameters defining the name of the neuron and an optional name of the bias.");
		notifyParse(event);
		return;
	    }
	else if (parameters.length == 2)
	    {
		// optional bias was added
		// test the parameters about containing unallowed characters
		for (int i=0; i<2; i++)
		    {
			if(parameters[i].contains(",") || parameters[i].contains(";"))
			    {
				// the parameter contains unallowed characters
				// fire a ParseEvent
				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.NEURON_STATE;
		arg = new String [] {parameters[0],parameters[1]};
		ParseEvent event = new ParseEvent(this, arg, ParseEvent.NEURON_TYPE);
		event.setParseMessage("neuron command parsed.");
		notifyParse(event);
		return;
	    }
	else
	    {
		// a neuron without bias definition
		// everything seems fine, fire ParseEvent
		actualState = StreamParser.NEURON_STATE;
		arg = new String [] {parameters[0]};
		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)
    {
	String [] arg = new String [] {originalString};
	// read the parameter
	if(s.equals(""))
	    {
		// no parameter
		// fire a ParseEvent
		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
		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;
		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
	String [] arg = new String [] {originalString};

	// read the parameter
	if(s.equals(""))
	    {
		// no parameter
		// fire a ParseEvent
		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
		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;
		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);
		if(event.getParseType() == ParseEvent.QUIT_TYPE)
		    ((ParseListener) (listeners.get(i))).quitReceived(event);
	    }
    }
}
