//
// Author:
//   Jb Evain (jbevain@gmail.com)
//
// Copyright (c) 2008 - 2015 Jb Evain
// Copyright (c) 2008 - 2011 Novell, Inc.
//
// Licensed under the MIT/X11 license.
//

using MonoFN.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;

namespace MonoFN.Cecil
{
    internal sealed class TypeDefinitionProjection
    {
        public readonly TypeAttributes Attributes;
        public readonly string Name;
        public readonly TypeDefinitionTreatment Treatment;
        public readonly Collection<MethodDefinition> RedirectedMethods;
        public readonly Collection<KeyValuePair<InterfaceImplementation, InterfaceImplementation>> RedirectedInterfaces;

        public TypeDefinitionProjection(TypeDefinition type, TypeDefinitionTreatment treatment, Collection<MethodDefinition> redirectedMethods, Collection<KeyValuePair<InterfaceImplementation, InterfaceImplementation>> redirectedInterfaces)
        {
            Attributes = type.Attributes;
            Name = type.Name;
            Treatment = treatment;
            RedirectedMethods = redirectedMethods;
            RedirectedInterfaces = redirectedInterfaces;
        }
    }

    internal sealed class TypeReferenceProjection
    {
        public readonly string Name;
        public readonly string Namespace;
        public readonly IMetadataScope Scope;
        public readonly TypeReferenceTreatment Treatment;

        public TypeReferenceProjection(TypeReference type, TypeReferenceTreatment treatment)
        {
            Name = type.Name;
            Namespace = type.Namespace;
            Scope = type.Scope;
            Treatment = treatment;
        }
    }

    internal sealed class MethodDefinitionProjection
    {
        public readonly MethodAttributes Attributes;
        public readonly MethodImplAttributes ImplAttributes;
        public readonly string Name;
        public readonly MethodDefinitionTreatment Treatment;

        public MethodDefinitionProjection(MethodDefinition method, MethodDefinitionTreatment treatment)
        {
            Attributes = method.Attributes;
            ImplAttributes = method.ImplAttributes;
            Name = method.Name;
            Treatment = treatment;
        }
    }

    internal sealed class FieldDefinitionProjection
    {
        public readonly FieldAttributes Attributes;
        public readonly FieldDefinitionTreatment Treatment;

        public FieldDefinitionProjection(FieldDefinition field, FieldDefinitionTreatment treatment)
        {
            Attributes = field.Attributes;
            Treatment = treatment;
        }
    }

    internal sealed class CustomAttributeValueProjection
    {
        public readonly AttributeTargets Targets;
        public readonly CustomAttributeValueTreatment Treatment;

        public CustomAttributeValueProjection(AttributeTargets targets, CustomAttributeValueTreatment treatment)
        {
            Targets = targets;
            Treatment = treatment;
        }
    }

    internal sealed class WindowsRuntimeProjections
    {
        private struct ProjectionInfo
        {
            public readonly string WinRTNamespace;
            public readonly string ClrNamespace;
            public readonly string ClrName;
            public readonly string ClrAssembly;
            public readonly bool Attribute;

            public ProjectionInfo(string winrt_namespace, string clr_namespace, string clr_name, string clr_assembly, bool attribute = false)
            {
                WinRTNamespace = winrt_namespace;
                ClrNamespace = clr_namespace;
                ClrName = clr_name;
                ClrAssembly = clr_assembly;
                Attribute = attribute;
            }
        }

        private static readonly Version version = new(4, 0, 0, 0);
        private static readonly byte[] contract_pk_token =
        {
            0xB0, 0x3F, 0x5F, 0x7F, 0x11, 0xD5, 0x0A, 0x3A
        };
        private static readonly byte[] contract_pk =
        {
            0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00,
            0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
            0x07, 0xD1, 0xFA, 0x57, 0xC4, 0xAE, 0xD9, 0xF0, 0xA3, 0x2E, 0x84, 0xAA, 0x0F, 0xAE, 0xFD, 0x0D,
            0xE9, 0xE8, 0xFD, 0x6A, 0xEC, 0x8F, 0x87, 0xFB, 0x03, 0x76, 0x6C, 0x83, 0x4C, 0x99, 0x92, 0x1E,
            0xB2, 0x3B, 0xE7, 0x9A, 0xD9, 0xD5, 0xDC, 0xC1, 0xDD, 0x9A, 0xD2, 0x36, 0x13, 0x21, 0x02, 0x90,
            0x0B, 0x72, 0x3C, 0xF9, 0x80, 0x95, 0x7F, 0xC4, 0xE1, 0x77, 0x10, 0x8F, 0xC6, 0x07, 0x77, 0x4F,
            0x29, 0xE8, 0x32, 0x0E, 0x92, 0xEA, 0x05, 0xEC, 0xE4, 0xE8, 0x21, 0xC0, 0xA5, 0xEF, 0xE8, 0xF1,
            0x64, 0x5C, 0x4C, 0x0C, 0x93, 0xC1, 0xAB, 0x99, 0x28, 0x5D, 0x62, 0x2C, 0xAA, 0x65, 0x2C, 0x1D,
            0xFA, 0xD6, 0x3D, 0x74, 0x5D, 0x6F, 0x2D, 0xE5, 0xF1, 0x7E, 0x5E, 0xAF, 0x0F, 0xC4, 0x96, 0x3D,
            0x26, 0x1C, 0x8A, 0x12, 0x43, 0x65, 0x18, 0x20, 0x6D, 0xC0, 0x93, 0x34, 0x4D, 0x5A, 0xD2, 0x93
        };
        private static Dictionary<string, ProjectionInfo> projections;
        private static Dictionary<string, ProjectionInfo> Projections
        {
            get
            {
                if (projections != null)
                    return projections;


                Dictionary<string, ProjectionInfo> new_projections = new()
                {
                    { "AttributeTargets", new("Windows.Foundation.Metadata", "System", "AttributeTargets", "System.Runtime") },
                    { "AttributeUsageAttribute", new("Windows.Foundation.Metadata", "System", "AttributeUsageAttribute", "System.Runtime", attribute: true) },
                    { "Color", new("Windows.UI", "Windows.UI", "Color", "System.Runtime.WindowsRuntime") },
                    { "CornerRadius", new("Windows.UI.Xaml", "Windows.UI.Xaml", "CornerRadius", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "DateTime", new("Windows.Foundation", "System", "DateTimeOffset", "System.Runtime") },
                    { "Duration", new("Windows.UI.Xaml", "Windows.UI.Xaml", "Duration", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "DurationType", new("Windows.UI.Xaml", "Windows.UI.Xaml", "DurationType", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "EventHandler`1", new("Windows.Foundation", "System", "EventHandler`1", "System.Runtime") },
                    { "EventRegistrationToken", new("Windows.Foundation", "System.Runtime.InteropServices.WindowsRuntime", "EventRegistrationToken", "System.Runtime.InteropServices.WindowsRuntime") },
                    { "GeneratorPosition", new("Windows.UI.Xaml.Controls.Primitives", "Windows.UI.Xaml.Controls.Primitives", "GeneratorPosition", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "GridLength", new("Windows.UI.Xaml", "Windows.UI.Xaml", "GridLength", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "GridUnitType", new("Windows.UI.Xaml", "Windows.UI.Xaml", "GridUnitType", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "HResult", new("Windows.Foundation", "System", "Exception", "System.Runtime") },
                    { "IBindableIterable", new("Windows.UI.Xaml.Interop", "System.Collections", "IEnumerable", "System.Runtime") },
                    { "IBindableVector", new("Windows.UI.Xaml.Interop", "System.Collections", "IList", "System.Runtime") },
                    { "IClosable", new("Windows.Foundation", "System", "IDisposable", "System.Runtime") },
                    { "ICommand", new("Windows.UI.Xaml.Input", "System.Windows.Input", "ICommand", "System.ObjectModel") },
                    { "IIterable`1", new("Windows.Foundation.Collections", "System.Collections.Generic", "IEnumerable`1", "System.Runtime") },
                    { "IKeyValuePair`2", new("Windows.Foundation.Collections", "System.Collections.Generic", "KeyValuePair`2", "System.Runtime") },
                    { "IMapView`2", new("Windows.Foundation.Collections", "System.Collections.Generic", "IReadOnlyDictionary`2", "System.Runtime") },
                    { "IMap`2", new("Windows.Foundation.Collections", "System.Collections.Generic", "IDictionary`2", "System.Runtime") },
                    { "INotifyCollectionChanged", new("Windows.UI.Xaml.Interop", "System.Collections.Specialized", "INotifyCollectionChanged", "System.ObjectModel") },
                    { "INotifyPropertyChanged", new("Windows.UI.Xaml.Data", "System.ComponentModel", "INotifyPropertyChanged", "System.ObjectModel") },
                    { "IReference`1", new("Windows.Foundation", "System", "Nullable`1", "System.Runtime") },
                    { "IVectorView`1", new("Windows.Foundation.Collections", "System.Collections.Generic", "IReadOnlyList`1", "System.Runtime") },
                    { "IVector`1", new("Windows.Foundation.Collections", "System.Collections.Generic", "IList`1", "System.Runtime") },
                    { "KeyTime", new("Windows.UI.Xaml.Media.Animation", "Windows.UI.Xaml.Media.Animation", "KeyTime", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "Matrix", new("Windows.UI.Xaml.Media", "Windows.UI.Xaml.Media", "Matrix", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "Matrix3D", new("Windows.UI.Xaml.Media.Media3D", "Windows.UI.Xaml.Media.Media3D", "Matrix3D", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "Matrix3x2", new("Windows.Foundation.Numerics", "System.Numerics", "Matrix3x2", "System.Numerics.Vectors") },
                    { "Matrix4x4", new("Windows.Foundation.Numerics", "System.Numerics", "Matrix4x4", "System.Numerics.Vectors") },
                    { "NotifyCollectionChangedAction", new("Windows.UI.Xaml.Interop", "System.Collections.Specialized", "NotifyCollectionChangedAction", "System.ObjectModel") },
                    { "NotifyCollectionChangedEventArgs", new("Windows.UI.Xaml.Interop", "System.Collections.Specialized", "NotifyCollectionChangedEventArgs", "System.ObjectModel") },
                    { "NotifyCollectionChangedEventHandler", new("Windows.UI.Xaml.Interop", "System.Collections.Specialized", "NotifyCollectionChangedEventHandler", "System.ObjectModel") },
                    { "Plane", new("Windows.Foundation.Numerics", "System.Numerics", "Plane", "System.Numerics.Vectors") },
                    { "Point", new("Windows.Foundation", "Windows.Foundation", "Point", "System.Runtime.WindowsRuntime") },
                    { "PropertyChangedEventArgs", new("Windows.UI.Xaml.Data", "System.ComponentModel", "PropertyChangedEventArgs", "System.ObjectModel") },
                    { "PropertyChangedEventHandler", new("Windows.UI.Xaml.Data", "System.ComponentModel", "PropertyChangedEventHandler", "System.ObjectModel") },
                    { "Quaternion", new("Windows.Foundation.Numerics", "System.Numerics", "Quaternion", "System.Numerics.Vectors") },
                    { "Rect", new("Windows.Foundation", "Windows.Foundation", "Rect", "System.Runtime.WindowsRuntime") },
                    { "RepeatBehavior", new("Windows.UI.Xaml.Media.Animation", "Windows.UI.Xaml.Media.Animation", "RepeatBehavior", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "RepeatBehaviorType", new("Windows.UI.Xaml.Media.Animation", "Windows.UI.Xaml.Media.Animation", "RepeatBehaviorType", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "Size", new("Windows.Foundation", "Windows.Foundation", "Size", "System.Runtime.WindowsRuntime") },
                    { "Thickness", new("Windows.UI.Xaml", "Windows.UI.Xaml", "Thickness", "System.Runtime.WindowsRuntime.UI.Xaml") },
                    { "TimeSpan", new("Windows.Foundation", "System", "TimeSpan", "System.Runtime") },
                    { "TypeName", new("Windows.UI.Xaml.Interop", "System", "Type", "System.Runtime") },
                    { "Uri", new("Windows.Foundation", "System", "Uri", "System.Runtime") },
                    { "Vector2", new("Windows.Foundation.Numerics", "System.Numerics", "Vector2", "System.Numerics.Vectors") },
                    { "Vector3", new("Windows.Foundation.Numerics", "System.Numerics", "Vector3", "System.Numerics.Vectors") },
                    { "Vector4", new("Windows.Foundation.Numerics", "System.Numerics", "Vector4", "System.Numerics.Vectors") }
                };

                Interlocked.CompareExchange(ref projections, new_projections, null);
                return projections;
            }
        }
        private readonly ModuleDefinition module;
        private Version corlib_version = new(255, 255, 255, 255);
        private AssemblyNameReference[] virtual_references;
        private AssemblyNameReference[] VirtualReferences
        {
            get
            {
                if (virtual_references == null)
                {
                    // force module to read its assembly references. that will in turn initialize virtual_references
                    Mixin.Read(module.AssemblyReferences);
                }

                return virtual_references;
            }
        }

        public WindowsRuntimeProjections(ModuleDefinition module)
        {
            this.module = module;
        }

        public static void Project(TypeDefinition type)
        {
            TypeDefinitionTreatment treatment = TypeDefinitionTreatment.None;
            MetadataKind metadata_kind = type.Module.MetadataKind;
            Collection<MethodDefinition> redirectedMethods = null;
            Collection<KeyValuePair<InterfaceImplementation, InterfaceImplementation>> redirectedInterfaces = null;

            if (type.IsWindowsRuntime)
            {
                if (metadata_kind == MetadataKind.WindowsMetadata)
                {
                    treatment = GetWellKnownTypeDefinitionTreatment(type);
                    if (treatment != TypeDefinitionTreatment.None)
                    {
                        ApplyProjection(type, new(type, treatment, redirectedMethods, redirectedInterfaces));
                        return;
                    }

                    TypeReference base_type = type.BaseType;
                    if (base_type != null && IsAttribute(base_type))
                    {
                        treatment = TypeDefinitionTreatment.NormalAttribute;
                    }
                    else
                    {
                        treatment = GenerateRedirectionInformation(type, out redirectedMethods, out redirectedInterfaces);
                    }
                }
                else if (metadata_kind == MetadataKind.ManagedWindowsMetadata && NeedsWindowsRuntimePrefix(type))
                {
                    treatment = TypeDefinitionTreatment.PrefixWindowsRuntimeName;
                }

                if (treatment == TypeDefinitionTreatment.PrefixWindowsRuntimeName || treatment == TypeDefinitionTreatment.NormalType)
                    if (!type.IsInterface && HasAttribute(type, "Windows.UI.Xaml", "TreatAsAbstractComposableClassAttribute"))
                        treatment |= TypeDefinitionTreatment.Abstract;
            }
            else if (metadata_kind == MetadataKind.ManagedWindowsMetadata && IsClrImplementationType(type))
            {
                treatment = TypeDefinitionTreatment.UnmangleWindowsRuntimeName;
            }

            if (treatment != TypeDefinitionTreatment.None)
                ApplyProjection(type, new(type, treatment, redirectedMethods, redirectedInterfaces));
        }

        private static TypeDefinitionTreatment GetWellKnownTypeDefinitionTreatment(TypeDefinition type)
        {
            ProjectionInfo info;
            if (!Projections.TryGetValue(type.Name, out info))
                return TypeDefinitionTreatment.None;

            TypeDefinitionTreatment treatment = info.Attribute ? TypeDefinitionTreatment.RedirectToClrAttribute : TypeDefinitionTreatment.RedirectToClrType;

            if (type.Namespace == info.ClrNamespace)
                return treatment;

            if (type.Namespace == info.WinRTNamespace)
                return treatment | TypeDefinitionTreatment.Internal;

            return TypeDefinitionTreatment.None;
        }

        private static TypeDefinitionTreatment GenerateRedirectionInformation(TypeDefinition type, out Collection<MethodDefinition> redirectedMethods, out Collection<KeyValuePair<InterfaceImplementation, InterfaceImplementation>> redirectedInterfaces)
        {
            bool implementsProjectedInterface = false;
            redirectedMethods = null;
            redirectedInterfaces = null;

            foreach (InterfaceImplementation implementedInterface in type.Interfaces)
            {
                if (IsRedirectedType(implementedInterface.InterfaceType))
                {
                    implementsProjectedInterface = true;
                    break;
                }
            }

            if (!implementsProjectedInterface)
                return TypeDefinitionTreatment.NormalType;

            HashSet<TypeReference> allImplementedInterfaces = new(new TypeReferenceEqualityComparer());
            redirectedMethods = new();
            redirectedInterfaces = new();

            foreach (InterfaceImplementation @interface in type.Interfaces)
            {
                TypeReference interfaceType = @interface.InterfaceType;

                if (IsRedirectedType(interfaceType))
                {
                    allImplementedInterfaces.Add(interfaceType);
                    CollectImplementedInterfaces(interfaceType, allImplementedInterfaces);
                }
            }

            foreach (InterfaceImplementation implementedInterface in type.Interfaces)
            {
                TypeReference interfaceType = implementedInterface.InterfaceType;
                if (IsRedirectedType(implementedInterface.InterfaceType))
                {
                    TypeReference etype = interfaceType.GetElementType();
                    TypeReference unprojectedType = new(etype.Namespace, etype.Name, etype.Module, etype.Scope)
                    {
                        DeclaringType = etype.DeclaringType,
                        projection = etype.projection
                    };

                    RemoveProjection(unprojectedType);

                    GenericInstanceType genericInstanceType = interfaceType as GenericInstanceType;
                    if (genericInstanceType != null)
                    {
                        GenericInstanceType genericUnprojectedType = new(unprojectedType);
                        foreach (TypeReference genericArgument in genericInstanceType.GenericArguments)
                            genericUnprojectedType.GenericArguments.Add(genericArgument);

                        unprojectedType = genericUnprojectedType;
                    }

                    InterfaceImplementation unprojectedInterface = new(unprojectedType);
                    redirectedInterfaces.Add(new(implementedInterface, unprojectedInterface));
                }
            }

            // Interfaces don't inherit methods of the interfaces they implement
            if (!type.IsInterface)
            {
                foreach (TypeReference implementedInterface in allImplementedInterfaces)
                {
                    RedirectInterfaceMethods(implementedInterface, redirectedMethods);
                }
            }

            return TypeDefinitionTreatment.RedirectImplementedMethods;
        }

        private static void CollectImplementedInterfaces(TypeReference type, HashSet<TypeReference> results)
        {
            TypeResolver typeResolver = TypeResolver.For(type);
            TypeDefinition typeDef = type.Resolve();

            foreach (InterfaceImplementation implementedInterface in typeDef.Interfaces)
            {
                TypeReference interfaceType = typeResolver.Resolve(implementedInterface.InterfaceType);
                results.Add(interfaceType);
                CollectImplementedInterfaces(interfaceType, results);
            }
        }

        private static void RedirectInterfaceMethods(TypeReference interfaceType, Collection<MethodDefinition> redirectedMethods)
        {
            TypeResolver typeResolver = TypeResolver.For(interfaceType);
            TypeDefinition typeDef = interfaceType.Resolve();

            foreach (MethodDefinition method in typeDef.Methods)
            {
                MethodDefinition redirectedMethod = new(method.Name, MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.NewSlot, typeResolver.Resolve(method.ReturnType));
                redirectedMethod.ImplAttributes = MethodImplAttributes.Runtime;

                foreach (ParameterDefinition parameter in method.Parameters)
                {
                    redirectedMethod.Parameters.Add(new(parameter.Name, parameter.Attributes, typeResolver.Resolve(parameter.ParameterType)));
                }

                redirectedMethod.Overrides.Add(typeResolver.Resolve(method));
                redirectedMethods.Add(redirectedMethod);
            }
        }

        private static bool IsRedirectedType(TypeReference type)
        {
            TypeReferenceProjection typeRefProjection = type.GetElementType().projection as TypeReferenceProjection;
            return typeRefProjection != null && typeRefProjection.Treatment == TypeReferenceTreatment.UseProjectionInfo;
        }

        private static bool NeedsWindowsRuntimePrefix(TypeDefinition type)
        {
            if ((type.Attributes & (TypeAttributes.VisibilityMask | TypeAttributes.Interface)) != TypeAttributes.Public)
                return false;

            TypeReference base_type = type.BaseType;
            if (base_type == null || base_type.MetadataToken.TokenType != TokenType.TypeRef)
                return false;

            if (base_type.Namespace == "System")
                switch (base_type.Name)
                {
                    case "Attribute":
                    case "MulticastDelegate":
                    case "ValueType":
                        return false;
                }

            return true;
        }

        public static bool IsClrImplementationType(TypeDefinition type)
        {
            if ((type.Attributes & (TypeAttributes.VisibilityMask | TypeAttributes.SpecialName)) != TypeAttributes.SpecialName)
                return false;
            return type.Name.StartsWith("<CLR>");
        }

        public static void ApplyProjection(TypeDefinition type, TypeDefinitionProjection projection)
        {
            if (projection == null)
                return;

            TypeDefinitionTreatment treatment = projection.Treatment;

            switch (treatment & TypeDefinitionTreatment.KindMask)
            {
                case TypeDefinitionTreatment.NormalType:
                    type.Attributes |= TypeAttributes.WindowsRuntime | TypeAttributes.Import;
                    break;

                case TypeDefinitionTreatment.NormalAttribute:
                    type.Attributes |= TypeAttributes.WindowsRuntime | TypeAttributes.Sealed;
                    break;

                case TypeDefinitionTreatment.UnmangleWindowsRuntimeName:
                    type.Attributes = (type.Attributes & ~TypeAttributes.SpecialName) | TypeAttributes.Public;
                    type.Name = type.Name.Substring("<CLR>".Length);
                    break;

                case TypeDefinitionTreatment.PrefixWindowsRuntimeName:
                    type.Attributes = (type.Attributes & ~TypeAttributes.Public) | TypeAttributes.Import;
                    type.Name = "<WinRT>" + type.Name;
                    break;

                case TypeDefinitionTreatment.RedirectToClrType:
                    type.Attributes = (type.Attributes & ~TypeAttributes.Public) | TypeAttributes.Import;
                    break;

                case TypeDefinitionTreatment.RedirectToClrAttribute:
                    type.Attributes = type.Attributes & ~TypeAttributes.Public;
                    break;

                case TypeDefinitionTreatment.RedirectImplementedMethods:
                {
                    type.Attributes |= TypeAttributes.WindowsRuntime | TypeAttributes.Import;

                    foreach (KeyValuePair<InterfaceImplementation, InterfaceImplementation> redirectedInterfacePair in projection.RedirectedInterfaces)
                    {
                        type.Interfaces.Add(redirectedInterfacePair.Value);

                        foreach (CustomAttribute customAttribute in redirectedInterfacePair.Key.CustomAttributes)
                            redirectedInterfacePair.Value.CustomAttributes.Add(customAttribute);

                        redirectedInterfacePair.Key.CustomAttributes.Clear();

                        foreach (MethodDefinition method in type.Methods)
                        {
                            foreach (MethodReference @override in method.Overrides)
                            {
                                if (TypeReferenceEqualityComparer.AreEqual(@override.DeclaringType, redirectedInterfacePair.Key.InterfaceType))
                                {
                                    @override.DeclaringType = redirectedInterfacePair.Value.InterfaceType;
                                }
                            }
                        }
                    }

                    foreach (MethodDefinition method in projection.RedirectedMethods)
                    {
                        type.Methods.Add(method);
                    }
                }
                    break;
            }

            if ((treatment & TypeDefinitionTreatment.Abstract) != 0)
                type.Attributes |= TypeAttributes.Abstract;

            if ((treatment & TypeDefinitionTreatment.Internal) != 0)
                type.Attributes &= ~TypeAttributes.Public;

            type.WindowsRuntimeProjection = projection;
        }

        public static TypeDefinitionProjection RemoveProjection(TypeDefinition type)
        {
            if (!type.IsWindowsRuntimeProjection)
                return null;

            TypeDefinitionProjection projection = type.WindowsRuntimeProjection;
            type.WindowsRuntimeProjection = null;

            type.Attributes = projection.Attributes;
            type.Name = projection.Name;

            if (projection.Treatment == TypeDefinitionTreatment.RedirectImplementedMethods)
            {
                foreach (MethodDefinition method in projection.RedirectedMethods)
                {
                    type.Methods.Remove(method);
                }

                foreach (KeyValuePair<InterfaceImplementation, InterfaceImplementation> redirectedInterfacePair in projection.RedirectedInterfaces)
                {
                    foreach (MethodDefinition method in type.Methods)
                    {
                        foreach (MethodReference @override in method.Overrides)
                        {
                            if (TypeReferenceEqualityComparer.AreEqual(@override.DeclaringType, redirectedInterfacePair.Value.InterfaceType))
                            {
                                @override.DeclaringType = redirectedInterfacePair.Key.InterfaceType;
                            }
                        }
                    }

                    foreach (CustomAttribute customAttribute in redirectedInterfacePair.Value.CustomAttributes)
                        redirectedInterfacePair.Key.CustomAttributes.Add(customAttribute);

                    redirectedInterfacePair.Value.CustomAttributes.Clear();
                    type.Interfaces.Remove(redirectedInterfacePair.Value);
                }
            }

            return projection;
        }

        public static void Project(TypeReference type)
        {
            TypeReferenceTreatment treatment;

            ProjectionInfo info;
            if (Projections.TryGetValue(type.Name, out info) && info.WinRTNamespace == type.Namespace)
                treatment = TypeReferenceTreatment.UseProjectionInfo;
            else
                treatment = GetSpecialTypeReferenceTreatment(type);

            if (treatment != TypeReferenceTreatment.None)
                ApplyProjection(type, new(type, treatment));
        }

        private static TypeReferenceTreatment GetSpecialTypeReferenceTreatment(TypeReference type)
        {
            if (type.Namespace == "System")
            {
                if (type.Name == "MulticastDelegate")
                    return TypeReferenceTreatment.SystemDelegate;
                if (type.Name == "Attribute")
                    return TypeReferenceTreatment.SystemAttribute;
            }

            return TypeReferenceTreatment.None;
        }

        private static bool IsAttribute(TypeReference type)
        {
            if (type.MetadataToken.TokenType != TokenType.TypeRef)
                return false;
            return type.Name == "Attribute" && type.Namespace == "System";
        }

        private static bool IsEnum(TypeReference type)
        {
            if (type.MetadataToken.TokenType != TokenType.TypeRef)
                return false;
            return type.Name == "Enum" && type.Namespace == "System";
        }

        public static void ApplyProjection(TypeReference type, TypeReferenceProjection projection)
        {
            if (projection == null)
                return;

            switch (projection.Treatment)
            {
                case TypeReferenceTreatment.SystemDelegate:
                case TypeReferenceTreatment.SystemAttribute:
                    type.Scope = type.Module.Projections.GetAssemblyReference("System.Runtime");
                    break;

                case TypeReferenceTreatment.UseProjectionInfo:
                    ProjectionInfo info = Projections[type.Name];
                    type.Name = info.ClrName;
                    type.Namespace = info.ClrNamespace;
                    type.Scope = type.Module.Projections.GetAssemblyReference(info.ClrAssembly);
                    break;
            }

            type.WindowsRuntimeProjection = projection;
        }

        public static TypeReferenceProjection RemoveProjection(TypeReference type)
        {
            if (!type.IsWindowsRuntimeProjection)
                return null;

            TypeReferenceProjection projection = type.WindowsRuntimeProjection;
            type.WindowsRuntimeProjection = null;

            type.Name = projection.Name;
            type.Namespace = projection.Namespace;
            type.Scope = projection.Scope;

            return projection;
        }

        public static void Project(MethodDefinition method)
        {
            MethodDefinitionTreatment treatment = MethodDefinitionTreatment.None;
            bool other = false;
            TypeDefinition declaring_type = method.DeclaringType;

            if (declaring_type.IsWindowsRuntime)
            {
                if (IsClrImplementationType(declaring_type))
                {
                    treatment = MethodDefinitionTreatment.None;
                }
                else if (declaring_type.IsNested)
                {
                    treatment = MethodDefinitionTreatment.None;
                }
                else if (declaring_type.IsInterface)
                {
                    treatment = MethodDefinitionTreatment.Runtime | MethodDefinitionTreatment.InternalCall;
                }
                else if (declaring_type.Module.MetadataKind == MetadataKind.ManagedWindowsMetadata && !method.IsPublic)
                {
                    treatment = MethodDefinitionTreatment.None;
                }
                else
                {
                    other = true;

                    TypeReference base_type = declaring_type.BaseType;
                    if (base_type != null && base_type.MetadataToken.TokenType == TokenType.TypeRef)
                    {
                        switch (GetSpecialTypeReferenceTreatment(base_type))
                        {
                            case TypeReferenceTreatment.SystemDelegate:
                                treatment = MethodDefinitionTreatment.Runtime | MethodDefinitionTreatment.Public;
                                other = false;
                                break;

                            case TypeReferenceTreatment.SystemAttribute:
                                treatment = MethodDefinitionTreatment.Runtime | MethodDefinitionTreatment.InternalCall;
                                other = false;
                                break;
                        }
                    }
                }
            }

            if (other)
            {
                bool seen_redirected = false;
                bool seen_non_redirected = false;

                foreach (MethodReference @override in method.Overrides)
                {
                    if (@override.MetadataToken.TokenType == TokenType.MemberRef && ImplementsRedirectedInterface(@override))
                    {
                        seen_redirected = true;
                    }
                    else
                    {
                        seen_non_redirected = true;
                    }
                }

                if (seen_redirected && !seen_non_redirected)
                {
                    treatment = MethodDefinitionTreatment.Runtime | MethodDefinitionTreatment.InternalCall | MethodDefinitionTreatment.Private;
                    other = false;
                }
            }

            if (other)
                treatment |= GetMethodDefinitionTreatmentFromCustomAttributes(method);

            if (treatment != MethodDefinitionTreatment.None)
                ApplyProjection(method, new(method, treatment));
        }

        private static MethodDefinitionTreatment GetMethodDefinitionTreatmentFromCustomAttributes(MethodDefinition method)
        {
            MethodDefinitionTreatment treatment = MethodDefinitionTreatment.None;

            foreach (CustomAttribute attribute in method.CustomAttributes)
            {
                TypeReference type = attribute.AttributeType;
                if (type.Namespace != "Windows.UI.Xaml")
                    continue;
                if (type.Name == "TreatAsPublicMethodAttribute")
                    treatment |= MethodDefinitionTreatment.Public;
                else if (type.Name == "TreatAsAbstractMethodAttribute")
                    treatment |= MethodDefinitionTreatment.Abstract;
            }

            return treatment;
        }

        public static void ApplyProjection(MethodDefinition method, MethodDefinitionProjection projection)
        {
            if (projection == null)
                return;

            MethodDefinitionTreatment treatment = projection.Treatment;

            if ((treatment & MethodDefinitionTreatment.Abstract) != 0)
                method.Attributes |= MethodAttributes.Abstract;

            if ((treatment & MethodDefinitionTreatment.Private) != 0)
                method.Attributes = (method.Attributes & ~MethodAttributes.MemberAccessMask) | MethodAttributes.Private;

            if ((treatment & MethodDefinitionTreatment.Public) != 0)
                method.Attributes = (method.Attributes & ~MethodAttributes.MemberAccessMask) | MethodAttributes.Public;

            if ((treatment & MethodDefinitionTreatment.Runtime) != 0)
                method.ImplAttributes |= MethodImplAttributes.Runtime;

            if ((treatment & MethodDefinitionTreatment.InternalCall) != 0)
                method.ImplAttributes |= MethodImplAttributes.InternalCall;

            method.WindowsRuntimeProjection = projection;
        }

        public static MethodDefinitionProjection RemoveProjection(MethodDefinition method)
        {
            if (!method.IsWindowsRuntimeProjection)
                return null;

            MethodDefinitionProjection projection = method.WindowsRuntimeProjection;
            method.WindowsRuntimeProjection = null;

            method.Attributes = projection.Attributes;
            method.ImplAttributes = projection.ImplAttributes;
            method.Name = projection.Name;

            return projection;
        }

        public static void Project(FieldDefinition field)
        {
            FieldDefinitionTreatment treatment = FieldDefinitionTreatment.None;
            TypeDefinition declaring_type = field.DeclaringType;

            if (declaring_type.Module.MetadataKind == MetadataKind.WindowsMetadata && field.IsRuntimeSpecialName && field.Name == "value__")
            {
                TypeReference base_type = declaring_type.BaseType;
                if (base_type != null && IsEnum(base_type))
                    treatment = FieldDefinitionTreatment.Public;
            }

            if (treatment != FieldDefinitionTreatment.None)
                ApplyProjection(field, new(field, treatment));
        }

        public static void ApplyProjection(FieldDefinition field, FieldDefinitionProjection projection)
        {
            if (projection == null)
                return;

            if (projection.Treatment == FieldDefinitionTreatment.Public)
                field.Attributes = (field.Attributes & ~FieldAttributes.FieldAccessMask) | FieldAttributes.Public;

            field.WindowsRuntimeProjection = projection;
        }

        public static FieldDefinitionProjection RemoveProjection(FieldDefinition field)
        {
            if (!field.IsWindowsRuntimeProjection)
                return null;

            FieldDefinitionProjection projection = field.WindowsRuntimeProjection;
            field.WindowsRuntimeProjection = null;

            field.Attributes = projection.Attributes;

            return projection;
        }

        private static bool ImplementsRedirectedInterface(MemberReference member)
        {
            TypeReference declaring_type = member.DeclaringType;
            TypeReference type;
            switch (declaring_type.MetadataToken.TokenType)
            {
                case TokenType.TypeRef:
                    type = declaring_type;
                    break;

                case TokenType.TypeSpec:
                    if (!declaring_type.IsGenericInstance)
                        return false;

                    type = ((TypeSpecification)declaring_type).ElementType;
                    if (type.MetadataType != MetadataType.Class || type.MetadataToken.TokenType != TokenType.TypeRef)
                        return false;

                    break;

                default:
                    return false;
            }

            TypeReferenceProjection projection = RemoveProjection(type);

            bool found = false;

            ProjectionInfo info;
            if (Projections.TryGetValue(type.Name, out info) && type.Namespace == info.WinRTNamespace)
            {
                found = true;
            }

            ApplyProjection(type, projection);

            return found;
        }

        public void AddVirtualReferences(Collection<AssemblyNameReference> references)
        {
            AssemblyNameReference corlib = GetCoreLibrary(references);
            corlib_version = corlib.Version;
            corlib.Version = version;

            if (virtual_references == null)
            {
                AssemblyNameReference[] winrt_references = GetAssemblyReferences(corlib);
                Interlocked.CompareExchange(ref virtual_references, winrt_references, null);
            }

            foreach (AssemblyNameReference reference in virtual_references)
                references.Add(reference);
        }

        public void RemoveVirtualReferences(Collection<AssemblyNameReference> references)
        {
            AssemblyNameReference corlib = GetCoreLibrary(references);
            corlib.Version = corlib_version;

            foreach (AssemblyNameReference reference in VirtualReferences)
                references.Remove(reference);
        }

        private static AssemblyNameReference[] GetAssemblyReferences(AssemblyNameReference corlib)
        {
            AssemblyNameReference system_runtime = new("System.Runtime", version);
            AssemblyNameReference system_runtime_interopservices_windowsruntime = new("System.Runtime.InteropServices.WindowsRuntime", version);
            AssemblyNameReference system_objectmodel = new("System.ObjectModel", version);
            AssemblyNameReference system_runtime_windowsruntime = new("System.Runtime.WindowsRuntime", version);
            AssemblyNameReference system_runtime_windowsruntime_ui_xaml = new("System.Runtime.WindowsRuntime.UI.Xaml", version);
            AssemblyNameReference system_numerics_vectors = new("System.Numerics.Vectors", version);

            if (corlib.HasPublicKey)
            {
                system_runtime_windowsruntime.PublicKey = system_runtime_windowsruntime_ui_xaml.PublicKey = corlib.PublicKey;

                system_runtime.PublicKey = system_runtime_interopservices_windowsruntime.PublicKey = system_objectmodel.PublicKey = system_numerics_vectors.PublicKey = contract_pk;
            }
            else
            {
                system_runtime_windowsruntime.PublicKeyToken = system_runtime_windowsruntime_ui_xaml.PublicKeyToken = corlib.PublicKeyToken;

                system_runtime.PublicKeyToken = system_runtime_interopservices_windowsruntime.PublicKeyToken = system_objectmodel.PublicKeyToken = system_numerics_vectors.PublicKeyToken = contract_pk_token;
            }

            return new[]
            {
                system_runtime,
                system_runtime_interopservices_windowsruntime,
                system_objectmodel,
                system_runtime_windowsruntime,
                system_runtime_windowsruntime_ui_xaml,
                system_numerics_vectors
            };
        }

        private static AssemblyNameReference GetCoreLibrary(Collection<AssemblyNameReference> references)
        {
            foreach (AssemblyNameReference reference in references)
                if (reference.Name == "mscorlib")
                    return reference;

            throw new BadImageFormatException("Missing mscorlib reference in AssemblyRef table.");
        }

        private AssemblyNameReference GetAssemblyReference(string name)
        {
            foreach (AssemblyNameReference assembly in VirtualReferences)
                if (assembly.Name == name)
                    return assembly;

            throw new();
        }

        public static void Project(ICustomAttributeProvider owner, CustomAttribute attribute)
        {
            if (!IsWindowsAttributeUsageAttribute(owner, attribute))
                return;

            CustomAttributeValueTreatment treatment = CustomAttributeValueTreatment.None;
            TypeDefinition type = (TypeDefinition)owner;

            if (type.Namespace == "Windows.Foundation.Metadata")
            {
                if (type.Name == "VersionAttribute")
                    treatment = CustomAttributeValueTreatment.VersionAttribute;
                else if (type.Name == "DeprecatedAttribute")
                    treatment = CustomAttributeValueTreatment.DeprecatedAttribute;
            }

            if (treatment == CustomAttributeValueTreatment.None)
            {
                bool multiple = HasAttribute(type, "Windows.Foundation.Metadata", "AllowMultipleAttribute");
                treatment = multiple ? CustomAttributeValueTreatment.AllowMultiple : CustomAttributeValueTreatment.AllowSingle;
            }

            if (treatment != CustomAttributeValueTreatment.None)
            {
                AttributeTargets attribute_targets = (AttributeTargets)attribute.ConstructorArguments[0].Value;
                ApplyProjection(attribute, new(attribute_targets, treatment));
            }
        }

        private static bool IsWindowsAttributeUsageAttribute(ICustomAttributeProvider owner, CustomAttribute attribute)
        {
            if (owner.MetadataToken.TokenType != TokenType.TypeDef)
                return false;

            MethodReference constructor = attribute.Constructor;

            if (constructor.MetadataToken.TokenType != TokenType.MemberRef)
                return false;

            TypeReference declaring_type = constructor.DeclaringType;

            if (declaring_type.MetadataToken.TokenType != TokenType.TypeRef)
                return false;

            // declaring type is already projected
            return declaring_type.Name == "AttributeUsageAttribute" && declaring_type.Namespace == /*"Windows.Foundation.Metadata"*/"System";
        }

        private static bool HasAttribute(TypeDefinition type, string @namespace, string name)
        {
            foreach (CustomAttribute attribute in type.CustomAttributes)
            {
                TypeReference attribute_type = attribute.AttributeType;
                if (attribute_type.Name == name && attribute_type.Namespace == @namespace)
                    return true;
            }
            return false;
        }

        public static void ApplyProjection(CustomAttribute attribute, CustomAttributeValueProjection projection)
        {
            if (projection == null)
                return;

            bool version_or_deprecated;
            bool multiple;

            switch (projection.Treatment)
            {
                case CustomAttributeValueTreatment.AllowSingle:
                    version_or_deprecated = false;
                    multiple = false;
                    break;

                case CustomAttributeValueTreatment.AllowMultiple:
                    version_or_deprecated = false;
                    multiple = true;
                    break;

                case CustomAttributeValueTreatment.VersionAttribute:
                case CustomAttributeValueTreatment.DeprecatedAttribute:
                    version_or_deprecated = true;
                    multiple = true;
                    break;

                default:
                    throw new ArgumentException();
            }

            AttributeTargets attribute_targets = (AttributeTargets)attribute.ConstructorArguments[0].Value;
            if (version_or_deprecated)
                attribute_targets |= AttributeTargets.Constructor | AttributeTargets.Property;
            attribute.ConstructorArguments[0] = new(attribute.ConstructorArguments[0].Type, attribute_targets);

            attribute.Properties.Add(new("AllowMultiple", new(attribute.Module.TypeSystem.Boolean, multiple)));

            attribute.projection = projection;
        }

        public static CustomAttributeValueProjection RemoveProjection(CustomAttribute attribute)
        {
            if (attribute.projection == null)
                return null;

            CustomAttributeValueProjection projection = attribute.projection;
            attribute.projection = null;

            attribute.ConstructorArguments[0] = new(attribute.ConstructorArguments[0].Type, projection.Targets);
            attribute.Properties.Clear();

            return projection;
        }
    }
}