// Goldie: GOLD Engine for D
// Written in the D programming language.

module /+S:PACKAGE+/goldie/+E:PACKAGE+/.token;

/+P:SET_VERSION+/

import std.string;
import std.traits;

import semitwist.treeout;
import semitwist.util.all;

import goldie.base;
import goldie.exception;
import goldie.lang;

/+S:REM+/
enum CommentType
{
	None,
	Line,
	Block,
}

enum TokenToStringMode
{
	Compact,           // Omit all whitespace, error and comment tokens.
	CompactWithSpaces, // Like Compact, but adds a space between each token.
	
	Smart,             // Default: Like Compact, but adds a space between two
	                   //   tokens whenever the last character of the first
					   //   token and the first character of the second token
					   //   are both either alphanumeric or an underscore.

	// Full doesn't currently work after the parse phase because
	// ignored tokens are not currently preserved by the parser.
	//TODO: Fix this
	Full,              // Includes all whitespace, error and comment tokens.
}
/+E:REM+/

version(GoldieStaticLang)
{
	import goldie.token;
	import /+P:PACKAGE+/DUMMY.lang;

	public template /+P:TOKEN_CLASSNAME+/DUMMY()
	{
		alias Base_/+P:TOKEN_CLASSNAME+/ /+P:TOKEN_CLASSNAME+/DUMMY;
	}

	public template /+P:TOKEN_CLASSNAME+/DUMMY(string staticName=null)
	{
		/+P:TOKEN_TEMPLATE_1P+/
	}

	public template /+P:TOKEN_CLASSNAME+/DUMMY(SymbolType staticSymbolType, string staticName)
	{
		/+P:TOKEN_TEMPLATE_2P+/
	}

	private template /+P:TOKEN_CLASSNAME+/DUMMY(string staticName, int staticRuleId)
	{
		/+P:TOKEN_TEMPLATE_RULE+/
	}

	public template /+P:IS_CORRECT_TOKEN_FUNCNAME+/DUMMY(string token, int type, string name)
	{
		static if(token == name)
			static if(/+P:LANG_CLASSNAME+/DUMMY./+S:TO:STATIC_IDENT+/isSymbolNameAmbiguous/+E:TO:STATIC_IDENT+/(token))
				static assert(false, "Token name '"~staticName~
					"' is ambiguous, please specify SymbolType"~
					" (ex: /+P:TOKEN_CLASSNAME+/!(SymbolType.Terminal, \"+\"))\n"~
					"Possible types: ["~/+P:LANG_CLASSNAME+/DUMMY.symbolTypesByName(name).goldSymbolTypesToString()~"]");
			else
				enum bool /+P:IS_CORRECT_TOKEN_FUNCNAME+/DUMMY = true;
		else
			enum bool /+P:IS_CORRECT_TOKEN_FUNCNAME+/DUMMY = false;
	}
	
	public template /+P:IS_CORRECT_TOKEN_FUNCNAME+/DUMMY(token, int type, string name)
	{
		static if(is( token:_/+P:TOKEN_CLASSNAME+/!(cast(SymbolType)type, name) ))
			enum bool /+P:IS_CORRECT_TOKEN_FUNCNAME+/DUMMY = true;
		else
			enum bool /+P:IS_CORRECT_TOKEN_FUNCNAME+/DUMMY = false;
	}
	
	public template /+P:TOKEN_CLASSNAME+/DUMMY(string staticName, subTokenTypes...)
		if(subTokenTypes.length != 1 || !isIntegral!(typeof(subTokenTypes[0])))
	{
		alias _Token_calc!(SymbolType.NonTerminal, staticName, /+P:RULE_ID_OF_FUNCNAME+/DUMMY!(staticName, subTokenTypes)) /+P:TOKEN_CLASSNAME+/DUMMY;
	}
	
	//TODO? Move this to goldie.lang
	public template /+P:RULE_ID_OF_FUNCNAME+/DUMMY(string staticName, subTokenTypes...)
	{
		/+P:RULE_ID_OF+/
	}
	
	public template /+P:SUBTOKENTYPE_NAME+/DUMMY(int ruleId, int index)
	{
		/+P:SUBTOKENTYPE_TEMPLATE+/
	}
	
	private class _/+P:TOKEN_CLASSNAME+/(SymbolType staticSymbolType, string staticName, int staticRuleId) : _/+P:TOKEN_CLASSNAME+/!(staticSymbolType, staticName)
	{
		// StringOf is a workaround for DMD Bug #1748
		//TODO: expand that "..."
		static enum string StringOf = "/+P:TOKEN_CLASSNAME+/!("~fullSymbolTypeToString(staticSymbolType)~", "~staticName.stringof~", ...)";

		//TODO: This should take Token_{langaugeName}[], not Token[]
		public this(Token[] sub, Language lang)
		{
			super(sub, lang, staticRuleId);
		}

		public @property /+P:SUBTOKENTYPE_NAME+/DUMMY!(staticRuleId, index) sub(int index)()
		{
			return cast(/+P:SUBTOKENTYPE_NAME+/DUMMY!(staticRuleId, index))subX[index];
		}
	}

	private class Base_/+P:TOKEN_CLASSNAME+/ : Token
	{
		static enum string StringOf = "/+P:TOKEN_CLASSNAME+/";

		public this(Symbol symbol, Language lang, string content,
					string file="{unknown}", int line=0,
					int srcIndexStart=0, int srcIndexEnd=0,
					CommentType commentMode=CommentType.None,
					string debugInfo="")
		{
			super(
				symbol, lang, content,
				file, line,
				srcIndexStart, srcIndexEnd,
				commentMode,
				debugInfo
			);
		}
		
		public this(Symbol symbol, Token[] sub, Language lang, int ruleId)
		{
			super(symbol, sub, lang, ruleId);
		}
	}
}

//TODO: Add way to get token's rule in string form (like what's shown in dumpcgt)
//TODO? Make wrong ctor private in Token_calc!{symbol}? Make all ctors private in Token_calc?
/+S:TOKEN_INHERIT+/public class Token/+E:TOKEN_INHERIT+/
{
	// StringOf is a workaround for DMD Bug #1748
	version(GoldieStaticLang)
		static enum string StringOf = "Token_calc!("~fullSymbolTypeToString(staticSymbolType)~", "~staticName.stringof~")";

	/+S:REM+/
	private string content = "";
	mixin(getter!(int, "ruleId"));
	mixin(getter!(string, "debugInfo"));
	mixin(getter!(CommentType, "commentMode"));
	mixin(getter!("protected", SymbolType, "type"));
	mixin(getter!("protected", Symbol, "symbol"));
	mixin(getter!(Language, "lang"));
	/+E:REM+/
	
	/+P:TOKEN_CTOR_TERMFIRST+/
	public this(/+S:REM+/Symbol symbol, /+E:REM+/Language lang, string content,
	            string file="{unknown}", int line=0,
				int srcIndexStart=0, int srcIndexEnd=0,
				CommentType commentMode=CommentType.None,
				string debugInfo="")
	{
		/+S:REM+/
		mixin(initMember("debugInfo", "content"/+, "commentMode"+/));
		
		// Workaround for DMD Bug #2881
		this.commentMode = commentMode;
		
		mixin(initMemberX("this._%s = %s",
			"lang", "file", "line", "symbol",
			"srcIndexStart", "srcIndexEnd"
		));
		this._ruleId = -1;
		this.type = symbol.type;
		
		if(type == SymbolType.NonTerminal)
			throw new IllegalArgumentException("Called wrong constructor for NonTerminal");
		/+E:REM+/
		
		version(GoldieStaticLang)
		{
			/+P:SET_STATICID_TERM+/
			super(
				/+P:LANG_INSTNAME+/DUMMY.symbolTable[staticId],
				lang, content,
				file, line,
				srcIndexStart, srcIndexEnd,
				commentMode,
				debugInfo
			);
		}
	}
	
	/+P:BLOCK_ELSE+/
	
	public this(/+S:REM+/Symbol symbol, /+E:REM+/Token[] sub, Language lang, int ruleId)
	{
		/+S:REM+/
		//TODO: Ensure ruleId is valid for this symbol in both dynamic and static
		this._symbol = symbol;
		this._ruleId = ruleId;
		this._lang = lang;
		this.type = symbol.type;
		this.subX = sub;
		
		if(type != SymbolType.NonTerminal)
			throw new IllegalArgumentException("Called wrong constructor for token that isn't a NonTerminal");
		/+E:REM+/

		version(GoldieStaticLang)
		{
			/+P:SET_STATICID_NONTERM+/
			super(/+P:LANG_INSTNAME+/DUMMY.symbolTable[staticId], sub, lang, ruleId);
		}
	}
	/+P:TOKEN_CTOR_END+/
	/+S:REM+/

	// The sub-tokens of this token (if this is a NonTerminal)
	public Token[] subX;

	mixin(getterLazy!(int, "line", `
		if(type == SymbolType.NonTerminal && subX.length > 0)
			return firstLeaf.line;
		else
			return _line;
	`));
	
	mixin(getterLazy!(string, "file", `
		if(type == SymbolType.NonTerminal && subX.length > 0)
			return firstLeaf.file;
		else
			return _file;
	`));
	
	mixin(getterLazy!(int, "srcIndexStart", `
		if(type == SymbolType.NonTerminal && subX.length > 0)
			return firstLeaf.srcIndexStart;
		else
			return _srcIndexStart;
	`));
	
	mixin(getterLazy!(int, "srcIndexEnd", `
		if(type == SymbolType.NonTerminal && subX.length > 0)
			return lastLeaf.srcIndexEnd;
		else
			return _srcIndexEnd;
	`));
	
	mixin(getterLazy!(Token, "firstLeaf", `
		if(type == SymbolType.NonTerminal && subX.length > 0)
		{
			foreach(Token subToken; subX)
			{
				if(subToken.type != SymbolType.NonTerminal || subToken.subX.length > 0)
					return subToken.firstLeaf;
			}
		}

		return this;
	`));
	
	mixin(getterLazy!(Token, "lastLeaf", `
		if(type == SymbolType.NonTerminal && subX.length > 0)
		{
			foreach_reverse(Token subToken; subX)
			{
				if(subToken.type != SymbolType.NonTerminal || subToken.subX.length > 0)
					return subToken.lastLeaf;
			}
		}

		return this;
	`));
	
	public @property int srcLength()
	{
		return srcIndexEnd - srcIndexStart;
	}

	public @property string typeName()
	{
		return symbolTypeToString(type);
	}

	public @property string name()
	{
		return _symbol.name;
	}

	//TODO? Change-to/suppliment-with something that returns Token_blah!(SymbolType.NonTerminal, "foo")
	mixin(getterLazy!(string, "fullName", `
		return "%s.%s".format(typeName, name);
	`));
	
	public @property int id()
	{
		return _symbol.id;
	}

	public bool matches(string parentSymbol, string[] subSymbols...)
	{
		if(_lang.isSymbolNameAmbiguous(parentSymbol))
			throw new Exception(
				"Symbol '%s' is ambiguous, it could be any of the following types: %s\nmatches() doesn't yet support disambiguation of symbol names at runtime."
					.format(parentSymbol, _lang.symbolTypesStrByName(parentSymbol))
			);

		if(!_lang.isSymbolNameValid(parentSymbol))
			throw new Exception(
				"'%s' is not a valid symbol name"
					.format(parentSymbol)
			);

		bool isParentNonTerminal =
			_lang.symbolsByName(parentSymbol)[0].type == SymbolType.NonTerminal;
			
		if(type == SymbolType.NonTerminal && isParentNonTerminal)
			return ruleId == _lang.ruleIdOf(parentSymbol, subSymbols);
		else if(type != SymbolType.NonTerminal && !isParentNonTerminal)
			return parentSymbol == _symbol.name && subSymbols.length == 0;
		else
			return false;
	}
	
	public override string toString()
	{
		return toString(TokenToStringMode.Smart);
	}
	
	public string toString(TokenToStringMode mode)
	{
		switch(mode)
		{
		case TokenToStringMode.Compact:
			return toStringCompact();
			
		case TokenToStringMode.CompactWithSpaces:
			return toStringCompactWithSpaces();
			
		case TokenToStringMode.Smart:
			return toStringSmart();
			
		case TokenToStringMode.Full:
			return toStringFull();
			
		default:
			throw new InternalException("Unknown TokenToStringMode: #%s".format(mode));
		}
	}
	
	public string toStringCompact()
	{
		string ret;
		
		switch(type)
		{
		case SymbolType.NonTerminal:
			foreach(Token tok; subX)
				ret ~= tok.toStringCompact();
			break;
			
		case SymbolType.Terminal:
			ret = content;
			break;
			
		case SymbolType.Whitespace:
		case SymbolType.EOF:
		case SymbolType.CommentStart:
		case SymbolType.CommentEnd:
		case SymbolType.CommentLine:
		case SymbolType.Error:
			ret = "";
			break;
			
		default:
			throw new InternalException("Unknown SymbolType: #%s".format(type));
		}
		
		return ret;
	}
	
	public string toStringCompactWithSpaces()
	{
		string ret;
		
		switch(type)
		{
		case SymbolType.NonTerminal:
			string[] toks;
			foreach(Token tok; subX)
				toks ~= tok.toStringCompactWithSpaces();
			ret = toks.join(" ");
			break;
			
		case SymbolType.Terminal:
			ret = content;
			break;
			
		case SymbolType.Whitespace:
		case SymbolType.EOF:
		case SymbolType.CommentStart:
		case SymbolType.CommentEnd:
		case SymbolType.CommentLine:
		case SymbolType.Error:
			ret = "";
			break;
			
		default:
			throw new InternalException("Unknown SymbolType: #%s".format(type));
		}
		
		return ret;
	}

	public string toStringSmart()
	{
		string ret;
		
		switch(type)
		{
		case SymbolType.NonTerminal:
			string[] toks;
			foreach(Token tok; subX)
			{
				string tokStr = tok.toStringSmart();
				
				if(tokStr == "")
					continue;
					
				if( toks.length > 0 &&
				    contains(letters~digits~'_', tokStr[0]     ) &&
				    contains(letters~digits~'_', toks[$-1][$-1]) )
				{
					toks ~= " ";
				}

				toks ~= tokStr;
			}
			ret = toks.join("");
			break;
			
		case SymbolType.Terminal:
			ret = content;
			break;
			
		case SymbolType.Whitespace:
		case SymbolType.EOF:
		case SymbolType.CommentStart:
		case SymbolType.CommentEnd:
		case SymbolType.CommentLine:
		case SymbolType.Error:
			ret = "";
			break;
			
		default:
			throw new InternalException("Unknown SymbolType: #%s".format(type));
		}
		
		return ret;
	}

	public string toStringFull()
	{
		string ret;
		
		if(type == SymbolType.NonTerminal)
		{
			foreach(Token tok; subX)
				ret ~= tok.toStringFull();
		}
		else
			ret = content;
			
		return ret;
	}

	public TreeNode toTreeNode(size_t index=0)
	{
		auto start = srcIndexStart;
		auto node = new TreeNode( "%s: %s".format(index, name) );
		node.addAttribute("srcIndexStart", start);
		//node.addAttribute("srcIndexEnd", srcIndexEnd());
		node.addAttribute("srcLength", srcIndexEnd - start);
		node.addAttribute("line", line+1);
		node.addAttribute("type", typeName);
		node.addAttribute("symbol", name);
		if(debugInfo() != "")
			node.addAttribute("debugInfo", debugInfo());
		//node.addAttribute("content", toStringRaw());
		
		if(type == SymbolType.NonTerminal)
		foreach(size_t i, Token tok; subX)
			node.addContent(tok.toTreeNode(i));
		
		return node;
	}
	
	public TreeNode toPsuedoTreeNode()
	{
		auto node = new TreeNode(toString(TokenToStringMode.Full));
		node.addAttribute("symbol", fullName);

		if(type == SymbolType.NonTerminal)
		foreach(Token tok; subX)
			node.addContent(tok.toPsuedoTreeNode());
		
		return node;
	}
	/+E:REM+/
}
