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

module goldie.lang;

import tango.io.Stdout;
import tango.io.device.File;
import tango.text.Util;

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

import goldie.base;
import goldie.file;
import goldie.lexer;
import goldie.parser;

enum GoldCommentType
{
	None,
	Line,
	Block,
}

//TODO? Optionally allow wchar and dchar
public class TokGold
{
	private GoldSymbol symbol;
	private char[] content = "";
	mixin(getter!(char[], "debugInfo"));
	mixin(getter!(GoldCommentType, "commentMode"));
	mixin(getter!(GoldSymbolType, "type"));
	
	public this(GoldSymbol symbol, char[] content,
	            char[] file="{unknown}", int line=1,
				int srcIndexStart=0, int srcIndexEnd=0,
				GoldCommentType commentMode=GoldCommentType.None,
				char[] debugInfo="")
	{
		mixin(initMember!(symbol, debugInfo, content/+, commentMode+/));
		
		// Workaround for DMD Bug #2881
		this.commentMode = commentMode;
		
		//TODO: Come up with way to do initMember on these
		this._file = file;
		this._line = line;
		this._srcIndexStart = srcIndexStart;
		this._srcIndexEnd = srcIndexEnd;
		
		this.type = /*cast(GoldSymbolType)*/symbol.type;
		
		if(type == GoldSymbolType.NonTerminal)
			throw new Exception("Called wrong constructor for NonTerminal");
	}
	
	public this(GoldSymbol symbol, TokGold[] subTokens)
	{
		this.type = /*cast(GoldSymbolType)*/symbol.type;
		mixin(initMember!(symbol, subTokens));
		
		if(type != GoldSymbolType.NonTerminal)
			throw new Exception("Called wrong constructor for token that isn't a NonTerminal");
	}

	// Maybe D2's alias this will eventually be able to clean this up...?
	private TokGold[] subTokens;
	public TokGold opIndex(size_t i)
	{
		return subTokens[i];
	}
	public size_t length()
	{
		return subTokens.length;
	}
	public TokGold[] opSlice()
	{
		return subTokens[];
	}
	public TokGold[] opSlice(size_t i1, size_t i2)
	{
		return subTokens[i1..i2];
	}
	// %^#@& opApply...
	int opApply(int delegate(ref size_t, ref TokGold) dg)
	{
		int result=0;
		foreach(size_t i, TokGold tok; subTokens)
		{
			result = dg(i, tok);
			if(result)
				break;
		}
		return result;
	}
	int opApply(int delegate(ref int, ref TokGold) dg)
	{
		return opApply(
			delegate int(ref size_t i, ref TokGold tok)
			{ int i2 = i; return dg(i2, tok); }
		);
	}
	int opApply(int delegate(ref TokGold) dg)
	{
		return opApply(
			delegate int(ref size_t i, ref TokGold tok)
			{ return dg(tok); }
		);
	}
	int opApplyReverse(int delegate(ref size_t, ref TokGold) dg)
	{
		int result=0;
		foreach_reverse(size_t i, TokGold tok; subTokens)
		{
			result = dg(i, tok);
			if(result)
				break;
		}
		return result;
	}
	int opApplyReverse(int delegate(ref int, ref TokGold) dg)
	{
		return opApplyReverse(
			delegate int(ref size_t i, ref TokGold tok)
			{ int i2 = i; return dg(i2, tok); }
		);
	}
	int opApplyReverse(int delegate(ref TokGold) dg)
	{
		return opApplyReverse(
			delegate int(ref size_t i, ref TokGold tok)
			{ return dg(tok); }
		);
	}

	mixin(getterLazy!(int, "line"));
	public int _line_gen()
	{
		if(type == GoldSymbolType.NonTerminal && length > 0)
			return firstLeaf.line;
		else
			return _line;
	}
	
	mixin(getterLazy!(char[], "file"));
	public char[] _file_gen()
	{
		if(type == GoldSymbolType.NonTerminal && length > 0)
			return firstLeaf.file;
		else
			return _file;
	}
	
	mixin(getterLazy!(int, "srcIndexStart"));
	public int _srcIndexStart_gen()
	{
		if(type == GoldSymbolType.NonTerminal && length > 0)
			return firstLeaf.srcIndexStart;
		else
			return _srcIndexStart;
	}
	
	mixin(getterLazy!(int, "srcIndexEnd"));
	public int _srcIndexEnd_gen()
	{
		if(type == GoldSymbolType.NonTerminal && length > 0)
			return lastLeaf.srcIndexEnd;
		else
			return _srcIndexEnd;
	}
	
	//TODO? Convert the rest of these to getterLazy
	mixin(getterLazy!(TokGold, "firstLeaf"));
	private TokGold _firstLeaf_gen()
	{
		if(type == GoldSymbolType.NonTerminal && subTokens.length > 0)
		{
			foreach(TokGold subToken; subTokens)
			{
				if(subToken.type != GoldSymbolType.NonTerminal || subToken.length > 0)
					return subToken.firstLeaf;
			}
		}

		return this;
	}
	
	mixin(getterLazy!(TokGold, "lastLeaf"));
	private TokGold _lastLeaf_gen()
	{
		if(type == GoldSymbolType.NonTerminal && subTokens.length > 0)
		{
			foreach_reverse(TokGold subToken; subTokens)
			{
				if(subToken.type != GoldSymbolType.NonTerminal || subToken.length > 0)
					return subToken.lastLeaf;
			}
		}

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

	public char[] typeName()
	{
		return goldSymbolTypeToString(type);
	}

	public wchar[] name()
	{
		return symbol.name.dup;
	}

	mixin(getterLazy!(wchar[], "fullName"));
	public wchar[] _fullName_gen()
	{
		return "{}.{}"w.sformat(typeName, name);
	}
	
	public int id()
	{
		return symbol.id;
	}

	//TODO: Can I do toString(bool raw=false)? (Ie, will Stdout.formatln("{}", this) still work?)
	//TODO? Get rid of the .dup on content for efficiency?
	public char[] toString()
	{
		char[] ret;
		
		switch(type)
		{
		case GoldSymbolType.NonTerminal:
			foreach(TokGold tok; subTokens)
				ret ~= tok.toString();
			break;
			
		case GoldSymbolType.Terminal:
			ret = content.dup;
			break;
			
		case GoldSymbolType.Whitespace:
		case GoldSymbolType.EOF:
		case GoldSymbolType.CommentStart:
		case GoldSymbolType.CommentEnd:
		case GoldSymbolType.CommentLine:
		case GoldSymbolType.Error:
			ret = "";
			break;
			
		default:
			throw new Exception("Unknown GoldSymbolType: #{}".sformat(type));
		}
		
		return ret;
	}
	
	// Doesn't currently work because ignored tokens
	// are not currently preserved by the parser.
	//TODO: Fix this
	public char[] toStringRaw()
	{
		char[] ret;
		
		if(type == GoldSymbolType.NonTerminal)
		{
			foreach(TokGold tok; subTokens)
				ret ~= tok.toStringRaw();
		}
		else
			ret = content.dup;
			
		return ret;
	}

	public TreeNode toTreeNode(size_t index=0)
	{
		auto start = srcIndexStart;
		auto node = new TreeNode( "{}: {}".sformat(index, name) );
		node.addAttribute("srcIndexStart", start);
		//node.addAttribute("srcIndexEnd", srcIndexEnd());
		node.addAttribute("srcLength", srcIndexEnd - start);
		node.addAttribute("line", line);
		node.addAttribute("type", typeName);
		node.addAttribute("symbol", name);
		if(debugInfo() != "")
			node.addAttribute("debugInfo", debugInfo());
		//node.addAttribute("content", toStringRaw());
		
		if(type == GoldSymbolType.NonTerminal)
		foreach(size_t i, TokGold tok; subTokens)
			node.addContent(tok.toTreeNode(i));
		
		return node;
	}
	
	public TreeNode toPsuedoTreeNode()
	{
		auto node = new TreeNode("{}".sformat(toStringRaw()));
		node.addAttribute("symbol", fullName);

		if(type == GoldSymbolType.NonTerminal)
		foreach(TokGold tok; subTokens)
			node.addContent(tok.toPsuedoTreeNode());
		
		return node;
	}
}

public class GoldLanguage
{
	//TODO: Think about turning these into accessors,
	//      or just move the lexing/parsing into this class
	public wchar[] name;
	public wchar[] ver;
	public wchar[] author;
	public wchar[] about;
	public bool    caseSensitive;
	public char[]  filename;
	
	public GoldSymbol[]    symbolTable;
	public GoldCharSet[]   characterSetTable;
	public GoldRule[]      ruleTable;
	public GoldDFAState[]  dfaTable;
	public GoldLALRState[] lalrTable;
	
	public int startSymbolIndex;
	public int initialDFAState;
	public int initialLALRState;
	
	public int eofSymbolIndex;
	public int errorSymbolIndex;

	private GoldLexer lexer;
	private GoldParser parser;
	
	public static bool outputDebugInfo=false;
	
	package this(char[] cgtFilename)
	{
		auto goldFile = new GoldFile(cgtFilename);
		filename = cgtFilename;
		
		name   = goldFile.grammarName;
		ver    = goldFile.grammarVersion;
		author = goldFile.grammarAuthor;
		
		mixin(initMemberFrom!(goldFile,
							  about, caseSensitive,
		                      startSymbolIndex, initialDFAState, initialLALRState,
	                          symbolTable, characterSetTable, ruleTable,
						      dfaTable, lalrTable));

		eofSymbolIndex   = getSymbolIndexByType(GoldSymbolType.EOF);
		errorSymbolIndex = getSymbolIndexByType(GoldSymbolType.Error);

		if(outputDebugInfo)
		{
			mixin(traceVal!("name", "ver", "author", "about", "caseSensitive",
							"startSymbolIndex", "initialDFAState", "initialLALRState", 
							"symbolTable.length", "characterSetTable.length",
							"ruleTable.length", "dfaTable.length", "lalrTable.length"
							));
			mixin(traceVal!("eofSymbolIndex", "errorSymbolIndex"));
			mixin(traceVal!("symbolTable[eofSymbolIndex]", "symbolTable[errorSymbolIndex]"));
			mixin(traceVal!("symbolTable"));

			Stdout.formatln("");
			mixin(traceVal!("symbolTable", "characterSetTable",
							"ruleTable", "dfaTable", "lalrTable"
							));
		}
	}

	public GoldLexer lexFile(char[] filename)
	{
		return lexCode(cast(char[])File.get(filename), filename);
	}

	public GoldLexer lexCode(char[] source, char[] filename="")
	{
		//if(lexer is null)
			lexer = new GoldLexer();
			
		lexer.process(source, this, filename);
		if(lexer.numErrors != 0)
			throw new Exception(lexer.errorMsg);

		return lexer;
	}
	
	public GoldParser parseTokens(TokGold[] tokens, char[] filename="", GoldLexer lexerUsed=null)
	{
		//if(parser is null)
			parser = new GoldParser();
			
		parser.process(tokens, this, filename, lexerUsed);
		return parser;
	}

	public GoldParser parseFile(char[] filename)
	{
		return parseTokens(lexFile(filename).tokens, filename, lexer);
	}

	public GoldParser parseCode(char[] source, char[] filename="")
	{
		return parseTokens(lexCode(source, filename).tokens, filename, lexer);
	}
	
	private int getSymbolIndexByType(GoldSymbolType type)
	{
		foreach(int i, GoldSymbol symbol; symbolTable)
			if(symbol.type == type)
				return i;
		throw new Exception("No symbol found by getSymbolIndexByType for type {}".sformat(type));
	}
	
	public GoldSymbol eofSymbol()
	{
		return symbolTable[eofSymbolIndex];
	}
	
	public GoldSymbol errorSymbol()
	{
		return symbolTable[errorSymbolIndex];
	}
}
