private TypeDefinition CreateRepositoryType(TypeDefinition repoTypeDef,
            TypeCodeGenInfo rti,
            MethodAttributes methodAttributes,
            string repoTypeNameFormat,
            TypeAttributes typeAttributes,
            bool isImplementation,
            IEnumerable<TypeReference> interfacesToImplement = null)
        {
            var tt = (ResourceType)rti.TransformedType;

            repoTypeDef.Namespace = this.assemblyName;
            repoTypeDef.Name = string.Format(repoTypeNameFormat, rti.TransformedType.Name);
            repoTypeDef.Attributes = typeAttributes;

            if (isImplementation)
                repoTypeDef.BaseType = rti.CustomRepositoryBaseTypeReference;

            repoTypeDef.Interfaces.AddRange(interfacesToImplement ?? Enumerable.Empty<TypeReference>());
            var baseTypeGenericDef = rti.CustomRepositoryBaseTypeDefinition;
            var baseTypeGenericArgs = rti.CustomRepositoryBaseTypeReference.GenericArguments.ToArray();

            foreach (var subType in tt.MergedTypes.Concat(tt))
            {
                if (subType.PostAllowed)
                {
                    AddRepositoryFormPostMethod(methodAttributes,
                        isImplementation,
                        subType,
                        repoTypeDef,
                        baseTypeGenericDef,
                        baseTypeGenericArgs);
                }
            }
            if (tt.PrimaryId != null)
            {
                AddRepositoryGetByIdMethod(rti,
                    methodAttributes,
                    isImplementation,
                    tt,
                    repoTypeDef,
                    baseTypeGenericDef,
                    baseTypeGenericArgs,
                    "Get");
                AddRepositoryGetByIdMethod(rti,
                    methodAttributes,
                    isImplementation,
                    tt,
                    repoTypeDef,
                    baseTypeGenericDef,
                    baseTypeGenericArgs,
                    "GetLazy");
            }

            // Constructor
            if (isImplementation)
            {
                var baseCtor = baseTypeGenericDef.GetConstructors().First();
                var baseCtorRef =
                    Import(baseCtor).MakeHostInstanceGeneric(baseTypeGenericArgs);

                var ctor = new MethodDefinition(
                    ".ctor",
                    MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName
                    | MethodAttributes.Public,
                    VoidTypeRef);

                baseCtor.Parameters.Select(x => new ParameterDefinition(x.Name, x.Attributes, Import(x.ParameterType)))
                    .AddTo(ctor.Parameters);

                ctor.Body.MaxStackSize = 8;
                var ctorIlProcessor = ctor.Body.GetILProcessor();
                ctorIlProcessor.Append(Instruction.Create(OpCodes.Ldarg_0));
                foreach (var ctorParam in ctor.Parameters)
                    ctorIlProcessor.Append(Instruction.Create(OpCodes.Ldarg, ctorParam));
                ctorIlProcessor.Append(Instruction.Create(OpCodes.Call, baseCtorRef));
                ctorIlProcessor.Append(Instruction.Create(OpCodes.Ret));
                repoTypeDef.Methods.Add(ctor);
            }

            if (!this.module.Types.Contains(repoTypeDef))
                this.module.Types.Add(repoTypeDef);
            return repoTypeDef;
        }
        private void CreateRepositoryInterfaceAndImplementation(TypeCodeGenInfo resourceTypeInfo)
        {
            var queryableRepoType =
                Import(typeof(IQueryableRepository<>))
                    .MakeGenericInstanceType(resourceTypeInfo.InterfaceType);

            var interfacesToImplement = new List<TypeReference> { queryableRepoType };

            var tt = resourceTypeInfo.TransformedType as ResourceType;
            if (tt.PatchAllowed || tt.MergedTypes.Any(x => x.PatchAllowed))
            {
                interfacesToImplement.Add(
                    Import(typeof(IPatchableRepository<>))
                        .MakeGenericInstanceType(resourceTypeInfo.InterfaceType));
            }

            if (tt.PostAllowed ||
                tt.MergedTypes.Any(x => x.PostAllowed))
            {
                interfacesToImplement.Add(
                    Import(typeof(IPostableRepository<,>))
                        .MakeGenericInstanceType(resourceTypeInfo.InterfaceType,
                            this.clientTypeInfoDict[tt.PostReturnType]
                                .InterfaceType));
            }

            var repoInterface = CreateRepositoryType(resourceTypeInfo.CustomRepositoryInterface,
                resourceTypeInfo,
                MethodAttributes.Abstract |
                MethodAttributes.HideBySig |
                MethodAttributes.NewSlot |
                MethodAttributes.Virtual |
                MethodAttributes.Public,
                "I{0}Repository",
                TypeAttributes.Interface | TypeAttributes.Public |
                TypeAttributes.Abstract,
                false,
                interfacesToImplement);

            var repoImplementation = CreateRepositoryType(new TypeDefinition(null, null, 0),
                resourceTypeInfo,
                MethodAttributes.NewSlot |
                MethodAttributes.HideBySig
                | MethodAttributes.Virtual |
                MethodAttributes.Public,
                "{0}Repository",
                TypeAttributes.Public,
                true,
                repoInterface.WrapAsEnumerable());
        }
        private TypeDefinition CreateProxy(ProxyBuilder proxyBuilder,
            Action<TypeCodeGenInfo, TypeDefinition> onTypeGenerated,
            TypeCodeGenInfo typeInfo,
            Dictionary<TypeCodeGenInfo, TypeDefinition> generatedTypeDict)
        {
            var targetType = typeInfo.TransformedType;
            var name = targetType.Name;

            TypeDefinition baseTypeDef = null;
            var tt = typeInfo.TransformedType;
            var rt = typeInfo.TransformedType as ResourceType;
            if (rt != null && rt.UriBaseType != null && rt.UriBaseType != rt)
            {
                var baseTypeInfo = this.clientTypeInfoDict[tt.BaseType];
                baseTypeDef = generatedTypeDict.GetOrCreate(baseTypeInfo,
                    () =>
                        CreateProxy(proxyBuilder,
                            onTypeGenerated,
                            baseTypeInfo,
                            generatedTypeDict));
            }
            var proxyType = proxyBuilder.CreateProxyType(name, typeInfo.InterfaceType.WrapAsEnumerable(), baseTypeDef);

            if (onTypeGenerated != null)
                onTypeGenerated(typeInfo, proxyType);

            return proxyType;
        }
        private void BuildInterfacesAndPocoTypes(IEnumerable<TransformedType> transformedTypes)
        {
            var resourceBaseRef = Import(typeof(ResourceBase));
            var resourceInterfaceRef = Import(typeof(IClientResource));

            var resourceBaseCtor =
                Import(
                    resourceBaseRef.Resolve().GetConstructors().First(x => !x.IsStatic && x.Parameters.Count == 0));
            foreach (var transformedType in transformedTypes)
            {
                var typeInfo = new TypeCodeGenInfo(this, transformedType);
                this.clientTypeInfoDict[transformedType] = typeInfo;

                typeInfo.InterfaceType.Namespace = this.assemblyName;

                var pocoDef = new TypeDefinition(
                    this.assemblyName,
                    transformedType.Name + "Resource",
                    TypeAttributes.Public);

                typeInfo.PocoType = pocoDef;

                // Empty public constructor
                var ctor = new MethodDefinition(
                    ".ctor",
                    MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName
                    | MethodAttributes.Public,
                    this.module.TypeSystem.Void);

                typeInfo.EmptyPocoCtor = ctor;
                pocoDef.Methods.Add(ctor);

                this.module.Types.Add(typeInfo.InterfaceType);
                this.module.Types.Add(pocoDef);
            }

            foreach (var kvp in this.clientTypeInfoDict)
            {
                var type = (TransformedType)kvp.Key;
                var typeInfo = kvp.Value;
                var pocoDef = typeInfo.PocoType;
                var interfaceDef = typeInfo.InterfaceType;
                var classMapping = type;

                // Implement interfaces

                pocoDef.Interfaces.Add(interfaceDef);

                // Inherit correct base class

                MethodReference baseCtorReference;

                if (type.BaseType != null && this.clientTypeInfoDict.ContainsKey(type.BaseType))
                {
                    var baseTypeInfo = this.clientTypeInfoDict[type.BaseType];
                    pocoDef.BaseType = baseTypeInfo.PocoType;

                    baseCtorReference = baseTypeInfo.PocoType.GetConstructors().First(x => x.Parameters.Count == 0);

                    interfaceDef.Interfaces.Add(baseTypeInfo.InterfaceType);
                    typeInfo.BaseType = baseTypeInfo.InterfaceType;
                }
                else
                {
                    interfaceDef.Interfaces.Add(resourceInterfaceRef);
                    pocoDef.BaseType = resourceBaseRef;
                    baseCtorReference = resourceBaseCtor;
                    typeInfo.BaseType = null;
                }

                typeInfo.UriBaseType =
                    type.Maybe()
                        .OfType<ResourceType>()
                        .Select(x => x.UriBaseType)
                        .Select(x => this.clientTypeInfoDict[x].InterfaceType)
                        .OrDefault();

                var ctorIlActions = new List<Action<ILProcessor>>();

                foreach (
                    var prop in
                        classMapping.Properties.Where(x => x.DeclaringType == classMapping))
                {
                    var propTypeRef = GetPropertyTypeReference(prop);

                    // For interface getters and setters
                    var interfacePropDef = AddInterfaceProperty(interfaceDef, prop.Name, propTypeRef);

                    if (prop.IsAttributesProperty)
                        AddAttribute(interfacePropDef, typeof(ResourceAttributesPropertyAttribute));
                    if (prop.IsEtagProperty)
                        AddAttribute(interfacePropDef, typeof(ResourceEtagPropertyAttribute));
                    if (prop.IsPrimaryKey)
                        AddAttribute(interfacePropDef, typeof(ResourceIdPropertyAttribute));
                    AddAttribute(interfacePropDef, typeof(ResourcePropertyAttribute)).Properties.Add(
                        new CustomAttributeNamedArgument("AccessMode",
                            new CustomAttributeArgument(Import(typeof(HttpMethod)),
                                prop.AccessMode)));

                    FieldDefinition backingField;
                    AddAutomaticProperty(pocoDef, prop.Name, propTypeRef, out backingField);
                    if (!prop.ExposedAsRepository)
                        AddPropertyFieldInitialization(backingField, prop.PropertyType, ctorIlActions);
                }

                var ctor = typeInfo.EmptyPocoCtor;
                ctor.Body.MaxStackSize = 8;
                var ctorIlProcessor = ctor.Body.GetILProcessor();
                ctorIlProcessor.Append(Instruction.Create(OpCodes.Ldarg_0));
                ctorIlProcessor.Append(Instruction.Create(OpCodes.Call, baseCtorReference));
                foreach (var ilAction in ctorIlActions)
                    ilAction(ctorIlProcessor);
                ctorIlProcessor.Append(Instruction.Create(OpCodes.Ret));
            }
        }
        private void AddResourceInfoAttribute(TypeCodeGenInfo typeInfo)
        {
            var interfaceDef = typeInfo.InterfaceType;
            var type = typeInfo.TransformedType;
            var attr = Import(typeof(ResourceInfoAttribute));
            var methodDefinition =
                Import(attr.Resolve().Methods.First(x => x.IsConstructor && x.Parameters.Count == 0));
            var custAttr =
                new CustomAttribute(methodDefinition);
            var stringTypeReference = this.module.TypeSystem.String;
            custAttr.Properties.Add(
                new CustomAttributeNamedArgument(
                    "UrlRelativePath",
                    new CustomAttributeArgument(stringTypeReference,
                        type.Maybe().OfType<ResourceType>().Select(x => x.UriRelativePath).OrDefault())));

            var typeTypeReference = Import(typeof(Type));
            custAttr.Properties.Add(
                new CustomAttributeNamedArgument(
                    "PocoType",
                    new CustomAttributeArgument(typeTypeReference, typeInfo.PocoType)));
            custAttr.Properties.Add(
                new CustomAttributeNamedArgument(
                    "InterfaceType",
                    new CustomAttributeArgument(typeTypeReference, typeInfo.InterfaceType)));
            custAttr.Properties.Add(
                new CustomAttributeNamedArgument(
                    "LazyProxyType",
                    new CustomAttributeArgument(typeTypeReference, typeInfo.LazyProxyType)));
            custAttr.Properties.Add(
                new CustomAttributeNamedArgument(
                    "PostFormType",
                    new CustomAttributeArgument(typeTypeReference, typeInfo.PostFormType)));
            custAttr.Properties.Add(
                new CustomAttributeNamedArgument(
                    "PatchFormType",
                    new CustomAttributeArgument(typeTypeReference, typeInfo.PatchFormType)));

            custAttr.Properties.Add(
                new CustomAttributeNamedArgument(
                    "JsonTypeName",
                    new CustomAttributeArgument(stringTypeReference, type.Name)));

            custAttr.Properties.Add(
                new CustomAttributeNamedArgument(
                    "UriBaseType",
                    new CustomAttributeArgument(typeTypeReference, typeInfo.UriBaseType)));

            var resourceType = typeInfo.TransformedType as ResourceType;
            if (resourceType != null && resourceType.ParentResourceType != null)
            {
                var parentResourceTypeInfo = this.clientTypeInfoDict[resourceType.ParentResourceType];
                custAttr.Properties.Add(
                    new CustomAttributeNamedArgument("ParentResourceType",
                        new CustomAttributeArgument(typeTypeReference, parentResourceTypeInfo.InterfaceType)));
            }

            if (typeInfo.BaseType != null)
            {
                custAttr.Properties.Add(
                    new CustomAttributeNamedArgument(
                        "BaseType",
                        new CustomAttributeArgument(typeTypeReference, typeInfo.BaseType)));
            }

            custAttr.Properties.Add(
                new CustomAttributeNamedArgument(
                    "IsValueObject",
                    new CustomAttributeArgument(this.module.TypeSystem.Boolean,
                        typeInfo.TransformedType.MappedAsValueObject)));

            interfaceDef.CustomAttributes.Add(custAttr);
            //var attrConstructor = attr.Resolve().GetConstructors();
        }
        private void AddRepositoryGetByIdMethod(TypeCodeGenInfo rti,
            MethodAttributes methodAttributes,
            bool isImplementation,
            TransformedType tt,
            TypeDefinition repoTypeDef,
            TypeDefinition baseTypeGenericDef,
            TypeReference[] baseTypeGenericArgs,
            string methodName)
        {
            var method = new MethodDefinition(methodName, methodAttributes, rti.InterfaceType);
            var idType = tt.PrimaryId.PropertyType;
            if (!(idType is TypeSpec))
                throw new NotSupportedException("Id needs to be a shared type.");
            var idTypeRef = Import(idType.Type);
            method.Parameters.Add(new ParameterDefinition(tt.PrimaryId.LowerCaseName,
                0,
                idTypeRef));
            repoTypeDef.Methods.Add(method);

            if (isImplementation)
            {
                var baseGetMethodRef =
                    Import(Import(typeof(ClientRepository<,>)).Resolve().Methods.First(x => x.Name == methodName))
                        .MakeHostInstanceGeneric(baseTypeGenericArgs);
                var ilproc = method.Body.GetILProcessor();

                ilproc.Emit(OpCodes.Ldarg_0);
                ilproc.Emit(OpCodes.Ldarg_1);
                if (idType.Type.IsValueType)
                    ilproc.Emit(OpCodes.Box, idTypeRef);
                else
                    ilproc.Emit(OpCodes.Castclass, idTypeRef);
                ilproc.Emit(OpCodes.Callvirt, baseGetMethodRef);
                ilproc.Emit(OpCodes.Ret);
            }
        }