using System;
using System.Linq;
using Indy.IL2CPU.Assembler;
using Indy.IL2CPU.Assembler.X86;
using CPUx86 = Indy.IL2CPU.Assembler.X86;

namespace Indy.IL2CPU.IL.X86
{
    public class X86MethodFooterOp : MethodFooterOp
    {
        public readonly int TotalArgsSize = 0;
        public readonly int ReturnSize = 0;
        public readonly MethodInformation.Variable[] Locals;
        public readonly MethodInformation.Argument[] Args;
        public readonly bool DebugMode;
        public readonly bool MethodIsNonDebuggable;

        public X86MethodFooterOp(ILReader aReader, MethodInformation aMethodInfo)
            : base(aReader, aMethodInfo)
        {
            if (aMethodInfo != null)
            {
                //			if (aMethodInfo.Locals.Length > 0) {
                //				TotalLocalsSize += aMethodInfo.Locals[aMethodInfo.Locals.Length - 1].Offset + aMethodInfo.Locals[aMethodInfo.Locals.Length - 1].Size;
                //			}
                Locals = aMethodInfo.Locals.ToArray();
                Args = aMethodInfo.Arguments.ToArray();
                ReturnSize = aMethodInfo.ReturnSize;
                DebugMode = aMethodInfo.DebugMode;
                MethodIsNonDebuggable = aMethodInfo.IsNonDebuggable;
            }
        }

        public override void DoAssemble()
        {
            AssembleFooter(ReturnSize, Assembler, Locals, Args, (from item in Args
                                                                 select item.Size).Sum(), DebugMode, MethodIsNonDebuggable);
        }

        public static void AssembleFooter(int aReturnSize, Assembler.Assembler aAssembler, MethodInformation.Variable[] aLocals, MethodInformation.Argument[] aArgs, int aTotalArgsSize, bool aDebugMode, bool aIsNonDebuggable)
        {
            int xReturnSize = aReturnSize;
            if (xReturnSize % 4 > 0)
            {
                xReturnSize += 4 - xReturnSize % 4;
            }
            new Label(EndOfMethodLabelNameNormal);
            new CPUx86.Move("ecx", "0");
            if (aReturnSize > 0)
            {
                int xOffset = 4;
                if (aArgs.Length > 0)
                {
                    xOffset = aArgs.First().Offset+4;
                    if (xOffset < 0) {
                        xOffset = 0; }
                }
                for (int i = 0; i < xReturnSize / 4; i++)
                {
                    new Assembler.X86.Pop(Registers.EAX);
                    new CPUx86.Move("[ebp + " + (xOffset + ((i + 1) * 4) + 4 - xReturnSize) + "]",
                                    Registers.EAX);
                }
            }
            new CPUx86.Jump(EndOfMethodLabelNameException);
            new Label(EndOfMethodLabelNameException);
            if (aDebugMode && aIsNonDebuggable)
            {
                new CPUx86.Call("DebugPoint_DebugResume");
            }
            if ((from xLocal in aLocals
                 where xLocal.IsReferenceType
                 select 1).Count() > 0 || (from xArg in aArgs
                                           where xArg.IsReferenceType
                                           select 1).Count() > 0) {
                new CPUx86.Push("ecx");
                Engine.QueueMethod(GCImplementationRefs.DecRefCountRef);
                foreach (MethodInformation.Variable xLocal in aLocals) {
                    if (xLocal.IsReferenceType) {
                        Op.Ldloc(aAssembler,
                                 xLocal,
                                 false);
                        new CPUx86.Call(Label.GenerateLabelName(GCImplementationRefs.DecRefCountRef));
                    }
                }
                foreach (MethodInformation.Argument xArg in aArgs) {
                    if (xArg.IsReferenceType) {
                        Op.Ldarg(aAssembler,
                                 xArg,
                                 false);
                        new CPUx86.Call(Label.GenerateLabelName(GCImplementationRefs.DecRefCountRef));
                    }
                }
                new CPUx86.Pop("ecx");
            }
            for (int j = aLocals.Length - 1; j >= 0; j--)
            {
                int xLocalSize = aLocals[j].Size;
                new CPUx86.Add(CPUx86.Registers.ESP, "0x" + xLocalSize.ToString("X"));
            }
            //new CPUx86.Add(CPUx86.Registers.ESP, "0x4");
            new CPUx86.Popd(CPUx86.Registers.EBP);
            new CPUx86.Ret(aTotalArgsSize - xReturnSize);
        }
    }
}