package net.yak.stalk;

import java.util.Scanner;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Parser extends Talk {
	Lex x;
	Terp terp;
	static Pattern instVarP;
	static Pattern localIdentP;
	static Pattern globalIdentP;
	static Pattern stringLitP;
	static Pattern openStringLitP;
	static Pattern restStringLitP;
	static Pattern commentP;
	static Pattern doubleSingeQuote;
	static Pattern unaryP;
	static Pattern keywordP;
	static Pattern almostKeywordP;
	static Pattern multiKeywordP;
	static Expr[] emptyExprArray;
	static {
		instVarP = Pattern
				.compile("/[a-z][a-z_0-9]*", Pattern.CASE_INSENSITIVE);
		localIdentP = Pattern.compile("[a-z][a-z_0-9]?",
				Pattern.CASE_INSENSITIVE);
		globalIdentP = Pattern.compile("[a-z][a-z_0-9][a-z_0-9]+",
				Pattern.CASE_INSENSITIVE);

		stringLitP = Pattern.compile("'(''|[^'])*'");
		openStringLitP = Pattern.compile("'(''|[^'])*");
		restStringLitP = Pattern.compile("(''|[^'])*'");

		commentP = Pattern.compile("\"[^\"]*\"");

		doubleSingeQuote = Pattern.compile("''");
		unaryP = Pattern.compile("[a-z]([a-z_0-9][a-z_0-9])*",
				Pattern.CASE_INSENSITIVE);
		keywordP = Pattern.compile("[a-z][a-z_0-9]([a-z_0-9][a-z_0-9])*[:]?",
				Pattern.CASE_INSENSITIVE);
		almostKeywordP = Pattern.compile("[a-z][a-z_0-9:]*[:][a-z_0-9:]*",
				Pattern.CASE_INSENSITIVE); // Contains at least a colon.
		multiKeywordP = Pattern.compile(
				"([a-z][a-z_0-9]([a-z_0-9][a-z_0-9])*[:])+",
				Pattern.CASE_INSENSITIVE); // Colons required in multi form.
		emptyExprArray = new Expr[0];
	}

	public Parser(Terp terp, String s) {
		this.terp = terp;
		this.x = new Lex(omitComments(s));
	}

	public static Expr parse(Terp terp, String s) {
		Parser p = new Parser(terp, s);
		return p.parseExpr();
	}
	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();
	}
	static String omitComments(String a) {
		return replace(commentP, " ", a);
	}
	static String reduceDoubleSingleQuotes(String a) {
		return replace(doubleSingeQuote, "'", a).replace('_', ' ');
	}
	void skipDots() {
		while (x.w.equals(".")) {
			x.advance();
		}
	}

	Expr parseExpr() {
		Vector<Expr> v = new Vector<Expr>();
		skipDots();
		while (x.w.length() > 0) {
			if (x.w.equals("]") || x.w.equals(")"))
				break;

			Expr s = parseStmt();
			v.add(s);
			skipDots();
		}
		return new Expr.Seq(v.toArray(emptyExprArray));
	}

	Expr parseStmt() {
		// simple cascade, or return, or assign.
		// Look for simple assign, which can be '=' or '@'.
		if (x.w.endsWith("=") || x.w.endsWith("@")) {
			// Trim off the trailing '@' and convert to lower.
			String varName = x.w.substring(0, x.w.length() - 1).toLowerCase();
			x.advance();
			Expr cascade = parseChain();

			if (varName.startsWith("/")) {
				return new Expr.PutInstVar(varName.substring(1), cascade);
			} else {
				if (varName.length() > 2) {
					return new Expr.PutGlobalVar(varName, cascade);
				} else {
					return new Expr.PutLocalVar(varName, cascade);
				}
			}
		} else {
			return parseChain();
		}
	}
	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..
		// say("ParseChain: entry: %s", x.w);
		Expr expr = parseKeyed(null); // parseKeyed should parse an initial
		// receiver..
		// say("ParseChain: pre-entry-expr: %s", expr);
		// say("ParseChain: pre-while: %s", x.w);
		while (x.w.equals(",")) {
			x.advance();
			// say("ParseChain: in-while-advanced: %s", x.w);
			if (unaryP.matcher(x.w).matches()) {
				while (unaryP.matcher(x.w).matches()) {
					expr = new Expr.Send(expr, x.w.toLowerCase(),
							emptyExprArray);
					x.advance();
				}
				if (keywordP.matcher(x.w).matches()) {
					expr = parseKeyed(expr);
				}
			} else {
				expr = parseKeyed(expr);
			}

			// say("ParseChain: in-while-expr: %s", expr);
		}
		// say("ParseChain: return-expr: %s", expr);
		return expr;
	}
	Expr parseKeyed(Expr receiver) {
		if (receiver == null) {
			receiver = parseUnary();
		}
		String keywords = "";
		Vector<Expr> args = null; // lazy
		while (keywordP.matcher(x.w).matches()) {
			// say("KEYWORDP matched %s", x.w);
			String kw = x.w.toLowerCase(); // toLower & remove ":"
			if (kw.endsWith(":"))
				kw = kw.substring(0, kw.length() - 1);
			keywords += kw + ":";
			x.advance();
			Expr arg = parseUnary();
			if (args == null)
				args = new Vector<Expr>(); // lazy
			args.add(arg);
		}
		if (almostKeywordP.matcher(x.w).matches()) {
			// Common error in number of chars with colons.
			toss("Suspicious token, looks like a keyword, but isn't: <%s>", x.w);
		}
		if (args == null) {
			return receiver; // No keywords.
		} else {
			return new Expr.Send(receiver, keywords, args
					.toArray(emptyExprArray));
		}
	}

	Expr parseUnary() {
		Expr expr = parsePrim();
		while (unaryP.matcher(x.w).matches()) {
			expr = new Expr.Send(expr, x.w.toLowerCase(), emptyExprArray);
			x.advance();
		}
		return expr;
	}
	Expr parsePrim() {
		Expr z = null;
		String w = x.w;
		if (w.equals("/")) {
			z = new Expr.GetLocalVar("self");
		} else if (w.equals("//")) {
			toss("super not implemented yet.");
		} else if (w.matches("[0-9]+")) {
			z = new Expr.Lit(new Pro.Num(terp, Double.parseDouble(w)));
		} else if (instVarP.matcher(w).matches()) {
			z = new Expr.GetInstVar(w.toLowerCase().substring(1));
		} else if (globalIdentP.matcher(w).matches()) {
			z = new Expr.GetGlobalVar(w.toLowerCase());
		} else if (localIdentP.matcher(w).matches()) {
			z = new Expr.GetLocalVar(w.toLowerCase());
		} else if (stringLitP.matcher(w).matches()) {
			z = new Expr.Lit(new Pro.Str(terp, reduceDoubleSingleQuotes(w
					.substring(1, w.length() - 1))));
		} else if (openStringLitP.matcher(w).matches()) {
			String rest = x.sc.findWithinHorizon(restStringLitP, 0);
			w = w + rest;
			z = new Expr.Lit(new Pro.Str(terp, reduceDoubleSingleQuotes(w
					.substring(1, w.length() - 1))));
		} else if (w.equals("[")) {
			z = parseBlock();
		} else if (w.equals("(")) {
			z = parseParen();
		} else if (keywordP.matcher(w).matches()) {
			toss(
					"Misplaced keyword, was expecting a variable or literal or block: <%s>",
					w);
		} else if (almostKeywordP.matcher(w).matches()) {
			toss(
					"Misplaced thing that looks sort of like a keyword, was expecting a variable or literal or block: <%s>",
					w);
		} else {
			toss("Syntax Error in parsePrim: <%s>", w);
		}
		x.advance();
		return z;
	}
	Expr parseBlock() {
		assert x.w.equals("[");
		x.advance();
		Expr body = parseExpr();
		assert x.w.equals("]");
		return new Expr.Block(body, null);
	}
	Expr parseParen() {
		assert x.w.equals("(");
		x.advance();
		Expr body = parseExpr();
		assert x.w.equals(")");
		return new Expr.Send(new Expr.Block(body, null), "run", emptyExprArray);
	}

	public static class Lex extends Talk {
		public String w;
		Scanner sc;

		public Lex(String prog) {
			// say("LEX Constructed on tihs program: <%s>", prog);
			sc = new Scanner(prog);
			advance();
		}

		public void advance() {
			if (sc.hasNext()) {
				w = sc.next();
				// say("LEX advanced to <%s>", w);
			} else {
				w = "";
				// say("LEX advanced to END.");
			}
		}
	}
}
