using System;
using System.Collections.Generic;
using System.Text;
using Unity.VisualScripting.FullSerializer.Internal;

namespace Unity.VisualScripting.FullSerializer
{
    /// <summary>
    /// The AOT compilation manager
    /// </summary>
    public class fsAotCompilationManager
    {
        private static Dictionary<Type, string> _computedAotCompilations = new Dictionary<Type, string>();
        private static List<AotCompilation> _uncomputedAotCompilations = new List<AotCompilation>();

        /// <summary>
        /// Ahead of time compilations that are available. The type maps to the
        /// object type the generated converter will serialize/deserialize, and
        /// the string is the text content for a converter that will do the
        /// serialization.
        /// <para />
        /// The generated serializer is completely independent and you don't need
        /// to do anything. Simply add the file to your project and it'll get
        /// used instead of the reflection based one.
        /// </summary>
        public static Dictionary<Type, string> AvailableAotCompilations
        {
            get
            {
                for (var i = 0; i < _uncomputedAotCompilations.Count; ++i)
                {
                    var item = _uncomputedAotCompilations[i];
                    _computedAotCompilations[item.Type] = GenerateDirectConverterForTypeInCSharp(item.Type, item.Members, item.IsConstructorPublic);
                }
                _uncomputedAotCompilations.Clear();

                return _computedAotCompilations;
            }
        }

        /// <summary>
        /// This is a helper method that makes it simple to run an AOT
        /// compilation on the given type.
        /// </summary>
        /// <param name="config">
        /// The configuration to use when running AOT compilation.
        /// </param>
        /// <param name="type">
        /// The type to perform the AOT compilation on.
        /// </param>
        /// <param name="aotCompiledClassInCSharp">
        /// The AOT class. Add this C# code to your project.
        /// </param>
        /// <returns>True if AOT compilation was successful.</returns>
        public static bool TryToPerformAotCompilation(fsConfig config, Type type, out string aotCompiledClassInCSharp)
        {
            if (fsMetaType.Get(config, type).EmitAotData())
            {
                aotCompiledClassInCSharp = AvailableAotCompilations[type];
                return true;
            }

            aotCompiledClassInCSharp = default(string);
            return false;
        }

        /// <summary>
        /// Adds a new AOT compilation unit.
        /// </summary>
        /// <param name="type">The type of object we are AOT compiling.</param>
        /// <param name="members">
        /// The members on the object which will be serialized/deserialized.
        /// </param>
        public static void AddAotCompilation(Type type, fsMetaProperty[] members, bool isConstructorPublic)
        {
            _uncomputedAotCompilations.Add(new AotCompilation
            {
                Type = type,
                Members = members,
                IsConstructorPublic = isConstructorPublic
            });
        }

        private static string GetConverterString(fsMetaProperty member)
        {
            if (member.OverrideConverterType == null)
            {
                return "null";
            }

            return $"typeof({fsTypeExtensions.CSharpName( /*includeNamespace:*/member.OverrideConverterType, true)})";
        }

        /// <summary>
        /// AOT compiles the object (in C#).
        /// </summary>
        private static string GenerateDirectConverterForTypeInCSharp(Type type, fsMetaProperty[] members, bool isConstructorPublic)
        {
            var sb = new StringBuilder();
            var typeName = fsTypeExtensions.CSharpName( /*includeNamespace:*/
                type, true);
            var typeNameSafeDecl = type.CSharpName(true, true);

            sb.AppendLine("using System;");
            sb.AppendLine("using System.Collections.Generic;");
            sb.AppendLine();
            sb.AppendLine("namespace Unity.VisualScripting.Dependencies.FullSerializer {");
            sb.AppendLine("    partial class fsConverterRegistrar {");
            sb.AppendLine("        public static Speedup." + typeNameSafeDecl + "_DirectConverter " + "Register_" + typeNameSafeDecl + ";");
            sb.AppendLine("    }");
            sb.AppendLine("}");
            sb.AppendLine();
            sb.AppendLine("namespace Unity.VisualScripting.Dependencies.FullSerializer.Speedup {");
            sb.AppendLine("    public class " + typeNameSafeDecl + "_DirectConverter : fsDirectConverter<" + typeName + "> {");
            sb.AppendLine("        protected override fsResult DoSerialize(" + typeName + " model, Dictionary<string, fsData> serialized) {");
            sb.AppendLine("            var result = fsResult.Success;");
            sb.AppendLine();
            foreach (var member in members)
            {
                sb.AppendLine("            result += SerializeMember(serialized, " + GetConverterString(member) + ", \"" + member.JsonName + "\", model." + member.MemberName + ");");
            }
            sb.AppendLine();
            sb.AppendLine("            return result;");
            sb.AppendLine("        }");
            sb.AppendLine();
            sb.AppendLine("        protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref " + typeName + " model) {");
            sb.AppendLine("            var result = fsResult.Success;");
            sb.AppendLine();
            for (var i = 0; i < members.Length; ++i)
            {
                var member = members[i];
                sb.AppendLine("            var t" + i + " = model." + member.MemberName + ";");
                sb.AppendLine("            result += DeserializeMember(data, " + GetConverterString(member) + ", \"" + member.JsonName + "\", out t" + i + ");");
                sb.AppendLine("            model." + member.MemberName + " = t" + i + ";");
                sb.AppendLine();
            }
            sb.AppendLine("            return result;");
            sb.AppendLine("        }");
            sb.AppendLine();
            sb.AppendLine("        public override object CreateInstance(fsData data, Type storageType) {");
            if (isConstructorPublic)
            {
                sb.AppendLine("            return new " + typeName + "();");
            }
            else
            {
                sb.AppendLine("            return Activator.CreateInstance(typeof(" + typeName + "), /*nonPublic:*/true);");
            }
            sb.AppendLine("        }");
            sb.AppendLine("    }");
            sb.AppendLine("}");

            return sb.ToString();
        }

        private struct AotCompilation
        {
            public Type Type;
            public fsMetaProperty[] Members;
            public bool IsConstructorPublic;
        }
    }
}
