static InlineCode WriteNodeProperties(
            CodeGenerationContext codeGenContext,
            List <NodeProperty> nodeProperties)
        {
            nodeProperties = nodeProperties.OrderBy(p => p.Name).ToList();
            var alwaysWrittenProperties = nodeProperties.Where(p => p.Conditional == null).ToList();
            var conditionalProperties   = nodeProperties.Where(p => p.Conditional != null).ToList();

            IEnumerable <AnyExpression> renderNode()
            {
                var sortedProperties = alwaysWrittenProperties.Concat(conditionalProperties).ToArray();

                // tricky code ahead. If there are always written props, we optimise by simply always writing the separator
                // except for the first.
                // If not, we generate the tracking var.
                if (alwaysWrittenProperties.Any())
                {
                    for (var index = 0; index < sortedProperties.Length; index++)
                    {
                        yield return(sortedProperties[index].ToCode(index == 0
              ? null
              : new InlineCode(codeGenContext.JsonWriter.WriteValueSeparator())));
                    }
                }
                else
                {
                    var propAlreadyWritten = New.Var <bool>("propAlreadyWritten");
                    yield return(propAlreadyWritten);

                    yield return(propAlreadyWritten.Assign(false));

                    var separator = new InlineCode(
                        If.ThenElse(
                            propAlreadyWritten.EqualTo(true),
                            Expression.Block(codeGenContext.JsonWriter.WriteValueSeparator()),
                            propAlreadyWritten.Assign(true))
                        );
                    foreach (var nodeProp in sortedProperties)
                    {
                        yield return(nodeProp.ToCode(separator));
                    }
                }
            }

            return(new InlineCode(renderNode()));
        }
        static IEnumerable <AnyExpression> RendererBlock(CompilerContext compilerContext)
        {
            var resourceIn = New.Parameter <object>("resource");
            var options    = New.Parameter <SerializationContext>("options");
            var stream     = New.Parameter <Stream>("stream");

            yield return(resourceIn);

            yield return(options);

            yield return(stream);

            var retVal = New.Var <Task>("retVal");

            var resource = Expression.Variable(compilerContext.Resource.ResourceType, "typedResource");

            yield return(resource);

            yield return(Expression.Assign(resource, Expression.Convert(resourceIn, compilerContext.Resource.ResourceType)));

            var jsonWriter = New.Var <JsonWriter>("jsonWriter");
            var buffer     = New.Var <ArraySegment <byte> >("buffer");

            yield return(jsonWriter);

            yield return(buffer);

            yield return(Expression.Assign(jsonWriter, Expression.New(typeof(JsonWriter))));

            var jsonFormatterResolver = New.Var <HydraJsonFormatterResolver>("resolver");
            var assignResolver        = jsonFormatterResolver.Assign(New.Instance <HydraJsonFormatterResolver>());

            yield return(jsonFormatterResolver);

            yield return(assignResolver);

            var codeGenContext = new CodeGenerationContext(jsonWriter, resource, options, jsonFormatterResolver);

            yield return(CodeGenerator.ResourceDocument(compilerContext, codeGenContext));

            yield return(Expression.Assign(buffer, jsonWriter.GetBuffer()));

            yield return(Expression.Assign(retVal, stream.WriteAsync(buffer)));

            yield return(retVal);
        }
        static NodeProperty WriteNodeLink(
            CodeGenerationContext codeGenContext,
            string linkRelationship,
            Uri linkUri,
            TypedExpression <string> resourceUri,
            ResourceLinkModel link)
        {
            var jsonWriter = codeGenContext.JsonWriter;

            IEnumerable <AnyExpression> getNodeLink()
            {
                yield return(jsonWriter.WritePropertyName(linkRelationship));

                yield return(jsonWriter.WriteBeginObject());

                yield return(jsonWriter.WriteRaw(Nodes.IdProperty));

                var uriCombinationMethodInfo = link.CombinationType == ResourceLinkCombination.SubResource
          ? Reflection.HydraTextExtensions.UriSubResourceCombine
          : Reflection.HydraTextExtensions.UriStandardCombine;

                var uriCombine = new MethodCall <string>(Expression.Call(
                                                             uriCombinationMethodInfo,
                                                             resourceUri,
                                                             Expression.Constant(linkUri, typeof(Uri))));

                yield return(jsonWriter.WriteString(uriCombine));

                if (link.Type != null)
                {
                    yield return(jsonWriter.WriteValueSeparator());

                    yield return(jsonWriter.WritePropertyName("@type"));

                    yield return(jsonWriter.WriteString(link.Type));
                }

                yield return(jsonWriter.WriteEndObject());
            }

            return(new NodeProperty(linkRelationship)
            {
                Code = new InlineCode(getNodeLink())
            });
        }
        public static CodeBlock ResourceDocument(
            CompilerContext compilerContext,
            CodeGenerationContext codeGenContext)
        {
            var contextUri = StringMethods.Concat(codeGenContext.BaseUri, New.Const(CompilerContext.ContextUri));

            var rootNode = WriteNode(
                compilerContext,
                codeGenContext,
                new[]
            {
                WriteContext(codeGenContext.JsonWriter, contextUri)
            });

            return(new CodeBlock(
                       codeGenContext.JsonWriter.WriteBeginObject(),
                       rootNode,
                       codeGenContext.JsonWriter.WriteEndObject()
                       ));
        }
        static IEnumerable <NodeProperty> GetNodeProperties(CompilerContext compilerContext,
                                                            CodeGenerationContext codeGenContext,
                                                            TypedExpression <string> resourceUri)
        {
            var propNames = compilerContext.Resource
                            .Hydra().ResourceProperties
                            .Select(p => p.Name)
                            .ToArray();

            var generatedIdNode =
                propNames.All(name => name != "@id") && compilerContext.Resource.Uris.Any()
          ? new[] { WriteId(codeGenContext.JsonWriter, resourceUri) }
          : Enumerable.Empty <NodeProperty>();

            var generatedTypeNode = propNames.Any(name => name == "@type")
        ? Enumerable.Empty <NodeProperty>()
        : new[]
            {
                compilerContext.Resource.Hydra().JsonLdTypeFunc != null
            ? WriteType(codeGenContext.JsonWriter, codeGenContext.TypeGenerator.Invoke(codeGenContext.ResourceInstance))
            : WriteType(codeGenContext.JsonWriter, compilerContext.Resource.Hydra().JsonLdType)
            };

            var linkNodes = compilerContext.Resource.Links
                            .Select(link => WriteNodeLink(codeGenContext, link.Relationship, link.Uri, resourceUri, link));

            var valueNodes = compilerContext.Resource.Hydra()
                             .ResourceProperties
                             .Where(resProperty => resProperty.IsValueNode)
                             .Select(resProperty => CreateNodePropertyValue(codeGenContext, resProperty, codeGenContext.ResourceInstance))
                             .ToList();

            var propNodes = compilerContext.Resource.Hydra()
                            .ResourceProperties
                            .Where(resProperty => !resProperty.IsValueNode)
                            .Select(resProperty => CreateNodeProperty(compilerContext, codeGenContext, resProperty))
                            .ToList();

            return(generatedIdNode.Concat(generatedTypeNode).Concat(propNodes).Concat(valueNodes).Concat(linkNodes).ToList());
        }
        static CodeBlock WriteNode(
            CompilerContext compilerContext,
            CodeGenerationContext codeGenContext,
            NodeProperty[] existingNodeProperties = null)
        {
            var resourceUri = codeGenContext.UriGenerator.Invoke(codeGenContext.ResourceInstance);

            var nodeProperties =
                new List <NodeProperty>(existingNodeProperties ?? Enumerable.Empty <NodeProperty>());

            nodeProperties.AddRange(
                GetNodeProperties(compilerContext, codeGenContext,
                                  resourceUri));

            IEnumerable <AnyExpression> render()
            {
                if (compilerContext.Resource.Hydra().Collection.IsCollection&&
                    !compilerContext.Resource.Hydra().Collection.IsHydraCollectionType)
                {
                    var collectionItemType  = compilerContext.Resource.Hydra().Collection.ItemType;
                    var hydraCollectionType = HydraTypes.Collection.MakeGenericType(collectionItemType);
                    var collectionCtor      =
                        hydraCollectionType.GetConstructor(new[]
                                                           { typeof(IEnumerable <>).MakeGenericType(collectionItemType), typeof(string) });

                    if (collectionCtor == null)
                    {
                        throw new InvalidOperationException(
                                  $"Could not generate new HydraCollection<{collectionItemType.Name}>(IEnumerable<{collectionItemType.Name}>, string>()");
                    }

                    var collectionWrapper = Expression.Variable(hydraCollectionType, "hydraCollectionType");

                    yield return(collectionWrapper);

                    var instantiateCollection = Expression.Assign(
                        collectionWrapper,
                        Expression.New(collectionCtor, codeGenContext.ResourceInstance,
                                       Expression.Constant(compilerContext.Resource.Hydra().Collection.ManagesRdfTypeName)));
                    yield return(instantiateCollection);

                    var hydraCollectionModel = compilerContext.MetaModel.GetResourceModel(hydraCollectionType);

                    var hydraCollectionProperties = GetNodeProperties(
                        compilerContext.Push(hydraCollectionModel),
                        codeGenContext.Push(collectionWrapper), resourceUri).ToList();

                    var nodeTypeNode = nodeProperties.FirstOrDefault(x => x.Name == "@type");

                    if (nodeTypeNode != null && compilerContext.Resource.Hydra().Collection.IsFrameworkCollection)
                    {
                        nodeProperties.Clear();
                        nodeProperties.AddRange(hydraCollectionProperties);
                    }
                    else
                    {
                        hydraCollectionProperties.RemoveAll(n => n.Name == "@type");
                        nodeProperties.AddRange(hydraCollectionProperties);
                    }
                }

                if (nodeProperties.Any())
                {
                    yield return(WriteNodeProperties(codeGenContext, nodeProperties));
                }
            }

            return(new CodeBlock(render()));
        }
 static NodeProperty CreateNodePropertyAsList(
     CompilerContext compilerContext,
     CodeGenerationContext codeGenContext,
     ResourceProperty property,
     List <(Type itemType, List <ResourceModel> models)> itemResourceRegistrations, ParameterExpression propertyValue,
        static NodeProperty CreateNodeProperty(
            CompilerContext compilerContext,
            CodeGenerationContext codeGenContext,
            ResourceProperty property)
        {
            Expression resource = codeGenContext.ResourceInstance;

            if (codeGenContext.ResourceInstance != resource)
            {
                throw new InvalidOperationException("yo backtrack");
            }

            var pi = property.Member;

            // var propertyValue;
            Debug.Assert(pi.DeclaringType != null, "pi.DeclaringType != null");
            var propertyValue = Expression.Variable(pi.PropertyType, $"val{pi.DeclaringType.Name}{pi.Name}");

            // propertyValue = resource.Property;
            var propertyValueAssignment = Expression.Assign(propertyValue, Expression.MakeMemberAccess(resource, pi));

            var preamble = new InlineCode(new Expression[] { propertyValue, propertyValueAssignment });

            if (compilerContext.MetaModel.TryGetResourceModel(pi.PropertyType, out var propertyResourceModel))
            {
                var jsonPropertyName = property.Name;
                return(new NodeProperty(jsonPropertyName)
                {
                    Preamble = preamble,
                    Code = new InlineCode(new[]
                    {
                        codeGenContext.JsonWriter.WritePropertyName(jsonPropertyName),
                        codeGenContext.JsonWriter.WriteBeginObject(),
                        WriteNode(
                            compilerContext.Push(propertyResourceModel),
                            codeGenContext.Push(propertyValue)),
                        codeGenContext.JsonWriter.WriteEndObject()
                    }),
                    Conditional = Expression.NotEqual(propertyValue, Expression.Default(pi.PropertyType))
                });
            }

            var itemTypes = (from i in pi.PropertyType.GetInterfaces()
                             .Concat(pi.PropertyType.IsInterface ? new[] { pi.PropertyType } : Array.Empty <Type>())
                             where i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable <>)
                             let itemType = i.GetGenericArguments()[0]
                                            where itemType != typeof(object)
                                            select itemType).ToList();

            // not an iri node itself, but is it a list of nodes?
            var itemResourceRegistrations = (
                from itemType in itemTypes
                let resourceModels = compilerContext.MetaModel.ResourceRegistrations.Where(r =>
                                                                                           r.ResourceType != null && itemType.IsAssignableFrom(r.ResourceType)).ToList()
                                     where resourceModels.Any()
                                     orderby resourceModels.Count() descending
                                     select
                                     (
                    itemType,
                    (from possible in resourceModels
                     orderby possible.ResourceType.GetInheritanceDistance(itemType)
                     select possible).ToList()
                                     )).ToList <(Type itemType, List <ResourceModel> models)>();

            if (itemResourceRegistrations.Any())
            {
                return(CreateNodePropertyAsList(compilerContext, codeGenContext, property, itemResourceRegistrations,
                                                propertyValue, preamble));
            }

            // not a list of iri or blank nodes
            var propValue = CreateNodePropertyValue(codeGenContext, property, resource);

            propValue.Preamble    = preamble;
            propValue.Conditional = Expression.NotEqual(propertyValue, Expression.Default(pi.PropertyType));
            return(propValue);

            // it's a list of nodes
        }