using System;
using System.Linq.Expressions;

using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.Internal;

namespace GamecraftModdingAPI.Blocks
{
    public partial class BlockEngine
    {
        /// <summary>
        /// Holds information needed to construct a component initializer
        /// </summary>
        internal struct BlockInitData
        {
            public FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> Group;
        }

        internal delegate FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> GetInitGroup(
            EntityComponentInitializer initializer);

        /// <summary>
        /// Accesses the group field of the initializer
        /// </summary>
        internal GetInitGroup InitGroup = CreateAccessor<GetInitGroup>("_group");

        //https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
        internal static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate
        {
            var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
            if (invokeMethod == null)
                throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined.");

            var delegateParameters = invokeMethod.GetParameters();
            if (delegateParameters.Length != 1)
                throw new InvalidOperationException("Delegate must have a single parameter.");

            var paramType = delegateParameters[0].ParameterType;

            var objParam = Expression.Parameter(paramType, "obj");
            var memberExpr = Expression.PropertyOrField(objParam, memberName);
            Expression returnExpr = memberExpr;
            if (invokeMethod.ReturnType != memberExpr.Type)
                returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType);

            var lambda =
                Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] {objParam});
            return lambda.Compile();
        }
    }
}