/*
  Copyright (C) 2009-2012 Jeroen Frijters

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.

  Jeroen Frijters
  jeroen@frijters.net
  
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

namespace IKVM.Reflection
{
	// this respresents a type name as in metadata:
	// - ns will be null for empty the namespace (never the empty string)
	// - the strings are not escaped
	struct TypeName : IEquatable<TypeName>
	{
		private readonly string ns;
		private readonly string name;

		internal TypeName(string ns, string name)
		{
			if (name == null)
			{
				throw new ArgumentNullException("name");
			}
			this.ns = ns;
			this.name = name;
		}

		internal string Name
		{
			get { return name; }
		}

		internal string Namespace
		{
			get { return ns; }
		}

		public static bool operator ==(TypeName o1, TypeName o2)
		{
			return o1.ns == o2.ns && o1.name == o2.name;
		}

		public static bool operator !=(TypeName o1, TypeName o2)
		{
			return o1.ns != o2.ns || o1.name != o2.name;
		}

		public override int GetHashCode()
		{
			return ns == null ? name.GetHashCode() : ns.GetHashCode() * 37 + name.GetHashCode();
		}

		public override bool Equals(object obj)
		{
			TypeName? other = obj as TypeName?;
			return other != null && other.Value == this;
		}

		public override string ToString()
		{
			return ns == null ? name : ns + "." + name;
		}

		bool IEquatable<TypeName>.Equals(TypeName other)
		{
			return this == other;
		}

		internal TypeName ToLowerInvariant()
		{
			return new TypeName(ns == null ? null : ns.ToLowerInvariant(), name.ToLowerInvariant());
		}

		internal static TypeName Split(string name)
		{
			int dot = name.LastIndexOf('.');
			if (dot == -1)
			{
				return new TypeName(null, name);
			}
			else
			{
				return new TypeName(name.Substring(0, dot), name.Substring(dot + 1));
			}
		}
	}

	struct TypeNameParser
	{
		private const string SpecialChars = "\\+,[]*&";
		private const short SZARRAY = -1;
		private const short BYREF = -2;
		private const short POINTER = -3;
		private readonly string name;
		private readonly string[] nested;
		private readonly string assemblyName;
		private readonly short[] modifiers;
		private readonly TypeNameParser[] genericParameters;

		internal static string Escape(string name)
		{
			if (name == null)
			{
				return null;
			}
			StringBuilder sb = null;
			for (int pos = 0; pos < name.Length; pos++)
			{
				char c = name[pos];
				switch (c)
				{
					case '\\':
					case '+':
					case ',':
					case '[':
					case ']':
					case '*':
					case '&':
						if (sb == null)
						{
							sb = new StringBuilder(name, 0, pos, name.Length + 3);
						}
						sb.Append("\\").Append(c);
						break;
					default:
						if (sb != null)
						{
							sb.Append(c);
						}
						break;
				}
			}
			return sb != null ? sb.ToString() : name;
		}

		internal static string Unescape(string name)
		{
			int pos = name.IndexOf('\\');
			if (pos == -1)
			{
				return name;
			}
			StringBuilder sb = new StringBuilder(name, 0, pos, name.Length - 1);
			for (; pos < name.Length; pos++)
			{
				char c = name[pos];
				if (c == '\\')
				{
					c = name[++pos];
				}
				sb.Append(c);
			}
			return sb.ToString();
		}

		internal static TypeNameParser Parse(string typeName, bool throwOnError)
		{
			if (throwOnError)
			{
				Parser parser = new Parser(typeName);
				return new TypeNameParser(ref parser, true);
			}
			else
			{
				try
				{
					Parser parser = new Parser(typeName);
					return new TypeNameParser(ref parser, true);
				}
				catch (ArgumentException)
				{
					return new TypeNameParser();
				}
			}
		}

		private TypeNameParser(ref Parser parser, bool withAssemblyName)
		{
			bool genericParameter = parser.pos != 0;
			name = parser.NextNamePart();
			nested = null;
			parser.ParseNested(ref nested);
			genericParameters = null;
			parser.ParseGenericParameters(ref genericParameters);
			modifiers = null;
			parser.ParseModifiers(ref modifiers);
			assemblyName = null;
			if (withAssemblyName)
			{
				parser.ParseAssemblyName(genericParameter, ref assemblyName);
			}
		}

		internal bool Error
		{
			get { return name == null; }
		}

		internal string FirstNamePart
		{
			get { return name; }
		}

		internal string AssemblyName
		{
			get { return assemblyName; }
		}

		private struct Parser
		{
			private readonly string typeName;
			internal int pos;

			internal Parser(string typeName)
			{
				this.typeName = typeName;
				this.pos = 0;
			}

			private void Check(bool condition)
			{
				if (!condition)
				{
					throw new ArgumentException("Invalid type name '" + typeName + "'");
				}
			}

			private void Consume(char c)
			{
				Check(pos < typeName.Length && typeName[pos++] == c);
			}

			private bool TryConsume(char c)
			{
				if (pos < typeName.Length && typeName[pos] == c)
				{
					pos++;
					return true;
				}
				else
				{
					return false;
				}
			}

			internal string NextNamePart()
			{
				SkipWhiteSpace();
				int start = pos;
				for (; pos < typeName.Length; pos++)
				{
					char c = typeName[pos];
					if (c == '\\')
					{
						pos++;
						Check(pos < typeName.Length && SpecialChars.IndexOf(typeName[pos]) != -1);
					}
					else if (SpecialChars.IndexOf(c) != -1)
					{
						break;
					}
				}
				Check(pos - start != 0);
				if (start == 0 && pos == typeName.Length)
				{
					return typeName;
				}
				else
				{
					return typeName.Substring(start, pos - start);
				}
			}

			internal void ParseNested(ref string[] nested)
			{
				while (TryConsume('+'))
				{
					Add(ref nested, NextNamePart());
				}
			}

			internal void ParseGenericParameters(ref TypeNameParser[] genericParameters)
			{
				int saved = pos;
				if (TryConsume('['))
				{
					SkipWhiteSpace();
					if (TryConsume(']') || TryConsume('*') || TryConsume(','))
					{
						// it's not a generic parameter list, but an array instead
						pos = saved;
						return;
					}
					do
					{
						SkipWhiteSpace();
						if (TryConsume('['))
						{
							Add(ref genericParameters, new TypeNameParser(ref this, true));
							Consume(']');
						}
						else
						{
							Add(ref genericParameters, new TypeNameParser(ref this, false));
						}
					}
					while (TryConsume(','));
					Consume(']');
					SkipWhiteSpace();
				}
			}

			internal void ParseModifiers(ref short[] modifiers)
			{
				while (pos < typeName.Length)
				{
					switch (typeName[pos])
					{
						case '*':
							pos++;
							Add(ref modifiers, POINTER);
							break;
						case '&':
							pos++;
							Add(ref modifiers, BYREF);
							break;
						case '[':
							pos++;
							Add(ref modifiers, ParseArray());
							Consume(']');
							break;
						default:
							return;
					}
					SkipWhiteSpace();
				}
			}

			internal void ParseAssemblyName(bool genericParameter, ref string assemblyName)
			{
				if (pos < typeName.Length)
				{
					if (typeName[pos] == ']' && genericParameter)
					{
						// ok
					}
					else
					{
						Consume(',');
						SkipWhiteSpace();
						if (genericParameter)
						{
							int start = pos;
							while (pos < typeName.Length)
							{
								char c = typeName[pos];
								if (c == '\\')
								{
									pos++;
									// a backslash itself is not legal in an assembly name, so we don't need to check for an escaped backslash
									Check(pos < typeName.Length && typeName[pos++] == ']');
								}
								else if (c == ']')
								{
									break;
								}
								else
								{
									pos++;
								}
							}
							Check(pos < typeName.Length && typeName[pos] == ']');
							assemblyName = typeName.Substring(start, pos - start).Replace("\\]", "]");
						}
						else
						{
							// only when an assembly name is used in a generic type parameter, will it be escaped
							assemblyName = typeName.Substring(pos);
						}
						Check(assemblyName.Length != 0);
					}
				}
				else
				{
					Check(!genericParameter);
				}
			}

			private short ParseArray()
			{
				SkipWhiteSpace();
				Check(pos < typeName.Length);
				char c = typeName[pos];
				if (c == ']')
				{
					return SZARRAY;
				}
				else if (c == '*')
				{
					pos++;
					SkipWhiteSpace();
					return 1;
				}
				else
				{
					short rank = 1;
					while (TryConsume(','))
					{
						Check(rank < short.MaxValue);
						rank++;
						SkipWhiteSpace();
					}
					return rank;
				}
			}

			private void SkipWhiteSpace()
			{
				while (pos < typeName.Length && Char.IsWhiteSpace(typeName[pos]))
				{
					pos++;
				}
			}

			private static void Add<T>(ref T[] array, T elem)
			{
				if (array == null)
				{
					array = new T[] { elem };
					return;
				}
				Array.Resize(ref array, array.Length + 1);
				array[array.Length - 1] = elem;
			}
		}

		internal Type GetType(Universe universe, Assembly context, bool throwOnError, string originalName, bool resolve, bool ignoreCase)
		{
			Debug.Assert(!resolve || !ignoreCase);
			TypeName name = TypeName.Split(this.name);
			Type type;
			if (assemblyName != null)
			{
				Assembly asm = universe.Load(assemblyName, context, throwOnError);
				if (asm == null)
				{
					return null;
				}
				if (resolve)
				{
					type = asm.ResolveType(name);
				}
				else if (ignoreCase)
				{
					type = asm.FindTypeIgnoreCase(name.ToLowerInvariant());
				}
				else
				{
					type = asm.FindType(name);
				}
			}
			else if (context == null)
			{
				if (resolve)
				{
					type = universe.Mscorlib.ResolveType(name);
				}
				else if (ignoreCase)
				{
					type = universe.Mscorlib.FindTypeIgnoreCase(name.ToLowerInvariant());
				}
				else
				{
					type = universe.Mscorlib.FindType(name);
				}
			}
			else
			{
				if (ignoreCase)
				{
					name = name.ToLowerInvariant();
					type = context.FindTypeIgnoreCase(name);
				}
				else
				{
					type = context.FindType(name);
				}
				if (type == null && context != universe.Mscorlib)
				{
					if (ignoreCase)
					{
						type = universe.Mscorlib.FindTypeIgnoreCase(name);
					}
					else
					{
						type = universe.Mscorlib.FindType(name);
					}
				}
				if (type == null && resolve)
				{
					if (universe.Mscorlib.__IsMissing && !context.__IsMissing)
					{
						type = universe.Mscorlib.ResolveType(name);
					}
					else
					{
						type = context.ResolveType(name);
					}
				}
			}
			return Expand(type, context, throwOnError, originalName, resolve, ignoreCase);
		}

		internal Type Expand(Type type, Assembly context, bool throwOnError, string originalName, bool resolve, bool ignoreCase)
		{
			Debug.Assert(!resolve || !ignoreCase);
			if (type == null)
			{
				if (throwOnError)
				{
					throw new TypeLoadException(originalName);
				}
				return null;
			}
			if (nested != null)
			{
				Type outer;
				foreach (string nest in nested)
				{
					outer = type;
					TypeName name = TypeName.Split(TypeNameParser.Unescape(nest));
					type = ignoreCase
						? outer.FindNestedTypeIgnoreCase(name.ToLowerInvariant())
						: outer.FindNestedType(name);
					if (type == null)
					{
						if (resolve)
						{
							type = outer.Module.universe.GetMissingTypeOrThrow(outer.Module, outer, name);
						}
						else if (throwOnError)
						{
							throw new TypeLoadException(originalName);
						}
						else
						{
							return null;
						}
					}
				}
			}
			if (genericParameters != null)
			{
				Type[] typeArgs = new Type[genericParameters.Length];
				for (int i = 0; i < typeArgs.Length; i++)
				{
					typeArgs[i] = genericParameters[i].GetType(type.Assembly.universe, context, throwOnError, originalName, resolve, ignoreCase);
					if (typeArgs[i] == null)
					{
						return null;
					}
				}
				type = type.MakeGenericType(typeArgs);
			}
			if (modifiers != null)
			{
				foreach (short modifier in modifiers)
				{
					switch (modifier)
					{
						case SZARRAY:
							type = type.MakeArrayType();
							break;
						case BYREF:
							type = type.MakeByRefType();
							break;
						case POINTER:
							type = type.MakePointerType();
							break;
						default:
							type = type.MakeArrayType(modifier);
							break;
					}
				}
			}
			return type;
		}
	}
}
