namespace Binge.Parsers
{
	using System;
	using System.Collections;
	using System.Xml;
	using Binge.Bits;

	public class XmlParser: Parser
	{
		public XmlParser (): base () {}
		public XmlParser (string file): base (file) {}
		public XmlParser (string file, Hashtable ns): base (file, ns) {}

		public override void Parse ()
		{
			Open (Filename);

			while (NodeType != XmlNodeType.Element)
				Read ();

			if (Name != "API")
				RequiredTagMissing ("API", true);

			while (Read ())
			{
				if (NodeType == XmlNodeType.EndElement)
				{
					if (Name == "API")
						break;

					UnexpectedTag ("/" + Name);
				}

				switch (Name)
				{
				case "Namespace":
					Namespace ns = ParseNamespace ();
					if (ns != null)
						Namespaces[ns.Name] = ns;
					break;
				default:
					UnexpectedTag (Name);
					Skip ();
					break;
				}
			}

			Close ();
		}

		Namespace ParseNamespace ()
		{
			Namespace ns = new Namespace ();

			if (ns.Throttle = this["Throttled"] == "True")
				if (!ParseThrottled)
					return null;

			if ((ns.Name = this["Name"]) == String.Empty)
				RequiredAttrMissing ("Name", true);

			while (Read ())
			{
				if (NodeType == XmlNodeType.EndElement)
				{
					if (Name == "Namespace")
						break;

					UnexpectedTag ("/" + Name);
					continue;
				}

				switch (Name)
				{
				case "Namespace":
					UnexpectedNesting ("Namespace");

					while (Read ())
						if (Name != "Namespace" && NodeType != XmlNodeType.EndElement)
							continue;

					break;
				case "Interface":
					Interface iface = ParseInterface ();
					if (iface != null)
						ns.Interfaces[iface.NativeName] = iface;
					break;
				case "Enum":
					Binge.Bits.Enum enm = ParseEnum ();
					if (enm != null)
						ns.Enums[enm.NativeName] = enm;
					break;
				case "Class":
					Class klass = ParseClass ();
					if (klass != null)
						ns.Classes[klass.NativeName] = klass;
					break;
				default:
					UnexpectedTag (Name);
					Skip ();
					break;
				}
			}

			return ns;
		}

		Interface ParseInterface ()
		{
			return null; // FIXME
		}

		EnumItem ParseEnumItem ()
		{
			EnumItem item = new EnumItem ();

			if ((item.NativeName = this["Name"]) == String.Empty)
				RequiredAttrMissing ("Name", true);

			item.NativeValue = this["Value"];

			return item;
		}

		Binge.Bits.Enum ParseEnum ()
		{
			Binge.Bits.Enum enm = new Binge.Bits.Enum ();

			if (enm.Throttle = this["Throttled"] == "True")
				if (! ParseThrottled)
					return null;

			if ((enm.NativeName = this["Name"]) == String.Empty)
				RequiredTagMissing ("Name", true);

			enm.Access = ParseMemberAccess ();
			enm.Unsigned = this["Unsigned"] == "True";

			while (Read ())
			{
				if (NodeType == XmlNodeType.EndElement)
				{
					if (Name == "Enum")
						break;

					UnexpectedTag ("/" + Name);
					continue;
				}

				if (Name == "EnumItem")
				{
					EnumItem item = ParseEnumItem ();
					if (item != null)
						enm.Items.Add (item);
				}
				else
				{
					UnexpectedTag (Name);
					Skip ();
				}
			}

			return enm;
		}

		Class ParseClass ()
		{
			Class klass = new Class ();

			if (klass.Throttle = this["Throttled"] == "True")
				if (! ParseThrottled)
					return null;

			if ((klass.NativeName = this["Name"]) == String.Empty)
				RequiredAttrMissing ("Name", true);

			while (Read ())
			{
				if (NodeType == XmlNodeType.EndElement)
				{
					if (Name == "Class")
						break;

					UnexpectedTag ("/" + Name);
					continue;
				}

				if (NodeType != XmlNodeType.Element)
					continue;

				switch (Name)
				{
				case "Class":
					UnexpectedNesting ("Class");

					while (Read())
						if (NodeType == XmlNodeType.EndElement && Name == "Class")
							break;

					break;
				case "Ancestor":
					string ancestor = this["Name"];

					if (ancestor != String.Empty)
						klass.Ancestors.Add (ancestor);
					else
						XmlDbg ("Class {0} contains empty ancestor", klass.NativeName);
					break;
				case "Enum":
					Binge.Bits.Enum enm = ParseEnum ();
					if (enm != null)
					{
						enm.Parent = klass;
						klass.Enums.Add (enm);
					}
					break;
				case "Field":
					Field field = ParseField ();
					if (field != null)
					{
						field.Parent = klass;
						klass.Fields.Add (field);
					}
					break;
				case "Property":
					Property prop = ParseProperty ();
					if (prop != null)
					{
						prop.Parent = klass;
						klass.Properties.Add (prop);
					}
					break;
				case "Constructor":
					Constructor ctor = ParseConstructor ();
					if (ctor != null)
					{
						ctor.Parent = klass;
						ctor.NativeName = klass.NativeName;
						klass.Constructors.Add (ctor);
					}
					break;
				case "Destructor":
					Destructor dtor = ParseDestructor ();
					if (dtor != null)
					{
						dtor.Parent = klass;
						dtor.NativeName = klass.NativeName;
						klass.Destructors.Add (dtor);
					}
					break;
				case "Method":
					Method method = ParseMethod ();
					if (method != null)
					{
						method.Parent = klass;
						klass.Methods.Add (method);
					}
					break;
				default:
					UnexpectedTag (Name);
					Skip ();
					break;
				}
			}

			return klass;
		}

		MemberAccess ParseMemberAccess ()
		{
			string str = this["Access"];

			if (str == "Default" || str == String.Empty)
				return MemberAccess.Default;
			else if (str == "Public")
				return MemberAccess.Public;
			else if (str == "Protected")
				return MemberAccess.Protected;
			else if (str == "Private")
				return MemberAccess.Private;
			else if (str == "Internal")
				return MemberAccess.Internal;
			else {
				UnexpectedAttr ("Access", str);
				return MemberAccess.Default;
			}
		}

		PassingConvention ParsePassBy ()
		{
			string str = this["PassBy"];

			if (str == string.Empty || str == "Default")
				return PassingConvention.Default;
			else if (str == "Value")
				return PassingConvention.Value;
			else if (str == "Pointer")
				return PassingConvention.Pointer;
			else if (str == "Reference")
				return PassingConvention.Reference;
			else if (str == "PointerToPointer")
				return PassingConvention.PointerToPointer;
			else {
				UnexpectedAttr ("PassBy", str);
				return PassingConvention.Default;
			}
		}

		bool ParseMemberBase (MemberBase member, bool reqname)
		{
			// NOTE If you change this method, change ParseMethodBase as well.

			if (member.Throttle = this["Throttled"] == "True")
				if (! ParseThrottled)
					return false;

			member.NativeName = this["Name"];

			if (reqname && member.NativeName == String.Empty)
				RequiredAttrMissing ("Name", true);

			member.Access = ParseMemberAccess ();
			member.IsStatic = this["Static"] == "True";

			return true;
		}

		Field ParseField ()
		{
			Field field = new Field ();
			field.IsReadOnly = this["ReadOnly"] == "True";

			if (! ParseMemberBase (field, true))
				return null;

			return field;
		}

		Property ParseProperty ()
		{
			Property prop = new Property ();
			prop.IsReadOnly = this["ReadOnly"] == "True";

			if (! ParseMemberBase (prop, true))
				return null;

			return prop;
		}

		ReturnType ParseReturnType ()
		{
			ReturnType returns = new ReturnType ();
			ParseParameter (returns, false);
			// FIXME: Should be: ReturnType returns = new ReturnType (ParseParameter (bool));
			return returns;
		}

		void ParseParameter (Parameter param, bool reqname)
		{
			param.NativeName = this["Name"];

			if ((param.NativeName = this["Name"]) == String.Empty && reqname)
				RequiredAttrMissing ("Name", true);

			//Why do we call PassBy twice?
			param.PassBy = ParsePassBy ();
			param.IsConst = GetAttribute ("Const") == "True";
			param.Default = this["Default"];

			if ((param.NativeType = this["Type"]) == String.Empty)
				RequiredAttrMissing ("Type", true);
		}

		bool ParseMethodBase (MethodBase method, bool reqname)
		{
			// FIXME Add flag for requiring return type.

			if (! ParseMemberBase (method, reqname))
				return false;

			if (IsEmptyAttribute)
				return true;

			while (Read () && NodeType == XmlNodeType.Element)
			{
				if (Name == "Parameter")
				{
					// FIXME: Should be: method.Parameters.Add (ParseParameter (bool));
					Parameter param = new Parameter ();
					ParseParameter (param, true);
					method.Parameters.Add (param);
				}
				else if (Name == "ReturnType")
				{
					// FIXME Print error if > 1 return type is found. Need
					// to create a clear default value for ReturnType class.
					method.Returns = ParseReturnType ();
				}
				else
					break;
			}

			return true;
		}

		Constructor ParseConstructor ()
		{
			Constructor ctor = new Constructor ();
			ParseMethodBase (ctor, false);
			return ctor;
		}

		Destructor ParseDestructor ()
		{
			Destructor dtor = new Destructor ();
			dtor.Name = this["Name"];
			return dtor;
		}

		Method ParseMethod ()
		{
			Method method = new Method ();
			method.IsVirtual = this["Virtual"] == "True";

			if (! ParseMethodBase (method, true))
				return null;

			return method;
		}

		// String.Empty and null mean essentially the same thing to us.
		private class XmlTextReader: System.Xml.XmlTextReader
		{
			public XmlTextReader (string filename): base (filename) {}

			public override string GetAttribute (string name)
			{
				string r = base.GetAttribute (name);
				return r == null ? String.Empty : r;
			}
		}

	}
}
