/**
 * Copyright 2010 Bing Ran<bing_ran@hotmail.com> 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 
 * use this file except in compliance with the License. You may obtain a copy 
 * of the License at http://www.apache.org/licenses/LICENSE-2.0.
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
package cn.bran.japid.compiler;

import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import cn.bran.japid.classmeta.AbstractTemplateClassMetaData;
import cn.bran.japid.classmeta.InnerClassMeta;
import cn.bran.japid.classmeta.MimeTypeEnum;
import cn.bran.japid.compiler.JapidParser.Token;
import cn.bran.japid.compiler.Tag.TagIf;
import cn.bran.japid.template.ActionRunner;
import cn.bran.japid.template.JapidTemplate;
import cn.bran.japid.template.RenderResult;
import cn.bran.japid.util.DirUtil;
import cn.bran.japid.util.WebUtils;

/**
 * based on the original code from the Play! Framework
 * 
 * the parent class for all three type compilers: regular template compiler, the
 * LayoutCompiler and the TagCompiler.
 * 
 * @author original Play! authors
 * @author Bing Ran<bing_ran@hotmail.com>
 * 
 */
public abstract class JapidAbstractCompiler {
	public static final String DO_LAYOUT = "doLayout";
	private static final String ELVIS = "?:";
	// pattern: } else if xxx {
	static final String ELSE_IF_PATTERN_STRING = "\\s*\\}\\s*else\\s*if\\s+([^\\(].*)\\s*";
	static final Pattern ELSE_IF_PATTERN = Pattern.compile(ELSE_IF_PATTERN_STRING);
	static final String OPEN_ELSE_IF_PATTERN_STRING = "\\s*else\\s*if\\s+([^\\(].*)\\s*";
	static final Pattern OPEN_ELSE_IF_PATTERN = Pattern.compile(OPEN_ELSE_IF_PATTERN_STRING);
	static final String OPEN_ELSE_STRING = "\\s*else\\s*";
	static final String SPACE_OR_NONE = "\\s*";
	static final String SPACE_AT_LEAST_ONE = "\\s+";

	// please trim the string before applying the pattern
	// the idea is to substitute the open for with "each" tag
	static final String OPEN_FOR_PATTERN_STRING = "for\\s+([^\\(].+)\\s*:\\s*(.+[^\\{])";
	static final Pattern OPEN_FOR_PATTERN = Pattern.compile(OPEN_FOR_PATTERN_STRING);

	// pattern: if xxx {
	static final String OPEN_IF_PATTERN1 = "if\\s+[^\\(].*";

	private static final String JAPID_RESULT = "cn.bran.play.JapidResult";

	private static final String ARGS = "args";

	protected static final String HTML = ".html";
	// private static final String DO_BODY = "doBody";
	protected static final String SPACE = " ";
	protected static final String NEW_LINE = "\n";
	protected JapidTemplate template;
	protected JapidParser parser;
	protected boolean doNextScan = true;

	// the tagsStack only tracks the regular tags, particularly not including
	// the open if
	private Stack<Tag> tagsStack = new Stack<Tag>();
	// the shadow is not used to keep the stacking of open if and regular tag
	private Stack<Tag> tagsStackShadow = new Stack<Tag>();

	protected int tagIndex;
	protected boolean skipLineBreak;
	protected boolean useWithPlay = true;

	public void compile(JapidTemplate t) {
		template = t;
		getTemplateClassMetaData().setOriginalTemplate(t.name);
		getTemplateClassMetaData().useWithPlay = this.useWithPlay;
		hop();
	}

	/**
	 * 
	 */
	protected void parse() {
		// Parse
		loop: for (;;) {

			if (doNextScan) {
				stateBeforePreviousState = previousState;
				previousState = state;
				state = parser.nextToken();
			} else {
				doNextScan = true;
			}

			String token = parser.getToken();

			switch (state) {
			case EOF:
				break loop;
			case PLAIN:
				plain(token);
				break;
			case VERBATIM:
				plain(token);
				break;
			case SCRIPT:
				script(token);
				break;
			case CLOSING_BRACE:
				closingBrace(token);
				break;
			case SCRIPT_LINE:
				if (previousState == Token.PLAIN && stateBeforePreviousState == Token.SCRIPT_LINE) {
					String spacer = this.getTemplateClassMetaData().removeLastSingleEmptyLine();
					if (spacer != null) {
						Tag currentScope = this.tagsStack.peek();
						// currentScope.bodyBuffer.append(text);
						int lastIndex = currentScope.bodyTextList.size() - 1;
						currentScope.bodyTextList.remove(lastIndex);
						currentScope.bodyTextList.set(lastIndex - 1, spacer);
					}
				}
				scriptline(token);
				break;
			case EXPR:
				expr(token, false);
				break;
			case EXPR_NATURAL_ESCAPED:
				expr(token, true);
				break;
			case EXPR_ESCAPED:
				expr(token, true);
				break;
			case MESSAGE:
				message(token);
				break;
			case ACTION:
				action(token, false);
				break;
			case ABS_ACTION:
				action(token, true);
				break;
			case COMMENT:
				skipLineBreak = true;
				break;
			case START_TAG:
				startTag(buildTag(token));
				break;
			case END_TAG:
				String tagName = token.trim();
				if (tagsStack.isEmpty()) {
					throw new JapidCompilationException(template, currentLine, "#{/" + tagName + "} is not opened.");
				}
				Tag tag = popStack();
				endTag(tag);
				break;
			case TEMPLATE_ARGS:
				templateArgs(token);
				break;
			}
		}
	}

	/**
	 * @return
	 */
	private Tag popStack() {
		// tagsStackShadow.pop();
		return tagsStack.pop();
	}

	protected void closingBrace(String token) {
		// ok a } after some space in a line
		// treat it as `}
		print("}");
	}

	protected void plain(String token) {
		String text = token.replace("\\", "\\\\").replaceAll("\"", "\\\\\"");
		// text = text.replace("``", "`"); // escaped `, already done by parser
		if (skipLineBreak && text.startsWith(NEW_LINE)) {
			text = text.substring(1);
		}

		// add static content to classmetadata and print the ref to the
		// generated source code
		if (this.getTemplateClassMetaData().getTrimStaticContent()) {
			String r = text.trim();
			if (r.length() == 0)
				return;
			else {
				text = text.trim();
			}
		}
		String lines = composeValidMultiLines(text);
		String ref = this.getTemplateClassMetaData().addStaticText(lines, text);
		if (ref != null) {
			// print the static content via the varaible
			// print("p(" + ref + ");");
			// print the static content directly
			print("p(" + lines + ");");

			markLine(parser.getLineNumber());
			println();
		}
	}

	/**
	 * break a line with newlines to multiple lines valid in java source code
	 * 
	 * <pre>
	 * "line1\n" +
	 * "line2\n"  + 
	 * "line3";
	 * 
	 * </pre>
	 * 
	 * @param src
	 * @return
	 */
	public static String composeValidMultiLines(String text) {
		// multi-line
		String[] lines = text.split(NEW_LINE, 10000);
		String result = "";
		for (int i = 0; i < lines.length; i++) {
			String line = lines[i];
			if (line.length() > 0 && (int) line.charAt(line.length() - 1) == 13) {
				// remove the last newline
				line = line.substring(0, line.length() - 1);
			}

			result += "\"" + line;

			if (i == lines.length - 1 && !text.endsWith(NEW_LINE)) {
				// last line
				result += "\"";
			} else if (i == lines.length - 1 && line.equals("")) {
				result += "\"";
			} else {
				// regular line
				result += "\\n\" + \n";
			}

			// markLine(parser.getLine() + i);
		}
		String emptySuffix = " + \n\"\"";
		if (result.endsWith(emptySuffix)) {
			result = result.substring(0, result.length() - emptySuffix.length());
		}
		return result;
	}

	protected abstract void startTag(Tag tag);

	protected void println() {
		// print(NEW_LINE);
		Tag currentScope = this.tagsStack.peek();
		currentScope.bodyTextList.add("");
		currentLine++;
	}

	/**
	 * always append to the last line
	 * 
	 * @param text
	 */
	protected void print(String text) {
		Tag currentScope = this.tagsStack.peek();
		// currentScope.bodyBuffer.append(text);
		int lastIndex = currentScope.bodyTextList.size() - 1;
		String lastLine = currentScope.bodyTextList.get(lastIndex);
		lastLine += text;
		currentScope.bodyTextList.set(lastIndex, lastLine);
		// else if (this.currentInnerClassName != null)
		// this.currentInnerClassRenderBody.append(text);
		// else
		// mainRenderBodySource.append(text);
	}

	protected void println(String text) {
		print(text);
		println();
		int i = 0;
		while (i++ < indentLevel) {
			print("\t");
		}
	}

	protected void markLine(int line) {
		if (!this.getTemplateClassMetaData().getTrimStaticContent()) {
			print("// line " + line);
		}

		template.linesMatrix.put(currentLine, line);
	}

	protected void scriptline(String token) {
		// String line = token.trim();
		// if ()
		script(token);
	}

	protected void script(String token) {
		String[] lines = new String[] { token };
		if (token.indexOf(NEW_LINE) > -1) {
			lines = parser.getToken().split(NEW_LINE);
		}

		for (int i = 0; i < lines.length; i++) {
			String line = lines[i];// .trim();
			if (startsWithIgnoreSpace(line, "import")) {
				getTemplateClassMetaData().addImportLine(line);
			} else if (startsWithIgnoreSpace(line, "//")) {
				// ignore
			} else if (startsWithIgnoreSpace(line, "extends")) {
				String layout = line.trim().substring("extends".length()).trim();
				// remove quotes if they present

				boolean hasParam = false;
				int p = 0;
				for (; p < layout.length(); p++) {
					char c = layout.charAt(p);
					if (c == ' ' || c == '\t' || c == '(') {
						hasParam = true;
						break;
					}
				}

				if (!hasParam) {
					layout = layout.replace("'", "");
					layout = layout.replace("\"", "");
					layout = removeEndingString(layout, ";");
					layout = removeEndingString(layout, HTML);
					layout = removeEndingString(layout, "/");
					if (layout.startsWith(".")) {
						// new feature allow extends .sub.layout.html
						if (layout.startsWith("./")) {
							layout = getTemplateClassMetaData().packageName + layout.substring(1);
						} else {
							layout = getTemplateClassMetaData().packageName + layout;
						}
					}
					getTemplateClassMetaData().superClass = layout.replace('/', '.');
				} else {
					String layoutName = layout.substring(0, p);
					layoutName = layoutName.replace("'", "");
					layoutName = layoutName.replace("\"", "");
					layoutName = layoutName.replace('/', '.');
					layoutName = removeEndingString(layoutName, HTML);

					// due to similarity, let's borrow a tag parsing
					Tag tag = new TagInvocationLineParser().parse(layoutName + layout.substring(p));

					if (tag.tagName.startsWith(".")) {
						// partial path, use current package as the root and
						// append the path to it
						tag.tagName = getTemplateClassMetaData().packageName + tag.tagName;
					}
					getTemplateClassMetaData().superClass = tag.tagName;
					getTemplateClassMetaData().superClassRenderArgs = tag.args;
				}
			} else if (startsWithIgnoreSpace(line, "contentType")) {
				// TODO: should also take standard tag name: Content-Type
				String contentType = line.trim().substring("contentType".length()).trim().replace("'", "").replace("\"", "");
				if (contentType.endsWith(";"))
					contentType = contentType.substring(0, contentType.length());
				getTemplateClassMetaData().setContentType(contentType);
			} else if (startsWithIgnoreSpace(line, "setHeader")) {
				String headerkv = line.trim().substring("setHeader".length()).trim();
				String[] split = headerkv.split("[ |\t]");
				if (split.length < 2) {
					throw new RuntimeException("setHeaader must take a key and a value string");
				}
				String name = split[0];
				String value = headerkv.substring(name.length()).trim();
				getTemplateClassMetaData().setHeader(name, value);
			} else if (startsWithIgnoreSpace(line, ARGS)) {
				String args = line.trim().substring(ARGS.length()).trim().replace(";", "").replace("'", "").replace("\"", "");
				Tag currentTag = this.tagsStack.peek();
				currentTag.callbackArgs = args;
			} else if (startsWithIgnoreSpace(line, "trim")) {
				String sw = line.trim().substring("trim".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
				if ("on".equals(sw) || "true".equals(sw)) {
					getTemplateClassMetaData().trimStaticContent();
				}
			} else if (startsWithIgnoreSpace(line, "stopwatch")) {
				String sw = line.trim().substring("stopwatch".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
				if ("on".equals(sw))
					getTemplateClassMetaData().turnOnStopwatch();
				// Tag currentTag = this.tagsStack.peek();
				// currentTag.bodyArgsString = contentType;
			} else if (startsWithIgnoreSpace(line, "log") || line.trim().equals("log")) {
				String args = line.trim().substring("log".length()).trim().replace(";", "");
				if (args.trim().length() == 0)
					args = "\"\"";
				String logLine = "System.out.println(\"" + this.template.name.replace('\\', '/') + "(line " + (parser.getLineNumber() + i)
						+ "): \" + " + args + ");";
				println(logLine);
			} else if (startsWithIgnoreSpace(line, "invoke")) {
				String args = line.trim().substring("invoke".length()).trim().replace(";", "");
				doActionInvokeDirective(args);
			} else if (startsWith(line, "a")) { // `a == `invoke, the a must be
												// the first char to avoid
												// collision
				String args = line.substring(2).trim().replace(";", "");
				doActionInvokeDirective(args);
			} else if (startsWithIgnoreSpace(line, "suppressNull") || line.trim().equals("suppressNull")) {
				String npe = line.trim().substring("suppressNull".length()).trim().replace(";", "").replace("'", "").replace("\"", "");
				if ("on".equals(npe) || "yes".equals(npe) || "".equals(npe))
					getTemplateClassMetaData().suppressNull();
			} else if (line.trim().equals("abstract")) {
				getTemplateClassMetaData().setAbstract(true);
			} else if (startsWithIgnoreSpace(line, "tag")) {
				String tagline = line.trim().substring(4);
				doTagDirective(tagline);
			} else if (startsWith(line, "t")) { // `t == `tag, the t must be the
												// first char to avoid collision
				String tagline = line.substring(2);
				doTagDirective(tagline);
			} else if (startsWithIgnoreSpace(line, "each") || startsWithIgnoreSpace(line, "Each")) {
				// support one line type of tag invocation.
				Tag tag = buildTagDirective(line);
				tag.tagName = "Each";
				tag.hasBody = true;
				startTag(tag);
			} else if (startsWithIgnoreSpace(line, "set")) {
				Tag set = buildTagDirective(line);
				if (line.contains(":"))
					set.hasBody = false;
				else
					set.hasBody = true;
				startTag(set);

				if (!set.hasBody) { // one liner.
					set = popStack();
				}
			} else if (startsWithIgnoreSpace(line, "get")) {
				Tag get = buildTagDirective(line);
				get.hasBody = false;
				startTag(get);
				get = popStack();
			} else if (startsWithIgnoreSpace(line, "def")) {
				// a function definition block
				Tag get = buildTagDirective(line);
				get.hasBody = true;
				startTag(get);
			} else if (line.trim().startsWith("noplay")) {
				// template is play independent
				getTemplateClassMetaData().useWithPlay = false;
			} else if (line.trim().equalsIgnoreCase("xml")) {
				// template is play independent
				getTemplateClassMetaData().setContentType(MimeTypeEnum.xml.header);
			} else if (line.trim().equalsIgnoreCase("json")) {
				// template is play independent
				getTemplateClassMetaData().setContentType(MimeTypeEnum.json.header);
			} else if (line.trim().equalsIgnoreCase("css")) {
				// template is play independent
				getTemplateClassMetaData().setContentType(MimeTypeEnum.css.header);
			} else if (line.trim().equalsIgnoreCase("txt") || line.trim().equalsIgnoreCase("text")) {
				// template is play independent
				getTemplateClassMetaData().setContentType(MimeTypeEnum.txt.header);
			} else if (line.trim().equalsIgnoreCase("js") || line.trim().equalsIgnoreCase("javascript")) {
				// template is play independent
				getTemplateClassMetaData().setContentType(MimeTypeEnum.js.header);
			} else if (line.trim().startsWith("verbatim")) {
				parser.verbatim = true;
				Tag get = buildTagDirective(line);
				get.hasBody = true;
				startTag(get);
			} else if (startsWithIgnoreSpace(line, "if")) {
				// `if expr {, the last { is optional
				String expr = line.trim();
				if (expr.matches(OPEN_IF_PATTERN1)) {
					// get the expression
					String trim = expr.substring(2).trim();
					boolean negative = trim.startsWith("!");
					if (negative)
						trim = trim.substring(1).trim();
					if (trim.endsWith("{")) {
						// semi-open
						expr = removeEndingString(trim, "{").trim();
						expr = "if(" + (negative ? "!" : "") +  "asBoolean(" + expr + ")) {";
					} else {
						// true open. out a shadow tag, since we want reuse the ` as the end delimitor
						Tag.TagIf iftag = new Tag.TagIf(trim, parser.getLineNumber());
						pushToStack(iftag);
						expr = "if(" + (negative ? "!" : "") + "asBoolean(" + trim + ")) {";
					}
					print(expr);
					markLine(parser.getLineNumber() + i);
					println();
				} else {
					// plain Java if
					print(expr);
					markLine(parser.getLineNumber() + i);
					println();
				}
			} else if (line.matches(ELSE_IF_PATTERN_STRING)) {
				// semi open
				String expr = line.trim();
				Matcher matcher = ELSE_IF_PATTERN.matcher(line);
				if (matcher.matches()) {
					expr = matcher.group(1).trim();
					boolean negative = expr.startsWith("!");
					if (negative)
						expr = expr.substring(1).trim();
					expr = "} else if(" + (negative ? "!" : "") +  "asBoolean(" + removeEndingString(expr, "{") + ")) {";
					print(expr);
					markLine(parser.getLineNumber() + i);
					println();
				} else {
					print(expr);
					markLine(parser.getLineNumber() + i);
					println();
				}
			} else if (line.matches(OPEN_ELSE_IF_PATTERN_STRING)) {
				// open
				String expr = line.trim();
				Matcher matcher = OPEN_ELSE_IF_PATTERN.matcher(line);
				if (matcher.matches()) {
					expr = matcher.group(1).trim();
					boolean negative = expr.startsWith("!");
					if (negative)
						expr = expr.substring(1).trim();

					// end previous if shadow and star a new one
					Tag tagShadow = tagsStackShadow.peek();
					if (tagShadow instanceof TagIf) {
						tagsStackShadow.pop();
						// to close an open if
						// start a new if
						Tag.TagIf iftag = new Tag.TagIf(expr, parser.getLineNumber());
						pushToStack(iftag);
						expr = "} else if(" + (negative ? "!" : "") +  "asBoolean(" + expr + ")) {";
					} else {
						throw new RuntimeException("the open \"else if\" statement is not properly matched to a previous if");
					}
				} 
				print(expr);
				markLine(parser.getLineNumber() + i);
				println();
			} else if (line.matches(OPEN_ELSE_STRING)) {
				Tag tagShadow = tagsStackShadow.peek();
				if (tagShadow instanceof TagIf) {
					tagsStackShadow.pop();
					// to close an open if
					// start a new if
					print("} else {");
					markLine(parser.getLineNumber() + i);
					println();
					Tag.TagIf iftag = new Tag.TagIf("", parser.getLineNumber());
					pushToStack(iftag);
				} else {
					throw new RuntimeException("the open \"else\" statement is not properly matched to a previous if");
				}
			} else if (line.trim().matches(OPEN_FOR_PATTERN_STRING)) {
				// simply replace it with a "each" tag call
				String expr = line.trim();
				Matcher matcher = OPEN_FOR_PATTERN.matcher(expr);
				if (matcher.matches()) {
					String instanceDecl = matcher.group(1);
					instanceDecl = cleanDeclPrimitive(instanceDecl);
					String collection = matcher.group(2);
					expr = "each " + collection + " | " + instanceDecl;
					Tag tag = buildTagDirective(expr);
					tag.tagName = "Each";
					tag.hasBody = true;
					startTag(tag);
				} else {
					// should not happen
					print(expr);
					markLine(parser.getLineNumber() + i);
					println();
				}
			} else if (line.trim().length() == 0) {
				// a single ` empty line, treated as the closing for `tag
				try {
					Tag tagShadow = tagsStackShadow.peek();
					if (tagShadow.isRoot()) {
//						System.out.println("");
					} else {
						tagShadow = tagsStackShadow.pop();

						if (tagShadow instanceof TagIf) {
							// to close an open if
							print("}");
							markLine(parser.getLineNumber() + i);
							println();
						} else {
							if (!tagsStack.empty()) {
								Tag tag = tagsStack.peek();
								if (!tag.isRoot()) {
									tag = popStack();
									endTag(tag);
								}
							}
						}
					}
				} catch (Exception e) {
					System.out.println(e);
				}
			} else {
				// OK plain Java code
				print(line);
				markLine(parser.getLineNumber() + i);
				println();
			}
		}
		skipLineBreak = true;
	}

	/**
	 * @param string
	 * @return
	 */
	private String removeEndingString(String string, String ending) {
		if (string.endsWith(ending))
			return string.substring(0, string.lastIndexOf(ending));
		else
			return string;
	}

	/**
	 * @param line
	 * @param string
	 * @return
	 */
	protected static boolean startsWithIgnoreSpace(String line, String string) {
		line = line.trim();
		return line.startsWith(string + " ") || line.startsWith(string + "\t");
	}

	/**
	 * @param line
	 * @param string
	 * @return
	 */
	private boolean startsWith(String line, String string) {
		return line.startsWith(string + " ") || line.startsWith(string + "\t");
	}

	/**
	 * @param args
	 */
	private void doActionInvokeDirective(String args) {
		if (!getTemplateClassMetaData().useWithPlay) {
			throw new RuntimeException("action invocation is only supported in Play environment. ");
		} else {
			this.getTemplateClassMetaData().setHasActionInvocation();
			if (args.trim().length() == 0)
				args = "whatyouwantoinvoke()";
			printActionInvocation(args);
		}
	}

	/**
	 * @param tagline
	 */
	private void doTagDirective(String tagline) {
		Tag tag = buildTagDirective(tagline);
		startTag(tag);
		if (!tag.hasBody) { // one liner.
			tag = tagsStackShadow.pop();
			tag = popStack();
			endTag(tag);
		}
	}

	protected void expr(String token, boolean escape) {
		String expr = token;

		int i = token.indexOf(ELVIS);
		String substitute = null;
		if (i > 0) {
			expr = token.substring(0, i);
			substitute = token.substring(i + ELVIS.length()).trim();
			if (substitute.startsWith("\""))
				substitute = substitute.substring(1);
			if (substitute.endsWith("\""))
				substitute = substitute.substring(0, substitute.length() - 1);
		}
		
		if (escape) {
			expr = "escape(" + expr + ")";
		}
		
		if (substitute != null) {
			// trap any null or empty string and use the substitute
			printLine("try {" +
					" Object o = " + expr + "; " + 
					"if (o.toString().length() ==0) { " + 
					"p(\"" + substitute + "\"); } " + 
					"else { p(o); } } " +
					"catch (NullPointerException npe) { " +
					"p(\"" + substitute + "\"); }");
//			printLine("try { Object o = expr; p(" + expr + "); } " +
//					"catch (NullPointerException npe) { " +
//					"p(\"" + substitute + "\"); }");
		}
		else {
			if (getTemplateClassMetaData().suppressNull)
				printLine("try { p(" + expr + "); } catch (NullPointerException npe) {}");
			else
				printLine("p(" + expr + ");");
		}
	}

	private void printLine(String string) {
		print(string);
		markLine(parser.getLineNumber());
		println();
	}

	protected void message(String token) {
		String expr = token.trim().replace('\'', '"');
		print(";p(getMessage(" + expr + "));");
		markLine(parser.getLineNumber());
		println();
	}

	/**
	 * TODO: remove all the dependency on the Play classes
	 * 
	 * @param token
	 * 
	 * @param absolute
	 */
	protected void action(String token, boolean absolute) {
		String action = token.trim();
		if (action.matches("^'.*'$") || action.matches("^\".*\"$")) {
			// static content like @{'my.css'}
			action = action.replace('\'', '"');
			// remove Play dependecy
			if (absolute) {
				// print("p(request.getBase() + play.mvc.Router.reverseWithCheck("
				// + action + ", play.Play.getVirtualFile(" + action + ")));");
				print("p(lookupStaticAbs(" + action + "));");
			} else {
				// print("p(play.mvc.Router.reverseWithCheck(" + action +
				// ", play.Play.getVirtualFile(" + action + ")));");
				print("p(lookupStatic(" + action + "));");
			}
		} else {
			if (!action.endsWith(")")) {
				action = action + "()";
			}
			// extract params if any
			int indexOfParam = action.indexOf("(");
			if (indexOfParam < 1) {
				throw new TemplateSyntaxException("action arguments must be enclosed in parenthesis.", template.name, action,
						this.currentLine);
			}

			String actionPart = action.substring(0, indexOfParam).trim();

			// extract the param list part
			String params = action.substring(indexOfParam + 1);
			params = params.substring(0, params.length() - 1).trim();
			if (params.length() == 0)
				params = "new Object[]{}";

			if (absolute) {
				print("p(lookupAbs(\"" + actionPart + "\", " + params + "));");
			} else {
				print("p(lookup(\"" + actionPart + "\", " + params + "));");
			}
		}
		markLine(parser.getLineNumber());
		println();
	}

	protected void hop() {

		String source = template.source;
		Tag rootTag = new Tag() {
			{
				tagName = ROOT_TAGNAME;
				startLine = 0;
				hasBody = true;
			}
		};

		this.tagsStack.push(rootTag);
		this.tagsStackShadow.push(rootTag);

		this.parser = new JapidParser(source);

		String tempName = template.name.replace("-", "_");// .replace('.', '_');
		String contentTypeHeader = MimeTypeEnum.getHeader(tempName.substring(tempName.lastIndexOf('.')));
		getTemplateClassMetaData().setContentType(contentTypeHeader);
		tempName = DirUtil.mapSrcToJava(tempName);
		tempName = tempName.substring(0, tempName.lastIndexOf(".java"));
		tempName = tempName.replace('\\', '/');
		// if (tempName.endsWith(HTML)) {
		// tempName = tempName.substring(0, tempName.indexOf(HTML));
		// }

		// extract path
		int lastSep = tempName.lastIndexOf('/');
		if (lastSep > 0) {
			String path = tempName.substring(0, lastSep);
			path = path.replace('/', '.');
			path = path.replace('\\', '.');
			getTemplateClassMetaData().packageName = path;
			getTemplateClassMetaData().setClassName(tempName.substring(lastSep + 1));
		} else {
			getTemplateClassMetaData().setClassName(tempName);
		}

		parse();
		// for (String n : BranTemplateCompiler.extensionsClassnames) {
		// println(" } ");
		// }
		// println("} }");
		// println("}");

		Tag tag = popStack();
		if (!tagsStack.empty())
			throw new RuntimeException("There is(are) " + tagsStack.size() + " unclosed tag(s) in the template: " + this.template.name);
		// remove print nothing statement to save a few CPU cycles
		this.getTemplateClassMetaData().body = tag.getBodyText().replace("p(\"\")", "").replace("pln(\"\")", "pln()");
		postParsing(tag);
		template.javaSource = this.getTemplateClassMetaData().generateCode();

	}

	/**
	 * add anything before the java source generation
	 */
	protected void postParsing(Tag tag) {
		this.getTemplateClassMetaData().renderArgs = tag.callbackArgs;
	}

	abstract protected AbstractTemplateClassMetaData getTemplateClassMetaData();

	protected void templateArgs(String token) {
		Tag currentTag = this.tagsStack.peek();
		String args = token;
		currentTag.callbackArgs = args;
	}

	/**
	 * @return
	 */
	protected Tag buildTag(String token) {
		String tagText = token.trim().replaceAll(NEW_LINE, SPACE);

		boolean hasBody = !parser.checkNext().endsWith("/");

		Tag tag = new TagInvocationLineParser().parse(tagText);
		if (tag.tagName == null || tag.tagName.length() == 0)
			throw new RuntimeException("tag name was empty: " + tagText);

		if (tag.tagName.startsWith(".")) {
			// partial path, use current package as the root and append the path
			// to it
			tag.tagName = getTemplateClassMetaData().packageName + tag.tagName;
		}
		tag.startLine = parser.getLineNumber();
		tag.hasBody = hasBody;
		tag.tagIndex = tagIndex++;
		return tag;
	}

	/**
	 * e.g.:
	 * 
	 * <pre>
	 * `tag myTag a, c |String c
	 * </pre>
	 * 
	 * @return
	 */
	protected Tag buildTagDirective(String token) {
		String tagText = token.trim();

		Tag tag = new TagInvocationLineParser().parse(tagText);
		if (tag.tagName == null || tag.tagName.length() == 0)
			throw new RuntimeException("tag name was empty: " + tagText);

		if (tag.tagName.startsWith(".")) {
			// partial path, use current package as the root and append the path
			// to it
			tag.tagName = getTemplateClassMetaData().packageName + tag.tagName;
		}
		tag.startLine = parser.getLineNumber();
		tag.tagIndex = tagIndex++;
		return tag;
	}

	/**
	 * @param actionInvocationWithCache
	 */
	protected static String createActionRunner(String actionInvocationWithCache) {
		List<String> params = new TagArgsParser(actionInvocationWithCache).split();
		String action = params.get(0);
		// remove the argument part to extract action string as key base
		int left = action.indexOf('(');
		if (left < 1) {
			throw new RuntimeException("invoke: action arguments must be enclosed in parenthesis.");
		}
		int right = action.lastIndexOf(')');
		String actionPath = "\"" + action.substring(0, left) + "\"";
		String args = action.substring(left + 1, right).trim();
		String ttl = "\"\"";

		if (params.size() >= 2) {
			ttl = params.get(1);
			if (params.size() > 2) {
				for (int i = 2; i < params.size(); i++) {
					args += "," + params.get(i);
				}

				if (args.startsWith(","))
					args = args.substring(1);

				if (args.endsWith(","))
					args = args.substring(0, args.length() - 1);
			}
		}
		return (createActionRunner(action, ttl, actionPath, args));
	}

	protected void printActionInvocation(String action) {
		println(createActionRunner(action));
	}

	/**
	 * @param tag
	 */
	protected void regularTagInvoke(Tag tag) {
		if ("extends".equals(tag.tagName)) {
			String layoutName = tag.args;
			layoutName = layoutName.replace("'", "");
			layoutName = layoutName.replace("\"", "");
			layoutName = removeEndingString(layoutName, HTML);
			layoutName = removeEndingString(layoutName, "/");

			getTemplateClassMetaData().superClass = layoutName.replace('/', '.');
		} else if (tag.tagName.equals("invoke")) {
			invokeAction(tag);
		} else {
			String tagVar = "_" + tag.getTagVarName() + tag.tagIndex;
			if (!tag.hasBody) {
				String tagline = tagVar + ".setOut(getOut()); ";
				tagline += tagVar + ".render(" + tag.args + ");";
				println(tagline);
			}
		}
	}

	/**
	 * @param tag
	 */
	protected void invokeAction(Tag tag) {
		if (tag.hasBody) {
			throw new JapidCompilationException(template, currentLine, "invoke tag cannot have a body. Must be ended with /}");
		}

		this.getTemplateClassMetaData().setHasActionInvocation();
		String action = tag.args;
		printActionInvocation(action);
	}

	/**
	 * @param tag
	 */
	protected void endRegularTag(Tag tag) {
		if (tag.hasBody) {
			InnerClassMeta inner = this.getTemplateClassMetaData().addCallTagBodyInnerClass(tag.tagName, tag.tagIndex, tag.callbackArgs,
					tag.getBodyText());
			if (inner == null)
				System.out.println(tag.tagName + " not allowed to have instance of this tag");
			String tagVar = "_" + tag.getTagVarName() + tag.tagIndex;
			String tagLine = tagVar + ".setOut(getOut()); "; // make sure to use the current string builder
			tagLine += tagVar + ".render(" + (WebUtils.asBoolean(tag.args) ? tag.args + ", " : "") + inner.getAnonymous() + ");";
			println(tagLine);
		} else {
			// for simple tag call without call back:
			this.getTemplateClassMetaData().addCallTagBodyInnerClass(tag.tagName, tag.tagIndex, null, null);
		}
	}

	/**
	 * define a string returning method from a block
	 * 
	 * @param tag
	 */
	protected void def(Tag tag) {
	}

	protected void endDef(Tag tag) {
		if (tag.hasBody) { // must always
			this.getTemplateClassMetaData().addDefTag(tag);
		}
	}

	protected void endTag(Tag tag) {
		String lastInStack = tag.tagName;
		String tagName = lastInStack;
		// if (!lastInStack.equals(tagName)) {
		// throw new JapidCompilationException(template, tag.startLine, "#{" +
		// tag.tagName + "} is not closed.");
		// }
		if (tagName.equals("def")) {
			endDef(tag);
		} else if (tagName.equals("doBody")) {
		} else if (tagName.equals("extends")) {
		} else if (tagName.equals("get")) {
			// } else if (tagName.equals("set")) { // the set is handled in the
			// JapidTemplateCompiler endTagSpecial()
		} else if (tagName.equals("invoke")) {
		} else if (tagName.equals(DO_LAYOUT)) {
		} else if (endTagSpecial(tag)) {
		} else {
			endRegularTag(tag);
		}
		markLine(tag.startLine);
		println();
		// tagIndex--;
		skipLineBreak = true;
	}

	/**
	 * sub class can detect special tag and return true to indicate the tag has
	 * been processed.
	 * 
	 * @param tag
	 * @return
	 */
	protected boolean endTagSpecial(Tag tag) {
		return false;
	}

	static String createActionRunner(String action, String ttl, String base, String keys) {
		String actionEscaped = action.replace("\"", "\\\"");
		String controllerActionPart = action.substring(0, action.indexOf('('));
		int lastDot = controllerActionPart.lastIndexOf('.');
		String controllerName = controllerActionPart.substring(0, lastDot);
		String actionName = controllerActionPart.substring(lastDot + 1);

		if (ttl == null) {
			String template =
					"		%s.put(getOut().length(), new %s() {\n" +
							"			@Override\n" +
							"			public %s run() {\n" +
							"				try {\n" +
							"					play.classloading.enhancers.ControllersEnhancer.ControllerInstrumentation.initActionCall();\n" +
							"					%s;\n" +
							"				} catch (%s jr) {\n" +
							"					return jr.getRenderResult();\n" +
							"				}\n" +
							"				throw new RuntimeException(\"No render result from running: %s\");\n" +
							"			}\n" +
							"		});";
			return String.format(template,
					AbstractTemplateClassMetaData.ACTION_RUNNERS,
					ActionRunner.class.getName(),
					RenderResult.class.getName(),
					action,
					JAPID_RESULT,
					actionEscaped);
		} else {
			// hardcode the cache action runner name to avoid dependency on the
			// Play jar

			String template =
					"		%s.put(getOut().length(), new %s(%s, %s, %s, %s) {\r\n" +
							"			@Override\r\n" +
							"			public void runPlayAction() throws %s {\r\n" +
							// "				super.checkActionCacheFor(%s.class, \"%s\");\n"
							// +
							"				%s; //\r\n" +
							"			}\r\n" +
							"		});\r\n";
			return String.format(template,
					AbstractTemplateClassMetaData.ACTION_RUNNERS,
					"cn.bran.play.CacheablePlayActionRunner",
					ttl,
					controllerName + ".class",
					"\"" + actionName + "\"",
					"".equals(keys) ? "\"\"" : keys,
					JAPID_RESULT,
					action
					);
			// return String.format(template,
			// AbstractTemplateClassMetaData.ACTION_RUNNERS,
			// "cn.bran.play.CacheablePlayActionRunner",
			// ttl,
			// base,
			// "".equals(keys) ? "\"\"" : keys,
			// JAPID_RESULT,
			// controllerName,
			// actionName,
			// action
			// );
		}

	}

	protected int currentLine = 1;

	protected int indentLevel = 0;
	JapidParser.Token state;
	JapidParser.Token previousState;
	JapidParser.Token stateBeforePreviousState;

	public JapidAbstractCompiler() {
		super();
	}

	public void setUseWithPlay(boolean play) {
		this.useWithPlay = play;
	}

	/**
	 * @param tag
	 */
	protected void pushToStack(Tag tag) {
		tagsStackShadow.push(tag);
		if (!(tag instanceof TagIf))
			tagsStack.push(tag);

	}

	/**
	 * check if the data type is primitive in a var declaration
	 * 
	 * @param decl
	 *   int i,  int[] ia, etc
	 * @return
	 */
	static String cleanDeclPrimitive(String decl) {
		decl = decl.trim();
		int i = decl.length();
		String var = "";
		String type = "";
		while (--i >=0) {
			char c = decl.charAt(i);
			if (c == ' ' || c == '\t') {
				var = decl.substring(i + 1); 
				type = decl.substring(0, i).trim();
				break;
			}
		}
		if ("int".equals(type)) {
			decl = "Integer " + var;
		}
		else if ("long".equals(type)) {
			decl = "Long " + var;
		}
		else if ("short".equals(type)) {
			decl = "Short " + var;
		}
		else if ("byte".equals(type)) {
			decl = "Byte " + var;
		}
		else if ("float".equals(type)) {
			decl = "Float " + var;
		}
		else if ("double".equals(type)) {
			decl = "Double " + var;
		}
		else if ("char".equals(type)) {
			decl = "Character " + var;
		}
		else if ("boolean".equals(type)) {
			decl = "Boolean " + var;
		}
			
		return decl;
	}
}