package parser;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import models.algebra.Constant;
import models.algebra.Expression;
import models.algebra.Symbol;
import models.algebra.Term;
import models.algebra.Type;
import models.algebra.Variable;
import models.dataConstraintModel.ChannelMember;
import models.dataConstraintModel.DataConstraintModel;
import models.dataConstraintModel.JsonAccessor;
import models.dataConstraintModel.JsonTerm;
import models.dataConstraintModel.ResourceHierarchy;
import models.dataConstraintModel.ResourcePath;
import models.dataConstraintModel.StateTransition;
import models.dataConstraintModel.StateTransitionTerm;
import models.dataFlowModel.DataTransferModel;
import models.dataFlowModel.DataTransferChannel;
import parser.exceptions.ExpectedAssignment;
import parser.exceptions.ExpectedChannel;
import parser.exceptions.ExpectedChannelName;
import parser.exceptions.ExpectedColon;
import parser.exceptions.ExpectedDoubleQuotation;
import parser.exceptions.ExpectedEquals;
import parser.exceptions.ExpectedInOrOutOrRefOrSubKeyword;
import parser.exceptions.ExpectedLeftCurlyBracket;
import parser.exceptions.ExpectedRHSExpression;
import parser.exceptions.ExpectedRightBracket;
import parser.exceptions.ExpectedRightCurlyBracket;
import parser.exceptions.ExpectedStateTransition;
import parser.exceptions.WrongJsonExpression;
import parser.exceptions.WrongLHSExpression;
import parser.exceptions.WrongPathExpression;
import parser.exceptions.WrongRHSExpression;

public class Parser {		
	protected TokenStream stream;

	public static final String CHANNEL = "channel";
	public static final String INIT = "init";
	public static final String IN = "in";
	public static final String OUT = "out";
	public static final String REF = "ref";
	public static final String SUB_CHANNEL = "sub";
	public static final String NATIVE = "native";
	public static final String LEFT_CURLY_BRACKET = "{";
	public static final String RIGHT_CURLY_BRACKET = "}";
	public static final String LEFT_CURLY_BRACKET_REGX = "\\{";
	public static final String RIGHT_CURLY_BRACKET_REGX = "\\}";
	public static final String LEFT_BRACKET = "(";
	public static final String RIGHT_BRACKET = ")";
	public static final String LEFT_BRACKET_REGX = "\\(";
	public static final String RIGHT_BRACKET_REGX = "\\)";
	public static final String LEFT_SQUARE_BRACKET = "[";
	public static final String RIGHT_SQUARE_BRACKET = "]";
	public static final String LEFT_SQUARE_BRACKET_REGX = "\\[";
	public static final String RIGHT_SQUARE_BRACKET_REGX = "\\]";
	public static final String ADD = "+";
	public static final String MUL = "*";
	public static final String SUB = "-";
	public static final String DIV = "/";
	public static final String MOD = "%";
	public static final String MINUS = "-";
	public static final String EQ = "==";
	public static final String NEQ = "!=";
	public static final String GT = ">";
	public static final String LT = "<";
	public static final String GE = ">=";
	public static final String LE = "<=";
	public static final String AND = "&&";
	public static final String OR = "||";
	public static final String NEG = "!";
	public static final String ADD_REGX = "\\+";
	public static final String MUL_REGX = "\\*";
	public static final String SUB_REGX = "\\-";
	public static final String DIV_REGX = "/";
	public static final String OR_REGX = "\\|\\|";
	public static final String EQUALS = "=";
	public static final String ASSIGNMENT = "=";
	public static final String COMMA = ",";
	public static final String COLON = ":";
	public static final String DOT = ".";
	public static final String DOT_REGX = "\\.";
	public static final String DOUBLE_QUOT = "\"";

	public Parser(final TokenStream stream) {
		this.stream = stream;
	}

	public Parser(final BufferedReader reader) {		
		this.stream = new TokenStream();
		try {
			String line;
			while ((line = reader.readLine()) != null) {
				stream.addLine(line);
			}
			reader.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public DataTransferModel doParse() 
			throws ExpectedRightBracket, ExpectedChannel, ExpectedChannelName, ExpectedLeftCurlyBracket, ExpectedRightCurlyBracket, 
					ExpectedInOrOutOrRefOrSubKeyword, ExpectedStateTransition, ExpectedEquals, 
					ExpectedRHSExpression, WrongLHSExpression, WrongRHSExpression, ExpectedAssignment, WrongPathExpression, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		return parseDataFlowModel();
	}

	public DataTransferModel parseDataFlowModel() 
			throws ExpectedRightBracket, ExpectedChannel, ExpectedChannelName, ExpectedLeftCurlyBracket, ExpectedRightCurlyBracket, 
					ExpectedInOrOutOrRefOrSubKeyword, ExpectedStateTransition, ExpectedEquals, 
					ExpectedRHSExpression, WrongLHSExpression, WrongRHSExpression, ExpectedAssignment, WrongPathExpression, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		DataTransferModel model = new DataTransferModel();
		DataTransferChannel channel;
		while ((channel = parseChannel(model)) != null) {
			if (channel.getInputChannelMembers().size() == 0) {
				model.addInputChannel(channel);
			} else {
				model.addChannel(channel);
			}
		}
		return model;
	}

	public DataTransferChannel parseChannel(DataTransferModel model) 
			throws ExpectedLeftCurlyBracket, ExpectedRightBracket, ExpectedRightCurlyBracket, ExpectedAssignment, 
					ExpectedRHSExpression, WrongLHSExpression, WrongRHSExpression, 
					ExpectedChannel, ExpectedChannelName, ExpectedInOrOutOrRefOrSubKeyword, 
					ExpectedStateTransition, ExpectedEquals, WrongPathExpression, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		if (!stream.hasNext()) return null;
		if (stream.checkNext().equals(RIGHT_CURLY_BRACKET)) return null;

		boolean isNative = false;
		String channelOrInitOrNativeKeyword = stream.next();
		if (channelOrInitOrNativeKeyword.equals(NATIVE)) {
			// A native channel
			isNative = true;
			channelOrInitOrNativeKeyword = stream.next();
		}
		if (!channelOrInitOrNativeKeyword.equals(CHANNEL) && !channelOrInitOrNativeKeyword.equals(SUB_CHANNEL)) {
			if (!channelOrInitOrNativeKeyword.equals(INIT))  throw new ExpectedChannel(stream.getLine());
			parseInit(model);
			channelOrInitOrNativeKeyword = stream.next();
			if (channelOrInitOrNativeKeyword.equals(NATIVE)) {
				// A native channel
				isNative = true;
				channelOrInitOrNativeKeyword = stream.next();
			}
		}
		if (!stream.hasNext()) throw new ExpectedChannelName(stream.getLine());

		String channelName = stream.next();
		if (channelName.equals(LEFT_CURLY_BRACKET)) throw new ExpectedChannelName(stream.getLine());

		int fromLine = stream.getLine();
		DataTransferChannel channel = new DataTransferChannel(channelName);
		if (isNative) {
			channel.setNative(true);
		}
		String leftBracket = stream.next();
		if (leftBracket.equals(LEFT_BRACKET)) {
			// has selectors
			String rightBracket = null;
			do {
				String selector = stream.next();
				Variable var = parseVariable(stream, model, selector);
				channel.addSelector(var);
				rightBracket = stream.next();
			} while (rightBracket.equals(COMMA));
			if (!rightBracket.equals(RIGHT_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
			leftBracket = stream.next();
		}
		if (!leftBracket.equals(LEFT_CURLY_BRACKET)) throw new ExpectedLeftCurlyBracket(stream.getLine());

		String inOrOutOrRefOrSub = null;
		while (stream.hasNext() && !(inOrOutOrRefOrSub = stream.checkNext()).equals(RIGHT_CURLY_BRACKET)) {
			ChannelMember channelMember = null;
			if (inOrOutOrRefOrSub.equals(IN)) {
				stream.next();
				channelMember = parseChannelMember(model, inOrOutOrRefOrSub);
				if (channelMember != null) {
					channel.addChannelMemberAsInput(channelMember);
				}
			} else if (inOrOutOrRefOrSub.equals(OUT)) {
				stream.next();
				channelMember = parseChannelMember(model, inOrOutOrRefOrSub);
				if (channelMember != null) {
					channel.addChannelMemberAsOutput(channelMember);
				}				
			} else if (inOrOutOrRefOrSub.equals(REF)) {
				stream.next();
				channelMember = parseChannelMember(model, inOrOutOrRefOrSub);
				if (channelMember != null) {
					channel.addChannelMemberAsReference(channelMember);
				}
			} else if (inOrOutOrRefOrSub.equals(SUB_CHANNEL)) {
				DataTransferChannel subChannel = parseChannel(model);
				if (subChannel != null) {
					channel.addChild(subChannel);
				}
			} else {
				throw new ExpectedInOrOutOrRefOrSubKeyword(stream.getLine());
			}
		}
		if (stream.hasNext()) stream.next();
		int toLine = stream.getLine();
		channel.setSourceText(stream.getSourceText(fromLine, toLine));
		return channel;
	}

	public void parseInit(DataTransferModel model) 
			throws ExpectedLeftCurlyBracket, ExpectedAssignment, ExpectedRHSExpression, WrongRHSExpression, 
					ExpectedRightBracket, ExpectedRightCurlyBracket, WrongPathExpression, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		String leftBracket = stream.next();
		if (!leftBracket.equals(LEFT_CURLY_BRACKET)) throw new ExpectedLeftCurlyBracket(stream.getLine());
		while (stream.hasNext() && !stream.checkNext().equals(RIGHT_CURLY_BRACKET)) {
			int fromLine = stream.getLine();
			ResourceHierarchy resourceHierarchy = parseResourceHierarchy(stream, model);

			if (!stream.hasNext()) throw new ExpectedAssignment(stream.getLine());
			String colon = stream.next();
			if (!colon.equals(COLON)) throw new ExpectedAssignment(stream.getLine());
			if (!stream.hasNext()) throw new ExpectedAssignment(stream.getLine());
			String equals = stream.next();
			if (!equals.equals(ASSIGNMENT)) throw new ExpectedAssignment(stream.getLine());

			int toLine = stream.getLine();
			Expression rightTerm = null;
			if (!stream.hasNext()) throw new ExpectedRHSExpression(stream.getLine());		
			rightTerm = parseTerm(stream, model);		
			if (rightTerm == null) throw new WrongRHSExpression(stream.getLine());

			resourceHierarchy.setInitialValue(rightTerm);
			resourceHierarchy.setInitText(stream.getSourceText(fromLine, toLine));
		}
		if (stream.hasNext()) stream.next();
	}

	public ChannelMember parseChannelMember(DataTransferModel model, final String inOrOutOrRef) 
			throws ExpectedRightBracket, ExpectedRightCurlyBracket, ExpectedStateTransition, ExpectedEquals, 
					ExpectedRHSExpression, WrongLHSExpression, WrongRHSExpression, WrongPathExpression, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		if (!stream.hasNext()) throw new ExpectedStateTransition(stream.getLine());
		StateTransitionTerm leftTerm = parseStateTransitionTerm(stream, model);
		if (leftTerm == null || !(leftTerm instanceof Term)) throw new WrongLHSExpression(stream.getLine());
		Expression rightTerm = null;

		if (!inOrOutOrRef.equals(REF)) {
			if (!stream.hasNext()) throw new ExpectedEquals(stream.getLine());
			String equals = stream.next();
			if (!equals.equals(EQUALS)) throw new ExpectedEquals(stream.getLine());

			if (!stream.hasNext()) throw new ExpectedRHSExpression(stream.getLine());		
			rightTerm = parseTerm(stream, model);		
			if (rightTerm == null) throw new WrongRHSExpression(stream.getLine());
		}

		ResourcePath resourcePath = (ResourcePath) leftTerm.getSymbol();
		ChannelMember channelMember = new ChannelMember(resourcePath);
		StateTransition stateTransition = new StateTransition();
		stateTransition.setCurStateExpression(((Term) leftTerm).getChild(0));
		stateTransition.setMessageExpression(((Term) leftTerm).getChild(1));
		if (!inOrOutOrRef.equals(REF)) stateTransition.setNextStateExpression(rightTerm);
		channelMember.setStateTransition(stateTransition);
		// for type definition
		if (resourcePath.getResourceStateType() == null && ((Term) leftTerm).getChild(0) instanceof Variable) {
			Variable stateVar = (Variable) ((Term) leftTerm).getChild(0);
			if (stateVar.getType() != null) {
				resourcePath.setResourceStateType(stateVar.getType());
			}
		}
		if (((Term) leftTerm).getChild(1) instanceof Term) {
			Term messageTerm = (Term) ((Term) leftTerm).getChild(1);
			if (messageTerm.getSymbol().getSignature() == null && messageTerm.getChildren().size() > 0) {
				Type[] signature = new Type[messageTerm.getChildren().size() + 1];
				int i = 1;
				for (Expression e: messageTerm.getChildren()) {
					if (e instanceof Variable && ((Variable) e).getType() != null) {
						signature[i] = ((Variable) e).getType();
					}
					i++;
				}
				messageTerm.getSymbol().setSignature(signature);
			}
		}
		return channelMember;
	}

	public Expression parseTerm(TokenStream stream, DataTransferModel model) throws ExpectedRightBracket, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		ArrayList<Expression> expressions = new ArrayList<>();
		ArrayList<Symbol> operators = new ArrayList<>();
		String operator = null;
		for (;;) {
			String leftBracketOrMinusOrNeg = stream.next();
			if (leftBracketOrMinusOrNeg.equals(LEFT_BRACKET)) {
				Expression exp = parseTerm(stream, model);
				String rightBracket = stream.next();
				if (!rightBracket.equals(RIGHT_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
				expressions.add(exp);
			} else if (leftBracketOrMinusOrNeg.equals(LEFT_CURLY_BRACKET)) {
				Expression exp = parseJsonTerm(stream, model);
				String rightBracket = stream.next();
				if (!rightBracket.equals(RIGHT_CURLY_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
				expressions.add(exp);
			} else if (leftBracketOrMinusOrNeg.equals(LEFT_SQUARE_BRACKET)) {
				Expression exp = parseListTerm(stream, model);
				String rightBracket = stream.next();
				if (!rightBracket.equals(RIGHT_SQUARE_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
				expressions.add(exp);
			} else {
				Symbol minusOrNeg = null;
				String symbolName = null;
				if (leftBracketOrMinusOrNeg.equals(MINUS)) {
					minusOrNeg = DataTransferModel.minus;		// not sub
					symbolName = stream.next();
				} else if (leftBracketOrMinusOrNeg.equals(NEG)) {
					minusOrNeg = DataTransferModel.neg;
					symbolName = stream.next();
				} else if (leftBracketOrMinusOrNeg.equals(DOUBLE_QUOT)) {
					symbolName = DOUBLE_QUOT + stream.next() + DOUBLE_QUOT;
					String doubleQuot = stream.next();
					if (!doubleQuot.equals(DOUBLE_QUOT)) throw new ExpectedDoubleQuotation(stream.getLine());
				} else {
					symbolName = leftBracketOrMinusOrNeg;
				}
				Expression exp = null;
				if (stream.checkNext() != null && stream.checkNext().equals(LEFT_BRACKET)) {
					// a function symbol
					Symbol symbol = model.getSymbol(symbolName);
					if (symbol == null) {
						symbol = new Symbol(symbolName);
						model.addSymbol(symbol);
					}
					Term term = new Term(symbol);
					int arity = 0;
					do {
						stream.next();		// LEFT_BRACKET or COMMA
						if (stream.checkNext().equals(RIGHT_BRACKET)) break;
						arity++;
						Expression subTerm = parseTerm(stream, model);
						term.addChild(subTerm, true);
						if (!stream.hasNext()) throw new ExpectedRightBracket(stream.getLine());
					} while (stream.checkNext().equals(COMMA));
					String rightBracket = stream.next();
					if (!rightBracket.equals(RIGHT_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
					symbol.setArity(arity);
					exp = term;
				} else {
					// constant or variable or json access
					Symbol symbol = model.getSymbol(symbolName);
					if (symbol != null && symbol.getArity() == 0) {
						// a constant
						exp = new Constant(symbol);
					} else {
						if (Character.isDigit(symbolName.charAt(0))) {
							// maybe a numerical value
							if (stream.checkNext() != null && stream.checkNext().equals(DOT)) {
								// Because tokens are separated by a DOT.
								stream.next();
								symbolName += DOT + stream.next();	// decimal fraction
							}
							Double d = Double.parseDouble(symbolName);
							// a numerical value
							if (symbolName.contains(DOT)) {
								exp = new Constant(symbolName, DataTransferModel.typeDouble);
							} else {
								exp = new Constant(symbolName, DataTransferModel.typeInt);
							}
						} else if (symbolName.startsWith(DOUBLE_QUOT) && symbolName.endsWith(DOUBLE_QUOT)) {
							// a string value
							exp = new Constant(symbolName, DataTransferModel.typeString);
						} else {
							// a variable
							exp = parseVariable(stream, model, symbolName);
						}
					}
				}
				if (minusOrNeg != null) {
					Term minusOrNegTerm = new Term(minusOrNeg);
					minusOrNegTerm.addChild(exp);
					expressions.add(minusOrNegTerm);
				} else {
					expressions.add(exp);
				}
			}
			operator = stream.checkNext();
			if (operator == null) {
				break;
			} else if (operator.equals(ADD)) {
				operators.add(DataTransferModel.add);
				stream.next();
			} else if (operator.equals(MUL)) {
				operators.add(DataTransferModel.mul);
				stream.next();
			} else if (operator.equals(SUB)) {
				operators.add(DataTransferModel.sub);	// not minus
				stream.next();
			} else if (operator.equals(DIV)) {
				operators.add(DataTransferModel.div);
				stream.next();
			} else if (operator.equals(MOD)) {
				operators.add(DataTransferModel.mod);
				stream.next();
			} else if (operator.equals(EQ)) {
				operators.add(DataTransferModel.eq);
				stream.next();
			} else if (operator.equals(NEQ)) {
				operators.add(DataTransferModel.neq);
				stream.next();
			} else if (operator.equals(GT)) {
				operators.add(DataTransferModel.gt);
				stream.next();
			} else if (operator.equals(LT)) {
				operators.add(DataTransferModel.lt);
				stream.next();
			} else if (operator.equals(GE)) {
				operators.add(DataTransferModel.ge);
				stream.next();
			} else if (operator.equals(LE)) {
				operators.add(DataTransferModel.le);
				stream.next();
			} else if (operator.equals(AND)) {
				operators.add(DataTransferModel.and);
				stream.next();
			} else if (operator.equals(OR)) {
				operators.add(DataTransferModel.or);
				stream.next();
			} else if (operator.equals(DOT)) {
				// json accessor
				while (operator.equals(DOT)) {
					Expression exp = expressions.remove(0);
					stream.next();		// DOT
					if (stream.checkNext() == null) throw new WrongJsonExpression(stream.getLine());
					String literalOrLeftCurlyBracket = stream.next();
					Expression paramTerm = null;
					if (literalOrLeftCurlyBracket.equals(LEFT_CURLY_BRACKET)) {
						// parameter
						paramTerm = parseTerm(stream, model);
						String rightCurlyBracket = stream.next();
						if (rightCurlyBracket == null || !rightCurlyBracket.equals(RIGHT_CURLY_BRACKET)) throw new WrongJsonExpression(stream.getLine());			
					} else {
						// literal
						paramTerm = new Constant(literalOrLeftCurlyBracket, DataTransferModel.typeString);
					}
					Type paramType = null;
					if (paramTerm instanceof Variable) {
						paramType = ((Variable) paramTerm).getType();
					} else if (paramTerm instanceof Term) {
						paramType = ((Term) paramTerm).getType();
					}
					Term term = null;
					if (paramType != null && DataConstraintModel.typeInt.isAncestorOf(paramType)) {
						term = new JsonAccessor(DataConstraintModel.dotParam);
					} else {
						term = new JsonAccessor(DataConstraintModel.dot);
					}
					term.addChild(exp);
					term.addChild(paramTerm);
					expressions.add(term);
					operator = stream.checkNext();
					if (operator == null) break;
				}
				if (operator == null) {
					break;
				} else if (operator.equals(ADD)) {
					operators.add(DataTransferModel.add);
					stream.next();
				} else if (operator.equals(MUL)) {
					operators.add(DataTransferModel.mul);
					stream.next();
				} else if (operator.equals(SUB)) {
					operators.add(DataTransferModel.sub);	// not minus
					stream.next();
				} else if (operator.equals(DIV)) {
					operators.add(DataTransferModel.div);
					stream.next();
				} else if (operator.equals(MOD)) {
					operators.add(DataTransferModel.mod);
					stream.next();
				} else if (operator.equals(EQ)) {
					operators.add(DataTransferModel.eq);
					stream.next();
				} else if (operator.equals(NEQ)) {
					operators.add(DataTransferModel.neq);
					stream.next();
				} else if (operator.equals(GT)) {
					operators.add(DataTransferModel.gt);
					stream.next();
				} else if (operator.equals(LT)) {
					operators.add(DataTransferModel.lt);
					stream.next();
				} else if (operator.equals(GE)) {
					operators.add(DataTransferModel.ge);
					stream.next();
				} else if (operator.equals(LE)) {
					operators.add(DataTransferModel.le);
					stream.next();
				} else if (operator.equals(AND)) {
					operators.add(DataTransferModel.and);
					stream.next();
				} else if (operator.equals(OR)) {
					operators.add(DataTransferModel.or);
					stream.next();
				} else {
					break;
				}
			} else {
				break;
			}
		}
		if (expressions.size() == 1) {
			// no arithmetic operators
			return expressions.get(0);
		}
		ArrayList<Expression> monomials = new ArrayList<>();
		ArrayList<Symbol> addSubs = new ArrayList<>();
		Expression first = expressions.get(0);
		int i = 1;
		Term rootTerm = null;
		for (Symbol op: operators) {
			Expression second = expressions.get(i);
			if (op.getName().equals(MUL) || op.getName().equals(DIV) || op.getName().equals(MOD)) {
				// higher priority than add and sub
				Term term = new Term(op);
				term.addChild(first);
				term.addChild(second);
				first = term;
			} else if (op.getName().equals(EQ) || op.getName().equals(NEQ) || op.getName().equals(GT) || op.getName().equals(LT)
						|| op.getName().equals(GE) || op.getName().equals(LE) || op.getName().equals(AND) || op.getName().equals(OR)) {
				// lower priority than add and sub
				if (first != null) monomials.add(first);
				Expression firstMonomial = monomials.get(0);
				int j = 1;
				for (Symbol op2: addSubs) {
					Expression secondMonomial = monomials.get(j);
					Term term = new Term(op2);
					term.addChild(firstMonomial);
					term.addChild(secondMonomial);
					firstMonomial = term;
					j++;
				}
				if (rootTerm == null) {
					rootTerm = new Term(op);
					rootTerm.addChild(firstMonomial);
				} else {
					rootTerm.addChild(firstMonomial);
					firstMonomial = rootTerm;
					rootTerm = new Term(op);
					rootTerm.addChild(firstMonomial);
				}
				monomials.clear();
				addSubs.clear();
				first = second;
			} else {
				// add or sub ==> new monomial
				monomials.add(first);
				addSubs.add(op);
				first = second;
			}
			i++;
		}
		if (first != null) monomials.add(first);
		Expression firstMonomial = monomials.get(0);
		i = 1;
		for (Symbol op: addSubs) {
			Expression secondMonomial = monomials.get(i);
			Term term = new Term(op);
			term.addChild(firstMonomial);
			term.addChild(secondMonomial);
			firstMonomial = term;
			i++;
		}
		if (rootTerm == null) {
			return firstMonomial;
		} else {
			rootTerm.addChild(firstMonomial);
			return rootTerm;
		}
	}

//	private Expression parseJsonTerm(TokenStream stream, DataTransferModel model) throws ExpectedRightBracket, WrongJsonExpression, ExpectedColon {
//		Term jsonTerm = new Constant(DataConstraintModel.nil);
//		jsonTerm.setType(DataConstraintModel.typeJson);
//		while (stream.checkNext() != null && !stream.checkNext().equals(RIGHT_CURLY_BRACKET)) {
//			String key = stream.next();
//			Constant keyExp = new Constant(key);
//			keyExp.setType(DataConstraintModel.typeString);
//			if (stream.checkNext() == null || !stream.checkNext().equals(COLON)) throw new ExpectedColon(stream.getLine());
//			String colon = stream.next();
//			Expression value = parseTerm(stream, model);
//			Term nextTerm = new Term(DataConstraintModel.addMember);
//			nextTerm.addChild(jsonTerm);
//			nextTerm.addChild(keyExp);
//			nextTerm.addChild(value);
//			jsonTerm = nextTerm;
//			if (stream.checkNext() == null || !stream.checkNext().equals(COMMA)) break;
//			String comma = stream.next();
//		}
//		return jsonTerm;
//	}
	
	private Expression parseJsonTerm(TokenStream stream, DataTransferModel model) throws ExpectedRightBracket, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		JsonTerm jsonTerm = new JsonTerm();
		while (stream.checkNext() != null && !stream.checkNext().equals(RIGHT_CURLY_BRACKET)) {
			if (stream.checkNext() == null || !stream.checkNext().equals(DOUBLE_QUOT)) throw new ExpectedDoubleQuotation(stream.getLine());
			String doubleQuot = stream.next();
			String key = stream.next();
			if (stream.checkNext() == null || !stream.checkNext().equals(DOUBLE_QUOT)) throw new ExpectedDoubleQuotation(stream.getLine());
			doubleQuot = stream.next();
			if (stream.checkNext() == null || !stream.checkNext().equals(COLON)) throw new ExpectedColon(stream.getLine());
			String colon = stream.next();
			Expression value = parseTerm(stream, model);
			jsonTerm.addMember(DOUBLE_QUOT + key + DOUBLE_QUOT, value);
			if (stream.checkNext() == null || !stream.checkNext().equals(COMMA)) break;
			String comma = stream.next();
		}
		return jsonTerm;
	}

	private Expression parseListTerm(TokenStream stream2, DataTransferModel model) throws ExpectedRightBracket, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		Term listTerm = new Constant(DataConstraintModel.nil);
		listTerm.setType(DataConstraintModel.typeList);
		while (stream.checkNext() != null && !stream.checkNext().equals(RIGHT_SQUARE_BRACKET)) {
			Expression element = parseTerm(stream, model);
			Term nextTerm = new Term(DataConstraintModel.cons);
			nextTerm.addChild(element);
			nextTerm.addChild(listTerm);
			listTerm = nextTerm;
			if (stream.checkNext() == null || !stream.checkNext().equals(COMMA)) break;
			String comma = stream.next();
		}
		return listTerm;
	}

	private Variable parseVariable(TokenStream stream, DataTransferModel model, String symbolName) {
		Variable var;
		if (stream.checkNext() != null && stream.checkNext().equals(COLON)) {
			// when a type is specified.
			stream.next();
			String typeName = stream.next();
			Type type = model.getType(typeName);
			if (type == null) {
				type = new Type(typeName, typeName);
			}
			var = new Variable(symbolName, type);							
		} else {
			var = new Variable(symbolName);
		}
		return var;
	}
	
	public StateTransitionTerm parseStateTransitionTerm(TokenStream stream, DataTransferModel model) 
			throws ExpectedRightBracket, ExpectedRightCurlyBracket, WrongPathExpression, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		ResourcePath resourcePath = parseResourcePath(stream, model);
		StateTransitionTerm term = new StateTransitionTerm(resourcePath);
		int arity = 0;
		do {
			stream.next();		// LEFT_BRACKET or COMMA
			arity++;
			Expression subTerm = parseTerm(stream, model);
			term.addChild(subTerm, true);
			if (!stream.hasNext()) throw new ExpectedRightBracket(stream.getLine());
		} while (stream.checkNext().equals(COMMA));
		String rightBracket = stream.next();
		if (!rightBracket.equals(RIGHT_BRACKET)) throw new ExpectedRightBracket(stream.getLine());
		resourcePath.setArity(arity);
		return term;
	}
	
	public ResourceHierarchy parseResourceHierarchy(TokenStream stream, DataTransferModel model) 
			throws ExpectedRightBracket, ExpectedRightCurlyBracket, WrongPathExpression {
		ResourceHierarchy hierarchy = null;
		do {
			String literalOrLeftCurlyBracket = stream.next();
			if (literalOrLeftCurlyBracket.equals(LEFT_CURLY_BRACKET)) {
				// No path parameter
				String rightCurlyBracket = stream.next();
				if (rightCurlyBracket == null || !rightCurlyBracket.equals(RIGHT_CURLY_BRACKET)) throw new ExpectedRightCurlyBracket(stream.getLine());				
				hierarchy = new ResourceHierarchy(hierarchy, 1);
			} else {
				// Path literal
				hierarchy = new ResourceHierarchy(hierarchy, literalOrLeftCurlyBracket);
			}
			if (!stream.hasNext()) throw new WrongPathExpression(stream.getLine());
			if (stream.checkNext().equals(LEFT_BRACKET)) break;
			if (stream.checkNext().equals(COLON)) break;
		} while (stream.next().equals(DOT));
		hierarchy = model.getOrPutResourceHierarchy(hierarchy);
		return hierarchy;
	}
	
	public ResourcePath parseResourcePath(TokenStream stream, DataTransferModel model) 
			throws ExpectedRightBracket, ExpectedRightCurlyBracket, WrongPathExpression, WrongJsonExpression, ExpectedColon, ExpectedDoubleQuotation {
		ResourcePath path = null;
		do {
			String literalOrLeftCurlyBracket = stream.next();
			if (literalOrLeftCurlyBracket.equals(LEFT_CURLY_BRACKET)) {
				// Path parameter
				Expression paramTerm = parseTerm(stream, model);
				String rightCurlyBracket = stream.next();
				if (rightCurlyBracket == null || !rightCurlyBracket.equals(RIGHT_CURLY_BRACKET)) throw new ExpectedRightCurlyBracket(stream.getLine());				
				path = new ResourcePath(path, paramTerm);
			} else {
				// Path literal
				if (path == null) {
					path = new ResourcePath(literalOrLeftCurlyBracket);					
				} else {
					path = new ResourcePath(path, literalOrLeftCurlyBracket);
				}
			}
			if (!stream.hasNext()) throw new WrongPathExpression(stream.getLine());
			if (stream.checkNext().equals(LEFT_BRACKET)) break;
		} while (stream.next().equals(DOT));
		model.addResourcePath(path);
		return path;
	}

	protected Boolean doesMatchToKeyword(final String token, final String specificTokenName) {
		if(token == null) return false;
		if(specificTokenName == null) return false;
		return token.equals(specificTokenName);
	}

	public static class TokenStream {
		private ArrayList<ArrayList<Token>> tokens = new ArrayList<>();
		private ArrayList<String> lines = new ArrayList<>();
		private int line = 0;
		private int n = 0;

		public TokenStream() {
			line = 0;
			n = 0;
		}

		public void addLine(String line) {
			lines.add(line);
			line = line.trim();
			ArrayList<Token> tokenList = splitByDoubleQuotation(line);
			tokenList = splitBy(tokenList, ADD, ADD_REGX);
			tokenList = splitBy(tokenList, MUL, MUL_REGX);
			tokenList = splitBy(tokenList, SUB, SUB_REGX);
			tokenList = splitBy(tokenList, DIV, DIV_REGX);
			tokenList = splitBy(tokenList, MOD, MOD);
			tokenList = splitBy(tokenList, EQ, EQ);
			tokenList = splitBy(tokenList, NEQ, NEQ);
			tokenList = splitBy(tokenList, GE, GE);
			tokenList = splitBy(tokenList, LE, LE);
			tokenList = splitBy(tokenList, GT, GT);
			tokenList = splitBy(tokenList, LT, LT);
			tokenList = splitBy(tokenList, AND, AND);
			tokenList = splitBy(tokenList, OR, OR_REGX);
			tokenList = splitBy(tokenList, NEG, NEG);
			tokenList = splitBy(tokenList, DOT, DOT_REGX);
			tokenList = splitBy(tokenList, COMMA, COMMA);
			tokenList = splitBy(tokenList, COLON, COLON);
			tokenList = splitBy(tokenList, LEFT_BRACKET, LEFT_BRACKET_REGX);
			tokenList = splitBy(tokenList, RIGHT_BRACKET, RIGHT_BRACKET_REGX);
			tokenList = splitBy(tokenList, EQUALS, EQUALS);
			tokenList = splitBy(tokenList, LEFT_CURLY_BRACKET, LEFT_CURLY_BRACKET_REGX);
			tokenList = splitBy(tokenList, RIGHT_CURLY_BRACKET, RIGHT_CURLY_BRACKET_REGX);
			tokenList = splitBy(tokenList, LEFT_SQUARE_BRACKET, LEFT_SQUARE_BRACKET_REGX);
			tokenList = splitBy(tokenList, RIGHT_SQUARE_BRACKET, RIGHT_SQUARE_BRACKET_REGX);
			tokens.add(tokenList);
		}
		
		private ArrayList<Token> splitBy(final List<Token> tokens, final String delimiter, final String delimiterRegx) {
			ArrayList<Token> newTokens = new ArrayList<>();
			for (Token token: tokens) {
				if (token.isAtomic()) {
					newTokens.add(token);
				} else {
					String[] splitTokens = token.split(delimiterRegx);
					boolean fFirstToken = true;
					for (String t: splitTokens) {
						if (!fFirstToken) {
							newTokens.add(new Token(delimiter, true));
						}
						if (t.length() > 0) {
							newTokens.add(new Token(t));
						}
						fFirstToken = false;
					}
					while (token.endsWith(delimiter)) {
						newTokens.add(new Token(delimiter, true));
						token = token.substring(0, token.length() - 1);
					}
				}
			}
			return newTokens;
		}
		
		private ArrayList<Token> splitByDoubleQuotation(String line) {
			ArrayList<Token> newTokens = new ArrayList<>();
			String[] tokens = line.split(DOUBLE_QUOT);
			boolean fFirstToken = true;
			for (int i = 0; i < tokens.length; i++) {
				String token = tokens[i];
				if (!fFirstToken) {
					newTokens.add(new Token(DOUBLE_QUOT, true));
				}
				if (!fFirstToken || token.length() > 0) {
					if (i % 2 == 0) {
						for (String t: token.split("[ \t]")) {
							newTokens.add(new Token(t));
						}
					} else {
						// string literal
						newTokens.add(new Token(token, true));
					}
				}
				fFirstToken = false;
			}
			if (line.endsWith(DOUBLE_QUOT)) {
				newTokens.add(new Token(DOUBLE_QUOT, true));
			}
			return newTokens;
		}

		public String next() {
			if (line >= tokens.size()) return null;
			while (n >= tokens.get(line).size()) {
				line++;
				n = 0;
				if (line >= tokens.size()) return null;
			}
			String token = tokens.get(line).get(n).getTokenStr();
			n++;
			return token;
		}

		public String checkNext() {
			if (line >= tokens.size()) return null;
			while (n >= tokens.get(line).size()) {
				line++;
				n = 0;
				if (line >= tokens.size()) return null;
			}
			return tokens.get(line).get(n).getTokenStr();
		}

		public boolean hasNext() {
			if (line >= tokens.size()) return false;
			while (n >= tokens.get(line).size()) {
				line++;
				n = 0;
				if (line >= tokens.size()) return false;
			}
			return true;
		}

		public int getLine() {
			return line;
		}

		public String getSourceText(int from, int to) {
			String text = "";
			for (int l = from; l <= to; l++) {
				text += lines.get(l) + "\n";
			}
			return text;
		}
	}
	
	public static class Token {
		String token;
		boolean isAtomic = false;
		
		public Token(String token) {
			this.token = token;
		}

		public Token(String token, boolean isAtomic) {
			this.token = token;
			this.isAtomic = isAtomic;
		}
		
		public String getTokenStr() {
			return token;
		}
		
		public boolean isAtomic() {
			return isAtomic;
		}
		
		public String[] split(String delimiterRegx) {
			return token.split(delimiterRegx);
		}

		public boolean endsWith(String delimiter) {
			return token.endsWith(delimiter);
		}

		public int length() {
			return token.length();
		}

		public Token substring(int beginIdx, int endIdx) {
			return new Token(token.substring(beginIdx, endIdx));
		}
	}
}
