//
// The provider for a tree of ECMA documents
//
// Authors:
//   Miguel de Icaza (miguel@ximian.com)
//   Joshua Tauberer (tauberer@for.net)
//
// (C) 2002, 2003 Ximian, Inc.
// (C) 2003 Joshua Tauberer.
//
// TODO:
//   Should cluster together constructors
//
// Easy:
//   Should render attributes on the signature.
//   Include examples as well.
//
namespace Monodoc {
using System;
using System.Reflection;
using System.IO;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.Text;
using System.Collections;
using ICSharpCode.SharpZipLib.Zip;
using Monodoc.Lucene.Net.Index;
using Monodoc.Lucene.Net.Documents;

using BF = System.Reflection.BindingFlags;

//
// Helper routines to extract information from an Ecma XML document
//
public class EcmaDoc {
	public static string GetFullClassName (XmlDocument doc)
	{
		return doc.SelectSingleNode ("/Type").Attributes ["FullName"].InnerText;
	}
	
	public static string GetClassName (XmlDocument doc)
	{
		return GetDisplayName (doc.SelectSingleNode ("/Type")).Replace ("+", ".");
	}

	public static string GetDisplayName (XmlNode type)
	{
		if (type.Attributes ["DisplayName"] != null) {
			return type.Attributes ["DisplayName"].Value;
		}
		return type.Attributes ["Name"].Value;
	}

	public static string GetClassAssembly (XmlDocument doc)
	{
		return doc.SelectSingleNode ("/Type/AssemblyInfo/AssemblyName").InnerText;
	}

	public static string GetClassNamespace (XmlDocument doc)
	{
		string s = doc.SelectSingleNode ("/Type").Attributes ["FullName"].InnerText;

		return s.Substring (0, s.LastIndexOf ("."));
	}
	
	public static string GetTypeKind (XmlDocument doc)
	{
		XmlNode node = doc.SelectSingleNode ("/Type/Base/BaseTypeName");

		if (node == null){
			if (GetFullClassName (doc) == "System.Object")
				return "Class";
			return "Interface";
		}

		switch (node.InnerText){

		case "System.Delegate":
		case "System.MulticastDelegate":
			return "Delegate";
		case "System.ValueType":
			return "Structure";
		case "System.Enum":
			return "Enumeration";
		default:
			return "Class";
		}
	}

	//
	// Utility function: converts a fully .NET qualified type name into a C#-looking one
	//
	public static string ConvertCTSName (string type)
	{
		if (!type.StartsWith ("System."))
			return type;

		if (type.EndsWith ("*"))
			return ConvertCTSName(type.Substring(0, type.Length - 1)) + "*";
		if (type.EndsWith ("&"))
			return ConvertCTSName(type.Substring(0, type.Length - 1)) + "&";
		if (type.EndsWith ("[]"))
			return ConvertCTSName(type.Substring(0, type.Length - 2)) + "[]";

		switch (type) {
		case "System.Byte": return "byte";
		case "System.SByte": return "sbyte";
		case "System.Int16": return "short";
		case "System.Int32": return "int";
		case "System.Int64": return "long";
			
		case "System.UInt16": return "ushort";
		case "System.UInt32": return "uint";
		case "System.UInt64": return "ulong";
			
		case "System.Single":  return "float";
		case "System.Double":  return "double";
		case "System.Decimal": return "decimal";
		case "System.Boolean": return "bool";
		case "System.Char":    return "char";
		case "System.String":  return "string";
			
		case "System.Object":  return "object";
		case "System.Void":  return "void";
		}

		if (type.LastIndexOf(".") == 6)
			return type.Substring(7);
		
		return type;
	}
}

//
// The Ecma documentation provider:
//
// It populates a tree with contents during the help assembly
//
public class EcmaProvider : Provider {
	string basedir;

	public EcmaProvider (string base_directory)
	{
		basedir = base_directory;

		if (!Directory.Exists (basedir))
			throw new FileNotFoundException (String.Format ("The directory `{0}' does not exist", basedir));
	}
	
	public override void PopulateTree (Tree tree)
	{
		string [] namespaces = Directory.GetDirectories (basedir);
		

		
		foreach (string ns in namespaces){
			string [] files = Directory.GetFiles (ns);
			Node ns_node = null;
			string tn = null;
			
			Hashtable nsnodes = new Hashtable();

			foreach (string file in files){
				if (!file.EndsWith (".xml"))
					continue;

				if (ns_node == null) {
					tn = Path.GetFileName (ns);
					Console.Error.WriteLine ("Processing namespace {0}", tn);
					ns_node = tree.LookupNode (tn, "N:" + tn);
					string ns_summary_file = basedir + "/" + tn + ".xml";
					
					nsnodes[ns_node] = nsnodes;
					
					if (File.Exists (ns_summary_file)) {
						XmlDocument nsSummaryFile = new XmlDocument ();
						nsSummaryFile.Load (ns_summary_file);
						
						XmlNode ns_summary = nsSummaryFile.SelectSingleNode ("Namespace/Docs/summary");
						if (ns_summary != null && ns_summary.InnerText != "To be added." && ns_summary.InnerText != "") {
							namespace_summaries [tn] = nsSummaryFile.SelectSingleNode ("Namespace/Docs/summary");
							namespace_remarks [tn] = nsSummaryFile.SelectSingleNode ("Namespace/Docs/remarks");
						}
						
					} else if (!namespace_summaries.ContainsKey (tn)) {
						namespace_summaries [tn] = null;
						namespace_remarks [tn] = null;
					}
				}
				Console.Error.WriteLine ("    Processing input file {0}", Path.GetFileName (file));

				PopulateClass (tn, ns_node, file);
			}
			
			// Sort the list of types in each namespace
			foreach (Node ns_node2 in nsnodes.Keys)
				ns_node2.Sort();
		}

	}
		
	struct TypeInfo : IComparable {
		public string type_assembly;
		public string type_name;
		public string type_full;
		public string type_kind;
		public XmlNode type_doc;

		public TypeInfo (string k, string a, string f, string s, XmlNode n)
		{
			type_assembly = a;
			type_name = s;
			type_doc = n;
			type_kind = k;
			type_full = f;
		}

		public int CompareTo (object b)
		{
			TypeInfo na = this;
			TypeInfo nb = (TypeInfo) b;

			return String.Compare (na.type_full, nb.type_full);
		}
	}
	
	//
	// Packs a file with the summary data
	//
	public override void CloseTree (HelpSource hs, Tree tree)
	{
		foreach (DictionaryEntry de in class_summaries){
			XmlDocument doc = new XmlDocument ();
			string ns = (string) de.Key;
			
			ArrayList list = (ArrayList) de.Value;
			list.Sort();

			XmlElement elements = doc.CreateElement ("elements");
			doc.AppendChild (elements);
			
			if (namespace_summaries [ns] != null)
				elements.AppendChild (doc.ImportNode ((XmlNode)namespace_summaries [ns],true));
			else
				elements.AppendChild (doc.CreateElement("summary"));
			
			if (namespace_remarks [ns] != null)
				elements.AppendChild (doc.ImportNode ((XmlNode)namespace_remarks [ns],true));
			else
				elements.AppendChild (doc.CreateElement("remarks"));
			
			Console.Error.WriteLine ("Have {0} elements in the {1}", list.Count, ns);
			foreach (TypeInfo p in list){
				XmlElement e = null;
				
				switch (p.type_kind){
				case "Class":
					e = doc.CreateElement ("class"); 
					break;
					
				case "Enumeration":
					e = doc.CreateElement ("enum");
					break;
					
				case "Structure":
					e = doc.CreateElement ("struct");
					break;
					
				case "Delegate":
					e = doc.CreateElement ("delegate");
					break;
					
				case "Interface":
					e = doc.CreateElement ("interface");
					break;
				}
				
				e.SetAttribute ("name", p.type_name);
				e.SetAttribute ("fullname", p.type_full);
				e.SetAttribute ("assembly", p.type_assembly);
				XmlNode copy = doc.ImportNode (p.type_doc, true);
				e.AppendChild (copy);
				elements.AppendChild (e);
			}
			hs.PackXml ("xml.summary." + ns, doc);
		}
		
		
		XmlDocument nsSummary = new XmlDocument ();
		XmlElement root = nsSummary.CreateElement ("elements");
		nsSummary.AppendChild (root);
		
		foreach (DictionaryEntry de in namespace_summaries) {
			XmlNode n = (XmlNode)de.Value;
			XmlElement summary = nsSummary.CreateElement ("namespace");
			summary.SetAttribute ("ns", (string)de.Key);
			root.AppendChild (summary);
			if (n != null)
				summary.AppendChild (nsSummary.ImportNode (n,true));
			else
				summary.AppendChild (nsSummary.CreateElement("summary"));
			
		}
		tree.HelpSource.PackXml ("mastersummary.xml", nsSummary);
	}
	       
	static Hashtable class_summaries = new Hashtable ();
	static Hashtable namespace_summaries = new Hashtable ();
	static Hashtable namespace_remarks = new Hashtable ();
	XmlDocument doc;
	
	void PopulateClass (string ns, Node ns_node, string file)
	{
		doc = new XmlDocument ();
		doc.Load (file);
		
		string name = EcmaDoc.GetClassName (doc);
		string assembly = EcmaDoc.GetClassAssembly (doc);
		string kind = EcmaDoc.GetTypeKind (doc);
		string full = EcmaDoc.GetFullClassName (doc);

		Node class_node;
		string file_code = ns_node.tree.HelpSource.PackFile (file);

		XmlNode class_summary = doc.SelectSingleNode ("/Type/Docs/summary");
		ArrayList l = (ArrayList) class_summaries [ns];
		if (l == null){
			l = new ArrayList ();
			class_summaries [ns] = (object) l;
		}
		l.Add (new TypeInfo (kind, assembly, full, name, class_summary));
	       
		class_node = ns_node.LookupNode (String.Format ("{0} {1}", name, kind), "ecma:" + file_code + "#" + name + "/");
		
		if (kind == "Delegate") {
			if (doc.SelectSingleNode("/Type/ReturnValue") == null)
				Console.Error.WriteLine("Delegate " + name + " does not have a ReturnValue node.  See the ECMA-style updates.");
		}

		if (kind == "Enumeration")
			return;

		if (kind == "Delegate")
			return;
		
		//
		// Always add the Members node
		//
		class_node.CreateNode ("Members", "*");

		PopulateMember (name, class_node, "Constructor", "Constructors");
		PopulateMember (name, class_node, "Method", "Methods");
		PopulateMember (name, class_node, "Property", "Properties");
		PopulateMember (name, class_node, "Field", "Fields");
		PopulateMember (name, class_node, "Event", "Events");
		PopulateMember (name, class_node, "Operator", "Operators");
	}

	class NodeIndex {
		public XmlNode node;
		public int     index;

		public NodeIndex (XmlNode node, int index)
		{
			this.node = node;
			this.index = index;
		}
	}

	struct NodeCompare : IComparer {
		public int Compare (object a, object b)
		{
			NodeIndex na = (NodeIndex) a;
			NodeIndex nb = (NodeIndex) b;

			return String.Compare (na.node.Attributes ["MemberName"].InnerText,
					       nb.node.Attributes ["MemberName"].InnerText);
		}
	}

	string GetMemberName (XmlNode node)
	{
		return node.Attributes ["MemberName"].InnerText;
	}
	
	//
	// Performs an XPath query on the document to extract the nodes for the various members
	// we also use some extra text to pluralize the caption
	//
	void PopulateMember (string typename, Node node, string type, string caption)
	{
		string select = type;
		if (select == "Operator") select = "Method";
		
		XmlNodeList list1 = doc.SelectNodes (String.Format ("/Type/Members/Member[MemberType=\"{0}\"]", select));
		ArrayList list = new ArrayList();
		int i = 0;
		foreach (XmlElement n in list1) {
			n.SetAttribute("assembler_index", (i++).ToString());
			if (type == "Method" && GetMemberName(n).StartsWith("op_")) continue;
			if (type == "Operator" && !GetMemberName(n).StartsWith("op_")) continue;
			list.Add(n);
		}
		
		int count = list.Count;
		
		if (count == 0)
			return;

		Node nodes_node;
		string key = type.Substring (0, 1);
		nodes_node = node.CreateNode (caption, key);
		
		switch (type) {
			case "Event":
			case "Field":
				foreach (XmlElement n in list)
					nodes_node.CreateNode (GetMemberName (n), n.GetAttribute("assembler_index"));
				break;

			case "Constructor":
				foreach (XmlElement n in list)
					nodes_node.CreateNode (EcmaHelpSource.MakeSignature(n, typename), n.GetAttribute("assembler_index"));
				break;

			case "Property": // properties with indexers can be overloaded too
			case "Method":
			case "Operator":
				foreach (XmlElement n in list) {
					bool multiple = false;
					foreach (XmlNode nn in list) {
						if (n != nn && GetMemberName(n) == nn.Attributes ["MemberName"].InnerText) {
							multiple = true;
							break;
						}
					}
					
					string group, name, sig;
					if (type != "Operator") {
						name = GetMemberName(n);
						sig = EcmaHelpSource.MakeSignature(n, null);
						group = name;
					} else {
						EcmaHelpSource.MakeOperatorSignature(n, out name, out sig);
						group = name;
					}
					
					if (multiple) {
						nodes_node.LookupNode (group, group)
							.CreateNode (sig, n.GetAttribute("assembler_index"));
					} else {
						nodes_node.CreateNode (name, n.GetAttribute("assembler_index"));
					}
				}
				
				foreach (Node n in nodes_node.Nodes) {
					if (!n.IsLeaf)
						n.Sort ();
				}
				
				break;
				
			default:
				throw new InvalidOperationException();
		}
		
		nodes_node.Sort ();
	}

}

//
// The Ecma HelpSource provider
//
public class EcmaHelpSource : HelpSource {

	public EcmaHelpSource (string base_file, bool create) : base (base_file, create)
	{
		ExtObject = new ExtensionObject (this);
	}

	public EcmaHelpSource ()
	{
		ExtObject = new ExtensionObject (this);
	}

	static string css_ecma;
	public static string css_ecma_code {
		get {
			if (css_ecma != null)
				return css_ecma;
			if (use_css) {
				System.Reflection.Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
				Stream str_css = assembly.GetManifestResourceStream ("mono-ecma.css");
				css_ecma = (new StreamReader (str_css)).ReadToEnd();
			} else {
				css_ecma = String.Empty;
			}
			return css_ecma;
		}
	}
	public override string GetText (string url, out Node match_node)
	{
		match_node = null;
		
		if (url == "root:")
		{
			XmlReader summary = GetHelpXml ("mastersummary.xml");
			if (summary == null)
				return null;

			XsltArgumentList args = new XsltArgumentList();
			args.AddExtensionObject("monodoc:///extensions", ExtObject);
			args.AddParam("show", "", "masteroverview");
			string s = Htmlize(new XPathDocument (summary), args);
			return BuildHtml (css_ecma_code, s); 
		}
		
		if (url.StartsWith ("ecma:")) {
			string s = GetTextFromUrl (url);
			return BuildHtml (css_ecma_code, s); 
		}

		return null;
	}


	string RenderMemberLookup (string typename, string member, ref Node type_node)
	{
		if (type_node.Nodes == null)
			return null;

		string membername = member;
		string[] argtypes = null;
		if (member.IndexOf("(") > 0) {
			membername = membername.Substring(0, member.IndexOf("("));
			member = member.Replace("@", "&");
			
			// reform the member signature with CTS names

			string x = member.Substring(member.IndexOf("(")+1);
			argtypes = x.Substring(0, x.Length-1).Split(',', ':'); // operator signatures have colons

			if (membername == ".ctor")
				membername = typename;

			member = membername + "(";
			for (int i = 0; i < argtypes.Length; i++) {
				argtypes[i] = EcmaDoc.ConvertCTSName(argtypes[i]);
				if (i > 0) member += ",";
				member += argtypes[i];
			}
			member += ")";
		}
		
		// Check if a node caption matches exactly
		
		bool isoperator = false;
		
		if ((membername == "op_Implicit" || membername == "op_Explicit") && argtypes.Length == 2) {
			isoperator = true;
			membername = "Conversion";
			member = argtypes[0] + " to " + argtypes[1];
		} else if (membername.StartsWith("op_")) {
			isoperator = true;
			membername = membername.Substring(3);
		}

		foreach (Node x in type_node.Nodes){
			if (x.Nodes == null)
				continue;
			if (isoperator && x.Caption != "Operators")
				continue;
			
			foreach (Node m in x.Nodes) {
				string caption = m.Caption;
				if (m.IsLeaf) {
					// No overloading (usually), is just the member name.  The whole thing for constructors.
					if (caption == membername || caption == member) {
						type_node = m;
						return GetTextFromUrl (m.URL);
					}
				} else if (caption == member) {
					// Though there are overloads, no arguments are in the url, so use this base node
					type_node = m;
					return GetTextFromUrl (m.URL);
				} else {
					// Check subnodes which are the overloads -- must match signature
					foreach (Node mm in m.Nodes) {
						if (mm.Caption == member) {
							type_node = mm;
							return GetTextFromUrl (mm.URL);
						}
					}
				}
			}
		}
		
		return null;
	}

	public static string MakeSignature (XmlNode n, string cstyleclass)
	{
		string sig;

		if (cstyleclass == null)
			sig = n.Attributes["MemberName"].InnerText;
		else {
			// constructor style
			sig = cstyleclass;
		}
	
		/*if (n.SelectSingleNode("MemberType").InnerText == "Method" || n.SelectSingleNode("MemberType").InnerText == "Constructor") {*/ // properties with indexers too
			XmlNodeList paramnodes = n.SelectNodes("Parameters/Parameter");
			sig += "(";
			bool first = true;
			foreach (XmlNode p in paramnodes) {
				if (!first) sig += ",";
				string type = p.Attributes["Type"].InnerText;
				type = EcmaDoc.ConvertCTSName(type);
				sig += type;
				first = false;
			}
			sig += ")";
		//}

		return sig;
	}
	
	public static void MakeOperatorSignature (XmlNode n, out string nicename, out string sig)
	{
		string name;

		name = n.Attributes["MemberName"].InnerText;
		nicename = name.Substring(3);
		
		switch (name) {
			// unary operators: no overloading possible
			case "op_UnaryPlus": case "op_UnaryNegation": case "op_LogicalNot": case "op_OnesComplement":
			case "op_Increment": case "op_Decrement": 
			case "op_True": case "op_False":
				sig = nicename;
				break;
			
			// binary operators: overloading is possible based on parameter types
			case "op_Addition": case "op_Subtraction": case "op_Multiply": case "op_Division": case "op_Modulus":
			case "op_BitwiseAnd": case "op_BitwiseOr": case "op_ExclusiveOr":
			case "op_LeftShift": case "op_RightShift":
			case "op_Equality": case "op_Inequality":
			case "op_GreaterThan": case "op_LessThan": case "op_GreaterThanOrEqual": case "op_LessThanOrEqual":
				XmlNodeList paramnodes = n.SelectNodes("Parameters/Parameter");
				sig = nicename + "(";
				bool first = true;
				foreach (XmlNode p in paramnodes) {
					if (!first) sig += ",";
					string type = p.Attributes["Type"].InnerText;
					type = EcmaDoc.ConvertCTSName(type);
					sig += type;
					first = false;
				}
				sig += ")";
				break;
			
			// overloading based on parameter and return type
			case "op_Implicit": case "op_Explicit":
				nicename = "Conversion";
				string arg = n.SelectSingleNode("Parameters/Parameter/@Type").InnerText;
				string ret = n.SelectSingleNode("ReturnValue/ReturnType").InnerText;
				sig = EcmaDoc.ConvertCTSName(arg) + " to " + EcmaDoc.ConvertCTSName(ret);
				break;
				
			default:
				throw new InvalidOperationException();
		}	
	}

	//
	// This routine has to perform a lookup on a type.
	//
	// Example: T:System.Text.StringBuilder
	//
	// The prefix is the kind of opereation being requested (T:, E:, M: etc)
	// ns is the namespace being looked up
	// type is the type being requested
	//
	// This has to walk our toplevel (which is always a namespace)
	// And then the type space, and then walk further down depending on the request
	//
	public override string RenderTypeLookup (string prefix, string ns, string type, string member, out Node match_node)
	{
		string url = GetUrlForType (prefix, ns, type, member, out match_node);
		if (url == null) return null;
		return GetTextFromUrl (url);
	}

	public virtual string GetIdFromUrl (string prefix, string ns, string type)
	{
		Node tmp_node = new Node (Tree, "", "");
		string url = GetUrlForType (prefix, ns, type, null, out tmp_node);
		if (url == null) return null;
		return GetFile (url.Substring (5), out url);
	}
	
	public string GetUrlForType (string prefix, string ns, string type, string member, out Node match_node)
	{
		if (!prefix.EndsWith(":"))
			throw new ArgumentException("prefix");
			
		// If a nested type, compare only inner type name to node list.
		// This should be removed when the node list doesn't lose the containing type name.
		type = ToEscapedTypeName (type.Replace("+", "."));

		foreach (Node ns_node in Tree.Nodes){
			string ns_node_namespace = ns_node.Element.Substring (2);

			if (ns_node_namespace != ns)
				continue;
			
			foreach (Node type_node in ns_node.Nodes){
				string element = type_node.Element;
				
				string cname;
				if (element.StartsWith("T:")) {
					cname = element.Substring(2);
					string _ns;
					RootTree.GetNamespaceAndType (cname, out _ns, out cname);
					cname = ToEscapedTypeName (cname);
					int pidx = cname.LastIndexOf (".");
					cname = cname.Substring(pidx+1);
					pidx = cname.LastIndexOf ("/");
					if (pidx != -1)
						cname = cname.Substring(0, pidx);
					cname = cname.Replace("+", ".");
				} else {				
					int pidx = element.IndexOf ("#");
					int sidx = element.IndexOf ("/");
					cname = element.Substring (pidx + 1, sidx-pidx-1);
				}
				
				//Console.WriteLine ("t:{0} cn:{1} p:{2}", type, cname, prefix);

				if (type == cname && prefix == "T:") {
					match_node = type_node;
					return type_node.URL;
				} else if (type.StartsWith (cname)){
					int p = cname.Length;

					match_node = type_node;
					if (type == cname){
						string ret = RenderMemberLookup (type, member, ref match_node);
						if (ret == null)
							return type_node.URL;
						return match_node.URL;

					} else if (type [p] == '/'){
						//
						// This handles summaries
						//
						match_node = null;
						foreach (Node nd in type_node.Nodes) {
							if (nd.Element [nd.Element.Length - 1] == type [p + 1]) {
								match_node = nd;
								break;
							}
						}
						
						string ret = type_node.URL;
						if (!ret.EndsWith("/")) ret += "/";
						return ret + type.Substring (p + 1);
					}
				}
			}
		}

		match_node = null;
		return null;
	}
	
	public static string ToEscapedTypeName (string typename)
	{
		StringBuilder filename = new StringBuilder (typename.Length);
		int numArgs = 0;
		int numLt = 0;
		bool copy = true;

		for (int i = 0; i < typename.Length; ++i) {
			char c = typename [i];
			switch (c) {
				case '<':
					copy = false;
					++numLt;
					break;
				case '>':
					--numLt;
					if (numLt == 0) {
						filename.Append ('`').Append ((numArgs+1).ToString());
						numArgs = 0;
						copy = true;
					}
					break;
				case ',':
					if (numLt == 1)
						++numArgs;
					break;
				default:
					if (copy)
						filename.Append (c);
					break;
			}
		}
		return filename.ToString ();
	}
	
	public override string GetNodeXPath (XPathNavigator n)
	{
		if (n.Matches ("/Type/Docs/param")) {
			string type_name = (string) n.Evaluate ("string (ancestor::Type/@FullName)");
			string param_name = (string) n.Evaluate ("string (@name)");
			
			return String.Format ("/Type [@FullName = '{0}']/Docs/param[@name='{1}']", type_name, param_name);
		}

		if (n.Matches ("/Type/Docs/*")) {
			string type_name = (string) n.Evaluate ("string (ancestor::Type/@FullName)");
			
			return String.Format ("/Type [@FullName = '{0}']/Docs/{1}", type_name, n.Name);
		}
		
		if (n.Matches ("/Type/Members/Member/Docs/*")) {
			string type_name = (string) n.Evaluate ("string (ancestor::Type/@FullName)");
			string member_name = (string) n.Evaluate ("string (ancestor::Member/@MemberName)");
			string member_sig = (string) n.Evaluate ("string (ancestor::Member/MemberSignature [@Language='C#']/@Value)");
			string param_name = (string) n.Evaluate ("string (@name)");
			
			if (param_name == null || param_name == "") {
				return String.Format (
				"/Type [@FullName = '{0}']/Members/Member [@MemberName = '{1}'][MemberSignature [@Language='C#']/@Value = '{2}']/Docs/{3}",
				type_name, member_name, member_sig, n.Name);
			} else {
				return String.Format (
				"/Type [@FullName = '{0}']/Members/Member [@MemberName = '{1}'][MemberSignature [@Language='C#']/@Value = '{2}']/Docs/param [@name = '{3}']",
				type_name, member_name, member_sig, param_name);
			}
		}
		
		Console.WriteLine ("WARNING: Was not able to get clean XPath expression for node {0}", EditingUtils.GetXPath (n));
		return base.GetNodeXPath (n);
	}

	protected virtual XmlReader GetNamespaceDocument (string ns) {
		return GetHelpXml ("xml.summary." + ns);
	}

	public override string RenderNamespaceLookup (string nsurl, out Node match_node)
	{
		foreach (Node ns_node in Tree.Nodes){
			if (ns_node.Element != nsurl)
				continue;

			match_node = ns_node;
			string ns_name = nsurl.Substring (2);
			
			XmlReader summary = GetNamespaceDocument (ns_name);
			if (summary == null)
				return null;

			XsltArgumentList args = new XsltArgumentList();
			args.AddExtensionObject("monodoc:///extensions", ExtObject);
			args.AddParam("show", "", "namespace");
			args.AddParam("namespace", "", ns_name);
			string s = Htmlize(new XPathDocument (summary), args);
			return BuildHtml (css_ecma_code, s); 

		}
		match_node = null;
		return null;
	}

	private string SelectString(XmlNode node, string xpath) {
		XmlNode ret = node.SelectSingleNode(xpath);
		if (ret == null) return "";
		return ret.InnerText;
	}
	private int SelectCount(XmlNode node, string xpath) {
		return node.SelectNodes(xpath).Count;
	}

	//
	// Returns the XmlDocument from the given url, and fills in `rest'
	//
	protected virtual XmlDocument GetXmlFromUrl(string url, out string rest) {
		// Remove ecma:
		url = url.Substring (5);
		string file = GetFile (url, out rest);

		// Console.WriteLine ("Got [{0}] and [{1}]", file, rest);
		return GetHelpXmlWithChanges (file);
	}
	
	string GetTextFromUrl (string url)
	{
		string rest, rest2;
		Node node;

		XmlDocument doc = GetXmlFromUrl (url, out rest);
		
		// Load base-type information so the stylesheet can draw the inheritance
		// tree and (optionally) include inherited members in the members list.
		XmlElement basenode = (XmlElement)doc.SelectSingleNode("Type/Base");
		XmlElement membersnode = (XmlElement)doc.SelectSingleNode("Type/Members");
		XmlNode basetype = doc.SelectSingleNode("Type/Base/BaseTypeName");
		int baseindex = 0;
		while (basetype != null) {
			// Add the ParentType node to Type/Base
			XmlElement inheritancenode = doc.CreateElement("ParentType");
			inheritancenode.SetAttribute("Type", basetype.InnerText);
			inheritancenode.SetAttribute("Order", (baseindex++).ToString());
			basenode.AppendChild(inheritancenode);
			
			// Load the base type XML data
			int dot = basetype.InnerText.LastIndexOf('.');
			string ns = basetype.InnerText.Substring(0, dot == -1 ? 0 : dot);
			string n = basetype.InnerText.Substring(dot == -1 ? 0 : dot+1);
			string basetypeurl = GetUrlForType("T:", ns, n, null, out node);
			XmlDocument basetypedoc = null;
			if (basetypeurl != null)
				basetypedoc = GetXmlFromUrl (basetypeurl, out rest2);
			if (basetypedoc == null) {
				inheritancenode.SetAttribute("Incomplete", "1");
				break;
			}
			
			if (SettingsHandler.Settings.ShowInheritedMembers) {
				// Add inherited members
				foreach (XmlElement member in basetypedoc.SelectNodes("Type/Members/Member[not(MemberType='Constructor')]")) {
					string sig = SelectString(member, "MemberSignature[@Language='C#']/@Value");
					if (sig.IndexOf(" static ") >= 0) continue;
					
					// If the signature of member matches the signature of a member already in the XML,
					// don't add it.
					string xpath = "@MemberName='" + SelectString(member, "@MemberName") + "'";
					xpath       += " and ReturnValue/ReturnType='" + SelectString(member, "ReturnValue/ReturnType") + "'";
					xpath       += " and count(Parameters/Parameter)=" + SelectCount(member, "Parameters/Parameter") + "";
					int pi = 1;
					foreach (XmlElement p in member.SelectNodes("Parameters/Parameter")) {
						xpath   += " and Parameters/Parameter[position()=" + pi + "]/@Type = '" + p.GetAttribute("Type") + "'";
						pi++;
					}
					
					// If a signature match is found, don't add.
					XmlNode match = membersnode.SelectSingleNode("Member[" + xpath + "]");
					if (match != null)
						continue;
					
					member.SetAttribute("DeclaredIn", basetype.InnerText);
					membersnode.AppendChild(membersnode.OwnerDocument.ImportNode(member, true));				
				}
			}
			
			basetype = basetypedoc.SelectSingleNode("Type/Base/BaseTypeName");
		}

		XsltArgumentList args = new XsltArgumentList();

		args.AddExtensionObject("monodoc:///extensions", ExtObject);
		
		if (rest == "") {
			args.AddParam("show", "", "typeoverview");
			string s = Htmlize(doc, args);
			return BuildHtml (css_ecma_code, s); 
		}
		
		string [] nodes = rest.Split (new char [] {'/'});
		
		switch (nodes.Length) {
			case 1:
				args.AddParam("show", "", "members");
				args.AddParam("index", "", "all");
				break;
			case 2:
				// Could either be a single member, or an overload thingy
				try {
					int.Parse (nodes [1]); // is it an int
					
					args.AddParam("show", "", "member");
					args.AddParam("index", "", nodes [1]);
				} catch {
					args.AddParam("show", "", "overloads");
					args.AddParam("index", "", nodes [1]);
				}
				break;
			case 3:
				args.AddParam("show", "", "member");
				args.AddParam("index", "", nodes [2]);
				break;
			default:
				return "What the hell is this URL " + url;
		}

		switch (nodes [0]){
		case "C":
			args.AddParam("membertype", "", "Constructor");
			break;
		case "M":
			args.AddParam("membertype", "", "Method");
			break;
		case "P":
			args.AddParam("membertype", "", "Property");
			break;
		case "F":
			args.AddParam("membertype", "", "Field");
			break;
		case "E":
			args.AddParam("membertype", "", "Event");
			break;
		case "O":
			args.AddParam("membertype", "", "Operator");
			break;
		case "*":
			args.AddParam("membertype", "", "All");
			break;
		default:
			return "Unknown url: " + url;
		}

		string html = Htmlize(doc, args);
		return BuildHtml (css_ecma_code, html); 
	}

	
	public override void RenderPreviewDocs (XmlNode newNode, XmlWriter writer)
	{
		XsltArgumentList args = new XsltArgumentList ();
		args.AddExtensionObject ("monodoc:///extensions", ExtObject);
		
		Htmlize (newNode, args, writer);
	}

	static XslTransform ecma_transform;

	public static string Htmlize (IXPathNavigable ecma_xml)
	{
		return Htmlize(ecma_xml, null);
	}
	
	public static string Htmlize (IXPathNavigable ecma_xml, XsltArgumentList args)
	{
		EnsureTransform ();
		
		StringWriter output = new StringWriter ();
		ecma_transform.Transform (ecma_xml, args, output, null);
		return output.ToString ();
	}
	
	static void Htmlize (IXPathNavigable ecma_xml, XsltArgumentList args, XmlWriter w)
	{
		EnsureTransform ();
		
		if (ecma_xml == null)
			return;

		ecma_transform.Transform (ecma_xml, args, w, null);
	}
	
	static XslTransform ecma_transform_css, ecma_transform_no_css;
	static void EnsureTransform ()
	{
		if (ecma_transform == null) {
			ecma_transform_css = new XslTransform ();
			ecma_transform_no_css = new XslTransform ();
			Assembly assembly = System.Reflection.Assembly.GetCallingAssembly ();
			
			Stream stream = assembly.GetManifestResourceStream ("mono-ecma-css.xsl");
			XmlReader xml_reader = new XmlTextReader (stream);
			ecma_transform_css.Load (xml_reader, null, null);
			
			stream = assembly.GetManifestResourceStream ("mono-ecma.xsl");
			xml_reader = new XmlTextReader (stream);
			ecma_transform_no_css.Load (xml_reader, null, null);
		}
		if (use_css)
			ecma_transform = ecma_transform_css;
		else
			ecma_transform = ecma_transform_no_css;
	}

	// This ExtensionObject stuff is used to check at run time whether
	// types and members have been implemented and whether there are any
	// MonoTODO attributes left on them. 

	public readonly ExtensionObject ExtObject;
	public class ExtensionObject {
		readonly EcmaHelpSource hs;

		//
		// We are setting this to quiet now, as we need to transition
		// monodoc to run with the 2.x runtime and provide accurate
		// information in those cases.
		//
		bool quiet = true;
		
		public ExtensionObject (EcmaHelpSource hs)
		{
			this.hs = hs;
		}
		
		public string Colorize(string code, string lang) {
			return(Mono.Utilities.Colorizer.Colorize(code,lang));
		}
		// Used by stylesheet to nicely reformat the <see cref=> tags. 
		public string MakeNiceSignature(string sig, string contexttype)
		{
			if (sig.Length < 3)
				return sig;
			if (sig[1] != ':')
				return sig;

			char s = sig[0];
			sig = sig.Substring(2);
			
			switch (s) {
				case 'N': return sig;
				case 'T': return ShortTypeName(sig, contexttype);

				case 'C': case 'M': case 'P': case 'F': case 'E':
					string type, mem, arg;
					
					// Get arguments
					int paren;
					if (s == 'C' || s == 'M')
						paren = sig.IndexOf("(");
					else if (s == 'P')
						paren = sig.IndexOf("[");
					else
						paren = 0;
					
					if (paren > 0 && paren < sig.Length-1) {
						string[] args = sig.Substring(paren+1, sig.Length-paren-2).Split(',');						
						for (int i = 0; i < args.Length; i++)
							args[i] = ShortTypeName(args[i], contexttype);
						arg = "(" + String.Join(", ", args) + ")";
						sig = sig.Substring(0, paren); 
					} else {
						arg = "";
					}

					// Get type and member names
					int dot = sig.LastIndexOf(".");
					if (s == 'C' || dot <= 0 || dot == sig.Length-1) {
						mem = "";
						type = sig;
					} else {
						type = sig.Substring(0, dot);
						mem = sig.Substring(dot);
					}
						
					type = ShortTypeName(type, contexttype);
					
					return type + mem + arg;

				default:
					return sig;
			}
		}
		
		public string EditUrl (XPathNodeIterator itr)
		{
			if (itr.MoveNext ())
				return hs.GetEditUri (itr.Current);
			
			return "";
		}

		public string EditUrlNamespace (string ns, string section)
		{
			if (hs is EcmaUncompiledHelpSource)
				return "edit:file:" + Path.Combine(((EcmaUncompiledHelpSource)hs).BasePath, ns + ".xml") + "@/Namespace/Docs/" + section; 
			return "";
		}

		private static string ShortTypeName(string name, string contexttype)
		{
			int dot = contexttype.LastIndexOf(".");
			if (dot < 0) return name;
			string contextns = contexttype.Substring(0, dot+1);

			if (name == contexttype)
				return name.Substring(dot+1);
			
			if (name.StartsWith(contextns))
				return name.Substring(contextns.Length);
			
			return name.Replace("+", ".");
		}

		public string MonoImpInfo(string assemblyname, string typename, string membername, string arglist, bool strlong)
		{
			if (quiet)
				return "";
				
			ArrayList a = new ArrayList();
			if (arglist != "") a.Add(arglist);
			return MonoImpInfo(assemblyname, typename, membername, a, strlong);
		}

		public string MonoImpInfo(string assemblyname, string typename, string membername, XPathNodeIterator itr, bool strlong)
		{
			if (quiet)
				return "";
				
			ArrayList rgs = new ArrayList ();
			while (itr.MoveNext ())
				rgs.Add (itr.Current.Value);
			
			return MonoImpInfo (assemblyname, typename, membername, rgs, strlong);
		}
		
		public string MonoImpInfo(string assemblyname, string typename, string membername, ArrayList arglist, bool strlong)
		{
			try {
				Assembly assembly = null;
				
				try {
					assembly = Assembly.LoadWithPartialName(assemblyname);
				} catch (Exception) {
					// nothing.
				}
				
				if (assembly == null) {
					/*if (strlong) return "The assembly " + assemblyname + " is not available to MonoDoc.";
					else return "";*/
					return ""; // silently ignore
				}

				Type t = assembly.GetType(typename, false);
				if (t == null) {
					if (strlong)
						return typename + " has not been implemented.";
					else
						return "Not implemented.";
				}

				// The following code is flakey and fails to find existing members
				return "";
#if false
				MemberInfo[] mis = t.GetMember(membername, BF.Static | BF.Instance | BF.Public | BF.NonPublic);

				if (mis.Length == 0)
					return "This member has not been implemented.";
				if (mis.Length == 1)
					return MonoImpInfo(mis[0], "member", strlong);

				// Scan for the appropriate member
				foreach (MemberInfo mi in mis) {
					System.Reflection.ParameterInfo[] pis;

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

					return MonoImpInfo(mi, "member", strlong);
				}
#endif
			} catch (Exception) {
				return "";
			}
		}
		
		public string MonoImpInfo(System.Reflection.MemberInfo mi, string itemtype, bool strlong)
		{
			if (quiet)
				return "";
				
			string s = "";

			object[] atts = mi.GetCustomAttributes(true);
			int todoctr = 0;
			foreach (object att in atts) if (att.GetType().Name == "MonoTODOAttribute") todoctr++;

			if (todoctr > 0) {
				if (strlong)
					s = "This " + itemtype + " is marked as being unfinished.<BR/>\n";
				else 
					s = "Unfinished.";
			}

			return s;
		}

		public string MonoImpInfo(string assemblyname, string typename, bool strlong)
		{
			if (quiet)
				return "";
				
			try {
				if (assemblyname == "")
					return "";

				Assembly assembly = Assembly.LoadWithPartialName(assemblyname);
				if (assembly == null)
					return "";

				Type t = assembly.GetType(typename, false);
				if (t == null) {
					if (strlong)
						return typename + " has not been implemented.";
					else
						return "Not implemented.";
				}

				string s = MonoImpInfo(t, "type", strlong);

				if (strlong) {
					MemberInfo[] mis = t.GetMembers(BF.Static | BF.Instance | BF.Public | BF.NonPublic);

					// Scan members for MonoTODO attributes
					int mctr = 0;
					foreach (MemberInfo mi in mis) {
						string mii = MonoImpInfo(mi, null, false);
						if (mii != "") mctr++; 
					}
					if (mctr > 0) {
						s += "This type has " + mctr + " members that are marked as unfinished.<BR/>";
					}
				}

				return s;

			} catch (Exception) {
				return "";
			}			
		}

		public bool MonoEditing ()
		{
			return SettingsHandler.Settings.EnableEditing;
		}
		
		public bool IsToBeAdded(string text) {
			return text.StartsWith("To be added");
		}
	}

	//
	// This takes one of the ecma urls, which look like this:
	// ecma:NUMERIC_ID#OPAQUE/REST
	//
	// NUMERIC_ID is the numeric ID assigned by the compressor
	// OPAQUE is opaque for node rendering (it typically contains T:System.Byte for example)
	// REST is the rest of the argument used to decode information
	//
	static string GetFile (string url, out string rest)
	{
		int pound = url.IndexOf ("#");
		int slash = url.IndexOf ("/");
		
		string fname = url.Substring (0, pound);
		rest = url.Substring (slash+1);

		return fname;
	}

#if false
	// This should have a little cache or something.
	static XmlDocument GetDocument (HelpSource hs, string fname)
	{
		Stream s = hs.GetHelpStream (fname);
		if (s == null){
			Console.Error.WriteLine ("Could not fetch document {0}", fname);
			return null;
		}
		
		XmlDocument doc = new XmlDocument ();

		doc.Load (s);
		
		return doc;
	}
#endif
	
	string GetKindFromCaption (string s)
	{
		int p = s.LastIndexOf (' ');
		if (p > 0)
			return s.Substring (p + 1);
		return null;
	}
	
	//
	// Obtain an URL of the type T:System.Object from the node
	// 
	public static string GetNiceUrl (Node node) {
		if (node.Element.StartsWith("N:"))
			return node.Element;
		string name, full;
		int bk_pos = node.Caption.IndexOf (' ');
		// node from an overview
		if (bk_pos != -1) {
			name = node.Caption.Substring (0, bk_pos);
			full = node.Parent.Caption + "." + name.Replace ('.', '+');
			return "T:" + full;
		}
		// node that lists constructors, methods, fields, ...
		if ((node.Caption == "Constructors") || (node.Caption == "Fields") || (node.Caption == "Events") 
			|| (node.Caption == "Members") || (node.Caption == "Properties") || (node.Caption == "Methods")
			|| (node.Caption == "Operators")) {
			bk_pos = node.Parent.Caption.IndexOf (' ');
			name = node.Parent.Caption.Substring (0, bk_pos);
			full = node.Parent.Parent.Caption + "." + name.Replace ('.', '+');
			return "T:" + full + "/" + node.Element; 
		}
		int pr_pos = node.Caption.IndexOf ('(');
		// node from a constructor
		if (node.Parent.Element == "C") {
			name = node.Parent.Parent.Parent.Caption;
			int idx = node.URL.IndexOf ('/');
			return node.URL[idx+1] + ":" + name + "." + node.Caption.Replace ('.', '+');
		// node from a method with one signature, field, property, operator
		} else if (pr_pos == -1) {
			bk_pos = node.Parent.Parent.Caption.IndexOf (' ');
			name = node.Parent.Parent.Caption.Substring (0, bk_pos);
			full = node.Parent.Parent.Parent.Caption + "." + name.Replace ('.', '+');
			int idx = node.URL.IndexOf ('/');
			return node.URL[idx+1] + ":" + full + "." + node.Caption;
		// node from a method with several signatures
		} else {
			bk_pos = node.Parent.Parent.Parent.Caption.IndexOf (' ');
			name = node.Parent.Parent.Parent.Caption.Substring (0, bk_pos);
			full = node.Parent.Parent.Parent.Parent.Caption + "." + name.Replace ('.', '+');
			int idx = node.URL.IndexOf ('/');
			return node.URL[idx+1] + ":" + full + "." + node.Caption;
		}
	}
				
	//
	// Populates the index.
	//
	public override void PopulateIndex (IndexMaker index_maker)
	{
		foreach (Node ns_node in Tree.Nodes){
			foreach (Node type_node in ns_node.Nodes){
				string typename = type_node.Caption.Substring (0, type_node.Caption.IndexOf (' '));
				string full = ns_node.Caption + "." + typename;

				string doc_tag = GetKindFromCaption (type_node.Caption);
				string url = "T:" + full;
					
				if (doc_tag == "Class" || doc_tag == "Structure" || doc_tag == "Interface"){

					index_maker.Add (type_node.Caption, typename, url);
					index_maker.Add (full + " " + doc_tag, full, url);

					foreach (Node c in type_node.Nodes){
						switch (c.Caption){
						case "Constructors":
							index_maker.Add ("  constructors", typename+"0", url + "/C");
							break;
						case "Fields":
							index_maker.Add ("  fields", typename+"1", url + "/F");
							break;
						case "Events":
							index_maker.Add ("  events", typename+"2", url + "/E");
							break;
						case "Properties":
							index_maker.Add ("  properties", typename+"3", url + "/P");
							break;
						case "Methods":
							index_maker.Add ("  methods", typename+"4", url + "/M");
							break;
						case "Operators":
							index_maker.Add ("  operators", typename+"5", url + "/O");
							break;
						}
					}

					//
					// Now repeat, but use a different sort key, to make sure we come after
					// the summary data above, start the counter at 6
					//
					string keybase = typename + "6.";
					
					foreach (Node c in type_node.Nodes){
						switch (c.Caption){
						case "Constructors":
							break;
						case "Fields":
							foreach (Node nc in c.Nodes){
								string res = nc.Caption;

								string nurl = String.Format ("F:{0}.{1}", full, res);
								index_maker.Add (String.Format ("{0}.{1} field", typename, res),
										 keybase + res, nurl);
								index_maker.Add (String.Format ("{0} field", res), res, nurl);
							}

							break;
						case "Events":
							foreach (Node nc in c.Nodes){
								string res = nc.Caption;
								string nurl = String.Format ("E:{0}.{1}", full, res);
								
								index_maker.Add (String.Format ("{0}.{1} event", typename, res),
										 keybase + res, nurl);
								index_maker.Add (String.Format ("{0} event", res), res, nurl);
							}
							break;
						case "Properties":
							foreach (Node nc in c.Nodes){
								string res = nc.Caption;
								string nurl = String.Format ("P:{0}.{1}", full, res);
								index_maker.Add (String.Format ("{0}.{1} property", typename, res),
										 keybase + res, nurl);
								index_maker.Add (String.Format ("{0} property", res), res, nurl);
							}
							break;
						case "Methods":
							foreach (Node nc in c.Nodes){
								string res = nc.Caption;
								int p = res.IndexOf ("(");
								if (p > 0)
									res = res.Substring (0, p); 
								string nurl = String.Format ("M:{0}.{1}", full, res);
								index_maker.Add (String.Format ("{0}.{1} method", typename, res),
										 keybase + res, nurl);
								index_maker.Add (String.Format ("{0} method", res), res, nurl);
							}
					
							break;
						case "Operators":
							foreach (Node nc in c.Nodes){
								string res = nc.Caption;
								string nurl = String.Format ("O:{0}.{1}", full, res);
								index_maker.Add (String.Format ("{0}.{1} operator", typename, res),
										 keybase + res, nurl);
							}
							break;
						}
					}
				} else if (doc_tag == "Enumeration"){
					//
					// Enumerations: add the enumeration values
					//
					index_maker.Add (type_node.Caption, typename, url);
					index_maker.Add (full + " " + doc_tag, full, url);

					// Now, pull the values.
					string rest;
					XmlDocument x = GetXmlFromUrl (type_node.URL, out rest);
					if (x == null)
						continue;
					
					XmlNodeList members = x.SelectNodes ("/Type/Members/Member");

					if (members == null)
						continue;

					foreach (XmlNode member_node in members){
						string enum_value = member_node.Attributes ["MemberName"].InnerText;
						string caption = enum_value + " value";
						index_maker.Add (caption, caption, url);
					}
				} else if (doc_tag == "Delegate"){
					index_maker.Add (type_node.Caption, typename, url);
					index_maker.Add (full + " " + doc_tag, full, url);
				}
			}
		}
	}
	//
	// Create list of documents for searching
	//
	public override void PopulateSearchableIndex (IndexWriter writer)
	{
		StringBuilder text;
		foreach (Node ns_node in Tree.Nodes) {
			Console.WriteLine ("\tNamespace: {0} ({1})", ns_node.Caption, ns_node.Nodes.Count);
			foreach (Node type_node in ns_node.Nodes) {
				string typename = type_node.Caption.Substring (0, type_node.Caption.IndexOf (' '));
				string full = ns_node.Caption + "." + typename;
				string doc_tag = GetKindFromCaption (type_node.Caption);
				string url = "T:" + full;
				string rest;
				XmlDocument xdoc = GetXmlFromUrl (type_node.URL, out rest);
				if (xdoc == null)
					continue;
				
				// 
				// For classes, structures or interfaces add a doc for the overview and
				// add a doc for every constructor, method, event, ...
				// 
				if (doc_tag == "Class" || doc_tag == "Structure" || doc_tag == "Interface"){
					
					// Adds a doc for every overview of every type
					SearchableDocument doc = new SearchableDocument ();
					doc.title = type_node.Caption;
					doc.hottext = typename;
					doc.url = url;
					
					XmlNode node_sel = xdoc.SelectSingleNode ("/Type/Docs");
					text  = new StringBuilder ();
					GetTextFromNode (node_sel, text);
					doc.text = text.ToString ();

					text  = new StringBuilder ();
					GetExamples (node_sel, text);
					doc.examples = text.ToString ();
					
					writer.AddDocument (doc.LuceneDoc);

					//Add docs for contructors, methods, etc.
					foreach (Node c in type_node.Nodes) { // c = Constructors || Fields || Events || Properties || Methods || Operators
						
						if (c.Element == "*")
							continue;
						int i = 1;
						foreach (Node nc in c.Nodes) {
							//xpath to the docs xml node
							string xpath;
							if (c.Caption == "Constructors")
								xpath = String.Format ("/Type/Members/Member[{0}]/Docs", i++);
							else if (c.Caption == "Operators")
								xpath = String.Format ("/Type/Members/Member[@MemberName='op_{0}']/Docs", nc.Caption);
							else
								xpath = String.Format ("/Type/Members/Member[@MemberName='{0}']/Docs", nc.Caption);
							//construct url of the form M:Array.Sort
							string urlnc;
							if (c.Caption == "Constructors")
								urlnc = String.Format ("{0}:{1}.{2}", c.Caption[0], ns_node.Caption, nc.Caption);
							else
								urlnc = String.Format ("{0}:{1}.{2}.{3}", c.Caption[0], ns_node.Caption, typename, nc.Caption);

							//create the doc
							SearchableDocument doc_nod = new SearchableDocument ();
							doc_nod.title = LargeName (nc);
							//dont add the parameters to the hottext
							int ppos = nc.Caption.IndexOf ('(');
							if (ppos != -1)
								doc_nod.hottext =  nc.Caption.Substring (0, ppos);
							else
								doc_nod.hottext = nc.Caption;

							doc_nod.url = urlnc;

							XmlNode xmln = xdoc.SelectSingleNode (xpath);
							if (xmln == null) {
								Console.WriteLine ("Problem: {0}, with xpath: {1}", urlnc, xpath);
								continue;
							}

							text = new StringBuilder ();
							GetTextFromNode (xmln, text);
							doc_nod.text = text.ToString ();

							text = new StringBuilder ();
							GetExamples (xmln, text);
							doc_nod.examples = text.ToString ();

							writer.AddDocument (doc_nod.LuceneDoc);
						}
					}
				//
				// Enumerations: add the enumeration values
				//
				} else if (doc_tag == "Enumeration"){
										
					XmlNodeList members = xdoc.SelectNodes ("/Type/Members/Member");
					if (members == null)
						continue;

					text = new StringBuilder ();
					foreach (XmlNode member_node in members) {
						string enum_value = member_node.Attributes ["MemberName"].InnerText;
						text.Append (enum_value);
						text.Append (" ");
						GetTextFromNode (member_node["Docs"], text);
						text.Append ("\n");
					}
					SearchableDocument doc = new SearchableDocument ();

					text = new StringBuilder ();
					GetExamples (xdoc.SelectSingleNode ("/Type/Docs"), text);
					doc.examples = text.ToString ();

					doc.title = type_node.Caption;
					doc.hottext = xdoc.DocumentElement.Attributes["Name"].Value;
					doc.url = url;
					doc.text = text.ToString();
					writer.AddDocument (doc.LuceneDoc);
				//
				// Add delegates
				//
				} else if (doc_tag == "Delegate"){
					SearchableDocument doc = new SearchableDocument ();
					doc.title = type_node.Caption;
					doc.hottext = xdoc.DocumentElement.Attributes["Name"].Value;
					doc.url = url; 
					
					XmlNode node_sel = xdoc.SelectSingleNode ("/Type/Docs");

					text = new StringBuilder ();
					GetTextFromNode (node_sel, text);
					doc.text = text.ToString();

					text = new StringBuilder ();
					GetExamples (node_sel, text);
					doc.examples = text.ToString();

					writer.AddDocument (doc.LuceneDoc);
				} 
			}
		}
	}
	
	//
	// Extract the interesting text from the docs node
	//
	void GetTextFromNode (XmlNode n, StringBuilder sb) 
	{
		//don't include example code
		if (n.Name == "code") 
			return;

		//include the url to which points the see tag
		if (n.Name == "see" && n.Attributes.Count > 0)
				sb.Append (n.Attributes [0].Value);
		
		//include the name of the parameter
		if (n.Name == "paramref" && n.Attributes.Count > 0)
			sb.Append (n.Attributes [0].Value);

		//include the contents for the node that contains text
		if (n.NodeType == XmlNodeType.Text)
			sb.Append (n.Value);
		
		//add the rest of xml tags recursively
		if (n.HasChildNodes)
			foreach (XmlNode n_child in n.ChildNodes)
				GetTextFromNode (n_child, sb);
	}
	//
	// Extract the code nodes from the docs
	//
	void GetExamples (XmlNode n, StringBuilder sb)
	{
		if (n.Name == "code") {
			sb.Append (n.InnerText);
		} else {
			if (n.HasChildNodes)
				foreach (XmlNode n_child in n.ChildNodes)
					GetExamples (n_child, sb);
		}
	}
	//
	// Extract a large name for the Node
	//  (copied from mono-tools/docbrowser/browser.Render()
	static string LargeName (Node matched_node)
	{
		string[] parts = matched_node.URL.Split('/', '#');			
		if(parts.Length == 3 && parts[2] != String.Empty) { //List of Members, properties, events, ...
			return parts[1] + ": " + matched_node.Caption;
		} else if(parts.Length >= 4) { //Showing a concrete Member, property, ...					
			return parts[1] + "." + matched_node.Caption;
		} else {
			return matched_node.Caption;
		}
	}

}

public class EcmaUncompiledHelpSource : EcmaHelpSource {
	readonly DirectoryInfo basedir;
	readonly XmlDocument basedoc;
	
	public new readonly string Name, BasePath;
	
	public EcmaUncompiledHelpSource (string base_file) : base ()
	{
		Console.Error.WriteLine("Loading uncompiled help from " + base_file);
		
		basedir = new DirectoryInfo(base_file);
		BasePath = basedir.FullName;
		
		basedoc = new XmlDocument();
		basedoc.Load(Path.Combine(basedir.FullName, "index.xml"));
		
		Name = basedoc.SelectSingleNode("Overview/Title").InnerText;
		
		bool has_content = false;
		
		foreach (XmlElement ns in basedoc.SelectNodes("Overview/Types/Namespace")) {
			has_content = true;
			Node nsnode = Tree.CreateNode(ns.GetAttribute("Name"), "N:" + ns.GetAttribute("Name"));
			
			bool has_types = false;
			foreach (XmlElement t in ns.SelectNodes("Type")) {
				has_types = true;
				string typename = EcmaDoc.GetDisplayName (t).Replace("+", ".");
				
				// Must load in each document to get the list of members...
				XmlDocument typedoc = new XmlDocument();
				typedoc.Load(Path.Combine(Path.Combine(basedir.FullName, ns.GetAttribute("Name")), t.GetAttribute("Name") + ".xml"));
				string kind = EcmaDoc.GetTypeKind (typedoc);
				
				string url = ns.GetAttribute("Name") + "." + t.GetAttribute("Name");
				Node typenode = nsnode.CreateNode(typename + " " + kind, "T:" + url);				
				//Node typemembers = typenode.CreateNode("Members", "T:" + url + "/*");
				
				Hashtable groups = new Hashtable();
				Hashtable groups_count = new Hashtable();
				foreach (XmlElement member in typedoc.SelectNodes("Type/Members/Member")) {
					string membername = member.GetAttribute("MemberName");
					string membertype = member.SelectSingleNode("MemberType").InnerText;
					
					if (membertype == "Constructor")
						membername = t.GetAttribute("Name");
					if (membername.StartsWith("op_"))
						membertype = "Operator";
					
					Node group;
					if (groups.ContainsKey(membertype)) {
						group = (Node)groups[membertype];
					} else {
						string membertypeplural = membertype + "s";
						if (membertypeplural == "Propertys") membertypeplural = "Properties";
						
						group = typenode.CreateNode(membertypeplural, "T:" + url + "/" + membertype[0]);
						groups[membertype] = group;
						groups_count[membertype] = 0;
					}
					
					if (membertype == "Constructor" || membertype == "Method" || 
						(membertype == "Property" && member.SelectNodes("Parameters/Parameter").Count > 0)) {
						membername = EcmaHelpSource.MakeSignature(member, membertype == "Constructor" ? membername : null);
					} else if (membertype == "Operator") {
						string dummy;
						EcmaHelpSource.MakeOperatorSignature(member, out dummy, out membername);
					}
					
					int index = (int)groups_count[membertype];
					groups_count[membertype] = index + 1;
					
					group.CreateNode(membername, index.ToString());
				}

				foreach (Node group in groups.Values)
					group.Sort();			
			}
			
			if (has_types)
				nsnode.Sort();
		}
		
		if (has_content)
			Tree.Sort();
	}
	
	public override string GetIdFromUrl (string prefix, string ns, string type)
	{
		if (prefix != "T:")
			throw new NotImplementedException();
		return Path.Combine(Path.Combine(basedir.FullName, ns), type + ".xml");
	}

	protected override XmlDocument GetXmlFromUrl(string url, out string rest) {
		// strip off the T:
		url = url.Substring(2);
		
		int sidx = url.IndexOf("/");
		if (sidx == -1) {
			rest = "";
		} else {
			rest = url.Substring(sidx+1);
			url = url.Substring(0, sidx);
		}
		
		string ns, type;
		if (!RootTree.GetNamespaceAndType (url, out ns, out type)) {
			Console.Error.WriteLine ("Could not determine namespace/type for {0}",
					url);
			return null;
		}
		
		string file = Path.Combine(Path.Combine(basedir.FullName, ns), 
				ToEscapedTypeName (type).Replace ('.', '+') + ".xml");
		if (!new FileInfo(file).Exists) return null;
		
		XmlDocument typedoc = new XmlDocument();
		typedoc.Load(file);
		return typedoc;
	}
	
	public override string GetText (string url, out Node match_node) {
		if (url == "root:") {
			match_node = null;
			
			//load index.xml
			XmlDocument index = new XmlDocument ();
			index.Load (Path.Combine (basedir.FullName, "index.xml"));
			XmlNodeList nodes = index.SelectNodes ("/Overview/Types/Namespace");
			
			//recreate masteroverview.xml
			XmlDocument summary = new XmlDocument ();
			XmlElement elements = summary.CreateElement ("elements");
			foreach (XmlNode node in nodes) {
				XmlElement ns = summary.CreateElement ("namespace");
				XmlAttribute attr = summary.CreateAttribute ("ns");
				attr.Value = EcmaDoc.GetDisplayName (node);
				ns.Attributes.Append (attr);
				elements.AppendChild (ns);
			}
			summary.AppendChild (elements);

			XmlReader reader = new XmlTextReader (new StringReader (summary.OuterXml));

			//transform the recently created masteroverview.xml
			XsltArgumentList args = new XsltArgumentList();
			args.AddExtensionObject("monodoc:///extensions", ExtObject);
			args.AddParam("show", "", "masteroverview");
			string s = EcmaHelpSource.Htmlize(new XPathDocument (reader), args);
			return BuildHtml (css_ecma_code, s); 
		}
		return base.GetText(url, out match_node);
	}
	
	protected override XmlReader GetNamespaceDocument (string ns) {
		XmlDocument nsdoc = new XmlDocument();
		nsdoc.Load(Path.Combine(basedir.FullName, ns + ".xml"));
		
		XmlDocument elements = new XmlDocument();
		XmlElement docnode = elements.CreateElement("elements");
		
		foreach (XmlElement doc in nsdoc.SelectNodes("Namespace/Docs/*")) {
			docnode.AppendChild(elements.ImportNode(doc, true));
		}
				
		foreach (XmlElement t in basedoc.SelectNodes("Overview/Types/Namespace[@Name='" + ns + "']/Type")) {
			XmlDocument typedoc = new XmlDocument();
			typedoc.Load(Path.Combine(Path.Combine(basedir.FullName, ns), t.GetAttribute("Name") + ".xml"));
			
			string typekind;
			switch (EcmaDoc.GetTypeKind(typedoc)) {
			case "Class": typekind = "class"; break;
			case "Enumeration": typekind = "enum"; break;
			case "Structure": typekind = "struct"; break;
			case "Delegate": typekind = "delegate"; break;
			case "Interface": typekind = "interface"; break;
			default: throw new InvalidOperationException();
			}
			
			XmlElement typenode = elements.CreateElement(typekind);
			typenode.SetAttribute("name", EcmaDoc.GetDisplayName (t).Replace ('+', '.'));
			typenode.SetAttribute("fullname", ns + "." + t.GetAttribute("Name"));
			typenode.AppendChild(elements.ImportNode(typedoc.SelectSingleNode("Type/Docs/summary"), true));
			
			docnode.AppendChild(typenode);
		}

		return new XmlNodeReader(docnode);
	}
	
}

}


