// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
// 
// 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.Generic;
using ICSharpCode.Decompiler.TypeSystem.Implementation;

namespace ICSharpCode.Decompiler.TypeSystem
{
	/// <summary>
	/// Static helper methods for reflection names.
	/// </summary>
	public static class ReflectionHelper
	{
		/// <summary>
		/// A reflection class used to represent <c>null</c>.
		/// </summary>
		public sealed class Null {}
		
		/// <summary>
		/// A reflection class used to represent <c>dynamic</c>.
		/// </summary>
		public sealed class Dynamic {}

		/// <summary>
		/// A reflection class used to represent <c>nint</c>.
		/// </summary>
		public sealed class NInt { }

		/// <summary>
		/// A reflection class used to represent <c>nuint</c>.
		/// </summary>
		public sealed class NUInt { }

		/// <summary>
		/// A reflection class used to represent an unbound type argument.
		/// </summary>
		public sealed class UnboundTypeArgument {}
		
		#region ICompilation.FindType
		/// <summary>
		/// Retrieves the specified type in this compilation.
		/// Returns <see cref="SpecialType.UnknownType"/> if the type cannot be found in this compilation.
		/// </summary>
		/// <remarks>
		/// This method cannot be used with open types; all type parameters will be substituted
		/// with <see cref="SpecialType.UnknownType"/>.
		/// </remarks>
		public static IType FindType(this ICompilation compilation, Type type)
		{
			return type.ToTypeReference().Resolve(new SimpleTypeResolveContext(compilation));
		}
		#endregion
		
		#region Type.ToTypeReference()
		/// <summary>
		/// Creates a reference to the specified type.
		/// </summary>
		/// <param name="type">The type to be converted.</param>
		/// <returns>Returns the type reference.</returns>
		/// <remarks>
		/// If the type is open (contains type parameters '`0' or '``0'),
		/// an <see cref="ITypeResolveContext"/> with the appropriate CurrentTypeDefinition/CurrentMember is required
		/// to resolve the type reference.
		/// For closed types, the root type resolve context for the compilation is sufficient.
		/// </remarks>
		public static ITypeReference ToTypeReference(this Type type)
		{
			if (type == null)
				return SpecialType.UnknownType;
			if (type.IsGenericType && !type.IsGenericTypeDefinition) {
				ITypeReference def = ToTypeReference(type.GetGenericTypeDefinition());
				Type[] arguments = type.GetGenericArguments();
				ITypeReference[] args = new ITypeReference[arguments.Length];
				bool allUnbound = true;
				for (int i = 0; i < arguments.Length; i++) {
					args[i] = ToTypeReference(arguments[i]);
					allUnbound &= args[i].Equals(SpecialType.UnboundTypeArgument);
				}
				if (allUnbound)
					return def;
				else
					return new ParameterizedTypeReference(def, args);
			} else if (type.IsArray) {
				return new ArrayTypeReference(ToTypeReference(type.GetElementType()), type.GetArrayRank());
			} else if (type.IsPointer) {
				return new PointerTypeReference(ToTypeReference(type.GetElementType()));
			} else if (type.IsByRef) {
				return new ByReferenceTypeReference(ToTypeReference(type.GetElementType()));
			} else if (type.IsGenericParameter) {
				if (type.DeclaringMethod != null) {
					return TypeParameterReference.Create(SymbolKind.Method, type.GenericParameterPosition);
				} else {
					return TypeParameterReference.Create(SymbolKind.TypeDefinition, type.GenericParameterPosition);
				}
			} else if (type.DeclaringType != null) {
				if (type == typeof(Dynamic))
					return SpecialType.Dynamic;
				else if (type == typeof(NInt))
					return SpecialType.NInt;
				else if (type == typeof(NUInt))
					return SpecialType.NUInt;
				else if (type == typeof(Null))
					return SpecialType.NullType;
				else if (type == typeof(UnboundTypeArgument))
					return SpecialType.UnboundTypeArgument;
				ITypeReference baseTypeRef = ToTypeReference(type.DeclaringType);
				int typeParameterCount;
				string name = SplitTypeParameterCountFromReflectionName(type.Name, out typeParameterCount);
				return new NestedTypeReference(baseTypeRef, name, typeParameterCount);
			} else {
				IModuleReference assemblyReference = new DefaultAssemblyReference(type.Assembly.FullName);
				int typeParameterCount;
				string name = SplitTypeParameterCountFromReflectionName(type.Name, out typeParameterCount);
				return new GetClassTypeReference(assemblyReference, type.Namespace, name, typeParameterCount);
			}
		}
		#endregion
		
		#region SplitTypeParameterCountFromReflectionName
		/// <summary>
		/// Removes the ` with type parameter count from the reflection name.
		/// </summary>
		/// <remarks>Do not use this method with the full name of inner classes.</remarks>
		public static string SplitTypeParameterCountFromReflectionName(string reflectionName)
		{
			int pos = reflectionName.LastIndexOf('`');
			if (pos < 0) {
				return reflectionName;
			} else {
				return reflectionName.Substring(0, pos);
			}
		}
		
		/// <summary>
		/// Removes the ` with type parameter count from the reflection name.
		/// </summary>
		/// <remarks>Do not use this method with the full name of inner classes.</remarks>
		public static string SplitTypeParameterCountFromReflectionName(string reflectionName, out int typeParameterCount)
		{
			int pos = reflectionName.LastIndexOf('`');
			if (pos < 0) {
				typeParameterCount = 0;
				return reflectionName;
			} else {
				string typeCount = reflectionName.Substring(pos + 1);
				if (int.TryParse(typeCount, out typeParameterCount))
					return reflectionName.Substring(0, pos);
				else
					return reflectionName;
			}
		}
		#endregion
		
		#region TypeCode support
		/// <summary>
		/// Retrieves a built-in type using the specified type code.
		/// </summary>
		public static IType FindType(this ICompilation compilation, TypeCode typeCode)
		{
			return compilation.FindType((KnownTypeCode)typeCode);
		}
		
		/// <summary>
		/// Creates a reference to the specified type.
		/// </summary>
		/// <param name="typeCode">The type to be converted.</param>
		/// <returns>Returns the type reference.</returns>
		public static ITypeReference ToTypeReference(this TypeCode typeCode)
		{
			return KnownTypeReference.Get((KnownTypeCode)typeCode);
		}
		
		/// <summary>
		/// Gets the type code for the specified type, or TypeCode.Empty if none of the other type codes match.
		/// </summary>
		public static TypeCode GetTypeCode(this IType type)
		{
			ITypeDefinition def = type as ITypeDefinition;
			if (def != null) {
				KnownTypeCode typeCode = def.KnownTypeCode;
				if (typeCode <= KnownTypeCode.String && typeCode != KnownTypeCode.Void)
					return (TypeCode)typeCode;
				else
					return TypeCode.Empty;
			}
			return TypeCode.Empty;
		}
		#endregion
		
		#region ParseReflectionName
		/// <summary>
		/// Parses a reflection name into a type reference.
		/// </summary>
		/// <param name="reflectionTypeName">The reflection name of the type.</param>
		/// <returns>A type reference that represents the reflection name.</returns>
		/// <exception cref="ReflectionNameParseException">The syntax of the reflection type name is invalid</exception>
		/// <remarks>
		/// If the type is open (contains type parameters '`0' or '``0'),
		/// an <see cref="ITypeResolveContext"/> with the appropriate CurrentTypeDefinition/CurrentMember is required
		/// to resolve the reference to the ITypeParameter.
		/// For looking up closed, assembly qualified type names, the root type resolve context for the compilation
		/// is sufficient.
		/// When looking up a type name that isn't assembly qualified, the type reference will look in
		/// <see cref="ITypeResolveContext.CurrentModule"/> first, and if the type is not found there,
		/// it will look in all other assemblies of the compilation.
		/// </remarks>
		/// <seealso cref="FullTypeName(string)"/>
		public static ITypeReference ParseReflectionName(string reflectionTypeName)
		{
			if (reflectionTypeName == null)
				throw new ArgumentNullException(nameof(reflectionTypeName));
			int pos = 0;
			ITypeReference r = ParseReflectionName(reflectionTypeName, ref pos);
			if (pos < reflectionTypeName.Length)
				throw new ReflectionNameParseException(pos, "Expected end of type name");
			return r;
		}
		
		static bool IsReflectionNameSpecialCharacter(char c)
		{
			switch (c) {
				case '+':
				case '`':
				case '[':
				case ']':
				case ',':
				case '*':
				case '&':
					return true;
				default:
					return false;
			}
		}
		
		/// <summary>
		/// Parses the reflection name starting at pos.
		/// If local is true, only parses local type names, not assembly qualified type names.
		/// </summary>
		static ITypeReference ParseReflectionName(string reflectionTypeName, ref int pos, bool local=false)
		{
			if (pos == reflectionTypeName.Length)
				throw new ReflectionNameParseException(pos, "Unexpected end");
			ITypeReference reference;
			if (reflectionTypeName[pos] == '`') {
				// type parameter reference
				pos++;
				if (pos == reflectionTypeName.Length)
					throw new ReflectionNameParseException(pos, "Unexpected end");
				if (reflectionTypeName[pos] == '`') {
					// method type parameter reference
					pos++;
					int index = ReadTypeParameterCount(reflectionTypeName, ref pos);
					reference = TypeParameterReference.Create(SymbolKind.Method, index);
				} else {
					// class type parameter reference
					int index = ReadTypeParameterCount(reflectionTypeName, ref pos);
					reference = TypeParameterReference.Create(SymbolKind.TypeDefinition, index);
				}
			} else {
				// not a type parameter reference: read the actual type name
				string typeName = ReadTypeName(reflectionTypeName, ref pos, out int tpc);
				string assemblyName = local ? null : SkipAheadAndReadAssemblyName(reflectionTypeName, pos);
				reference = CreateGetClassTypeReference(assemblyName, typeName, tpc);
			}
			// read type suffixes
			while (pos < reflectionTypeName.Length) {
				switch (reflectionTypeName[pos++]) {
					case '+':
						int tpc;
						string typeName = ReadTypeName(reflectionTypeName, ref pos, out tpc);
						reference = new NestedTypeReference(reference, typeName, tpc);
						break;
					case '*':
						reference = new PointerTypeReference(reference);
						break;
					case '&':
						reference = new ByReferenceTypeReference(reference);
						break;
					case '[':
						// this might be an array or a generic type
						if (pos == reflectionTypeName.Length)
							throw new ReflectionNameParseException(pos, "Unexpected end");
						if (reflectionTypeName[pos] != ']' && reflectionTypeName[pos] != ',') {
							// it's a generic type
							List<ITypeReference> typeArguments = new List<ITypeReference>();
							bool first = true;
							while (first || pos < reflectionTypeName.Length && reflectionTypeName[pos] == ',') {
								if (first) {
									first = false;
								} else {
									pos++; // skip ','
								}
								if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == '[') {
									// non-local type names are enclosed in another set of []
									pos++;

									typeArguments.Add(ParseReflectionName(reflectionTypeName, ref pos));

									if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ']')
										pos++;
									else
										throw new ReflectionNameParseException(pos, "Expected end of type argument");
								} else {
									// local type names occur directly in the outer []
									typeArguments.Add(ParseReflectionName(reflectionTypeName, ref pos, local: true));
								}
							}
							
							if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ']') {
								pos++;
								reference = new ParameterizedTypeReference(reference, typeArguments);
							} else {
								throw new ReflectionNameParseException(pos, "Expected end of generic type");
							}
						} else {
							// it's an array
							int dimensions = 1;
							while (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ',') {
								dimensions++;
								pos++;
							}
							if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ']') {
								pos++; // end of array
								reference = new ArrayTypeReference(reference, dimensions);
							} else {
								throw new ReflectionNameParseException(pos, "Invalid array modifier");
							}
						}
						break;
					case ',' when !local:
						// assembly qualified name, ignore everything up to the end/next ']'
						while (pos < reflectionTypeName.Length && reflectionTypeName[pos] != ']')
							pos++;
						break;
					default:
						pos--; // reset pos to the character we couldn't read
						if (reflectionTypeName[pos] == ']' || reflectionTypeName[pos] == ',')
							return reference; // return from a nested generic
						else
							throw new ReflectionNameParseException(pos, "Unexpected character: '" + reflectionTypeName[pos] + "'");
				}
			}
			return reference;
		}
		
		static ITypeReference CreateGetClassTypeReference(string assemblyName, string typeName, int tpc)
		{
			IModuleReference assemblyReference;
			if (assemblyName != null) {
				assemblyReference = new DefaultAssemblyReference(assemblyName);
			} else {
				assemblyReference = null;
			}
			int pos = typeName.LastIndexOf('.');
			if (pos < 0)
				return new GetClassTypeReference(assemblyReference, string.Empty, typeName, tpc);
			else
				return new GetClassTypeReference(assemblyReference, typeName.Substring(0, pos), typeName.Substring(pos + 1), tpc);
		}
		
		static string SkipAheadAndReadAssemblyName(string reflectionTypeName, int pos)
		{
			int nestingLevel = 0;
			while (pos < reflectionTypeName.Length) {
				switch (reflectionTypeName[pos++]) {
					case '[':
						nestingLevel++;
						break;
					case ']':
						if (nestingLevel == 0)
							return null;
						nestingLevel--;
						break;
					case ',':
						if (nestingLevel == 0) {
							// first skip the whitespace
							while (pos < reflectionTypeName.Length && reflectionTypeName[pos] == ' ')
								pos++;
							// everything up to the end/next ']' is the assembly name
							int endPos = pos;
							while (endPos < reflectionTypeName.Length && reflectionTypeName[endPos] != ']')
								endPos++;
							return reflectionTypeName.Substring(pos, endPos - pos);
						}
						break;
				}
			}
			return null;
		}
		
		static string ReadTypeName(string reflectionTypeName, ref int pos, out int tpc)
		{
			int startPos = pos;
			// skip the simple name portion:
			while (pos < reflectionTypeName.Length && !IsReflectionNameSpecialCharacter(reflectionTypeName[pos]))
				pos++;
			if (pos == startPos)
				throw new ReflectionNameParseException(pos, "Expected type name");
			string typeName = reflectionTypeName.Substring(startPos, pos - startPos);
			if (pos < reflectionTypeName.Length && reflectionTypeName[pos] == '`') {
				pos++;
				tpc = ReadTypeParameterCount(reflectionTypeName, ref pos);
			} else {
				tpc = 0;
			}
			return typeName;
		}
		
		internal static int ReadTypeParameterCount(string reflectionTypeName, ref int pos)
		{
			int startPos = pos;
			while (pos < reflectionTypeName.Length) {
				char c = reflectionTypeName[pos];
				if (c < '0' || c > '9')
					break;
				pos++;
			}
			int tpc;
			if (!int.TryParse(reflectionTypeName.Substring(startPos, pos - startPos), out tpc))
				throw new ReflectionNameParseException(pos, "Expected type parameter count");
			return tpc;
		}
		#endregion
	}
}
