/*
 * Bernoulli Compiler
 * Copyright (c) Cornell University
 * Department of Computer Science
 * 
 * Kamen Yotov (kamen@yotov.org)
 * 
 * $Source: C:/CVS/kyotov/kyotov/Research/BC/XmlSignature/XmlSignature.cs,v $
 * $Revision: 1.2 $
 * $Date: 2002/08/13 04:01:31 $
 */

using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Collections;
using System.CodeDom;
using System.CodeDom.Compiler;

using Microsoft.CSharp;

using BC.Core.Grammars.Symbols;

namespace XmlSignature
{
	class XmlSignature
	{
		static XmlDocument d;
		static XmlNamespaceManager nm;

		static XmlNodeList keywords;
		static XmlNodeList terminals;
		static XmlNodeList nonterminals;

		static string identity (XmlNode n)
		{
			if (n.LocalName == "roottoken")
				return "";
			else
				return String.Format("{0}_{1}", identity(n.ParentNode), n.Attributes["identity"].Value);
		}

		static string name (XmlNode n)
		{
			if (n.Attributes["name"] != null)
				return "T" + n.Attributes["name"].Value;
			else
				return String.Format("T_{0}", n.Attributes["identity"].Value);
		}

		static CodeExpression expression (XmlNode n)
		{
			if (n.Attributes["bind"] != null)
			{
				XmlNodeList l = d.SelectNodes(n.Attributes["bind"].Value, nm);

				if (l.Count != 1)
					throw new Exception(String.Format("Binding \"{0}\" not unique (or absent)!", n.Attributes["bind"].Value));

				return new CodeFieldReferenceExpression(null, name(l[0]));
			}
			else if (n.Attributes["value"] != null)
				return new CodeObjectCreateExpression(typeof(Terminal), new CodePrimitiveExpression(identity(n)), new CodePrimitiveExpression(n.Attributes["value"].Value));
			else
				return new CodeObjectCreateExpression(typeof(Terminal), new CodePrimitiveExpression(identity(n)));
		}

		static string keyword
		{
			get
			{
				XmlNodeList l = d.SelectNodes(String.Format("//{0}:token[attribute::identity='keyword']", d.DocumentElement.Prefix), nm);

				if (l.Count != 1)
					throw new Exception("Token 'keyword' not unique (or absent)!");

				return identity(l[0]);
			}
		}

		static string ns
		{
			get
			{
				return d.DocumentElement.SelectSingleNode("attribute::namespace", nm).Value;
			}
		}

		static string cn
		{
			get
			{
				return d.DocumentElement.SelectSingleNode("attribute::class", nm).Value;
			}
		}

		static void Main (string[] a)
		{
			Console.Error.WriteLine(
				"XmlSignature v0.2\n" +
				"Copyright (c) 2002 Cornell University\n"
			);

			if (!(
				a.Length == 2 &&
				File.Exists(a[0]) &&
				File.Exists(a[1])))
			{
				Console.Error.WriteLine(
					"ERROR: Invalid arguments!\n" +
					"\n" +
					"SYNTAX: XmlSignature <input(XML)> <schema(XSD)> <output-directory>"
				);

				return;
			}


			Console.Error.WriteLine("{0}:\n", a[0]);

			Process(a[0], a[1]);

			Console.Error.WriteLine("Analisys complete!\n{0} keywords;\n{1} terminals;\n{2} nonterminals;", keywords.Count, terminals.Count, nonterminals.Count);

			Generate();

			Console.Error.WriteLine("Done!");
		}

		static void handler (object o, ValidationEventArgs a)
		{
			Console.Error.WriteLine("{0}: {1}", a.Severity.ToString(), a.Message);
		}

		static void Process (string f, string xsd)
		{
			d = new XmlDocument();

			XmlValidatingReader dr = new XmlValidatingReader(new XmlTextReader(new StreamReader(f)));

			Console.Error.WriteLine("Loading schema...");

			dr.Schemas.Add(XmlSchema.Read(new StreamReader(xsd), new ValidationEventHandler(handler)));

			dr.ValidationEventHandler += new ValidationEventHandler(handler);
			dr.ValidationType = ValidationType.Schema;

			Console.Error.WriteLine("Loading document...");

			d.Load(dr);

			nm = new XmlNamespaceManager(d.NameTable);

			nm.AddNamespace(d.DocumentElement.Prefix, "http://bernoulli.cs.cornell.edu/Signature.xsd");

			Console.Error.WriteLine("Running XPath queries...");

			keywords = d.SelectNodes(String.Format("/{0}:root/{0}:keywords/{0}:keyword/text()", d.DocumentElement.Prefix), nm);
			terminals = d.SelectNodes(String.Format("//{0}:token[attribute::declaration='true']", d.DocumentElement.Prefix), nm);
			nonterminals = d.SelectNodes(String.Format("/{0}:root/{0}:nonterminals/{0}:nonterminal/text()", d.DocumentElement.Prefix), nm);
		}

		static void Generate ()
		{
			Console.Error.WriteLine("Building source tree...");

			CodeCompileUnit ccu = new CodeCompileUnit();
			CodeNamespace cns = new CodeNamespace(ns);

			ccu.Namespaces.Add(cns);

			CodeTypeDeclaration ctd = new CodeTypeDeclaration(cn);

			cns.Types.Add(ctd);

			CodeMemberField cmf = new CodeMemberField(typeof(string[]), "keywords");

			CodeExpression[] cea = new CodeExpression[keywords.Count];

			for (int i = 0; i < keywords.Count; i++)
				cea[i] = new CodePrimitiveExpression(keywords[i].Value);

			cmf.Attributes = MemberAttributes.Static | MemberAttributes.Public;
			cmf.InitExpression = new CodeArrayCreateExpression(typeof(string), cea);

			ctd.Members.Add(cmf);
			ctd.Attributes = MemberAttributes.Public;

			foreach (XmlNode n in keywords)
			{
				string t = n.Value.ToLower();

				if (t[0] != '_')
					t = '_' + t;

				cmf = new CodeMemberField(typeof(Terminal), "T_keyword" + t);
				cmf.InitExpression = new CodeObjectCreateExpression(typeof(Terminal), 
					new CodePrimitiveExpression(keyword + t), new CodePrimitiveExpression(n.Value));

				cmf.Attributes = MemberAttributes.Static | MemberAttributes.Public;

				ctd.Members.Add(cmf);
			}

			foreach (XmlNode n in terminals)
			{
				cmf = new CodeMemberField(typeof(Terminal), name(n));

				cmf.Attributes = MemberAttributes.Static | MemberAttributes.Public;
				cmf.InitExpression = expression(n);

				ctd.Members.Add(cmf);
			}

			cmf = new CodeMemberField(typeof(Hashtable), "hKeywords");

			cmf.Attributes = MemberAttributes.Static | MemberAttributes.Private;
			cmf.InitExpression = new CodeObjectCreateExpression(typeof(Hashtable));

			ctd.Members.Add(cmf);

			CodeMemberMethod cmm = new CodeMemberMethod();

			cmm.Name = "T_keyword";
			cmm.Attributes = MemberAttributes.Static | MemberAttributes.Public;
			cmm.ReturnType = new CodeTypeReference(typeof(Terminal));
			cmm.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "name"));

			CodeIndexerExpression cie = new CodeIndexerExpression(
				new CodeVariableReferenceExpression("hKeywords"),
				new CodeVariableReferenceExpression("name")
			);

			CodeConditionStatement ccs = new CodeConditionStatement(
				new CodeBinaryOperatorExpression(
					new CodeMethodInvokeExpression(
						new CodeVariableReferenceExpression("hKeywords"),
						"Contains",
						new CodeVariableReferenceExpression("name")
					),
					CodeBinaryOperatorType.IdentityEquality,
					new CodePrimitiveExpression(false)),
				new CodeStatement[]
				{
					new CodeAssignStatement(cie, 
					new CodeObjectCreateExpression(typeof(Terminal),
					new CodeBinaryOperatorExpression(
					new CodePrimitiveExpression(keyword + "_"),
					CodeBinaryOperatorType.Add,
					new CodeVariableReferenceExpression("name"))))
				}
			);

			cmm.Statements.Add(ccs);
			cmm.Statements.Add(
				new CodeMethodReturnStatement(				
					new CodeCastExpression(typeof(Terminal),cie)));

			ctd.Members.Add(cmm);

			foreach (XmlNode n in nonterminals)
			{
				cmf = new CodeMemberField(typeof(NonTerminal), "N" + n.Value);

				cmf.Attributes = MemberAttributes.Static | MemberAttributes.Public;
				cmf.InitExpression = new CodeObjectCreateExpression(typeof(NonTerminal), new CodePrimitiveExpression(n.Value));

				ctd.Members.Add(cmf);
			}

			Console.Error.WriteLine("Generating source ({0}.cs)...", cn);

			ICodeGenerator cg = new CSharpCodeProvider().CreateGenerator();

			cg.GenerateCodeFromCompileUnit(ccu, Console.Out, new CodeGeneratorOptions());
		}
	}
}