using MonoFN.Cecil;
using MonoFN.Cecil.Rocks;
using System;

namespace FishNet.CodeGenerating.Helping.Extension
{

    internal static class MethodReferenceExtensions
    {
        /// <summary>
        /// Makes a generic method with specified arguments.
        /// </summary>
        /// <param name="method"></param>
        /// <param name="genericArguments"></param>
        /// <returns></returns>
        public static GenericInstanceMethod MakeGenericMethod(this MethodReference method, params TypeReference[] genericArguments)
        {
            GenericInstanceMethod result = new GenericInstanceMethod(method);
            foreach (TypeReference argument in genericArguments)
                result.GenericArguments.Add(argument);
            return result;
        }

        /// <summary>
        /// Makes a generic method with the same arguments as the original.
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        public static GenericInstanceMethod MakeGenericMethod(this MethodReference method)
        {
            GenericInstanceMethod result = new GenericInstanceMethod(method);
            foreach (ParameterDefinition pd in method.Parameters)
                result.GenericArguments.Add(pd.ParameterType);

            return result;
        }

        /// <summary>
        /// Returns a method reference for a generic method.
        /// </summary>
        public static MethodReference GetMethodReference(this MethodReference mr, CodegenSession session, TypeReference typeReference)
        {
            return mr.GetMethodReference(session, new TypeReference[] { typeReference });
        }

        /// <summary>
        /// Returns a method reference for a generic method.
        /// </summary>
        public static MethodReference GetMethodReference(this MethodReference mr, CodegenSession session, TypeReference[] typeReferences)
        {
            if (mr.HasGenericParameters)
            {
                if (typeReferences == null || typeReferences.Length == 0)
                {
                    session.LogError($"Method {mr.Name} has generic parameters but TypeReferences are null or 0 length.");
                    return null;
                }
                else
                {
                    GenericInstanceMethod gim = mr.MakeGenericMethod(typeReferences);
                    return gim;
                }
            }
            else
            {
                return mr;
            }
        }


        /// <summary>
        /// Gets a Resolve favoring cached results first.
        /// </summary>
        internal static MethodDefinition CachedResolve(this MethodReference methodRef, CodegenSession session)
        {
            return session.GetClass<GeneralHelper>().GetMethodReferenceResolve(methodRef);
        }

        /// <summary>
        /// Given a method of a generic class such as ArraySegment`T.get_Count,
        /// and a generic instance such as ArraySegment`int
        /// Creates a reference to the specialized method  ArraySegment`int`.get_Count
        /// <para> Note that calling ArraySegment`T.get_Count directly gives an invalid IL error </para>
        /// </summary>
        /// <param name="self"></param>
        /// <param name="instanceType"></param>
        /// <returns></returns>
        public static MethodReference MakeHostInstanceGeneric(this MethodReference self, CodegenSession session, GenericInstanceType instanceType)
        {
            MethodReference reference = new MethodReference(self.Name, self.ReturnType, instanceType)
            {
                CallingConvention = self.CallingConvention,
                HasThis = self.HasThis,
                ExplicitThis = self.ExplicitThis
            };

            foreach (ParameterDefinition parameter in self.Parameters)
                reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));

            foreach (GenericParameter generic_parameter in self.GenericParameters)
                reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));

            return session.ImportReference(reference);
        }
        /// <summary>
        /// Given a method of a generic class such as ArraySegment`T.get_Count,
        /// and a generic instance such as ArraySegment`int
        /// Creates a reference to the specialized method  ArraySegment`int`.get_Count
        /// <para> Note that calling ArraySegment`T.get_Count directly gives an invalid IL error </para>
        /// </summary>
        /// <param name="self"></param>
        /// <param name="instanceType"></param>
        /// <returns></returns>
        public static MethodReference MakeHostInstanceGeneric(this MethodReference self, TypeReference typeRef, params TypeReference[] args)
        {
            GenericInstanceType git = typeRef.MakeGenericInstanceType(args);
            MethodReference reference = new MethodReference(self.Name, self.ReturnType, git)
            {
                CallingConvention = self.CallingConvention,
                HasThis = self.HasThis,
                ExplicitThis = self.ExplicitThis
            };

            foreach (ParameterDefinition parameter in self.Parameters)
                reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));

            foreach (GenericParameter generic_parameter in self.GenericParameters)
                reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));

            return reference;
        }
        public static bool Is<T>(this MethodReference method, string name)
        {
            return method.DeclaringType.Is<T>() && method.Name == name;
        }
        public static bool Is<T>(this TypeReference td)
        {
            return Is(td, typeof(T));
        }

        public static bool Is(this TypeReference td, Type t)
        {
            if (t.IsGenericType)
            {
                return td.GetElementType().FullName == t.FullName;
            }
            return td.FullName == t.FullName;
        }



    }


}