// Updater program for syncing Mono's ECMA-style documentation files
// with an assembly.
// By Joshua Tauberer <tauberer@for.net>

using System;
using System.Collections;
using System.Text;
using System.Reflection;
using System.Xml;

using Mono.GetOptions;

[assembly: AssemblyTitle("Monodocer - The Mono Documentation Tool")]
[assembly: AssemblyCopyright("Copyright (c) 2004 Joshua Tauberer <tauberer@for.net>\nreleased under the GPL.")]
[assembly: AssemblyDescription("A tool for creating and updating Mono XML documentation files for assemblies.")]

[assembly: Mono.UsageComplement("")]

public class Stub {
	
	static string srcPath;
	static Assembly[] assemblies;
	
	static bool nooverrides = true, delete = false, ignoremembers = false;
	static bool pretty = false;
	
	static int additions = 0, deletions = 0;

	static string name;
	static XmlDocument slashdocs;
	
	private class Opts : Options {
		[Option("The root {directory} of an assembly's documentation files.")]
		public string path = null;

		[Option("When updating documentation, write the updated files to this {path}.")]
		public string updateto = null;

		[Option(-1, "The assembly to document.  Specify a {file} path or the name of a GAC'd assembly.")]
		public string[] assembly = null;

		[Option("Document only the {type name}d by this argument.")]
		public string type = null;

		[Option("Update only the types in this {namespace}.")]
		public string @namespace = null;

		[Option("Allow monodocer to delete members from files.")]
		public bool delete = false;

		[Option("Include overridden methods in documentation.")]
		public bool overrides = false;

		[Option("Don't update members.")]
		public bool ignoremembers = false;

		[Option("The {name} of the project this documentation is for.")]
		public string name;

		[Option("An XML documemntation {file} made by the /doc option of mcs/csc the contents of which will be imported.")]
		public string importslashdoc;
		
		[Option("Indent the XML files nicely.")]
		public bool pretty = false;
		
	}
	
	private static int n(object o) {
		return o == null ? 0 : 1;
	}
	
	public static void Main(string[] args) {
		Opts opts = new Opts();
		opts.ProcessArgs(args);

		if (args.Length == 0) {
			opts.DoHelp();
			return;
		}
		
		nooverrides = !opts.overrides;
		delete = opts.delete;
		ignoremembers = opts.ignoremembers;
		name = opts.name;
		pretty = opts.pretty;

		try {
			// PARSE BASIC OPTIONS AND LOAD THE ASSEMBLY TO DOCUMENT
			
			if (opts.path == null)
				throw new InvalidOperationException("The path option is required.");
			
			srcPath = opts.path;

			if (n(opts.type) + n(opts.@namespace) > 1)
				throw new InvalidOperationException("You cannot specify both 'type' and 'namespace'.");
			
			if (opts.assembly == null)
				throw new InvalidOperationException("The assembly option is required.");
				
			assemblies = new Assembly [opts.assembly.Length];
			for (int i = 0; i < opts.assembly.Length; i++)
				assemblies [i] = LoadAssembly (opts.assembly [i]);
				
			// IMPORT FROM /DOC?
			
			if (opts.importslashdoc != null) {
				try {
					slashdocs = new XmlDocument();
					slashdocs.Load(opts.importslashdoc);
				} catch (Exception e) {
					Console.Error.WriteLine("Could not load /doc file: " + e.Message);
					Environment.ExitCode = 1;
					return;
				}
			}
			
			// PERFORM THE UPDATES
			
			if (opts.type != null)
				DoUpdateType(opts.path, opts.type, opts.updateto);
			else if (opts.@namespace != null)
				DoUpdateNS(opts.@namespace, opts.path + "/" + opts.@namespace, opts.updateto == null ? opts.path + "/" + opts.@namespace : opts.updateto + "/" + opts.@namespace);
			else
				DoUpdateAssemblies(opts.path, opts.updateto == null ? opts.path : opts.updateto);
		
		} catch (InvalidOperationException error) {
			Console.Error.WriteLine(error.Message);
			Environment.ExitCode = 1;
			return;
			
		} catch (System.IO.IOException error) {
			Console.Error.WriteLine(error.Message);
			Environment.ExitCode = 1;
			return;

		} catch (Exception error) {
			Console.Error.WriteLine(error);
			Environment.ExitCode = 1;
		}

		Console.Error.WriteLine("Members Added: {0}, Members Deleted: {1}", additions, deletions);
	}
	
	private static Assembly LoadAssembly (string name)
	{
		Assembly assembly = null;
		try {
			assembly = Assembly.LoadFile (name);
		} catch (System.IO.FileNotFoundException) { }

		if (assembly == null) {
			try {
				assembly = Assembly.LoadWithPartialName (name);
			} catch (Exception) { }
		}
			
		if (assembly == null)
			throw new InvalidOperationException("Assembly " + name + " not found.");

		return assembly;
	}

	private static void WriteXml(XmlElement element, System.IO.TextWriter output) {
		XmlTextWriter writer = new XmlTextWriter(output);
		writer.Formatting = Formatting.Indented;
		writer.Indentation = 2;
		writer.IndentChar = ' ';
		element.WriteTo(writer);
		output.WriteLine();	
	}
	
	private static XmlDocument CreateIndexStub() {
		XmlDocument index = new XmlDocument();

		XmlElement index_root = index.CreateElement("Overview");
		index.AppendChild(index_root);

		if (assemblies.Length == 0)
			throw new Exception ("No assembly");

		XmlElement index_assemblies = index.CreateElement("Assemblies");
		index_root.AppendChild(index_assemblies);

		XmlElement index_remarks = index.CreateElement("Remarks");
		index_remarks.InnerText = "To be added.";
		index_root.AppendChild(index_remarks);

		XmlElement index_copyright = index.CreateElement("Copyright");
		index_copyright.InnerText = "To be added.";
		index_root.AppendChild(index_copyright);

		XmlElement index_types = index.CreateElement("Types");
		index_root.AppendChild(index_types);
		
		return index;
	}
	
	private static void WriteNamespaceStub(string ns, string outdir) {
		XmlDocument index = new XmlDocument();

		XmlElement index_root = index.CreateElement("Namespace");
		index.AppendChild(index_root);
		
		index_root.SetAttribute("Name", ns);

		XmlElement index_docs = index.CreateElement("Docs");
		index_root.AppendChild(index_docs);

		XmlElement index_summary = index.CreateElement("summary");
		index_summary.InnerText = "To be added.";
		index_docs.AppendChild(index_summary);

		XmlElement index_remarks = index.CreateElement("remarks");
		index_remarks.InnerText = "To be added.";
		index_docs.AppendChild(index_remarks);

		using (System.IO.TextWriter writer
		= new System.IO.StreamWriter(new System.IO.FileStream(outdir + "/" + ns + ".xml", System.IO.FileMode.CreateNew))) {
			WriteXml(index.DocumentElement, writer);
		}
	}
	
	public static void DoUpdateType(string basepath, string typename, string dest) {
		Type type = null;
		foreach (Assembly assembly in assemblies) {
			type = assembly.GetType(typename, false);
			if (type != null)
				break;
		}
		if (type == null) throw new InvalidOperationException("Type not found: " + typename);
		if (type.Namespace == null) throw new InvalidOperationException("Types in the root namespace cannot be documented: " + typename);
		string typename2 = type.FullName.Substring(type.Namespace == "" ? 0 : type.Namespace.Length+1);

		XmlDocument basefile = new XmlDocument();
		if (!pretty) basefile.PreserveWhitespace = true;
		string typefile = basepath + "/" + type.Namespace + "/" + typename2 + ".xml";
		try {
			basefile.Load(typefile);
		} catch (Exception e) {
			throw new InvalidOperationException("Error loading " + typefile + ": " + e.Message, e);
		}
		
		if (dest == null) {
			DoUpdateType2(basefile, type, typefile);
		} else if (dest == "-") {
			DoUpdateType2(basefile, type, null);
		} else {
			DoUpdateType2(basefile, type, dest + "/" + type.Namespace + "/" + typename2 + ".xml");
		}	
	}

	
	public static void DoUpdateNS(string ns, string nspath, string outpath) {
		Hashtable seenTypes = new Hashtable();
		Assembly assembly = assemblies [0];
		
		foreach (System.IO.FileInfo file in new System.IO.DirectoryInfo(nspath).GetFiles("*.xml")) {
			XmlDocument basefile = new XmlDocument();
			if (!pretty) basefile.PreserveWhitespace = true;
			string typefile = nspath + "/" + file.Name;
			try {
				basefile.Load(typefile);
			} catch (Exception e) {
				throw new InvalidOperationException("Error loading " + typefile + ": " + e.Message, e);
			}

			string typename = basefile.SelectSingleNode("Type/@FullName").InnerText;
			Type type = assembly.GetType(typename, false);
			if (type == null) {
				Console.Error.WriteLine(typename + " is no longer in the assembly.");
				continue;
			}			

			seenTypes[type] = seenTypes;
			DoUpdateType2(basefile, type, outpath + "/" + file.Name);
		}
		
		// Stub types not in the directory
		foreach (Type type in assembly.GetTypes()) {
			if (type.Namespace == null) continue;
			if (type.Namespace != ns || seenTypes.ContainsKey(type))
				continue;

			XmlElement td = StubType(type);
			if (td == null) continue;
			
			Console.Error.WriteLine("New Type: " + type.FullName);

			using (System.IO.TextWriter writer
			= new System.IO.StreamWriter(new System.IO.FileStream(outpath + "/" + GetTypeFileName(type) + ".xml", System.IO.FileMode.CreateNew))) {
				WriteXml(td, writer);
			}
			
		}
	}
	
	private static string GetTypeFileName(Type type) {
		int start = 0;
		if (type.Namespace != null && type.Namespace != "")
			start = type.Namespace.Length + 1;
		return type.FullName.Substring(start);
	}
	
	private static void AddIndexAssembly (Assembly assembly, XmlElement parent)
	{
		XmlElement index_assembly = parent.OwnerDocument.CreateElement("Assembly");
		index_assembly.SetAttribute("Name", assembly.GetName().Name);
		index_assembly.SetAttribute("Version", assembly.GetName().Version.ToString());
		MakeAttributes(index_assembly, assembly, true);
		parent.AppendChild(index_assembly);
	}

	private static void DoUpdateAssemblies (string source, string dest) 
	{
		string indexfile = dest + "/index.xml";
		XmlDocument index;
		if (System.IO.File.Exists(indexfile)) {
			index = new XmlDocument();
			index.Load(indexfile);

			// Format change
			ClearElement(index.DocumentElement, "Assembly");
			ClearElement(index.DocumentElement, "Attributes");
		} else {
			index = CreateIndexStub();
		}
		
		if (name == null) {
			string defaultTitle = "Untitled";
			if (assemblies.Length == 1)
				defaultTitle = assemblies[0].GetName().Name;
			WriteElementInitialText(index.DocumentElement, "Title", defaultTitle);
		} else {
			WriteElementText(index.DocumentElement, "Title", name);
		}
		
		XmlElement index_types = WriteElement(index.DocumentElement, "Types");
		XmlElement index_assemblies = WriteElement(index.DocumentElement, "Assemblies");
		index_assemblies.RemoveAll ();
		
		Hashtable goodfiles = new Hashtable();

		foreach (Assembly assm in assemblies) {
			AddIndexAssembly (assm, index_assemblies);
			DoUpdateAssembly (assm, index_types, source, dest, goodfiles);
		}
		
		CleanupFiles (dest, goodfiles);
		CleanupIndexTypes (index_types, goodfiles);

		using (System.IO.TextWriter writer
			= new System.IO.StreamWriter(new System.IO.FileStream(indexfile, System.IO.FileMode.Create)))
			WriteXml(index.DocumentElement, writer);
	
	}
		
	private static void DoUpdateAssembly (Assembly assembly, XmlElement index_types, string source, string dest, Hashtable goodfiles) 
	{
		foreach (Type type in assembly.GetTypes()) {
			if (type.Namespace == null) continue;
			
			// Must get the A+B form of the type name.
			string typename = GetTypeFileName(type);
			
			string typefile = source + "/" + type.Namespace + "/" + typename + ".xml";
			System.IO.FileInfo file = new System.IO.FileInfo(typefile);
			if (file.Exists) {
				// Update
				XmlDocument basefile = new XmlDocument();
				if (!pretty) basefile.PreserveWhitespace = true;
				try {
					basefile.Load(typefile);
				} catch (Exception e) {
					throw new InvalidOperationException("Error loading " + typefile + ": " + e.Message, e);
				}
				
				DoUpdateType2(basefile, type, dest + "/" + type.Namespace + "/" + typename + ".xml");
			} else {
				// Stub
				XmlElement td = StubType(type);
				if (td == null) continue;
				
				System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(dest + "/" + type.Namespace);
				if (!dir.Exists) {
					dir.Create();
					Console.Error.WriteLine("Namespace Directory Created: " + type.Namespace);
				}

				Console.Error.WriteLine("New Type: " + type.FullName);

				using (System.IO.TextWriter writer
				= new System.IO.StreamWriter(new System.IO.FileStream(dest + "/" + type.Namespace + "/" + typename + ".xml", System.IO.FileMode.CreateNew))) {
					WriteXml(td, writer);
				}
			}
			
			// Add namespace and type nodes into the index file as needed
			XmlElement nsnode = (XmlElement)index_types.SelectSingleNode("Namespace[@Name='" + type.Namespace + "']");
			if (nsnode == null) {
				nsnode = index_types.OwnerDocument.CreateElement("Namespace");
				nsnode.SetAttribute("Name", type.Namespace);
				index_types.AppendChild(nsnode);
			}
			string doc_typename = GetDocTypeName (type);
			XmlElement typenode = (XmlElement)nsnode.SelectSingleNode("Type[@Name='" + typename + "']");
			if (typenode == null) {
				typenode = index_types.OwnerDocument.CreateElement("Type");
				typenode.SetAttribute("Name", typename);
				nsnode.AppendChild(typenode);
			}
			if (typename != doc_typename)
				typenode.SetAttribute("DisplayName", doc_typename);
			else
				typenode.RemoveAttribute("DisplayName");
				
			// Ensure the namespace index file exists
			if (!new System.IO.FileInfo(dest + "/" + type.Namespace + ".xml").Exists) {
				Console.Error.WriteLine("New Namespace File: " + type.Namespace);
				WriteNamespaceStub(type.Namespace, dest);
			}

			// mark the file as corresponding to a type
			goodfiles[type.Namespace + "/" + typename + ".xml"] = goodfiles;
		}
	}

	private static void CleanupFiles (string dest, Hashtable goodfiles)
	{
		// Look for files that no longer correspond to types
		foreach (System.IO.DirectoryInfo nsdir in new System.IO.DirectoryInfo(dest).GetDirectories("*")) {
			foreach (System.IO.FileInfo typefile in nsdir.GetFiles("*.xml")) {
				if (!goodfiles.ContainsKey(nsdir.Name + "/" + typefile.Name)) {
					string newname = typefile.FullName + ".remove";
					try { System.IO.File.Delete(newname); } catch (Exception) { }
					try { typefile.MoveTo(newname); } catch (Exception) { }					
					Console.Error.WriteLine("Class no longer present; file renamed: " + nsdir.Name + "/" + typefile.Name);
				}
			}
		}
	}
		
	private static void CleanupIndexTypes (XmlElement index_types, Hashtable goodfiles)
	{
		// Look for type nodes that no longer correspond to types
		foreach (XmlElement typenode in index_types.SelectNodes("Namespace/Type")) {
			string fulltypename = ((XmlElement)typenode.ParentNode).GetAttribute("Name") + "/" + typenode.GetAttribute("Name") + ".xml";
			if (!goodfiles.ContainsKey(fulltypename))
				typenode.ParentNode.RemoveChild(typenode);
		}
	}
		
	public static void DoUpdateType2(XmlDocument basefile, Type type, string output) {
		Console.Error.WriteLine("Updating: " + type.FullName);
		
		Hashtable seenmembers = new Hashtable();

		// Update type metadata
		UpdateType(basefile.DocumentElement, type, false);

		// Update existing members.  Delete member nodes that no longer should be there,
		// and remember what members are already documented so we don't add them again.
		if (!ignoremembers)
		foreach (XmlElement oldmember in basefile.SelectNodes("Type/Members/Member")) {
			MemberInfo oldmember2 = GetMember(type, oldmember);
 			string sig = oldmember2 != null ? MakeMemberSignature(oldmember2) : null;
			
			// Interface implementations and overrides are deleted from the docs
			// unless the overrides option is given.
			if (oldmember2 != null && (!IsNew(oldmember2) || sig == null))
				oldmember2 = null;
			
			// Deleted (or signature changed)
			if (oldmember2 == null) {
				Console.Error.WriteLine("Member Deleted: " + oldmember.SelectSingleNode("MemberSignature[@Language='C#']/@Value").InnerText);
				if (!delete && MemberDocsHaveUserContent(oldmember)) {
					Console.Error.WriteLine("Member deletions must be enabled with the --delete option.");
				} else {
					oldmember.ParentNode.RemoveChild(oldmember);
					deletions++;
				}
				continue;
			}
			
			// Duplicated
			if (seenmembers.ContainsKey(oldmember2)) {
				Console.Error.WriteLine("Duplicate member: " + oldmember.SelectSingleNode("MemberSignature[@Language='C#']/@Value").InnerText);
				oldmember.ParentNode.RemoveChild(oldmember);
				deletions++;
				continue;
			}
			
			// Update signature information
			UpdateMember(oldmember, oldmember2);
			
			seenmembers[sig] = 1;
		}
		
		if (!IsDelegate(type) && !ignoremembers) {
			XmlNode members = basefile.SelectSingleNode("Type/Members");
			foreach (MemberInfo m in type.GetMembers(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance|BindingFlags.DeclaredOnly)) {
				if (m is Type) continue;
				
				string sig = MakeMemberSignature(m);
				if (sig == null) continue;
				if (seenmembers.ContainsKey(sig)) continue;
				
				// To be nice on diffs, members/properties/events that are overrides or are interface implementations
				// are not added in.
				if (!IsNew(m)) continue;
				
				XmlElement mm = MakeMember(basefile, m);
				if (mm == null) continue;
				members.AppendChild( mm );
	
				Console.Error.WriteLine("Member Added: " + mm.SelectSingleNode("MemberSignature/@Value").InnerText);
				additions++;
			}
		}
		
		// Import code snippets from files
		foreach (XmlNode code in basefile.GetElementsByTagName("code")) {
			if (!(code is XmlElement)) continue;
			string file = ((XmlElement)code).GetAttribute("src");
			if (file != "") {
				try {
					using (System.IO.StreamReader reader = new System.IO.StreamReader(srcPath + "/" + file)) {
						code.InnerText = reader.ReadToEnd();
					}
				} catch (Exception e) {
					Console.Error.WriteLine("Could not load code file '" + srcPath + "/" + file + "': " + e.Message);
				}
			}
		}

		System.IO.TextWriter writer;
		if (output == null)
			writer = Console.Out;
		else
			writer = new System.IO.StreamWriter(new System.IO.FileStream(output, System.IO.FileMode.Create));
		WriteXml(basefile.DocumentElement, writer);
		writer.Close();
	}
	
	private static bool MemberDocsHaveUserContent(XmlElement e) {
		e = (XmlElement)e.SelectSingleNode("Docs");
		if (e == null) return false;
		foreach (XmlElement d in e.SelectNodes("*"))
			if (d.InnerText != "" && !d.InnerText.StartsWith("To be added"))
				return true;
		return false;
	}
	
	private static bool IsNew(MemberInfo m) {
		if (!nooverrides) return true;
		if (m is MethodInfo && !IsNew((MethodInfo)m)) return false;
		if (m is PropertyInfo && !IsNew(((PropertyInfo)m).GetGetMethod())) return false;
		if (m is PropertyInfo && !IsNew(((PropertyInfo)m).GetSetMethod())) return false;
		if (m is EventInfo && !IsNew(((EventInfo)m).GetAddMethod())) return false;
		if (m is EventInfo && !IsNew(((EventInfo)m).GetRaiseMethod())) return false;
		if (m is EventInfo && !IsNew(((EventInfo)m).GetRemoveMethod())) return false;
		return true;
	}
	
	private static bool IsNew(MethodInfo m) {
		if (m == null) return true;
		MethodInfo b = m.GetBaseDefinition();
		if (b == null || b == m) return true;
		return false;
	}
	
	// UPDATE HELPER FUNCTIONS	
	
	private static XmlElement FindMatchingMember(Type type, XmlElement newfile, XmlElement oldmember) {
		MemberInfo oldmember2 = GetMember(type, oldmember);
		if (oldmember2 == null) return null;
		
		string membername = oldmember.GetAttribute("MemberName");
		foreach (XmlElement newmember in newfile.SelectNodes("Members/Member[@MemberName='" + membername + "']")) {
			if (GetMember(type, newmember) == oldmember2) return newmember;
		}
		
		return null;
	}
	
	private static MemberInfo GetMember(Type type, XmlElement member) {
		string membertype = member.SelectSingleNode("MemberType").InnerText;
		
		string returntype = null;
		XmlNode returntypenode = member.SelectSingleNode("ReturnValue/ReturnType");
		if (returntypenode != null) returntype = returntypenode.InnerText;
		
		// Get list of parameter types for member
		ArrayList memberparams = new ArrayList();
		foreach (XmlElement param in member.SelectNodes("Parameters/Parameter"))
			memberparams.Add(param.GetAttribute("Type"));

		string memberName = member.GetAttribute ("MemberName");
		do {
			int idx = memberName.IndexOf ("<");
			if (idx != -1)
				memberName = memberName.Substring (0, idx);
		} while (false);
		
		// Loop through all members in this type with the same name
		MemberInfo[] mis = type.GetMember(memberName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
		foreach (MemberInfo mi in mis) {
			if (mi is Type) continue;
			if (GetMemberType(mi) != membertype) continue;

			string sig = MakeMemberSignature(mi);
			if (sig == null) continue; // not publicly visible

			if (mi is MethodInfo) {
				// Casting operators can overload based on return type.
				if (returntype != GetDocTypeFullName (((MethodInfo)mi).ReturnType)) 
					continue;
			}

			ParameterInfo[] pis = null;
			if (mi is MethodInfo || mi is ConstructorInfo)
				pis = ((MethodBase)mi).GetParameters();
			else if (mi is PropertyInfo)
				pis = ((PropertyInfo)mi).GetIndexParameters();
			
			if (pis == null)
				pis = new ParameterInfo[0];
				
			if (pis.Length != memberparams.Count) continue;			
			
			bool good = true;
			for (int i = 0; i < pis.Length; i++)
				if (GetDocParameterType (pis[i].ParameterType) != (string)memberparams[i]) {
					good = false;
					break;
				}
			if (!good) continue;

			return mi;
		}
		
		return null;
	}
	
	// CREATE A STUB DOCUMENTATION FILE	

	public static XmlElement StubType(Type type) {
		string typesig = MakeTypeSignature(type);
		if (typesig == null) return null; // not publicly visible
		
		XmlDocument doc = new XmlDocument();
		XmlElement root = doc.CreateElement("Type");
		
		UpdateType(root, type, true);
		
		return root;
	}
	
	// STUBBING/UPDATING FUNCTIONS
	
	public static void UpdateType(XmlElement root, Type type, bool addmembers) {
		root.SetAttribute("Name", GetDocTypeName (type));
		root.SetAttribute("FullName", GetDocTypeFullName (type));

		WriteElementAttribute(root, "TypeSignature[@Language='C#']", "Language", "C#");
		WriteElementAttribute(root, "TypeSignature[@Language='C#']", "Value", MakeTypeSignature(type));
		
		XmlElement ass = WriteElement(root, "AssemblyInfo");
		WriteElementText(ass, "AssemblyName", type.Assembly.GetName().Name);
		WriteElementText(ass, "AssemblyVersion", type.Assembly.GetName().Version.ToString());
		if (type.Assembly.GetName().CultureInfo.Name != "")
			WriteElementText(ass, "AssemblyCulture", type.Assembly.GetName().CultureInfo.Name);
		else
			ClearElement(ass, "AssemblyCulture");
		
		// Why-oh-why do we put assembly attributes in each type file?
		// Neither monodoc nor monodocs2html use them, so I'm deleting them
		// since they're outdated in current docs, and a waste of space.
		//MakeAttributes(ass, type.Assembly, true);
		XmlNode assattrs = ass.SelectSingleNode("Attributes");
		if (assattrs != null)
			ass.RemoveChild(assattrs);
		
		NormalizeWhitespace(ass);
		
		if (type.IsGenericType) {
			XmlElement typeargs = WriteElement(root, "TypeParameters");
			ClearElementChildren(typeargs);
			foreach (Type arg in type.GetGenericArguments()) {
				XmlElement argnode = typeargs.OwnerDocument.CreateElement("TypeParameter");
				typeargs.AppendChild(argnode);
				argnode.InnerText = arg.Name;
			}
		} else {
			ClearElement(root, "TypeParameters");
		}
		
		if (type.BaseType != null) {
			XmlElement basenode = WriteElement(root, "Base");
			
			string basetypename = GetDocTypeFullName (type.BaseType);
			if (basetypename == "System.MulticastDelegate") basetypename = "System.Delegate";
			WriteElementText(root, "Base/BaseTypeName", basetypename);
			
			// Document how this type instantiates the generic parameters of its base type
			if (type.BaseType.IsGenericType) {
				ClearElement(basenode, "BaseTypeArguments");
				Type genericDefinition = type.BaseType.GetGenericTypeDefinition();
				for (int i = 0; i < type.BaseType.GetGenericArguments().Length; i++) {
					Type typearg = type.BaseType.GetGenericArguments()[i];
					Type typeparam = genericDefinition.GetGenericArguments()[i];
					
					XmlElement bta = WriteElement(basenode, "BaseTypeArguments");
					XmlElement arg = bta.OwnerDocument.CreateElement("BaseTypeArgument");
					bta.AppendChild(arg);
					arg.SetAttribute("TypeParamName", typeparam.Name);
					arg.InnerText = GetDocTypeFullName (typearg);
				}
			}
		} else {
			ClearElement(root, "Base");
		}

		if (!IsDelegate(type) && !type.IsEnum) {
			// Get a sorted list of interface implementations.  Don't include
			// interfaces that are implemented by a base type or another interface
			// because they go on the base type or base interface's signature.
			ArrayList interface_names = new ArrayList();
			foreach (Type i in type.GetInterfaces())
				if ((type.BaseType == null || Array.IndexOf(type.BaseType.GetInterfaces(), i) == -1) && InterfaceNotFromAnother(i, type.GetInterfaces()))
					interface_names.Add(GetDocTypeFullName (i));
			interface_names.Sort();

			XmlElement interfaces = WriteElement(root, "Interfaces");
			interfaces.RemoveAll();
			foreach (string iname in interface_names) {
				XmlElement iface = root.OwnerDocument.CreateElement("Interface");
				interfaces.AppendChild(iface);
				WriteElementText(iface, "InterfaceName", iname);
			}
		} else {
			ClearElement(root, "Interfaces");
		}

		MakeAttributes(root, type, false);
		
		if (IsDelegate(type)) {
			MakeParameters(root, type.GetMethod("Invoke").GetParameters());
			MakeReturnValue(root, type.GetMethod("Invoke"));
		}
		
		if (!IsDelegate(type) && addmembers) {
			XmlElement members = WriteElement(root, "Members");
			
			foreach (MemberInfo m in type.GetMembers(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance|BindingFlags.DeclaredOnly)) {
				if (m is Type) continue;
				if (!IsNew(m)) continue;
				
				XmlElement mm = MakeMember(root.OwnerDocument, m);
				if (mm == null) continue;
				members.AppendChild( mm );
			}
		}
			
		XmlElement docs = WriteElement(root, "Docs");
		MakeDocNode(type, docs, true);
		
		NormalizeWhitespace(root);
	}
	
	private static void UpdateMember(XmlElement me, MemberInfo mi) {	
		WriteElementAttribute(me, "MemberSignature[@Language='C#']", "Language", "C#");
		WriteElementAttribute(me, "MemberSignature[@Language='C#']", "Value", MakeMemberSignature(mi));

		WriteElementText(me, "MemberType", GetMemberType(mi));
		
		MakeAttributes(me, mi, false);
		MakeReturnValue(me, mi);
		MakeParameters(me, mi);
		
		string fieldValue;
		if (mi is FieldInfo && GetFieldConstValue((FieldInfo)mi, out fieldValue))
			WriteElementText(me, "MemberValue", fieldValue);
		
		ParameterInfo[] paraminfo = null;
		Type[] genericParams = null;
		Type returntype = null;
		bool returnisreturn = true; // the name of the node is 'return', vs 'value'
		bool addremarks = true;
		
		if (mi is MethodInfo || mi is ConstructorInfo) {
			paraminfo = ((MethodBase)mi).GetParameters();
			try {
				genericParams = ((MethodBase)mi).GetGenericArguments ();
			}
			catch (NotSupportedException) {
				genericParams = null;
			}
		}
		else if (mi is PropertyInfo) {
			paraminfo = ((PropertyInfo)mi).GetIndexParameters();
		}
			
		if (mi is MethodInfo) {
			returntype = ((MethodInfo)mi).ReturnType;
		} else if (mi is PropertyInfo) {
			returntype = ((PropertyInfo)mi).PropertyType;
			returnisreturn = false;
		}

		// no remarks section for enum members
		if (mi.DeclaringType.IsEnum)
			addremarks = false;

		string slashdocsig = MakeSlashDocMemberSig(mi);

		MakeDocNode(me, genericParams, paraminfo, returntype, returnisreturn, addremarks, slashdocsig);
		
	}
	
	private static bool GetFieldConstValue(FieldInfo field, out string value) {
		value = null;
		if (field.DeclaringType.IsEnum) return false;
		if (field.IsLiteral || (field.IsStatic && field.IsInitOnly)) {
			object val = field.GetValue(null);
			if (val == null) value = "null";
			else if (val is Enum) value = val.ToString();
			else if (val is IFormattable) {
				value = ((IFormattable)val).ToString();
				if (val is string)
					value = "\"" + value + "\"";
			}
			if (value != null && value != "")
				return true;
		}
		return false;
	}
	
	// XML HELPER FUNCTIONS
	
	private static XmlElement WriteElement(XmlElement parent, string element) {
		XmlElement ret = (XmlElement)parent.SelectSingleNode(element);
		if (ret == null) {
			string[] path = element.Split('/');
			foreach (string p in path) {
				ret = (XmlElement)parent.SelectSingleNode(p);
				if (ret == null) {
					string ename = p;
					if (ename.IndexOf('[') >= 0) // strip off XPath predicate
						ename = ename.Substring(0, ename.IndexOf('['));
					ret = parent.OwnerDocument.CreateElement(ename);
					parent.AppendChild(ret);
					parent = ret;
				} else {
					parent = ret;
				}
			}
		}
		return ret;
	}
	private static void WriteElementText(XmlElement parent, string element, string value) {
		XmlElement node = WriteElement(parent, element);
		node.InnerText = value;
	}
	private static void WriteElementInitialText(XmlElement parent, string element, string value) {
		XmlElement node = (XmlElement)parent.SelectSingleNode(element);
		if (node != null)
			return;
		node = WriteElement(parent, element);
		node.InnerText = value;
	}
	private static void WriteElementAttribute(XmlElement parent, string element, string attribute, string value) {
		XmlElement node = WriteElement(parent, element);
		if (node.GetAttribute(attribute) == value) return;
		node.SetAttribute(attribute, value);
	}
	private static void ClearElement(XmlElement parent, string name) {
		XmlElement node = (XmlElement)parent.SelectSingleNode(name);
		if (node != null)
			parent.RemoveChild(node);
	}
	private static void ClearElementChildren(XmlElement parent) {
		while (parent.FirstChild != null)
			parent.RemoveChild(parent.FirstChild);
	}
	
	// DOCUMENTATION HELPER FUNCTIONS
	
	private static void MakeDocNode(Type type, XmlElement e, bool addremarks) {
		ParameterInfo[] parameters = null;
		Type[] genericParams = type.GetGenericArguments ();
		if (type.DeclaringType != null) {
			Type[] declGenParams = type.DeclaringType.GetGenericArguments ();
			if (declGenParams != null && genericParams.Length == declGenParams.Length) {
				genericParams = null;
			}
			else if (declGenParams != null) {
				Type[] nestedParams = new Type [genericParams.Length - declGenParams.Length];
				for (int i = 0; i < nestedParams.Length; ++i) {
					nestedParams [i] = genericParams [i+declGenParams.Length];
				}
				genericParams = nestedParams;
			}
		}
		Type returntype = null;
		if (IsDelegate(type)) {
			parameters = type.GetMethod("Invoke").GetParameters();
			returntype = type.GetMethod("Invoke").ReturnType;
		}
		MakeDocNode(genericParams, parameters, returntype, true, e, addremarks, "T:" + GetDocTypeFullName (type));
	}
	
	private static void MakeDocNode(XmlElement root, Type[] genericParams, ParameterInfo[] parameters, Type returntype, bool returnisreturn, bool addremarks, string slashdocsig) {
		XmlElement e = WriteElement(root, "Docs");
		MakeDocNode(genericParams, parameters, returntype, returnisreturn, e, addremarks, slashdocsig);
	}
	
	private static void MakeDocNode(Type[] genericParams, ParameterInfo[] parameters, Type returntype, bool returnisreturn, XmlElement e, bool addremarks, string slashdocname) {
		WriteElementInitialText(e, "summary", "To be added.");
		
		if (parameters != null) {
			string[] values = new string [parameters.Length];
			for (int i = 0; i < values.Length; ++i)
				values [i] = parameters [i].Name;
			UpdateParameters (e, "param", values);
		}

		if (genericParams != null) {
			string[] values = new string [genericParams.Length];
			for (int i = 0; i < values.Length; ++i)
				values [i] = genericParams [i].Name;
			UpdateParameters (e, "typeparam", values);
		}

		string retnodename = null;
		if (returntype != null && returntype != typeof(void)) {
			retnodename = returnisreturn ? "returns" : "value";
			string retnodename_other = !returnisreturn ? "returns" : "value";
			
			// If it has a returns node instead of a value node, change its name.
			XmlElement retother = (XmlElement)e.SelectSingleNode(retnodename_other);
			if (retother != null) {
				XmlElement retnode = e.OwnerDocument.CreateElement(retnodename);
				foreach (XmlNode node in retother)
					retnode.AppendChild(node.CloneNode(true));
				e.ReplaceChild(retnode, retother);
			} else {
				WriteElementInitialText(e, retnodename, "To be added.");
			}
		} else {
			ClearElement(e, "returns");
			ClearElement(e, "value");
		}

		if (addremarks)
			WriteElementInitialText(e, "remarks", "To be added.");
		
		if (slashdocs != null && slashdocname != null) {
			XmlElement elem = (XmlElement)slashdocs.SelectSingleNode("doc/members/member[@name='" + slashdocname + "']");
			if (elem != null) {
				XmlElement xsummary = (XmlElement)elem.SelectSingleNode("summary");
				if (xsummary != null)
					WriteElementText(e, "summary", xsummary.InnerText);

				XmlElement xremarks = (XmlElement)elem.SelectSingleNode("remarks");
				if (xremarks != null)
					WriteElementText(e, "remarks", xremarks.InnerText);
				else if (xsummary != null)
					ClearElement(e, "remarks");

				if (retnodename != null) {
					XmlElement xreturns = (XmlElement)elem.SelectSingleNode(retnodename);
					if (xreturns != null)
						WriteElementText(e, retnodename, xreturns.InnerText);
				}
				
				foreach (XmlElement p in elem.SelectNodes("param")) {
					XmlElement p2 = (XmlElement)e.SelectSingleNode("param[@name='" + p.GetAttribute("name") + "']");
					if (p2 != null)
						p2.InnerText = p.InnerText;
				}
			}
		}
		
		NormalizeWhitespace(e);
	}

	private static void UpdateParameters (XmlElement e, string element, string[] values)
	{	
		if (values != null) {
			XmlNode[] paramnodes = new XmlNode[values.Length];
			
			// Some documentation had param nodes with leading spaces.
			foreach (XmlElement paramnode in e.SelectNodes(element)){
				paramnode.SetAttribute("name", paramnode.GetAttribute("name").Trim());
			}
			
			// If a member has only one parameter, we can track changes to
			// the name of the parameter easily.
			if (values.Length == 1) {
				int c = e.SelectNodes(element).Count;
				if (c == 1) {
					UpdateParameterName (e, (XmlElement) e.SelectSingleNode(element), values [0]);
				} else if (c > 1) {
					foreach (XmlElement paramnode in e.SelectNodes(element)) {
						if (paramnode.InnerText.StartsWith("To be added"))
							paramnode.ParentNode.RemoveChild(paramnode);
						else
							UpdateParameterName (e, paramnode, values [0]);
					}
				}
			}

			bool reinsert = false;

			// Pick out existing and still-valid param nodes, and
			// create nodes for parameters not in the file.
			Hashtable seenParams = new Hashtable();
			for (int pi = 0; pi < values.Length; pi++) {
				string p = values [pi];
				seenParams[p] = pi;
				
				paramnodes[pi] = e.SelectSingleNode(element + "[@name='" + p + "']");
				if (paramnodes[pi] != null) continue;
				
				XmlElement pe = e.OwnerDocument.CreateElement(element);
				pe.SetAttribute("name", p);
				pe.InnerText = "To be added.";
				paramnodes[pi] = pe;
				reinsert = true;
			}

			// Remove parameters that no longer exist and check all params are in the right order.
			int idx = 0;
			foreach (XmlElement paramnode in e.SelectNodes(element)) {
				string name = paramnode.GetAttribute("name");
				if (!seenParams.ContainsKey(name)) {
					if (!delete && !paramnode.InnerText.StartsWith("To be added")) {
						Console.Error.Write("The following param node can only be deleted if the --delete option is given: ");
						Console.Error.WriteLine(paramnode.OuterXml);
					} else {
						paramnode.ParentNode.RemoveChild(paramnode);
					}
					continue;
				}

				if ((int)seenParams[name] != idx)
					reinsert = true;
				
				idx++;
			}
			
			// Re-insert the parameter nodes at the top of the doc section.
			if (reinsert)
				for (int pi = values.Length-1; pi >= 0; pi--)
					e.PrependChild(paramnodes[pi]);
		} else {
			// Clear all existing param nodes
			foreach (XmlNode paramnode in e.SelectNodes(element)) {
				if (!delete && !paramnode.InnerText.StartsWith("To be added")) {
					Console.Error.WriteLine("The following param node can only be deleted if the --delete option is given:");
					Console.Error.WriteLine(paramnode.OuterXml);
				} else {
					paramnode.ParentNode.RemoveChild(paramnode);
				}
			}
		}
	}

	private static void UpdateParameterName (XmlElement docs, XmlElement pe, string newName)
	{
		string existingName = pe.GetAttribute ("name");
		pe.SetAttribute ("name", newName);
		if (existingName == newName)
			return;
		foreach (XmlElement paramref in docs.SelectNodes (".//paramref"))
			if (paramref.GetAttribute ("name").Trim () == existingName)
				paramref.SetAttribute ("name", newName);
	}

	private static void NormalizeWhitespace(XmlElement e) {
		// Remove all text and whitespace nodes from the element so it
		// is outputted with nice indentation and no blank lines.
		ArrayList deleteNodes = new ArrayList();
		foreach (XmlNode n in e)
			if (n is XmlText || n is XmlWhitespace || n is XmlSignificantWhitespace)
				deleteNodes.Add(n);
		foreach (XmlNode n in deleteNodes)
				n.ParentNode.RemoveChild(n);
	}
	
	private static void MakeAttributes(XmlElement root, object attributes, bool assemblyAttributes) {
		System.Collections.Generic.IList<CustomAttributeData> at;
		if (attributes is Assembly)
			at = CustomAttributeData.GetCustomAttributes((Assembly)attributes);
		else if (attributes is MemberInfo)
			at = CustomAttributeData.GetCustomAttributes((MemberInfo)attributes);
		else if (attributes is Module)
			at = CustomAttributeData.GetCustomAttributes((Module)attributes);
		else if (attributes is ParameterInfo)
			at = CustomAttributeData.GetCustomAttributes((ParameterInfo)attributes);
		else
			throw new ArgumentException("unsupported type: " + attributes.GetType().ToString());
	
		if (at.Count == 0) {
			ClearElement(root, "Attributes");
			return;
		}

		bool b = false;
		XmlElement e = (XmlElement)root.SelectSingleNode("Attributes");
		if (e != null)
			e.RemoveAll();
		else
			e = root.OwnerDocument.CreateElement("Attributes");
		
		foreach (CustomAttributeData a in at) {
			if (GetTypeVisibility(a.Constructor.DeclaringType.Attributes) == null) continue; // hide non-visible attributes
			
			if (a.Constructor.DeclaringType == typeof(System.Reflection.AssemblyKeyFileAttribute) || a.Constructor.DeclaringType == typeof(System.Reflection.AssemblyDelaySignAttribute)) continue; // hide security-related attributes
			if (a.Constructor.DeclaringType == typeof(System.Runtime.InteropServices.OutAttribute)) continue; // hide this because it is given in RefType attribute
			
			b = true;
			
			ArrayList fields = new ArrayList();

			foreach (CustomAttributeTypedArgument f in a.ConstructorArguments) {
				fields.Add(MakeAttributesValueString(f.Value));
			}
			foreach (CustomAttributeNamedArgument f in a.NamedArguments) {
				fields.Add(f.MemberInfo.Name + "=" + MakeAttributesValueString(f.TypedValue.Value));
			}

			string a2 = String.Join(", ", (string[])fields.ToArray(typeof(string)));
			if (a2 != "") a2 = "(" + a2 + ")";
			
			XmlElement ae = root.OwnerDocument.CreateElement("Attribute");
			e.AppendChild(ae);
			
			string name = a.Constructor.DeclaringType.FullName;
			if (name.EndsWith("Attribute")) name = name.Substring(0, name.Length-"Attribute".Length);
			WriteElementText(ae, "AttributeName", name + a2);
		}
		
		if (b && e.ParentNode == null)
			root.AppendChild(e);
		else if (!b)
			ClearElement(root, "Attributes");
		
		NormalizeWhitespace(e);
	}
	
	private static string MakeAttributesValueString(object v) {
		if (v == null) return "null";
		else if (v is string) return "\"" + v + "\"";
		else if (v is bool) return (bool)v ? "true" : "false";
		else if (v is Type) return "typeof(" + GetCSharpFullName ((Type)v) + ")";
		else if (v is Enum) return v.GetType().FullName + "." + v.ToString().Replace(", ", "|");
		else return v.ToString();
	}
	
	private static void MakeParameters(XmlElement root, ParameterInfo[] parameters) {
		XmlElement e = WriteElement(root, "Parameters");
		e.RemoveAll();
		foreach (ParameterInfo p in parameters) {
			XmlElement pe = root.OwnerDocument.CreateElement("Parameter");
			e.AppendChild(pe);
			pe.SetAttribute("Name", p.Name);
			pe.SetAttribute("Type", GetDocParameterType (p.ParameterType));
			if (p.ParameterType.IsByRef) {
				if (p.IsOut) pe.SetAttribute("RefType", "out");
				else pe.SetAttribute("RefType", "ref");
			}
			MakeAttributes(pe, p, false);
		}
	}

	private static void MakeParameters(XmlElement root, MemberInfo mi) {
		if (mi is ConstructorInfo) MakeParameters(root, ((ConstructorInfo)mi).GetParameters());
		else if (mi is MethodInfo) MakeParameters(root, ((MethodInfo)mi).GetParameters());
		else if (mi is PropertyInfo) {
			ParameterInfo[] parameters = ((PropertyInfo)mi).GetIndexParameters();
			if (parameters.Length > 0)
				MakeParameters(root, parameters);
			else
				return;
		}
		else if (mi is FieldInfo) return;
		else if (mi is EventInfo) return;
		else throw new ArgumentException();
	}

	private static string GetDocParameterType (Type type)
	{
		return GetDocTypeFullName (type).Replace ("@", "&");
	}

	private static void MakeReturnValue(XmlElement root, Type type, ICustomAttributeProvider attributes) {
		XmlElement e = WriteElement(root, "ReturnValue");
		e.RemoveAll();
		WriteElementText(e, "ReturnType", GetDocTypeFullName (type));
		if (attributes != null)
			MakeAttributes(root, attributes, false);
	}
	
	private static void MakeReturnValue(XmlElement root, MemberInfo mi) {
		if (mi is ConstructorInfo) return;
		else if (mi is MethodInfo) MakeReturnValue(root, ((MethodInfo)mi).ReturnType, ((MethodInfo)mi).ReturnTypeCustomAttributes);
		else if (mi is PropertyInfo) MakeReturnValue(root, ((PropertyInfo)mi).PropertyType, null);
		else if (mi is FieldInfo) MakeReturnValue(root, ((FieldInfo)mi).FieldType, null);
		else if (mi is EventInfo) MakeReturnValue(root, ((EventInfo)mi).EventHandlerType, null);
		else throw new ArgumentException(mi + " is a " + mi.GetType().FullName);
	}
	
	private static MemberInfo GetInterfaceDefinition(MethodInfo mi) {
		if (mi.DeclaringType.IsInterface) return null;

		foreach (Type i in mi.DeclaringType.GetInterfaces()) {
			System.Reflection.InterfaceMapping map = mi.DeclaringType.GetInterfaceMap(i);
			for (int idx = 0; idx < map.InterfaceMethods.Length; idx++) {
				if (map.TargetMethods[idx] == mi)
					return map.InterfaceMethods[idx];
			}
		}
		
		return null;
	}

	private static XmlElement MakeMember(XmlDocument doc, MemberInfo mi) {
		if (mi is Type) return null;
		
		string sigs = MakeMemberSignature(mi);
		if (sigs == null) return null; // not publicly visible
		
		// no documentation for property/event accessors.  Is there a better way of doing this?
		if (mi.Name.StartsWith("get_")) return null;
		if (mi.Name.StartsWith("set_")) return null;
		if (mi.Name.StartsWith("add_")) return null;
		if (mi.Name.StartsWith("remove_")) return null;
		if (mi.Name.StartsWith("raise_")) return null;
		
		XmlElement me = doc.CreateElement("Member");
		me.SetAttribute("MemberName", GetMemberName (mi));
		
		UpdateMember(me, mi);
		
		return me;
	}

	private static string GetMemberName (MemberInfo mi)
	{
		MethodBase mb = mi as MethodBase;
		if (mb == null)
			return mi.Name;
		try {
			StringBuilder sb = new StringBuilder ();
			sb.Append (mi.Name);
			Type[] typeParams = mb.GetGenericArguments ();
			if (typeParams.Length > 0) {
				sb.Append ("<");
				sb.Append (typeParams [0].Name);
				for (int i = 1; i < typeParams.Length; ++i)
					sb.Append (",").Append (typeParams [i].Name);
				sb.Append (">");
			}
			return sb.ToString ();
		}
		catch (NotSupportedException) {
			return mi.Name;
		}
	}
	
	static bool IsDelegate(Type type) {
		return typeof(System.Delegate).IsAssignableFrom (type) && !type.IsAbstract;
	}
	
	/// SIGNATURE GENERATION FUNCTIONS
	
	private static bool InterfaceNotFromAnother(Type i, Type[] i2) {
		foreach (Type t in i2)
			if (i != t && Array.IndexOf(t.GetInterfaces(), i) != -1)
				return false;
		return true;
	}
	
	static string GetTypeKind (Type t) {
		if (t.IsEnum) return "enum";
		if (t.IsClass || t == typeof(System.Enum)) return "class";
		if (t.IsInterface) return "interface";
		if (t.IsValueType) return "struct";
		throw new ArgumentException(t.FullName);
	}

	static string GetTypeVisibility (TypeAttributes ta) {
		switch (ta & TypeAttributes.VisibilityMask) {
		case TypeAttributes.Public:
		case TypeAttributes.NestedPublic:
				return "public";

		case TypeAttributes.NestedFamily:
		case TypeAttributes.NestedFamORAssem:
				return "protected";

		default:
				return null;
		}
	}

	static string MakeTypeSignature (Type type) {
		StringBuilder sig = new StringBuilder();
		
		string visibility = GetTypeVisibility(type.Attributes);
		if (visibility == null) return null;
		sig.Append(visibility);
		sig.Append(" ");

		if (type.IsAbstract && !type.IsInterface) sig.Append("abstract ");
		if (type.IsSealed && !IsDelegate(type) && !type.IsValueType) sig.Append("sealed ");

		if (IsDelegate(type)) {
			sig.Append("delegate ");
			MethodInfo invoke = type.GetMethod ("Invoke");
			string arguments = GetMethodParameters(invoke.GetParameters());
			string return_value = GetCSharpFullName (invoke.ReturnType);
			sig.Append(return_value);
			sig.Append(" ");
			sig.Append(GetCSharpName (type));
			sig.Append("(");
			sig.Append(arguments);
			sig.Append(");");
			return sig.ToString();
		}
		
		sig.Append(GetTypeKind(type));
		sig.Append(" ");
		sig.Append(GetCSharpName (type));

		if (!type.IsEnum) {
			Type basetype = type.BaseType;
			if (basetype == typeof(object) || type.IsValueType) // don't show this in signatures
				basetype = null;
			
			ArrayList interface_names = new ArrayList();
			foreach (Type i in type.GetInterfaces())
				if ((type.BaseType == null || Array.IndexOf(type.BaseType.GetInterfaces(), i) == -1) && InterfaceNotFromAnother(i, type.GetInterfaces()))
					interface_names.Add(GetCSharpFullName (i));
			interface_names.Sort();
			
			if (basetype != null || interface_names.Count > 0)
				sig.Append(" : ");
			
			if (basetype != null) {
				sig.Append(GetCSharpFullName (basetype));
				if (interface_names.Count > 0)
					sig.Append(", ");
			}
			
			for (int i = 0; i < interface_names.Count; i++){
					if (i != 0) sig.Append(", ");
					sig.Append(interface_names[i]);
			}
		}

		return sig.ToString();
	}


	static string GetFieldVisibility (FieldInfo field) {
		if (field.IsPublic) return "public";
		if (field.IsFamily) return "protected";
		return null;
	}

	static string MakeFieldSignature (FieldInfo field) {
		if (field.DeclaringType.IsEnum && field.Name == "value__")
			return null; // This member of enums aren't documented.
		
		string visibility = GetFieldVisibility (field);
		if (visibility == null) return null;
		if (field.DeclaringType.IsEnum) return field.Name;
		
		string type = GetCSharpFullName (field.FieldType);
		
		string modifiers = String.Empty;
		if (field.IsStatic && !field.IsLiteral) modifiers += " static";
		if (field.IsInitOnly) modifiers += " readonly";
		if (field.IsLiteral) modifiers += " const";
		
		string fieldValue;
		if (GetFieldConstValue(field, out fieldValue))
			fieldValue = " = " + fieldValue;
		else
			fieldValue = "";

		return String.Format ("{0}{1} {2} {3}{4};",
						visibility, modifiers, type, field.Name, fieldValue);
	}

	static string GetMethodVisibility (MethodBase method) {
		if (method.IsPublic) return "public";
		if (method.IsFamily) return "protected";
		return null;
	}

	static string GetMethodParameters (ParameterInfo[] pi) {
		if (pi.Length == 0) return "";
		
		StringBuilder sb = new StringBuilder ();

		int i = 0;
		foreach (ParameterInfo parameter in pi) {
			if (i != 0) sb.Append (", ");
			if (parameter.ParameterType.IsByRef) {
				if (parameter.IsOut) sb.Append("out ");
				else sb.Append("ref ");
			}
			string param = GetCSharpFullName (parameter.ParameterType).Replace ("@", "");
			sb.Append (param);
			sb.Append (" ");
			sb.Append (parameter.Name);
			i++;
		}

		return sb.ToString();
	}

	static string MakeMethodSignature (MethodInfo method) {
		StringBuilder sb = new StringBuilder ();
		sb.Append (GetMethodVisibility (method));
		if (sb.Length == 0)
				return null;
		
		string modifiers = String.Empty;
		if (method.IsStatic) modifiers += " static";
		if (method.IsVirtual && !method.IsAbstract) {
			if ((method.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual";
			else modifiers += " override";
		}
		if (method.IsAbstract && !method.DeclaringType.IsInterface) modifiers += " abstract";
		if (method.IsFinal) modifiers += " sealed";
		if (modifiers == " virtual sealed") modifiers = "";

		sb.Append (modifiers);

		// Special signature for destructors.
		if (method.Name == "Finalize" && method.GetParameters().Length == 0)
			return "~" + method.DeclaringType.Name + " ();";	

		sb.Append (" ").Append (GetCSharpFullName (method.ReturnType));
		sb.Append (" ").Append (method.Name);
		try {
			Type[] args = method.GetGenericArguments ();
			if (args.Length > 0) {
				sb.Append ("<");
				sb.Append (args [0].Name);
				for (int i = 1; i < args.Length; ++i)
					sb.Append (",").Append (args [i].Name);
				sb.Append (">");
			}
		}
		catch (NotSupportedException) {
			/* ignore */
		}
		sb.Append (" (").Append (GetMethodParameters (method.GetParameters())).Append (");");

		return sb.ToString ();
	}

	static string MakeConstructorSignature (ConstructorInfo constructor) {
		string visibility = GetMethodVisibility (constructor);
		if (visibility == null)
			return null;

		string name = GetTypeName (constructor.DeclaringType.Name);
		string parameters = GetMethodParameters (constructor.GetParameters());

		return String.Format ("{0} {1} ({2});",
						visibility, name, parameters);
	}


	static string MakePropertySignature (PropertyInfo property) {
		// Check accessibility of get and set, since they can be different these days.
		string get_visible = null, set_visible = null;
		MethodBase get_method = property.GetGetMethod (true);
		MethodBase set_method = property.GetSetMethod (true);
		if (get_method != null) get_visible = GetMethodVisibility(get_method);
		if (set_method != null) set_visible = GetMethodVisibility(set_method);
		if (get_visible == null && set_visible == null) return null; // neither are visible
		
		// Pick an accessor to use for static/virtual/override/etc. checks.
		MethodBase method = property.GetSetMethod (true);
		if (method == null)
			method = property.GetGetMethod (true);
	
		string modifiers = String.Empty;
		if (method.IsStatic) modifiers += " static";
		if (method.IsVirtual && !method.IsAbstract) {
				if ((method.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual";
				else modifiers += " override";
		}
		if (method.IsAbstract && !method.DeclaringType.IsInterface) modifiers += " abstract";
		if (method.IsFinal) modifiers += " sealed";
		if (modifiers == " virtual sealed") modifiers = "";
	
		MemberInfo[] defs = property.DeclaringType.GetDefaultMembers ();
		string name = property.Name;
		foreach (MemberInfo mi in defs) {
			if (mi == property) {
				name = "this";
				break;
			}
		}
	
		string type_name = GetCSharpFullName (property.PropertyType);
		
		string parameters = GetMethodParameters (property.GetIndexParameters());
		if (parameters != "") parameters = "[" + parameters + "]";		
		
		string visibility;
		if (get_visible != null && (set_visible == null || (set_visible != null && get_visible == set_visible)))
			visibility = get_visible;
		else if (set_visible != null && get_visible == null)
			visibility = set_visible;
		else
			visibility = "public"; // if they are different, but both externally accessible, the greater access must be public, I think
		
		string accessors = "{";
		if (set_visible != null) {
			if (set_visible != visibility)
				accessors += " " + set_visible;
			accessors += " set;";
		}
		if (get_visible != null) {
			if (get_visible != visibility)
				accessors += " " + get_visible;
			accessors += " get;";
		}
		accessors += " }";
	
		return String.Format ("{0}{1} {2} {3}{4} {5};",
						visibility, modifiers, type_name, name, parameters, accessors);
	}
		
	static string MakeEventSignature (EventInfo ev) {
		MethodInfo add = ev.GetAddMethod ();

		string visibility = GetMethodVisibility(add);
		if (visibility == null)
			return null;

		string modifiers = String.Empty;
		if (add.IsStatic) modifiers += " static";
		if (add.IsVirtual && !add.IsAbstract) {
			if ((add.Attributes & MethodAttributes.NewSlot) != 0) modifiers += " virtual";
			else modifiers += " override";
		}
		if (add.IsAbstract && !ev.DeclaringType.IsInterface) modifiers += " abstract";
		if (add.IsFinal) modifiers += " sealed";
		if (modifiers == " virtual sealed") modifiers = "";
		
		string name = ev.Name;
		string type = GetCSharpFullName (ev.EventHandlerType);

		return String.Format ("{0}{1} event {2} {3};",
						visibility, modifiers, type, name);
	}
	
	static string MakeMemberSignature(MemberInfo mi) {
		if (mi is ConstructorInfo) return MakeConstructorSignature((ConstructorInfo)mi);
		if (mi is MethodInfo) return MakeMethodSignature((MethodInfo)mi);
		if (mi is PropertyInfo) return MakePropertySignature((PropertyInfo)mi);
		if (mi is FieldInfo) return MakeFieldSignature((FieldInfo)mi);
		if (mi is EventInfo) return MakeEventSignature((EventInfo)mi);
		throw new ArgumentException(mi.ToString());
	}

	static string GetMemberType(MemberInfo mi) {
		if (mi is ConstructorInfo) return "Constructor";
		if (mi is MethodInfo) return "Method";
		if (mi is PropertyInfo) return "Property";
		if (mi is FieldInfo) return "Field";
		if (mi is EventInfo) return "Event";
		throw new ArgumentException();
	}

	// Converts a fully .NET qualified type name into a C#-looking one
	private static string MakeSlashDocMemberSig(MemberInfo m) {
		string typename = GetCSharpFullName (m.DeclaringType);
		string memname = m.Name;
		
		if (m is ConstructorInfo) {
			return "C:" + typename + ".#ctor(" + MakeSlashDocMemberSigArgs(((ConstructorInfo)m).GetParameters()) + ")";
		}
		if (m is MethodInfo) {
			return "M:" + typename + "." + memname + MakeSlashDocMemberSigArgs(((MethodInfo)m).GetParameters()) + MakeSlashDocMemberSigReturn((MethodInfo)m);
		}
		if (m is PropertyInfo && ((PropertyInfo)m).GetIndexParameters().Length == 0)
			return "P:" + typename + "." + memname;
		if (m is PropertyInfo && ((PropertyInfo)m).GetIndexParameters().Length > 0) {
			return "P:" + typename + "." + memname + MakeSlashDocMemberSigArgs(((PropertyInfo)m).GetIndexParameters());
		}
		if (m is FieldInfo)
			return "F:" + typename + "." + memname;
		if (m is EventInfo)
			return "F:" + typename + "." + memname;
		throw new ArgumentException();
	}
	
	private static string MakeSlashDocMemberSigReturn(MethodInfo m) {
		if (m.Name != "op_Implicit" && m.Name != "op_Explicit") return "";
		return "~" + GetCSharpFullName (m.ReturnType);
	}
	
	private static string MakeSlashDocMemberSigArgs(ParameterInfo[] ps) {
		StringBuilder buf = new StringBuilder ();
		foreach (ParameterInfo p in ps) {
			if (buf.Length > 0)
				buf.Append (",");
			FillDocTypeName (p.ParameterType, buf, true, true, '+', true);
		}
		if (buf.Length > 0)
			return "(" + buf.ToString () + ")";
		return "";
	}

	private static StringBuilder FillDocTypeName (Type type, StringBuilder buf, bool includeNamespace, bool includeDeclType, char declSep, bool csharp)
	{
		if (type == null)
			throw new ArgumentNullException ("type");
		if (type.IsArray) {
			FillDocTypeName (type.GetElementType(), buf, includeNamespace, 
				includeDeclType, declSep, csharp).Append ("[");
			int rank = type.GetArrayRank ();
			if (rank > 1)
				buf.Append (new string (',', rank-1));
			return buf.Append ("]");
		}
		if (type.IsByRef) {
			return FillDocTypeName (type.GetElementType(), buf, 
				includeNamespace, includeDeclType, declSep, csharp).Append ("@");
		}
		if (type.IsPointer) {
			return FillDocTypeName (type.GetElementType(), buf, 
				includeNamespace, includeDeclType, declSep, csharp).Append ("*");
		}
		if (type.IsGenericParameter)
			return buf.Append (type.Name);
		if (!type.IsGenericType) {
			string t = type.FullName;
			if (!csharp || !t.StartsWith ("System.")) {
				if (includeNamespace)
					buf.Append (type.Namespace).Append (".");
				if (type.DeclaringType != null && includeDeclType)
					buf.Append (type.DeclaringType.Name).Append (declSep);
				return buf.Append (type.Name);
			}
			
			switch (t) {
			case "System.Byte":    return buf.Append ("byte");
			case "System.SByte":   return buf.Append ("sbyte");
			case "System.Int16":   return buf.Append ("short");
			case "System.Int32":   return buf.Append ("int");
			case "System.Int64":   return buf.Append ("long");

			case "System.UInt16":  return buf.Append ("ushort");
			case "System.UInt32":  return buf.Append ("uint");
			case "System.UInt64":  return buf.Append ("ulong");

			case "System.Single":  return buf.Append ("float");
			case "System.Double":  return buf.Append ("double");
			case "System.Decimal": return buf.Append ("decimal");
			case "System.Boolean": return buf.Append ("bool");
			case "System.Char":    return buf.Append ("char");
			case "System.Void":    return buf.Append ("void");
			case "System.String":  return buf.Append ("string");
			case "System.Object":  return buf.Append ("object");
			}
			
			// Types in the system namespace just get their type name returned.
			if (includeNamespace && t.StartsWith("System.")) {
				string sysname = t.Substring(7);
				if (sysname.IndexOf(".") == -1)
					return buf.Append (sysname);
			}

			return buf.Append (includeNamespace ? t : type.Name);
		}
		if (includeNamespace)
			buf.Append (type.Namespace).Append (".");
		Type[] genArgs = type.GetGenericArguments ();
		int genArg = 0;
		if (type.DeclaringType != null) {
			if (includeDeclType) {
				buf.Append (GetTypeName (type.DeclaringType.Name));
				if (type.DeclaringType.IsGenericType) {
					buf.Append ("<");
					int max = type.DeclaringType.GetGenericArguments ().Length;
					FillDocTypeName (genArgs [genArg++], buf, includeNamespace, 
							includeDeclType, declSep, csharp);
					while (genArg < max) {
						buf.Append (",");
						FillDocTypeName (genArgs [genArg++], buf, includeNamespace, 
								includeDeclType, declSep, csharp);
					}
					buf.Append (">");
				}
				buf.Append (declSep);
			}
			else
				genArg = type.DeclaringType.IsGenericType 
					? type.DeclaringType.GetGenericArguments ().Length
					: 0;
		}
		buf.Append (GetTypeName (type.Name));
		if (genArg < genArgs.Length) {
			buf.Append ("<");
			FillDocTypeName (genArgs [genArg++], buf, includeNamespace, 
					includeDeclType, declSep, csharp);
			while (genArg < genArgs.Length) {
				buf.Append (",");
				FillDocTypeName (genArgs [genArg++], buf, includeNamespace, 
						includeDeclType, declSep, csharp);
			}
			buf.Append (">");
		}
		return buf;
	}

	private static string GetDocTypeName (Type type)
	{
		return FillDocTypeName (type, new StringBuilder (), false, true, '+', false).ToString ();
	}

	private static string GetDocTypeFullName (Type type)
	{
		return FillDocTypeName (type, new StringBuilder (), true, true, '+', false).ToString ();
	}

	private static string GetCSharpName (Type type)
	{
		return FillDocTypeName (type, new StringBuilder (), false, true, '.', true).ToString ();
	}

	private static string GetCSharpFullName (Type type)
	{
		return FillDocTypeName (type, new StringBuilder (), true, true, '.', true).ToString ();
	}

	private static string GetTypeName (string name)
	{
		int n = name.IndexOf ("`");
		if (n >= 0)
			return name.Substring (0, n);
		return name;
	}
}
