//
// XApiReader.cs
//
// Author:
//   Jb Evain (jbevain@novell.com)
//
// (C) 2007 Novell, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

using System;
using System.Collections;
using System.Text;
using System.Xml.XPath;

using Mono.Cecil;

namespace Mono.Linker {

	public class XApiReader {

		static readonly string _name = "name";
		static readonly string _ns = string.Empty;

		LinkContext _context;
		XPathDocument _document;
		IXApiVisitor _visitor;

		AssemblyDefinition _assembly;
		string _namespace;
		Stack _types = new Stack ();
		StringBuilder _signature;

		public XApiReader (XPathDocument document, IXApiVisitor visitor)
		{
			_document = document;
			_visitor = visitor;
		}

		public void Process (LinkContext context)
		{
			_context = context;
			ProcessAssemblies (_document.CreateNavigator ());
		}

		void OnAssembly (XPathNavigator nav)
		{
			_assembly = GetAssembly (nav);

			_visitor.OnAssembly (nav, _assembly);

			ProcessAttributes (nav);
			ProcessNamespaces (nav);
		}

		AssemblyDefinition GetAssembly (XPathNavigator nav)
		{
			AssemblyNameReference name = new AssemblyNameReference ();
			name.Name = GetName (nav);
			name.Version = new Version (GetAttribute (nav, "version"));

			AssemblyDefinition assembly = _context.Resolve (name);
			ProcessReferences (assembly);
			return assembly;
		}

		void ProcessReferences (AssemblyDefinition assembly)
		{
			foreach (AssemblyNameReference name in assembly.MainModule.AssemblyReferences)
				_context.Resolve (name);
		}

		void OnAttribute (XPathNavigator nav)
		{
			_visitor.OnAttribute (nav);
		}

		void PushType (TypeDefinition type)
		{
			_types.Push (type);
		}

		TypeDefinition PeekType ()
		{
			return (TypeDefinition) _types.Peek ();
		}

		TypeDefinition PopType ()
		{
			return (TypeDefinition) _types.Pop ();
		}

		void OnNamespace (XPathNavigator nav)
		{
			_namespace = GetName (nav);

			ProcessClasses (nav);
		}

		void OnClass (XPathNavigator nav)
		{
			string name = GetClassName (nav);

			TypeDefinition type = _assembly.MainModule.Types [name];
			if (type == null)
				return;

			_visitor.OnClass (nav, type);

			PushType (type);

			ProcessAttributes (nav);
			ProcessInterfaces (nav);
			ProcessFields (nav);
			ProcessMethods (nav);
			ProcessConstructors (nav);
			ProcessProperties (nav);
			ProcessEvents (nav);
			ProcessClasses (nav);

			PopType ();
		}

		string GetClassName (XPathNavigator nav)
		{
			if (IsNestedClass ())
				return PeekType ().FullName + "/" + GetName (nav);

			return _namespace + "." + GetName (nav);
		}

		bool IsNestedClass ()
		{
			return _types.Count > 0;
		}

		void OnField (XPathNavigator nav)
		{
			TypeDefinition declaring = PeekType ();

			FieldDefinition field = declaring.Fields.GetField (GetName (nav));
			if (field != null)
				_visitor.OnField (nav, field);

			ProcessAttributes (nav);
		}

		void OnInterface (XPathNavigator nav)
		{
			string name = GetName (nav);

			TypeDefinition type = _context.GetType (GetTypeName (name));
			if (type != null)
				_visitor.OnInterface (nav, type);
		}

		void OnMethod (XPathNavigator nav)
		{
			InitMethodSignature (nav);

			ProcessParameters (nav);

			string signature = GetMethodSignature ();

			MethodDefinition method = GetMethod (signature);
			if (method != null)
				_visitor.OnMethod (nav, method);

			ProcessAttributes (nav);
		}

		MethodDefinition GetMethod (string signature)
		{
			return GetMethod (PeekType ().Methods, signature);
		}

		MethodDefinition GetConstructor (string signature)
		{
			return GetMethod (PeekType ().Constructors, signature);
		}

		static MethodDefinition GetMethod (ICollection methods, string signature)
		{
			foreach (MethodDefinition method in methods)
				if (signature == GetSignature (method))
					return method;

			return null;
		}

		static string GetSignature (MethodDefinition method)
		{
			return method.ToString ().Replace ("<", "[").Replace (">", "]");
		}

		string GetMethodSignature ()
		{
			_signature.Append (")");
			return _signature.ToString ();
		}

		void InitMethodSignature (XPathNavigator nav)
		{
			_signature = new StringBuilder ();

			string returntype = GetAttribute (nav, "returntype");
			if (returntype == null || returntype.Length == 0)
				returntype = Constants.Void;

			_signature.Append (NormalizeTypeName (returntype));
			_signature.Append (" ");
			_signature.Append (PeekType ().FullName);
			_signature.Append ("::");

			string name = GetName (nav);
			_signature.Append (GetMethodName (name));

			_signature.Append ("(");
		}

		static string GetMethodName (string name)
		{
			return GetStringBefore (name, "(");
		}

		static string NormalizeTypeName (string name)
		{
			return name.Replace ("+", "/").Replace ("<", "[").Replace (">", "]");
		}

		static string GetTypeName (string name)
		{
			return GetStringBefore (NormalizeTypeName (name), "[");
		}

		static string GetStringBefore (string str, string marker)
		{
			int pos = str.IndexOf (marker);
			if (pos == -1)
				return str;

			return str.Substring (0, pos);
		}

		void OnParameter (XPathNavigator nav)
		{
			string type = GetAttribute (nav, "type");
			int pos = int.Parse (GetAttribute (nav, "position"));

			if (pos > 0)
				_signature.Append (",");
			_signature.Append (NormalizeTypeName (type));
		}

		void OnConstructor (XPathNavigator nav)
		{
			InitMethodSignature (nav);

			ProcessParameters (nav);

			string signature = GetMethodSignature ();

			MethodDefinition ctor = GetConstructor (signature);
			if (ctor != null)
				_visitor.OnConstructor (nav, ctor);

			ProcessAttributes (nav);
		}

		void OnProperty (XPathNavigator nav)
		{
			string name = GetName (nav);
			TypeDefinition type = PeekType ();

			PropertyDefinition [] props = type.Properties.GetProperties (name);
			if (props != null && props.Length > 0)
				_visitor.OnProperty (nav, props [0]);

			ProcessAttributes (nav);
			ProcessMethods (nav);
		}

		void OnEvent (XPathNavigator nav)
		{
			string name = GetName (nav);
			TypeDefinition type = PeekType ();

			EventDefinition evt = type.Events.GetEvent (name);
			if (evt != null)
				_visitor.OnEvent (nav, evt);

			ProcessAttributes (nav);
		}

		void ProcessAssemblies (XPathNavigator nav)
		{
			ProcessChildren (nav, "assemblies//assembly", new OnChildren (OnAssembly));
		}

		void ProcessAttributes (XPathNavigator nav)
		{
			ProcessChildren (nav, "attributes//attribute", new OnChildren (OnAttribute));
		}

		void ProcessNamespaces (XPathNavigator nav)
		{
			ProcessChildren (nav, "namespaces//namespace", new OnChildren (OnNamespace));
		}

		void ProcessClasses (XPathNavigator nav)
		{
			ProcessChildren (nav, "classes//class", new OnChildren (OnClass));
		}

		void ProcessInterfaces (XPathNavigator nav)
		{
			ProcessChildren (nav, "intefaces//interface", new OnChildren (OnInterface));
		}

		void ProcessFields (XPathNavigator nav)
		{
			ProcessChildren (nav, "fields//field", new OnChildren (OnField));
		}

		void ProcessMethods (XPathNavigator nav)
		{
			ProcessChildren (nav, "methods//method", new OnChildren (OnMethod));
		}

		void ProcessConstructors (XPathNavigator nav)
		{
			ProcessChildren (nav, "constructors//constructor", new OnChildren (OnConstructor));
		}

		void ProcessParameters (XPathNavigator nav)
		{
			ProcessChildren (nav, "parameters//parameter", new OnChildren (OnParameter));
		}

		void ProcessProperties (XPathNavigator nav)
		{
			ProcessChildren (nav, "properties//property", new OnChildren (OnProperty));
		}

		void ProcessEvents (XPathNavigator nav)
		{
			ProcessChildren (nav, "events//event", new OnChildren (OnEvent));
		}

		static void ProcessChildren (XPathNavigator nav, string children, OnChildren action)
		{
			XPathNodeIterator iterator = nav.Select (children);
			while (iterator.MoveNext ())
				action (iterator.Current);
		}

		delegate void OnChildren (XPathNavigator nav);

		static string GetName (XPathNavigator nav)
		{
			return GetAttribute (nav, _name);
		}

		static string GetAttribute (XPathNavigator nav, string attribute)
		{
			return nav.GetAttribute (attribute, _ns);
		}
	}
}
