using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Reflection;
using System.Security;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Runtime.Versioning;
using Microsoft.Win32.SafeHandles;

#if !FEATURE_CORECLR
namespace System
{
    [SecurityCritical]
    internal class SafeTypeNameParserHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        #region QCalls
        [SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        private static extern void _ReleaseTypeNameParser(IntPtr pTypeNameParser);
        #endregion

        public SafeTypeNameParserHandle()
            : base(true)
        {
        }

        [SecurityCritical]
        protected override bool ReleaseHandle()
        {
            _ReleaseTypeNameParser(handle);
            handle = IntPtr.Zero;
            return true;
        }
    }

    internal sealed class TypeNameParser : IDisposable
    {
        #region QCalls
        [SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        private static extern void _CreateTypeNameParser(string typeName, ObjectHandleOnStack retHandle, bool throwOnError);

        [SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        private static extern void _GetNames(SafeTypeNameParserHandle pTypeNameParser, ObjectHandleOnStack retArray);

        [SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        private static extern void _GetTypeArguments(SafeTypeNameParserHandle pTypeNameParser, ObjectHandleOnStack retArray);

        [SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        private static extern void _GetModifiers(SafeTypeNameParserHandle pTypeNameParser, ObjectHandleOnStack retArray);

        [SecurityCritical]
        [ResourceExposure(ResourceScope.None)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        private static extern void _GetAssemblyName(SafeTypeNameParserHandle pTypeNameParser, StringHandleOnStack retString);
        #endregion

        #region Static Members
        [SecuritySafeCritical]
        internal static Type GetType(
            string typeName,
            Func<AssemblyName, Assembly> assemblyResolver,
            Func<Assembly, string, bool, Type> typeResolver,
            bool throwOnError,
            bool ignoreCase,
            ref StackCrawlMark stackMark)
        {
            if (typeName == null)
                throw new ArgumentNullException("typeName");
            if (typeName.Length > 0 && typeName[0] == '\0')
                throw new ArgumentException(Environment.GetResourceString("Format_StringZeroLength"));
            Contract.EndContractBlock();

            Type ret = null;

            SafeTypeNameParserHandle handle = CreateTypeNameParser(typeName, throwOnError);

            if (handle != null)
            {
                // If we get here the typeName must have been successfully parsed.
                // Let's construct the Type object.
                using (TypeNameParser parser = new TypeNameParser(handle))
                {
                    ret = parser.ConstructType(assemblyResolver, typeResolver, throwOnError, ignoreCase, ref stackMark);
                }
            }

            return ret;
        }
        #endregion

        #region Private Data Members
        [SecurityCritical]
        private SafeTypeNameParserHandle m_NativeParser;
        private static readonly char[] SPECIAL_CHARS = {',', '[', ']', '&', '*', '+', '\\'}; /* see typeparse.h */
        #endregion

        #region Constructor and Disposer
        [SecuritySafeCritical]
        private TypeNameParser(SafeTypeNameParserHandle handle)
        {
            m_NativeParser = handle;
        }

        [SecuritySafeCritical]
        public void Dispose()
        {
            m_NativeParser.Dispose();
        }
        #endregion

        #region private Members
        [SecuritySafeCritical]
        private unsafe Type ConstructType(
            Func<AssemblyName, Assembly> assemblyResolver,
            Func<Assembly, string, bool, Type> typeResolver,
            bool throwOnError,
            bool ignoreCase,
            ref StackCrawlMark stackMark)
        {
            // assembly name
            Assembly assembly = null;
            string asmName = GetAssemblyName();

            // GetAssemblyName never returns null
            Contract.Assert(asmName != null);

            if (asmName.Length > 0)
            {
                assembly = ResolveAssembly(asmName, assemblyResolver, throwOnError, ref stackMark);

                if (assembly == null)
                {
                    // Cannot resolve the assembly. If throwOnError is true we should have already thrown.
                    return null;
                }
            }

            string[] names = GetNames();
            if (names == null)
            {
                // This can only happen if the type name is an empty string or if the first char is '\0'
                if (throwOnError)
                    throw new TypeLoadException(Environment.GetResourceString("Arg_TypeLoadNullStr"));

                return null;
            }

            Type baseType = ResolveType(assembly, names, typeResolver, throwOnError, ignoreCase, ref stackMark);

            if (baseType == null)
            {
                // Cannot resolve the type. If throwOnError is true we should have already thrown.
                Contract.Assert(throwOnError == false);
                return null;
            }

            SafeTypeNameParserHandle[] typeArguments = GetTypeArguments();

            Type[] types = null;
            if (typeArguments != null)
            {
                types = new Type[typeArguments.Length];
                for (int i = 0; i < typeArguments.Length; i++)
                {
                    Contract.Assert(typeArguments[i] != null);

                    using (TypeNameParser argParser = new TypeNameParser(typeArguments[i]))
                    {
                        types[i] = argParser.ConstructType(assemblyResolver, typeResolver, throwOnError, ignoreCase, ref stackMark);
                    }

                    if (types[i] == null)
                    {
                        // If throwOnError is true argParser.ConstructType should have already thrown.
                        Contract.Assert(throwOnError == false);
                        return null;
                    }
                }
            }

            int[] modifiers = GetModifiers();

            fixed (int* ptr = modifiers)
            {
                IntPtr intPtr = new IntPtr(ptr);
                return RuntimeTypeHandle.GetTypeHelper(baseType, types, intPtr, modifiers == null ? 0 : modifiers.Length);
            }
        }

        [SecuritySafeCritical]
        private static Assembly ResolveAssembly(string asmName, Func<AssemblyName, Assembly> assemblyResolver, bool throwOnError, ref StackCrawlMark stackMark)
        {
            Contract.Requires(asmName != null && asmName.Length > 0);

            Assembly assembly = null;

            if (assemblyResolver == null)
            {
                if (throwOnError)
                {
                    assembly = RuntimeAssembly.InternalLoad(asmName, null, ref stackMark, false /*forIntrospection*/);
                }
                else
                {
                    // When throwOnError is false we should only catch FileNotFoundException.
                    // Other exceptions like BadImangeFormatException should still fly.
                    try
                    {
                        assembly = RuntimeAssembly.InternalLoad(asmName, null,  ref stackMark, false /*forIntrospection*/);
                    }
                    catch (FileNotFoundException)
                    {
                        return null;
                    }
                }
            }
            else
            {
                assembly = assemblyResolver(new AssemblyName(asmName));
                if (assembly == null && throwOnError)
                {
                    throw new FileNotFoundException(Environment.GetResourceString("FileNotFound_ResolveAssembly", asmName));
                }
            }

            return assembly;
        }

        private static Type ResolveType(Assembly assembly, string[] names, Func<Assembly, string, bool, Type> typeResolver, bool throwOnError, bool ignoreCase, ref StackCrawlMark stackMark)
        {
            Contract.Requires(names != null && names.Length > 0);

            Type type = null;

            // both the customer provided and the default type resolvers accept escaped type names
            string OuterMostTypeName = EscapeTypeName(names[0]);

            // Resolve the top level type.
            if (typeResolver != null)
            {
                type = typeResolver(assembly, OuterMostTypeName, ignoreCase);

                if (type == null && throwOnError)
                {
                    string errorString = assembly == null ?
                        Environment.GetResourceString("TypeLoad_ResolveType", OuterMostTypeName) :
                        Environment.GetResourceString("TypeLoad_ResolveTypeFromAssembly", OuterMostTypeName, assembly.FullName);

                    throw new TypeLoadException(errorString);
                }
            }
            else
            {
                if (assembly == null)
                {
                    type = RuntimeType.GetType(OuterMostTypeName, throwOnError, ignoreCase, false, ref stackMark);
                }
                else
                {
                    type = assembly.GetType(OuterMostTypeName, throwOnError, ignoreCase);
                }
            }

            // Resolve nested types.
            if (type != null)
            {
                BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Public;
                if (ignoreCase)
                    bindingFlags |= BindingFlags.IgnoreCase;

                for (int i = 1; i < names.Length; i++)
                {
                    type = type.GetNestedType(names[i], bindingFlags);

                    if (type == null)
                    {
                        if (throwOnError)
                            throw new TypeLoadException(Environment.GetResourceString("TypeLoad_ResolveNestedType", names[i], names[i-1]));
                        else
                            break;
                    }
                }
            }

            return type;
        }

        private static string EscapeTypeName(string name)
        {
            if (name.IndexOfAny(SPECIAL_CHARS) < 0)
                return name;

            StringBuilder sb = StringBuilderCache.Acquire();
            foreach (char c in name)
            {
                if (Array.IndexOf<char>(SPECIAL_CHARS, c) >= 0)
                    sb.Append('\\');

                sb.Append(c);
            }

            return StringBuilderCache.GetStringAndRelease(sb);
        }

        [SecuritySafeCritical]
        private static SafeTypeNameParserHandle CreateTypeNameParser(string typeName, bool throwOnError)
        {
            SafeTypeNameParserHandle retHandle = null;
            _CreateTypeNameParser(typeName, JitHelpers.GetObjectHandleOnStack(ref retHandle), throwOnError);

            return retHandle;
        }

        [SecuritySafeCritical]
        private string[] GetNames()
        {
            string[] names = null;
            _GetNames(m_NativeParser, JitHelpers.GetObjectHandleOnStack(ref names));

            return names;
        }

        [SecuritySafeCritical]
        private SafeTypeNameParserHandle[] GetTypeArguments()
        {
            SafeTypeNameParserHandle[] arguments = null;
            _GetTypeArguments(m_NativeParser, JitHelpers.GetObjectHandleOnStack(ref arguments));

            return arguments;
        }

        [SecuritySafeCritical]
        private int[] GetModifiers()
        {
            int[] modifiers = null;
            _GetModifiers(m_NativeParser, JitHelpers.GetObjectHandleOnStack(ref modifiers));

            return modifiers;
        }

        [SecuritySafeCritical]
        private string GetAssemblyName()
        {
            string assemblyName = null;
            _GetAssemblyName(m_NativeParser, JitHelpers.GetStringHandleOnStack(ref assemblyName));
            
            return assemblyName;
        }
        #endregion
    }
}
#endif //!FEATURE_CORECLR
