package terse.vm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import terse.vm.Expr.Block;

public class Parser extends Static {
	Cls cls;
	Terp terp;
	TLex lex;
	HashMap<String, Integer> localVars; // maps to Local Var index.
	HashMap<String, String> localVarSpelling;
	int temp = 0;

	static Pattern doubleSingeQuote = Pattern.compile("''");

	// This is the public function to parse a string.
	public static Expr.Top parseMethod(Cls cls, String methName, String code) {
		Terp terp = cls.terp;
		Parser p = new Parser(cls, methName, code);
		int numArgs = 0;
		for (int i = 0; i < methName.length(); i++) {
			if (methName.charAt(i) == ':') {
				String paramName = Character
						.toString((char) ((int) 'a' + numArgs));
				p.localVars.put(paramName, numArgs);
				p.localVarSpelling.put(paramName, paramName);
				++numArgs;
			}
		}
		p.parseExpr(); // Once to learn names of variables.
		if (p.lex.w.trim().length() > 0) {
			terp.toss("Parser:  Leftover word after parsing: <%s>",
					p.lex.w.trim());
		}
		if (p.lex.sub.trim().length() > 0) {
			terp.toss("Parser:  Leftover junk after parsing: <%s>",
					p.lex.sub.trim());
		}
		p.lex = new TLex(terp, code); // Ugly.
		Expr expr = p.parseExpr(); // Again for code generation.
		Expr.Top top = new Expr.Top(p.localVars.size(), numArgs, p.localVars,
				p.localVarSpelling, cls, methName, expr);
		// say("parseMethod <<< %s.%s <<< %s", cls, methName, code);
		// say("parseMethod >>> %s", top);
		return top;
	}

	private Parser(Cls cls, String methName, String code) {
		this.cls = cls;
		this.terp = cls.terp;
		this.lex = new TLex(terp, code);
		this.localVars = new HashMap<String, Integer>();
		this.localVarSpelling = new HashMap<String, String>();
	}

	// private static String replace(Pattern p, String replacement, String a) {
	// Matcher m = p.matcher(a);
	// StringBuffer sb = new StringBuffer();
	// while (m.find()) {
	// m.appendReplacement(sb, replacement);
	// }
	// m.appendTail(sb);
	// return sb.toString();
	// }
	private void skipStmtEnders() { // i.e. dots & semicolons
		while (lex.endsStmt()) {
			lex.advance();
		}
	}

	private Expr parseExpr() {
		Expr[] v = emptyExprs;
		skipStmtEnders();
		while (lex.t != null) {
			if (lex.closesBlock() || lex.closesParen())
				break;

			Expr s = parseStmt();
			v = append(v, s);
			skipStmtEnders();
		}
		return new Expr.Seq(terp, v);
	}

	private Expr parseStmt() {
		if (lex.isAssignish()) {
			// We just peeked with a raw Pattern Matcher.
			// We did not consume any actual tokens.
			boolean isDestructuring = false;
			assert lex.t == Pat.NAME;
			String[] varNames = strs(lex.w);
			lex.advance();

			while (lex.isTupler()) {
				isDestructuring = true;
				lex.advance();

				if (lex.isAssign()) {
					// Trailing ',' before '=' is OK, especially for the
					// singleton case.
					break;
				} else if (lex.t == Pat.NAME) {
					varNames = append(varNames, lex.w);
					lex.advance();
				} else {
					terp.toss(
							"Was expecting a Name or '@' or '=', but got %s <%s>",
							lex.t, lex.w);
				}
			}
			assert lex.isAssign();
			lex.advance();
			Expr chain = parseTuple();

			int[] indices = emptyInts;
			for (int i = 0; i < varNames.length; i++) {
				String varName = varNames[i];
				String varKey = varName.toLowerCase();
				indices = append(indices, grokAssignedVariable(varName, varKey));
				// if (index < 0) {
				// putters = append(putters, new Expr.PutInstVar(varName,
				// cls.allVars.get(varKey), chain));
				// } else {
				// putters = append(putters, new Expr.PutLocalVar(varName,
				// index, chain));
				// }
			}

			if (isDestructuring) {
				++temp;
				String tempVar = fmt("temporaryzzz%dzzz", temp);
				int tempIndex = grokAssignedVariable(tempVar, tempVar);
				Expr getTemp = new Expr.GetLocalVar(terp, tempVar, tempIndex);

				// A block with one param, the temp Var.
				Expr.Seq seq = new Expr.Seq(terp, emptyExprs);
				Expr.Block block = new Block(seq, strs(tempVar),
						ints(tempIndex));

				for (int i = 0; i < varNames.length; i++) {
					int index = indices[i];
					Expr gotAt = new Expr.Send(getTemp, "at:",
							exprs(new Expr.Lit(terp.newNum(i))));
					if (index < 0) {
						seq.body = append(
								seq.body,
								new Expr.PutInstVar(varNames[i], cls.allVars
										.get(varNames[i].toLowerCase()), gotAt));
					} else {
						seq.body = append(seq.body, new Expr.PutLocalVar(
								varNames[i], index, gotAt));
					}
				}
				return new Expr.Send(block, "run:", exprs(chain));

			} else {
				assert indices.length == 1;
				int index = indices[0];
				if (index < 0) {
					return new Expr.PutInstVar(varNames[0],
							cls.allVars.get(varNames[0].toLowerCase()), chain);
				} else {
					return new Expr.PutLocalVar(varNames[0], index, chain);
				}
			}
		} else {
			return parseTuple();
		}
	}

	// When a variable is assigned, it must be either Inst or Local.
	// If it is in the class's allVars, then it is Inst.
	// Otherwise if we don't have it as a local yet, add it.
	// Returns -1 if inst var, or Local Var index if local.
	private int grokAssignedVariable(String varName, String varKey) {
		// Determine Inst or Local. Globals are never assigned.
		if (cls.allVars.containsKey(varKey)) {
			return -1;
		}
		int index;
		if (localVars.containsKey(varKey)) {
			index = localVars.get(varKey);
		} else {
			// Does not exist yet, so make it.
			index = localVars.size();
			localVars.put(varKey, index);
			localVarSpelling.put(varKey, varName);
		}
		return index;
	}

	private Expr parseTuple() {
		boolean itsATuple = false;
		Expr[] v = exprs(parseChain());
		while (lex.isTupler()) {
			itsATuple = true;
			lex.advance();
			if (lex.endsStmt() || lex.closesBlock() || lex.closesParen()) {
				break;
			}
			v = append(v, parseChain());
		}
		if (itsATuple) {
			// Use "[ ... ]vec" to construct the Tuple.
			return new Expr.Send(new Expr.Block(new Expr.Seq(terp, v),
					emptyStrs, emptyInts), "vec", emptyExprs);
		} else {
			// It's not a tuple at all.
			return v[0];
		}
	}

	private Expr parseChain() {
		// Normal SmallTalk "cascades" using ";", but we don't.
		// Instead we "chain" with "--".
		// Whereas cascade sends subsequent messages to the same receiver,
		// this chaining sends subsequent messages to the previous result.
		Expr unary = parseUnary();
		Expr expr = parseKeyed(unary, 999);
		while (lex.isChain()) {
			lex.advance();
			while (lex.t == Pat.NAME) { // parse Unary messages at front of
				// chain.
				expr = new Expr.Send(expr, lex.w.toLowerCase(), emptyExprs);
				lex.advance();
			}
			if (lex.t == Pat.KEYWORD) {
				expr = parseKeyed(expr, 999);
			}
		}
		return expr;
	}

	private Expr parseKeyed(Expr receiver, int cap) {
		if (lex.t == Pat.KEYWORD && lex.keywordStrength() < cap) {
			while (lex.t == Pat.KEYWORD && lex.keywordStrength() < cap) {
				String keywords = "";
				Expr[] args = emptyExprs;
				int strength = lex.keywordStrength();
				while (lex.t == Pat.KEYWORD
						&& lex.keywordStrength() == strength) {
					keywords += lex.keywordName().toLowerCase() + ":";
					lex.advance();
					Expr unary = parseUnary();
					Expr arg = parseKeyed(unary, strength); // Do keywords of
					// smaller strength.
					args = append(args, arg);
				}
				receiver = new Expr.Send(receiver, keywords, args);
			}
			return receiver;
		} else {
			return receiver; // No keyword with strength less than cap.
		}
	}

	private Expr parseUnary() {
		Expr expr = parsePrim();
		while (lex.t == Pat.NAME) {
			expr = new Expr.Send(expr, lex.w.toLowerCase(), emptyExprs);
			lex.advance();
		}
		return expr;
	}

	private Expr parsePrim() {
		Expr z = null;
		switch (lex.t) {
		case RESERVED:
			String lower = lex.w.toLowerCase();
			if (lower.equals("nil"))
				z = new Expr.Lit(terp.instNil);
			else if (lower.equals("se"))
				z = new Expr.GetSelf(terp);
			else if (lower.equals("self"))
				z = new Expr.GetSelf(terp);
			else if (lower.equals("sup"))
				z = new Expr.Lit(terp.instSuper);
			else if (lower.equals("super"))
				z = new Expr.Lit(terp.instSuper);
			break;
		case NUMBER:
			z = new Expr.Lit(new Pro.Num(terp, Double.parseDouble(lex.w)));
			break;
		case STRING:
			// For old regexp that included quotes:
			// z = new Expr.Lit(new Pro.Str(terp,
			// reduceDoubleSingleQuotes(lex.w.substring(1, lex.w.length() -
			// 1))));
			// For new one that leaves them out:
			z = new Expr.Lit(new Pro.Str(terp, lex.w));
			break;
		case OTHER:
			z = lex.opensBlock() ? parseBlock()
					: lex.opensParen() ? parseParen() : null;
			break;
		case NAME:
			z = lex.isMacro() ? parseMacro() : getVar(lex.w,
					lex.w.toLowerCase());
			break;
		case KEYWORD:
			terp.toss(
					"Misplaced keyword, was expecting a variable or literal or block: <%s>",
					lex.w);
			break;
		}
		if (z == null) {
			terp.toss("Syntax Error in parsePrim: <%s>", lex.w);
		}
		// NOTA BENE: All of the above cases do not advance. We do it here.
		lex.advance();
		return z;
	}

	private Expr getVar(String varName, String varKey) {
		// NOTICE this is using "allVars" rather than JustAtThisClass vars.
		// TODO: factor this out.
		// Determine Inst or Local. Globals are never assigned.
		if (cls.allVars.containsKey(varKey)) {
			return new Expr.GetInstVar(terp, varName, cls.allVars.get(varKey));
		}
		int index;
		if (localVars.containsKey(varKey)) {
			index = localVars.get(varKey);
			return new Expr.GetLocalVar(terp, varName, index);
		} else {
			return new Expr.GetGlobalVar(terp, varKey);
		}
	}

	private Expr parseBlock() {
		assert lex.opensBlock();
		lex.advance();
		String[] params = emptyStrs;
		int[] paramsLocalIndex = emptyInts;
		while (lex.marksBlockParam()) {
			String marker = lex.w; // Just for error message.
			lex.advance(); // Beyond the marker.
			if (lex.t != Pat.NAME) {
				terp.toss(
						"Expected a variable name for Block Parameter after <%s> but got <%s>",
						marker, lex.w);
			}
			params = append(params, lex.w); // Keep case, since this is
			Integer localIndex = localVars.get(lex.w.toLowerCase());
			// On first pass, we don't know locals, so say -1, to make sure it
			// is never eval'ed.
			int index = (localIndex == null) ? -1 : localIndex.intValue();
			paramsLocalIndex = append(paramsLocalIndex, index);
			// definition point.
			// Params count as Assignments, from the point of view of
			// allocating local variables. Forbid using inst var for param.
			int negativeIfInstanceVar = grokAssignedVariable(lex.w,
					lex.w.toLowerCase());
			if (negativeIfInstanceVar < 0) {
				terp.toss(
						"Instance variable <%s> not allowed as param in Blk.",
						lex.w);
			}

			lex.advance(); // Beyond the var name.
		}
		Expr body = parseExpr();
		// say("@@@@@@@ body=<%s>", body);
		assert lex.closesBlock() : fmt("lex=%s<%s> body=<%s>", lex.t, lex.w,
				body);
		// Do not advance; parsePrim() does that after switch().
		return new Expr.Block(body, params, paramsLocalIndex);
	}

	private Expr parseParen() {
		assert lex.opensParen();
		lex.advance();
		if (lex.closesParen()) {
			// The unit tuple (). It's an empty but mutable vector, not a
			// literal.
			return new Expr.Send(new Expr.Block(new Expr.Seq(terp, emptyExprs),
					emptyStrs, emptyInts), "vec", emptyExprs);
		}
		Expr body = parseExpr();
		assert lex.closesParen();
		// Do not advance; parsePrim() does that after switch().
		return new Expr.Send(new Expr.Block(body, emptyStrs, emptyInts), "run",
				emptyExprs);
	}

	private Expr parseMacro() {
		ArrayList<String> names = new ArrayList<String>();
		ArrayList<Expr.Block> blocks = new ArrayList<Expr.Block>();
		while (lex.t == Pat.NAME && lex.isMacro()) {
			names.add(lex.w);
			lex.advance();
			Expr.Block b = (Expr.Block) parseBlock();
			blocks.add(b);
		}
		final int sz = names.size();
		if (sz == 1) {
			String name = names.get(0);
			return new Expr.Send(blocks.get(0), name.toLowerCase(), emptyExprs);
		} else {
			String name = names.get(0) + names.get(1);
			name += ":";
			for (int i = 2; i < sz; i++) {
				String nextName = names.get(0);
				name += nextName + ":";
			}
			Expr[] args = new Expr[sz - 1];
			for (int i = 1; i < sz; i++)
				args[i - 1] = blocks.get(i); // Leave off the first.
			return new Expr.Send(blocks.get(0), name.toLowerCase(), args);
		}
	}

	enum Pat {
		WHITE("\\s\\s*"), COMMENT("\"[^\"]*\""), STRING("'[^']*'"), NUMBER(
				"-?[0-9][0-9]*(\\.[0-9][0-9]*)?([Ee][+-]?[0-9][0-9]*)?"), NAME(
				"[A-Za-z][A-Za-z0-9]*"), KEYWORD(
				"([A-Za-z][A-Za-z0-9]*)\\s*([:/]+)"), OTHER("--|."), RESERVED(
				"(self|se|super|sup|nil)\\b", Pattern.CASE_INSENSITIVE);

		Pattern p;

		Pat(String s) {
			this.p = Pattern.compile(s, 0);
		}

		Pat(String s, int flags) {
			this.p = Pattern.compile(s, flags);
		}

		Matcher matcher(String x) {
			// say("Attempting %s pattern %s ON %s", this, p, x);
			return p.matcher(x);
		}
	}

	static final class TLex extends Static {
		static Pattern ASSIGNISH = Pattern
				.compile("[A-Za-z][A-Za-z0-9,\\s]*[@=]");

		Terp terp;

		// The parser will use these fields:
		public String w; // current word
		public Pat t; // type that matched
		public Matcher m;

		String putback_w;
		Pat putback_t;
		Matcher putback_m;
		String putback_sub;

		// These will be used for finding errors.
		final String prog; // program to parse
		String sub; // Remaining substring of p to parse

		TLex(Terp t, String prog) {
			this.terp = t;
			this.prog = prog;
			this.w = "";
			this.t = null;
			this.m = null;
			this.putback_w = null;
			this.putback_t = null;
			this.putback_m = null;
			this.putback_sub = null;
			this.sub = prog;
			advance();
		}

		// You can back up one token.
		void back() {
			w = putback_w;
			t = putback_t;
			m = putback_m;
			sub = putback_sub;
		}

		private boolean attempt(Pat pat) {
			m = pat.matcher(sub);
			if (m.lookingAt()) {
				w = sub.substring(m.start(), m.end()); // Detected word.
				sub = sub.substring(m.end() - m.start()); // New substring of p
				// to match.
				t = pat; // Which pattern matched.

				return true;
			}
			return false;
		}

		void advance() {
			// Save state before advancing, so we can go back.
			putback_w = w;
			putback_t = t;
			putback_m = m;
			putback_sub = sub;

			while (true) {
				if (sub.length() == 0) {
					// EOF condition.
					w = "";
					t = null;
					return;
				}
				// Skip white space & comments.
				if (!attempt(Pat.WHITE) && !attempt(Pat.COMMENT))
					break;
			}

			if (sub.length() == 0) {
				// EOF condition.
				w = "";
				t = null;
				return;
			}

			// Check for reserved words first.
			// Must check KEYWORD before NAME, or NAME will overtake it.
			if (attempt(Pat.RESERVED) || attempt(Pat.KEYWORD)
					|| attempt(Pat.NAME) || attempt(Pat.NUMBER)) {
				return;
			} else if (attempt(Pat.STRING)) {
				StringBuffer sb = new StringBuffer();
				while (true) {
					sb.append(w.substring(1, w.length() - 1)); // leave off
					// initial &
					// final <'>
					// If another string starts immediately, it was a doubled
					// <''>.
					if (Pat.STRING.matcher(sub).lookingAt()) {
						sb.append("'"); // Add one <'> for the doubled <''>
						// input.
						attempt(Pat.STRING);
					} else {
						break;
					}
				}
				w = sb.toString();
				return;
			} else if (attempt(Pat.OTHER)) {
				return;
			} else {
				StringBuffer sb = new StringBuffer();
				for (int i = 0; i < prog.length(); i++) {
					sb.append(fmt("%d ", (int) prog.charAt(i)));
				}
				terp.say("PROG = %s", sb);
				terp.say("Entire Program (%d chars): <%s>", prog.length(), prog);
				terp.say("Previous Token: %s <%s>", putback_t, putback_w);
				terp.toss(
						"Parser Error at char <%d>, remaining unparsed code: <%s>",
						prog.length() - sub.length(), sub);
			}
		}

		boolean isAssignish() {
			return ASSIGNISH.matcher(w + sub).lookingAt();
		}

		boolean endsStmt() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == '.' || c == ';';
		}

		boolean marksBlockParam() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == ':' || c == '/' || c == '=' || c == '@';
		}

		boolean isAssign() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == '=' || c == '@';
		}

		boolean isAssignedVaraible() {
			if (t != Pat.NAME)
				return false;
			advance();
			boolean z = isAssign();
			back();
			return z;
		}

		boolean isTupler() {
			if (t != Pat.OTHER)
				return false;
			return w.equals(",");
		}

		boolean isChain() {
			if (t != Pat.OTHER)
				return false;
			return w.equals("--");
		}

		boolean opensBlock() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == '[' || c == '{' || c == '!';
		}

		boolean closesBlock() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == ']' || c == '}' || c == '?';
		}

		boolean opensParen() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == '(';
		}

		boolean closesParen() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == ')';
		}

		/** Name without the trailing colons or slashes. */
		String keywordName() {
			assert (t == Pat.KEYWORD);
			return m.group(1);
		}

		/** Number of trailing colons or slashes */
		int keywordStrength() {
			assert (t == Pat.KEYWORD);
			return m.group(2).length();
		}

		/** Look ahead at the next token, to distinguish MACRO. */
		boolean isMacro() {
			assert (t == Pat.NAME);
			advance();
			boolean z = opensBlock();
			back();
			return z;
		}
	}

	public static String[] separateSlashedMethodIntoNameAndAbbrev(String s) {
		// TODO
		return null;
	}
}
