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

module tools.parse.cmd;

import std.conv;
import std.file;
static import std.path;
import std.stdio;

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

import goldie.all;
import tools.util;

class Cmd
{
	mixin(getter!(bool, "shouldExit"));

	public this(string[] args)
	{
		init();
		shouldExit = !parse(args);
	}
	
	// Direct command-line args (may be baked, not guaranteed to be raw)
	public bool   help;
	public bool   detailhelp;
	public string langFile;
	public string inputFilename;

	public bool   parseTreePretty=false;
	public string parseTreeOut;
	public string parseTreeType="json";

	public bool   tokensPretty=false;
	public string tokensOut;
	public string tokensType="json";

	public bool  debugOut=false;

	private bool noParseTree=false;
	private bool noTokens=false;

	// Generated
	public TreeFormatter parseFormatter;
	public TreeFormatter tokenFormatter;
	public bool   saveParseTree;
	public bool   saveTokens;
	public string lang;
	public string langSrc;
	public string langDir;

	// Private ------
	private CmdLineParser cmd;
	
	private void init()
	{
		//TODO: Think about turning defineArg!() into a runtime member func of CmdLineParser
		cmd = new CmdLineParser();
		mixin(defineArg!(cmd, "help",    help,            ArgFlag.Optional, "Displays a help summary and exits" ));
		mixin(defineArg!(cmd, "detail",  detailhelp,      ArgFlag.Optional, "Displays a detailed help message and exits" ));
		mixin(defineArg!(cmd, "",        inputFilename,   ArgFlag.Required, "Source filename" ));
		mixin(defineArg!(cmd, "lang",    langFile,        ArgFlag.Optional,   "CGT file of language (default: \"{goldie}/lang/{src ext}.cgt\")" ));
		mixin(defineArg!(cmd, "ptn",     noParseTree,     ArgFlag.Optional|ArgFlag.Advanced,   "Don't save parse tree" ));
		mixin(defineArg!(cmd, "tkn",     noTokens,        ArgFlag.Optional|ArgFlag.Advanced,   "Don't save tokens" ));
		mixin(defineArg!(cmd, "ptp",     parseTreePretty, ArgFlag.Optional|ArgFlag.Advanced,   `Pretty format parse tree file` ));
		mixin(defineArg!(cmd, "tkp",     tokensPretty,    ArgFlag.Optional|ArgFlag.Advanced,   `Pretty format token file` ));
		mixin(defineArg!(cmd, "ptt",     parseTreeType,   ArgFlag.Optional|ArgFlag.Advanced|ArgFlag.ToLower, `Parse tree file type ("json" or "xml")` ));
		mixin(defineArg!(cmd, "tkt",     tokensType,      ArgFlag.Optional|ArgFlag.Advanced|ArgFlag.ToLower, `Token file type ("json" or "xml")` ));
		mixin(defineArg!(cmd, "pto",     parseTreeOut,    ArgFlag.Optional|ArgFlag.Advanced,   "Parse tree output filename" ));
		mixin(defineArg!(cmd, "tko",     tokensOut,       ArgFlag.Optional|ArgFlag.Advanced,   "Token output filename" ));
		mixin(defineArg!(cmd, "debug",   debugOut,        ArgFlag.Optional|ArgFlag.Advanced,   "Display debug information" ));
		
		mixin(setArgAllowableValues!("ptt", "json", "xml"));
		mixin(setArgAllowableValues!("tkt", "json", "xml"));
	}
	
	enum helpHeader = 
		helpInfoHeader("Parse", "2009-2012 Nick Sabalausky") ~
		"\n\n" ~
		"Parses a source file according to a cgt file and saves the result.\n";
	
	// Returns: Should processing proceed? If false, the program should exit.
	private bool parse(string[] args)
	{
		enum sampleUsageMsg =
			"Sample Usage:\n"~
			"    goldie-parse lang/valid_sample2.calc\n"~
			"    goldie-parse lang/valid_sample2.calc -lang:lang/calc.cgt\n";
		cmd.parse(args);
		if(detailhelp)
		{
			writeln(helpHeader);
			writeln(sampleUsageMsg);
			write(cmd.getDetailedUsage());
			return false;
		}
		if(!cmd.success || help)
		{
			writeln(helpHeader);
			writeln(sampleUsageMsg);
			write(cmd.getUsage(14));
			if(!help)
			{
				writeln();
				write(cmd.errorMsg);
			}
			return false;
		}
		
		saveParseTree = !noParseTree;
		saveTokens    = !noTokens;

		if(parseTreePretty || parseTreeOut != "")
			saveParseTree = true;
		if(parseTreeOut == "")
			parseTreeOut = inputFilename~".pt."~parseTreeType;
			
		if(tokensPretty || tokensOut != "")
			saveTokens = true;
		if(tokensOut == "")
			tokensOut = inputFilename~".tk."~tokensType;
		
		parseFormatter = saveParseTree? getFormatter(parseTreeType, parseTreePretty) : null;
		tokenFormatter = saveTokens?    getFormatter(   tokensType,    tokensPretty) : null;
		
		if(langFile == "")
		{
			lang = text(getExecPath(), "../lang/", std.path.getExt(inputFilename));
			langFile = lang ~ ".cgt";
		}
		
		lang = std.path.getName(langFile);
		langSrc = lang ~ ".grmp";
		if(!exists(langSrc))
		{
			string langSrcAlt = lang~".grm";
			if(exists(langSrcAlt))
				langSrc = langSrcAlt;
		}
		
		return true;
	}
	
	private TreeFormatter getFormatter(string type, bool pretty)
	{
		TreeFormatter formatter = 
			(type=="xml"  &&  pretty)? formatterPrettyXML   :
			(type=="xml"  && !pretty)? formatterTrimmedXML  :
			(type=="json" &&  pretty)? formatterPrettyJSON  :
			(type=="json" && !pretty)? formatterTrimmedJSON :
			null;
		return formatter;
	}
}
