using System;
using System.Collections;
using System.IO;
using System.Diagnostics;
using System.Reflection;
using SysType = System.Type;

public class visitor {
	static string classname = "CLASSNAME";
	static string args = "ARGTYPE ARG", argnames = "";
	static string returntype = "void";
	static string modifiers = "";
	static bool rewrite = false;
	readonly static string progname = Environment.GetCommandLineArgs()[0];
	public static void Main(string[] argv) {
		int nusage = 0;
		TextWriter w = Console.Out;
		for (int i = 0; i < argv.Length; i++)
			if (argv[i] == "-?") {
				Usage();
				return;
			} else if (argv[i] == "-rewrite") {
				rewrite = true;
				returntype = "AST";
				args = "";
				classname = "rewrite";
			} else if (argv[i] == "-class" && i+1 < argv.Length)
				classname = argv[++i];
			else if (argv[i] == "-args" && i+1 < argv.Length)
				args = argv[++i];
			else if (argv[i] == "-return" && i+1 < argv.Length)
				returntype = argv[++i];
			else if (argv[i] == "-out" && i+1 < argv.Length) {
				if (w != Console.Out)
					w.Close();
				try {
					w = new StreamWriter(argv[++i]);
				} catch (Exception e) {
					Console.Error.WriteLine("{0}: can't write \"{1}\": {2}", progname, argv[i], e.Message);
					w = Console.Out;					
				}
			} else if (argv[i] == "-public"   || argv[i] == "-virtual"
			       ||  argv[i] == "-private"  || argv[i] == "-static"
			       ||  argv[i] == "-sealed"   || argv[i] == "-internal"
			       ||  argv[i] == "-override" || argv[i] == "-abstract"
			       ||  argv[i] == "-protected"|| argv[i] == "-sealed"
			       ||  argv[i] == "-new")
				modifiers += argv[i].Substring(1) + " ";
			else {
				Console.Error.WriteLine("{0}: unrecognized option \"{1}\"", progname, argv[i]);
				if (nusage++ == 0)
					Usage();
			}
		if (args != "") {
			string[] parms = args.Split(',');
			foreach (string arg in parms)
				argnames += ", " + arg.Trim().Split(' ')[1];
			args = ", " + args;
		}
		Generate(w);
		if (w != Console.Out)
			w.Close();
	}
	static void Usage() {
		Console.Error.WriteLine("Usage: {0} [ option ]...\nOptions:", progname);
		foreach (string opt in new string[] {"-?", "-rewrite", "-class classname", "-args argument",
			"-return typename", "-out filename", "-public", "-virtual", "-private", "-static", "-sealed", "-internal",
			"-override", "-abstract", "-protected", "-sealed", "-new"})
				Console.Error.WriteLine("\t{0}", opt);
	}
	static Assembly baseAssembly = Assembly.Load("base");
	static SysType[] GetTypes() {
		return baseAssembly.GetExportedTypes();
	}
	static SysType GetType(string name) {
		return baseAssembly.GetType(name);
	}
	class compare : IComparer {
		int IComparer.Compare(object x, object y) {
			SysType u = (SysType)x, v = (SysType)y;
			return u.Name.CompareTo(v.Name);
		}
	}
	static void Generate(TextWriter w) {
		w.WriteLine(!rewrite ?
@"using System;
using System.IO;

public class {0} {{
	protected TextWriter w;
	public {0}(TextWriter w) {{
		this.w = w;
	}}" : @"using System;

public class {0} {{
	virtual public AST match(AST ast) {{
		return ast;
	}}", classname, args);
		ArrayList q = new ArrayList();
		foreach (SysType t in GetTypes())
			if (InheritsFrom(t, "AST"))
				q.Add(t);
		q.Sort(new compare());
		foreach (SysType t in q)
			ProcessType(t, w);
		GenerateVisit(GetTypes(), w);
		w.WriteLine("}");
	}
	static void GenerateVisit(SysType[] types, TextWriter w) {
		w.WriteLine("\tvirtual public {1} visit(AST ast{0}) {{", args, returntype);
		int n = 0;
		foreach (SysType t in types)
			if (t.BaseType != null && t.BaseType.Name == "AST") {
				if (returntype == "void")
					w.WriteLine("\t\t{2}if (ast is {0}) {0}(({0})ast{1});", t.Name, argnames, n > 0 ? "else " : "     ");
				else
					w.WriteLine("\t\tif (ast is {0}) return {0}(({0})ast{1});", t.Name, argnames);
				n++;
			}
		w.WriteLine("\t\t{0}throw new ArgumentException();", n > 0 ? "else " : "");
		w.WriteLine("\t}");
	}
	static bool IsAnnotated(FieldInfo f) {
		Attribute[] attrs = Attribute.GetCustomAttributes(f, true);
		return attrs.Length > 1 || attrs.Length == 1 && !MayBeNull(f);
	}
	static bool InheritsFrom(SysType t, string name) {
		while ((t = t.BaseType) != null)
			if (t.Name == name)
				return true;
		return false;
	}
	static void ProcessType(SysType t, TextWriter w) {
		if (t.IsAbstract && InheritsFrom(t, "AST"))
			ProcessAbstractType(t, w);
		else if (InheritsFrom(t, "AST"))
			ProcessConcreteType(t, w);
	}
	static void ProcessAbstractType(SysType t, TextWriter w) {
		w.WriteLine("\t{3}{2} {0}({0} ast{1}) {{", t.Name, args, returntype, modifiers);
		int n = 0;
		foreach (SysType u in GetTypes())
			if (u.BaseType == t) {
				w.Write("\t\t{1}if (ast is {0}) ", u.Name, n > 0 ? "else " : "     ");
				if (returntype != "void")
					w.Write("return ");
				w.WriteLine("{0}(({0})ast{1});", u.Name, argnames);
				n++;
			}
		w.WriteLine("\t\t{0}throw new ArgumentException();", n > 0 ? "else " : "");
		w.WriteLine("\t}");
	}
	static void ProcessConcreteType(SysType t, TextWriter w) {
		w.WriteLine("\t{3}{2} {0}({0} ast{1}) {{", t.Name, args, returntype, modifiers);
		foreach (FieldInfo f in t.GetFields())
			if (f.Name != "parent")
				ProcessField(f, w);
		if (rewrite)
			w.WriteLine("\t\treturn match(ast);");
		else if (returntype != "void")
			w.WriteLine("\t\treturn null;");
		w.WriteLine("\t}");
	}
	static bool MayBeNull(FieldInfo f) {
		return Attribute.GetCustomAttribute(f, baseAssembly.GetType("MayBeNullAttribute")) != null;
	}
	static void ProcessField(FieldInfo f, TextWriter w) {
		SysType t = f.FieldType;
		if (t.Name.EndsWith("List") && InheritsFrom(t, "CollectionBase")) {
			string elemname = t.Name.Substring(0, t.Name.Length - 4);
			SysType u = GetType(elemname);
			if (u != null && InheritsFrom(u, "AST") && !IsAnnotated(f)) {
				string indent = "\t\t";
				if (MayBeNull(f)) {
					w.WriteLine("\t\tif (ast.{0} != null)", f.Name);
					indent += "\t";
				}
				if (!rewrite) {
					w.WriteLine("{2}foreach ({0} x in ast.{1})", elemname, f.Name, indent);
					w.WriteLine("{2}\t{0}(x{1});", elemname, argnames, indent);
				} else {
					w.WriteLine("{1}for (int i = 0; i < ast.{0}.Count; i++)", f.Name, indent);
					w.WriteLine("{3}\tast.{1}[i] = ({0}){0}(ast.{1}[i]{2});", elemname, f.Name, argnames, indent);
				}
			} else if (!rewrite)
				CommentField(f, w);
		} else if (InheritsFrom(t, "AST") && !IsAnnotated(f)) {
			if (MayBeNull(f)) {
				w.WriteLine("\t\tif (ast.{0} != null)", f.Name);
				w.Write("\t");
			}
			if (!rewrite)
				w.WriteLine("\t\t{0}(ast.{1}{2});", t.Name, f.Name, argnames);
			else
				w.WriteLine("\t\tast.{1} = ({0}){0}(ast.{1}{2});", t.Name, f.Name, argnames);
		} else if (!rewrite)
			CommentField(f, w);
	}
	static void CommentField(FieldInfo f, TextWriter w) {
		w.Write("\t\t//");
		Attribute[] attrs = Attribute.GetCustomAttributes(f);
		if (attrs.Length > 0) {
			w.Write(" [");
			for (int i = 0; i < attrs.Length; i++) {
				if (i > 0)
					w.Write(",");
				string name = attrs[i].GetType().Name;
				if (name.EndsWith("Attribute"))
					name = name.Substring(0, name.Length-9);
				w.Write("{0}", name);
			}
			w.Write("]");
		}
		w.WriteLine(" {0} ast.{1}", f.FieldType.Name, f.Name);
	}
}