using System;

using IL2CPU.API;
using Cosmos.IL2CPU;
using Cosmos.IL2CPU.X86.IL;

using XSharp;
using XSharp.Assembler;
using x86 = XSharp.Assembler.x86;

// ReSharper disable once CheckNamespace
namespace Cosmos.Core_Asm
{
    public class DelegateInvokeAsm : AssemblerMethod
    {
        public override void AssembleNew(Assembler aAssembler, object aMethodInfo)
        {
            var xAssembler = aAssembler;
            var xMethodInfo = (_MethodInfo) aMethodInfo;
            var xMethodBaseAsInfo = xMethodInfo.MethodBase as global::System.Reflection.MethodInfo;
            if (xMethodBaseAsInfo.ReturnType != typeof(void))
            {
                throw new Exception("Events with return type not yet supported!");
            }

            /*
            * EAX contains the GetInvocationList() array at the index at which it was last used
            * EDX contains the index at which the EAX is
            * EBX contains the number of items in the array
            * ECX contains the argument size
            */

            XS.ClearInterruptFlag();

            XS.Comment("Get Invoke list count");
            var xGetInvocationListMethod = typeof(MulticastDelegate).GetMethod("GetInvocationList");
            Ldarg.DoExecute(aAssembler, xMethodInfo, 0);
            XS.Call(LabelName.Get(xGetInvocationListMethod));
            XS.Add(XSRegisters.ESP, 4);
            XS.Pop(XSRegisters.EAX);
            XS.Add(XSRegisters.EAX, 8);
            XS.Set(XSRegisters.EBX, XSRegisters.EAX, sourceIsIndirect: true);

            XS.Comment("Get invoke method");
            XS.Add(XSRegisters.EAX, 8);
            XS.Set(XSRegisters.EDI, XSRegisters.EAX, sourceIsIndirect: true, sourceDisplacement: 4);

            XS.Comment("Get ArgSize");
            int xArgSizeOffset = Ldfld.GetFieldOffset(typeof(global::System.Delegate), "$$ArgSize$$");
            Ldarg.DoExecute(aAssembler, xMethodInfo, 0);
            XS.Add(XSRegisters.ESP, 4);
            XS.Pop(XSRegisters.ECX);
            XS.Add(XSRegisters.ECX, (uint) xArgSizeOffset);
            XS.Set(XSRegisters.ECX, XSRegisters.ECX, sourceIsIndirect: true);

            XS.Comment("Set current invoke list index");
            XS.Set(XSRegisters.EDX, 0);

            XS.Label(".BEGIN_OF_LOOP");
            {
                XS.Compare(XSRegisters.EDX, XSRegisters.EBX);
                XS.Jump(x86.ConditionalTestEnum.GreaterThanOrEqualTo, ".END_OF_INVOKE");

                XS.PushAllRegisters();

                XS.Comment("Check if delegate has $this");
                XS.Set(XSRegisters.EDI, XSRegisters.EBP, sourceDisplacement: Ldarg.GetArgumentDisplacement(xMethodInfo, 0));
                XS.Add(XSRegisters.EDI, 4);
                XS.Set(XSRegisters.EDI, XSRegisters.EDI, sourceDisplacement: Ldfld.GetFieldOffset(xMethodInfo.MethodBase.DeclaringType, "System.Object System.Delegate._target"));
                XS.Compare(XSRegisters.EDI, 0);
                XS.Jump(x86.ConditionalTestEnum.Zero, ".NO_THIS");
                XS.Label(".HAS_THIS");
                XS.Push(XSRegisters.EDI);
                XS.Push(0);
                XS.Label(".NO_THIS");
                XS.Set(XSRegisters.EDI, XSRegisters.EAX, sourceIsIndirect: true, sourceDisplacement: 4);
                XS.Set(XSRegisters.EDI, XSRegisters.EDI, sourceDisplacement: Ldfld.GetFieldOffset(xMethodInfo.MethodBase.DeclaringType, "System.IntPtr System.Delegate._methodPtr"));

                XS.Comment("Check if delegate has args");
                XS.Compare(XSRegisters.ECX, 0);
                XS.Jump(x86.ConditionalTestEnum.Zero, ".NO_ARGS");
                XS.Label(".HAS_ARGS");
                XS.Sub(XSRegisters.ESP, XSRegisters.ECX);
                XS.Push(XSRegisters.EDI);
                XS.Set(XSRegisters.EDI, XSRegisters.ESP);
                XS.Add(XSRegisters.EDI, 4);
                XS.Set(XSRegisters.ESI, XSRegisters.EBP);
                XS.Add(XSRegisters.ESI, 8);
                new x86.Movs { Size = 8, Prefixes = x86.InstructionPrefixes.Repeat };
                XS.Pop(XSRegisters.EDI);
                XS.Label(".NO_ARGS");
                XS.Call(XSRegisters.EDI);

                XS.PopAllRegisters();
                XS.Increment(XSRegisters.EDX);
                XS.Jump(".BEGIN_OF_LOOP");
            }

            XS.Label(".END_OF_INVOKE");
            XS.Set(XSRegisters.EDX, XSRegisters.EBP, sourceDisplacement: Ldarg.GetArgumentDisplacement(xMethodInfo, 0));
            XS.Set(XSRegisters.EDX, XSRegisters.EDX, sourceDisplacement: Ldfld.GetFieldOffset(xMethodInfo.MethodBase.DeclaringType, "$$ReturnsValue$$"));
            XS.Compare(XSRegisters.EDX, 0);
            XS.Jump(x86.ConditionalTestEnum.Equal, ".NO_RETURN");

            XS.Label(".HAS_RETURN");
            XS.Exchange(XSRegisters.EBP, XSRegisters.EDX, destinationDisplacement: 8);
            XS.Exchange(XSRegisters.EBP, XSRegisters.EDX, destinationDisplacement: 4);
            XS.Exchange(XSRegisters.EBP, XSRegisters.EDX, destinationIsIndirect: true);
            XS.Push(XSRegisters.EDX);
            XS.Set(XSRegisters.ESP, XSRegisters.EDI, destinationDisplacement: 12);

            XS.Label(".NO_RETURN");
            XS.EnableInterrupts();
        }
    }
}
