﻿//----------------------
// <auto-generated>
// </auto-generated>
//----------------------


namespace MyNamespace
{
    #pragma warning disable // Disable all warnings

    public partial class Banana : Fruit
    {

        [System.Text.Json.Serialization.JsonPropertyName("Amember")]
        public string Amember { get; set; }

    }

    [JsonInheritanceConverter(typeof(Fruit), "discriminator")]
    [JsonInheritanceAttribute("Banana", typeof(Banana))]
    public partial class Fruit
    {

        [System.Text.Json.Serialization.JsonPropertyName("ID")]
        public long ID { get; set; }

    }

    public partial class MyContainer
    {

        [System.Text.Json.Serialization.JsonPropertyName("Item")]
        public Banana Item { get; set; }

    }

    [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Interface, AllowMultiple = true)]
    internal class JsonInheritanceAttribute : System.Attribute
    {
        public JsonInheritanceAttribute(string key, System.Type type)
        {
            Key = key;
            Type = type;
        }

        public string Key { get; }

        public System.Type Type { get; }
    }

    internal class JsonInheritanceConverterAttribute : System.Text.Json.Serialization.JsonConverterAttribute
    {
        public string DiscriminatorName { get; }

        public JsonInheritanceConverterAttribute(System.Type baseType, string discriminatorName = "discriminator")
            : base(typeof(JsonInheritanceConverter<>).MakeGenericType(baseType))
        {
            DiscriminatorName = discriminatorName;
        }
    }

    public class JsonInheritanceConverter<TBase> : System.Text.Json.Serialization.JsonConverter<TBase>
    {
        private readonly string _discriminatorName;

        public JsonInheritanceConverter()
        {
            var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute<JsonInheritanceConverterAttribute>(typeof(TBase));
            _discriminatorName = attribute?.DiscriminatorName ?? "discriminator";
        }

        public JsonInheritanceConverter(string discriminatorName)
        {
            _discriminatorName = discriminatorName;
        }

        public string DiscriminatorName { get { return _discriminatorName; } }

        public override TBase Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options)
        {
            var document = System.Text.Json.JsonDocument.ParseValue(ref reader);
            var hasDiscriminator = document.RootElement.TryGetProperty(_discriminatorName, out var discriminator);
            var subtype = GetDiscriminatorType(document.RootElement, typeToConvert, hasDiscriminator ? discriminator.GetString() : null);

            var bufferWriter = new System.IO.MemoryStream();
            using (var writer = new System.Text.Json.Utf8JsonWriter(bufferWriter))
            {
                document.RootElement.WriteTo(writer);
            }

            return (TBase)System.Text.Json.JsonSerializer.Deserialize(bufferWriter.ToArray(), subtype, options);
        }

        public override void Write(System.Text.Json.Utf8JsonWriter writer, TBase value, System.Text.Json.JsonSerializerOptions options)
        {
            if (value != null)
            {
                writer.WriteStartObject();
                writer.WriteString(_discriminatorName, GetDiscriminatorValue(value.GetType()));

                var bytes = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes((object)value, options);
                var document = System.Text.Json.JsonDocument.Parse(bytes);
                foreach (var property in document.RootElement.EnumerateObject())
                {
                    property.WriteTo(writer);
                }

                writer.WriteEndObject();
            }
            else
            {
                writer.WriteNullValue();
            }
        }

        public string GetDiscriminatorValue(System.Type type)
        {
            var jsonInheritanceAttributeDiscriminator = GetSubtypeDiscriminator(type);
            if (jsonInheritanceAttributeDiscriminator != null)
            {
                return jsonInheritanceAttributeDiscriminator;
            }

            return type.Name;
        }

        protected System.Type GetDiscriminatorType(System.Text.Json.JsonElement jObject, System.Type objectType, string discriminatorValue)
        {
            if (discriminatorValue != null)
            {
                var jsonInheritanceAttributeSubtype = GetObjectSubtype(objectType, discriminatorValue);
                if (jsonInheritanceAttributeSubtype != null)
                {
                    return jsonInheritanceAttributeSubtype;
                }

                if (objectType.Name == discriminatorValue)
                {
                    return objectType;
                }

                var typeName = objectType.Namespace + "." + discriminatorValue;
                var subtype = System.Reflection.IntrospectionExtensions.GetTypeInfo(objectType).Assembly.GetType(typeName);
                if (subtype != null)
                {
                    return subtype;
                }
            }

            throw new System.InvalidOperationException("Could not find subtype of '" + objectType.Name + "' with discriminator '" + discriminatorValue + "'.");
        }

        private System.Type GetObjectSubtype(System.Type baseType, string discriminatorValue)
        {
            foreach (var attribute in System.Reflection.CustomAttributeExtensions.GetCustomAttributes<JsonInheritanceAttribute>(System.Reflection.IntrospectionExtensions.GetTypeInfo(baseType), true))
            {
                if (attribute.Key == discriminatorValue)
                    return attribute.Type;
            }

            return null;
        }

        private string GetSubtypeDiscriminator(System.Type objectType)
        {
            foreach (var attribute in System.Reflection.CustomAttributeExtensions.GetCustomAttributes<JsonInheritanceAttribute>(System.Reflection.IntrospectionExtensions.GetTypeInfo(objectType), true))
            {
                if (attribute.Type == objectType)
                    return attribute.Key;
            }

            return null;
        }
    }
}