using System;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Specialized;
using System.Xml;

public class SyntaxErrorException: ApplicationException {}

public class NamespaceList: ArrayList
{
	public void Archive()
	{
		Archive (new XmlTextWriter (Console.Out));
	}

	public void Archive(XmlWriter writer)
	{
		writer.WriteStartDocument();
		writer.WriteStartElement("API");

		foreach (Namespace ns in this)
			ns.Archive (writer);

		writer.WriteEndElement();
		writer.WriteEndDocument();
	}
}


public class Namespace
{
	public string    Name    = String.Empty;
	public ArrayList Classes = new ArrayList ();
	public ArrayList Enums   = new ArrayList ();

	public void Archive(XmlWriter writer)
	{
		writer.WriteStartElement("Namespace");
		writer.WriteAttributeString("Name", Name);

		foreach (Enum enm in Enums)
			enm.Archive(writer);

		foreach (Class klass in Classes)
			klass.Archive(writer);

		writer.WriteEndElement();
	}
}


public enum Access { Public, Private, Protected };
public enum ParameterMode { Value, Pointer, PointerToPointer, Reference };
public enum MethodType { Normal, Signal, Slot };


public class Class
{
	public string Name;

	public ArrayList Ancestors = new ArrayList();
	public ArrayList Constructors = new ArrayList();
	public Destructor Destructor;
	public ArrayList Methods = new ArrayList();
	public ArrayList Enums = new ArrayList();

	public void Archive(XmlWriter writer)
	{
		if ( Parser.OmitStubs && Constructors.Count == 0 &&	
				 Methods.Count == 0 && Enums.Count == 0 )
			return;
	
		writer.WriteStartElement("Class");
		writer.WriteAttributeString("Name", Name);

		foreach (string ancestor in Ancestors)
		{
			writer.WriteStartElement("Ancestor");
			writer.WriteAttributeString("Name", ancestor);
			writer.WriteEndElement();
		}

		foreach (Constructor constructor in Constructors)
			constructor.Archive(writer);

		if (Destructor != null)
			Destructor.Archive(writer);

		foreach (Method method in Methods)
			method.Archive(writer);

		foreach (Enum @enum in Enums)
			@enum.Archive(writer);

		writer.WriteEndElement();
	}
}


public class Member
{
	public string Name;
}


public class Constructor: Member
{
	// we inherit a name field. Why do we need that?
	public Access Access;
	public ArrayList Parameters = new ArrayList();
	public int Id;

	public void Archive(XmlWriter writer)
	{
		writer.WriteStartElement("Constructor");
		writer.WriteAttributeString("Name", Name);
		writer.WriteAttributeString("Access", Access.ToString() );
		writer.WriteAttributeString("Id", Id.ToString() );

		foreach (Parameter parameter in Parameters)
			parameter.Archive(writer);

		writer.WriteEndElement();
	}
}


public class Destructor: Member
{
	public void Archive(XmlWriter writer)
	{
		writer.WriteStartElement("Destructor");
		writer.WriteAttributeString("Name", Name);
		writer.WriteEndElement();
	}
}


public class Method: Member
{
	public Access Access;
	public ReturnType ReturnType = new ReturnType();
	public bool Static = false;
	public bool Virtual = false;
	public bool Const = false;
	public MethodType MethodType = MethodType.Normal;
	public int Id;
	public ArrayList Parameters = new ArrayList();
	
	public void Archive(XmlWriter writer)
	{
		writer.WriteStartElement("Method");
		writer.WriteAttributeString("Name", Name);
		writer.WriteAttributeString("Access", Access.ToString() );
		
		
		
		writer.WriteAttributeString("Id", Id.ToString() );
		
		if (Static)
			writer.WriteAttributeString("Static", Static.ToString() );
		if (Virtual)
			writer.WriteAttributeString("Virtual", Virtual.ToString() );
		if (Const)
			writer.WriteAttributeString("Const", Const.ToString() );
			
		if (MethodType != MethodType.Normal)
			writer.WriteAttributeString("MethodType", MethodType.ToString() );

		ReturnType.Archive(writer);	
		
		foreach (Parameter parameter in Parameters)
			parameter.Archive(writer);
		
		writer.WriteEndElement();
	}
}


public class Parameter
{
	public string Type;
	public bool Const = false;
	public bool Unsigned = false;
	public string Name;
	public string Default;
	public ParameterMode Mode;

	public void Archive(XmlWriter writer)
	{
		writer.WriteStartElement("Parameter");
		writer.WriteAttributeString("Name", Name);
		writer.WriteAttributeString("Type", Type);
		
		if (Const)
			writer.WriteAttributeString("Const", Const.ToString() );

		if (Unsigned)
			writer.WriteAttributeString("Unsigned", Unsigned.ToString() );	

		
		//FIXME: Make this optional
		if (Mode != ParameterMode.Value)
			writer.WriteAttributeString("Mode", Mode.ToString() );
		
		if (Default != null )
			writer.WriteAttributeString("Default", Default);
		writer.WriteEndElement();
	}
}
	

public class ReturnType
{
	public string Type;
	public bool Const = false;
	public bool Unsigned = false;
	public ParameterMode Mode;
	
	public void Archive(XmlWriter writer)
	{
		writer.WriteStartElement("ReturnType");
		writer.WriteAttributeString("Type", Type);
		
		if (Mode != ParameterMode.Value)
			writer.WriteAttributeString("Mode", Mode.ToString() );
		
		if (Const)
			writer.WriteAttributeString("Const", Const.ToString() );

		if (Unsigned)
			writer.WriteAttributeString("Unsigned", Unsigned.ToString() );	
				
		writer.WriteEndElement();
	}
}


public class Enum
{
	public string Name;
	public ArrayList Items = new ArrayList();
	public bool Unsigned = false; // Is underlying type uint?

	public void Archive(XmlWriter writer)
	{
		writer.WriteStartElement("Enum");
		writer.WriteAttributeString("Name", Name);
		if ( Unsigned )
			writer.WriteAttributeString("Unsigned", Unsigned.ToString() );

		foreach ( EnumItem item in Items )
			item.Archive(writer);

		writer.WriteEndElement();
	}
}


public class EnumItem
{
	public string Name;
	public long Value; // type long let's us hold a uint or int

	public void Archive(XmlWriter writer)
	{
		writer.WriteStartElement("EnumItem");
		writer.WriteAttributeString("Name", Name);
		writer.WriteAttributeString("Value", Value.ToString() );
		writer.WriteEndElement();
	}

}






// Parser begins here -----------------------

public class Parser
{
	public static Namespace @namespace   = new Namespace ();
	public static bool      ShowWarnings = false;
	public static bool      OmitStubs    = false;

	static void Warning (int no, string fmt, params object[] args)
	{
		if (ShowWarnings)
			Console.Error.WriteLine
				("Warning (line "+no+"): " + fmt, args);
	}

	static void Warning (string fmt, params object[] args)
	{
		if (ShowWarnings)
			Console.Error.WriteLine
				("Warning: " + fmt, args);
	}

	private Lexan lexan;
	private Access access = Access.Private;
	private MethodType methodType = MethodType.Normal;
	private string className = String.Empty;
	private Hashtable methodCountTable = new Hashtable();
	private int errorCount = 0;

	// Making these visible here is definitely not ideal, but
	// some mutually-recursive methods access.
	private int unnamedParameterCount = 0;

	public Parser( StreamReader reader )
	{
		lexan = new Lexan(reader);
	}

	public Parser()
	{
		lexan = new Lexan();
	}


	public void DumpList(ArrayList list)
	{
		if (list != null)
			foreach (string item in list)
				Console.WriteLine(item);
		else
			Console.WriteLine("empty list");
	}

	public void Parse()
	{
		Msg("Parse()");

		while (lexan.CurrentToken.Value != Token.Type.End)
			DeclarationList();
		Warning("Error count: {0}", errorCount);
		Msg("END");
	}


	private void DeclarationList()
	{
		Msg("DeclarationList()");
		Declaration();
		DeclarationTail();
	}


	private void DeclarationTail()
	{
		Msg("DeclarationTail()");
		if ( lexan.CurrentToken.IsIdentifier("class") )
		{
			Declaration();
			DeclarationTail();
		}
		else
			return;
	}


	private void Declaration()
	{
		Msg("Declaration()");
		if (lexan.CurrentToken.IsIdentifier("class") )
		{
			ClassSpecifier();
			Expect( Token.Type.Semicolon );
		}
		else if (lexan.CurrentToken.IsIdentifier("inline") )
			InlineFunction();
		else
			lexan.Advance();
	}
	
	
	private void ClassSpecifier()
	{
		//Msg("ClassSpecifier()");
		try
		{
			Class @class = new Class();
			methodCountTable.Clear();

			ClassHead(@class);
			ClassTail(@class);

			@namespace.Classes.Add(@class);
		}
		catch (SyntaxErrorException) {}

		// This exception will abort adding the class. This is a back-stop
		// that should only be reached if (1) a function def outside of a class
		// is encountered (most often) or (2) a truly bizarre error occurs.
	}
	
	
	private void ClassHead(Class @class)
	{
		Msg("ClassHead()");
		ClassKey();
		className = Ident();
		
		@class.Name = className;
			
		BaseInfo(@class);
	}
	
	
	private void ClassKey()
	{
		Msg("ClassKey()");
		Expect( "class" );
	}
	
	
	private void BaseInfo(Class @class)
	{
		Msg("BaseInfo()");
		if ( lexan.CurrentToken.Value != Token.Type.Colon )
		{
			Msg("No inheritance info");
			return;
		}
		else
		{
			Expect( Token.Type.Colon );
			BaseList(@class.Ancestors);
		}
	}
	
	
	private void BaseList(ArrayList ancestors)
	{
		Msg("BaseList()");
		BaseItem(ancestors);
		BaseTail(ancestors);
			
	}
	
	
	private void BaseTail(ArrayList ancestors)
	{
		Msg("BaseTail()");
		if ( lexan.CurrentToken.Value == Token.Type.Comma )
		{
			lexan.Advance();
			BaseList(ancestors);
		}
		else
			return;
	}
	
	
	private void BaseItem(ArrayList ancestors)
	{
		Msg("BaseItem()");
		Expect(  "public" );
		string baseClassName = Ident();
		ancestors.Add(baseClassName);
	}

	
	private void ClassTail(Class @class)
	{
		Msg("ClassTail()");
		if ( lexan.CurrentToken.Value == Token.Type.LBrace )
		{
			Expect( Token.Type.LBrace );
			MemberSpecification(@class);
			Expect( Token.Type.RBrace );
			return;
		}
		else
			return;
	}
	
	
	private void InlineFunction()
	{
		Expect("inline");
		
		do lexan.Advance();
		while (lexan.CurrentToken.Value != Token.Type.LBrace);
		Expect(Token.Type.LBrace);
		
		do lexan.Advance();
		while (lexan.CurrentToken.Value != Token.Type.RBrace);
	}
	
	
	private void MemberSpecification(Class @class)
	{
		Msg("MemberSpecification()");
		
		if ( lexan.CurrentToken.Value == Token.Type.RBrace)
		{
			return;
		}
		else if ( lexan.CurrentToken.IsIdentifier("public") ||
					lexan.CurrentToken.IsIdentifier("protected") ||
					lexan.CurrentToken.IsIdentifier("private") )
		{
			AccessSpecifier(@class);
			MemberSpecification(@class);

		}
		else if ( lexan.CurrentToken.IsIdentifier("signals") ||
					lexan.CurrentToken.IsIdentifier("slots") )
		{
			SignalOrSlot();
			Expect(Token.Type.Colon);
			MemberSpecification(@class);
		}
		
		else
		{
			if ( lexan.CurrentToken.Value == Token.Type.Ident ||
					lexan.CurrentToken.Value == Token.Type.SwungDash )
			{
				MemberDeclaration(@class);
				MemberSpecification(@class);

			}
		}
	}
	
	
	private void MemberDeclaration(Class @class)
	{
		try
		{
		
			if ( lexan.CurrentToken.IsIdentifier(className) &&
					lexan.NextToken.Value == Token.Type.LParen )
			{
				ConstructorDeclaration(@class.Constructors);
			}
			else if ( lexan.CurrentToken.Value == Token.Type.SwungDash )
			{
				@class.Destructor = DestructorDeclaration();
			}
			else if ( lexan.CurrentToken.IsIdentifier("enum") )
			{
				@class.Enums.Add( EnumDeclaration() );
			}
			else
			{
				@class.Methods.Add( MethodDeclaration() );
			}
		}
		catch (SyntaxErrorException)
		{}
			
	}
		
	
	private void AccessSpecifier(Class @class)
	{
		Msg("AccessSpecifier()");
		// If we got here, we already know the current token
		// is an access indicator
		switch ( (string) lexan.CurrentToken.Attribute)
		{
			case "public": access = Access.Public; break;
			case "protected": access = Access.Protected; break;
			case "private": access = Access.Private; break;
		}
		lexan.Advance();
		
		// When we hit an access specifier, reset our assumption
		// that subsequent methods are "normal" (i.e. not signals
		// or slots)
		
		methodType = MethodType.Normal;
		
		if ( lexan.CurrentToken.IsIdentifier("signals") ||
					lexan.CurrentToken.IsIdentifier("slots") )
		{
			SignalOrSlot();
		}
		
		Expect(Token.Type.Colon);
	}
		
	
	
	private void SignalOrSlot()
	{
		if (lexan.CurrentToken.IsIdentifier("signals") )
			methodType = MethodType.Signal;
		else // Must be a slot -- otherwise we wouldn't be here
			methodType = MethodType.Slot;
		lexan.Advance();
	}
	
	
	private void ConstructorDeclaration(ArrayList constructors)
	{
		Msg("ConstructorDeclaration()");
		Constructor constructor = new Constructor();
		constructor.Name = className;
		constructor.Access = access;
		constructor.Id = constructors.Count;
		
		
		lexan.Advance();
		Expect( Token.Type.LParen );
		ParameterList(constructor.Parameters);
		lexan.Advance(); // Consume ")"
		Expect( Token.Type.Semicolon );
		constructors.Add(constructor);
	}
	
		
	private Destructor DestructorDeclaration()
	{
		Msg("DestructorDeclaration()");
		lexan.Advance();
		Expect(className);
		Expect(Token.Type.LParen);
		Expect(Token.Type.RParen);
		Expect(Token.Type.Semicolon);
	
		Destructor destructor = new Destructor();
		destructor.Name = className;
		return destructor;
	}
	
	
	private Method MethodDeclaration()
	{
		Msg("MethodDeclaration()");
		
		Method method = new Method();
		
		// NOTE: In the <if> stmt below, "virtual", "static", and
		// "inline" apply to the method itself, but "const" here
		// (i.e. in front of the method) applies to the return
		// type itself.
		
		if ( (string) lexan.CurrentToken.Attribute == "virtual" )
		{
			method.Virtual = true;
			lexan.Advance();
		}
		else if ( (string) lexan.CurrentToken.Attribute == "static" )
		{
			method.Static = true;
			lexan.Advance();
		}
		else if ( (string) lexan.CurrentToken.Attribute == "inline" )
			lexan.Advance();
		
		if (lexan.CurrentToken.IsIdentifier("const") )
		{
			method.ReturnType.Const = true;
			lexan.Advance();
		}
		
		
		if (lexan.CurrentToken.IsIdentifier("unsigned") )
		{
			method.ReturnType.Unsigned = true;
			lexan.Advance();
		}
		
		string returnType = Ident();
		method.ReturnType.Type = returnType;
		
		if ( lexan.CurrentToken.Value == Token.Type.Star )
		{
			if (lexan.NextToken.Value == Token.Type.Star )
			{
				method.ReturnType.Mode = ParameterMode.PointerToPointer;
				lexan.Advance();
				lexan.Advance();
			}
			else
			{		
				method.ReturnType.Mode = ParameterMode.Pointer;
				lexan.Advance();
			}
		}
		else if (lexan.CurrentToken.Value == Token.Type.Ampersand)
		{
			method.ReturnType.Mode = ParameterMode.Reference;
			lexan.Advance();
		}
		
		method.MethodType = methodType;
		method.Access = access;
		
		string methodName = Ident();
		if (methodName == null || methodName == String.Empty )
			methodName = "Error";
		method.Name = methodName;
		if ( methodCountTable.ContainsKey(methodName) )
		{
			int count = (int) methodCountTable[methodName] + 1;
			method.Id = count;
			methodCountTable[methodName] = count;
		}
		else
			methodCountTable.Add(methodName, 0);
		
		Expect(Token.Type.LParen);
		ParameterList( method.Parameters );
		Expect(Token.Type.RParen);
		
		MethodTail(method);
		
		return method;
	}
	
	
	private void MethodTail(Method method)
	{
		if (lexan.CurrentToken.IsIdentifier("const") )
		{
			method.Const = true;
			lexan.Advance();
		}
			
		if (lexan.CurrentToken.Value == Token.Type.LBrace)
		{
			do lexan.Advance();
			while (lexan.CurrentToken.Value != Token.Type.RBrace);
			lexan.Advance();
		}	
		else
			Expect(Token.Type.Semicolon);
	}
		
	
	private void ParameterList(ArrayList parameters)
	{
		// This is ugly.
		if (parameters.Count == 0)
			unnamedParameterCount = 0;
	
		Msg("ParameterList()");
		if ( lexan.CurrentToken.Value != Token.Type.RParen )
		{
			parameters.Add( ParameterDeclaration() );
			ParameterTail(parameters);
		}
		else
			return;
	}
	
	
	// This method parsing method deviates from the grammar documentation
	// a bit
	private Parameter ParameterDeclaration()
	{
		string parameterName;
		
		Parameter parameter = new Parameter();
		parameter.Mode = ParameterMode.Value;
		
		if (lexan.CurrentToken.IsIdentifier("const") )
		{
			parameter.Const = true;
			lexan.Advance();
		}
		
		if (lexan.CurrentToken.IsIdentifier("unsigned") )
		{
			parameter.Unsigned = true;
			lexan.Advance();
		}
		
		string parameterBaseType = Ident();
		parameter.Type = parameterBaseType;
		
		if (lexan.CurrentToken.Value == Token.Type.Star)
		{
			if (lexan.NextToken.Value == Token.Type.Star)
			{
				parameter.Mode = ParameterMode.PointerToPointer;
				lexan.Advance();
				lexan.Advance();
			}
			else
			{
				parameter.Mode = ParameterMode.Pointer;
				lexan.Advance();
			}
		}
		else if (lexan.CurrentToken.Value == Token.Type.Ampersand )
		{
			parameter.Mode = ParameterMode.Reference;
			lexan.Advance();
		}
	
		if (lexan.CurrentToken.Value == Token.Type.Ident)
		{
			parameterName = (string) lexan.CurrentToken.Attribute;
			parameter.Name = parameterName;
			lexan.Advance();
		}
		else
		{
			parameterName = "arg" + ++unnamedParameterCount;
			parameter.Name = parameterName;
		}
		
		if (lexan.CurrentToken.Value == Token.Type.Equals)
		{
			lexan.Advance();
			// Default values are mostly integers, but there are 
			// a few boolean, enumerated types, and functions --
			// the latter two categories may require special handling
			// Also, the handling of negative default values below is not
			// very pretty. Finally, if we get a really strange token
			// Attribute might be null, so we check for that
			if ( lexan.CurrentToken.Value == Token.Type.Minus )
			{
				lexan.Advance();
				parameter.Default = "-" + lexan.CurrentToken.Attribute.ToString();
			}
			else if (lexan.CurrentToken.Attribute != null )
			{
				parameter.Default = lexan.CurrentToken.Attribute.ToString();
			}
			lexan.Advance();
			
		}
		return parameter;
	}
	
	
	private void ParameterTail(ArrayList parameters)
	{
		if ( lexan.CurrentToken.Value == Token.Type.Comma )
		{
			lexan.Advance();
			ParameterList(parameters);
		}
	}

	
	private Enum EnumDeclaration()
	{
		Enum @enum = new Enum();
		
		lexan.Advance(); // skip keyword "enum"
		
		if ( lexan.CurrentToken.Value == Token.Type.Ident )
		{
			@enum.Name = Ident();
		}
		else
			@enum.Name = "Anonymous";
		
		Expect( Token.Type.LBrace );
		EnumList( @enum);

		Expect( Token.Type.RBrace );
		Expect( Token.Type.Semicolon);
		return @enum;
	}
	
	
	private void EnumList( Enum @enum )
	{
		long currentEnumValue = 0;
		EnumItem item;
		string aliasFor;
		Hashtable table = new Hashtable(); // To find aliases quickly
		bool negative = false;
		 
		while (true)
		{
			item = new EnumItem();
			item.Name = Ident();
			
			negative = false;
			
			if (lexan.CurrentToken.Value == Token.Type.Equals)
			{
				lexan.Advance();
				
				if (lexan.CurrentToken.Value == Token.Type.Minus)
					{
						negative = true;
						lexan.Advance();
					}
				
				if (lexan.CurrentToken.Value == Token.Type.IntegerLiteral ||
					lexan.CurrentToken.Value == Token.Type.HexLiteral ||
					lexan.CurrentToken.Value == Token.Type.OctalLiteral )
				{
					item.Value = ( negative? -1 : 1 ) *
						Convert.ToUInt32(lexan.CurrentToken.Attribute);
					currentEnumValue = item.Value + 1;
					lexan.Advance();
				}
				else // Assume it's an alias
				{
					aliasFor = Ident();
					if ( table.ContainsKey(aliasFor) )
						item.Value = (long) table[aliasFor];
				}
			}
			else
			{	
				item.Value = currentEnumValue;
				currentEnumValue++;
			}
		
			if (item.Value > Int32.MaxValue)
				@enum.Unsigned = true;
				
			table.Add(item.Name, item.Value);	
			@enum.Items.Add(item);
		
			// 
			
			
			if ( lexan.CurrentToken.Value == Token.Type.Comma )
				lexan.Advance();
			else if ( lexan.CurrentToken.Value == Token.Type.RBrace )
				break;
			else
			{	// If there is any trailing stuff after the member
				// swallow it
				while ( lexan.CurrentToken.Value != Token.Type.Comma &&
						lexan.CurrentToken.Value != Token.Type.RBrace &&
						lexan.CurrentToken.Value != Token.Type.End)
					lexan.Advance();
				lexan.Advance();
			}
		}
	}
	
	
	
	private string Ident()
	// Check that the current token is an identifier, and return the name, or 
	// null is it's not an identifier. Advances to the next token in any case.
	{
		Msg("Ident()");
		string identifierName = lexan.CurrentToken.Attribute as string;
		Expect( Token.Type.Ident );
		return identifierName;
	}
	
	
	private void Expect( Token.Type typeToMatch )
	{
		if ( typeToMatch != lexan.CurrentToken.Value )
		{
			Warning(lexan.LineNumber, "Expected {0} but got {1}",
				typeToMatch, lexan.CurrentToken.Value);

			errorCount++;

			// New error recovery strategy: Advance until we get to
			// a semicolon. If we were trying to match a semicolon,
			// stop. Otherwise advance one past the semicolon and
			// raise an exception to let the method parser know
			while ( lexan.CurrentToken.Value != Token.Type.Semicolon &&
					lexan.CurrentToken.Value != Token.Type.End )
				lexan.Advance();

			if ( typeToMatch != Token.Type.Semicolon)
			{
				lexan.Advance();
				throw new SyntaxErrorException();
			}
		}
		lexan.Advance();
	}


	private void Expect( string toMatch )
	{
		if ( ! lexan.CurrentToken.IsIdentifier(toMatch) )
		{
			errorCount++;
			string error = "Expected {0} but found ";

			if ( lexan.CurrentToken.Value == Token.Type.Ident )
				error += "'" + lexan.CurrentToken.Attribute + "'";
			else
				error += "<" + lexan.CurrentToken.Value + ">";

			Warning(lexan.LineNumber, error, toMatch);
		}

		lexan.Advance();
	}


	private void EmitLine( string format, params object[] arg )
	{
		Console.WriteLine( format, arg );
	}

	private void Emit( string format, params object[] arg )
	{
		Console.Write( format, arg );
	}

	private void Msg( string format, params object[] arg )
	{
		#if DEBUG
			Console.Error.WriteLine( format, arg );
			Console.Out.Flush();
		#endif
	}

	private void Msg( string text )
	{
		#if DEBUG
			Console.Error.WriteLine( text);
			Console.Out.Flush();
		#endif
	}

}


/*
public class Testing
{
	static void Main()
	{
		Parser parser = new Parser();
		parser.Parse();
		Console.Error.WriteLine("Archiving {0} classes.", parser.Classes.Count);
		parser.Classes.Archive();
		Console.WriteLine();
	}
}
*/
