using ChocolArm64.Decoders;
using ChocolArm64.State;
using ChocolArm64.Translation;
using System.Reflection.Emit;

namespace ChocolArm64.Instructions
{
    static class InstEmitAluHelper
    {
        public static void EmitAdcsCCheck(ILEmitterCtx context)
        {
            //C = (Rd == Rn && CIn) || Rd < Rn
            context.EmitSttmp();
            context.EmitLdtmp();
            context.EmitLdtmp();

            EmitDataLoadRn(context);

            context.Emit(OpCodes.Ceq);

            context.EmitLdflg((int)PState.CBit);

            context.Emit(OpCodes.And);

            context.EmitLdtmp();

            EmitDataLoadRn(context);

            context.Emit(OpCodes.Clt_Un);
            context.Emit(OpCodes.Or);

            context.EmitStflg((int)PState.CBit);
        }

        public static void EmitAddsCCheck(ILEmitterCtx context)
        {
            //C = Rd < Rn
            context.Emit(OpCodes.Dup);

            EmitDataLoadRn(context);

            context.Emit(OpCodes.Clt_Un);

            context.EmitStflg((int)PState.CBit);
        }

        public static void EmitAddsVCheck(ILEmitterCtx context)
        {
            //V = (Rd ^ Rn) & ~(Rn ^ Rm) < 0
            context.Emit(OpCodes.Dup);

            EmitDataLoadRn(context);

            context.Emit(OpCodes.Xor);

            EmitDataLoadOpers(context);

            context.Emit(OpCodes.Xor);
            context.Emit(OpCodes.Not);
            context.Emit(OpCodes.And);

            context.EmitLdc_I(0);

            context.Emit(OpCodes.Clt);

            context.EmitStflg((int)PState.VBit);
        }

        public static void EmitSbcsCCheck(ILEmitterCtx context)
        {
            //C = (Rn == Rm && CIn) || Rn > Rm
            EmitDataLoadOpers(context);

            context.Emit(OpCodes.Ceq);

            context.EmitLdflg((int)PState.CBit);

            context.Emit(OpCodes.And);

            EmitDataLoadOpers(context);

            context.Emit(OpCodes.Cgt_Un);
            context.Emit(OpCodes.Or);

            context.EmitStflg((int)PState.CBit);
        }

        public static void EmitSubsCCheck(ILEmitterCtx context)
        {
            //C = Rn == Rm || Rn > Rm = !(Rn < Rm)
            EmitDataLoadOpers(context);

            context.Emit(OpCodes.Clt_Un);

            context.EmitLdc_I4(1);

            context.Emit(OpCodes.Xor);

            context.EmitStflg((int)PState.CBit);
        }

        public static void EmitSubsVCheck(ILEmitterCtx context)
        {
            //V = (Rd ^ Rn) & (Rn ^ Rm) < 0
            context.Emit(OpCodes.Dup);

            EmitDataLoadRn(context);

            context.Emit(OpCodes.Xor);

            EmitDataLoadOpers(context);

            context.Emit(OpCodes.Xor);
            context.Emit(OpCodes.And);

            context.EmitLdc_I(0);

            context.Emit(OpCodes.Clt);

            context.EmitStflg((int)PState.VBit);
        }

        public static void EmitDataLoadRm(ILEmitterCtx context)
        {
            context.EmitLdintzr(((IOpCodeAluRs64)context.CurrOp).Rm);
        }

        public static void EmitDataLoadOpers(ILEmitterCtx context)
        {
            EmitDataLoadRn(context);
            EmitDataLoadOper2(context);
        }

        public static void EmitDataLoadRn(ILEmitterCtx context)
        {
            IOpCodeAlu64 op = (IOpCodeAlu64)context.CurrOp;

            if (op.DataOp == DataOp.Logical || op is IOpCodeAluRs64)
            {
                context.EmitLdintzr(op.Rn);
            }
            else
            {
                context.EmitLdint(op.Rn);
            }
        }

        public static void EmitDataLoadOper2(ILEmitterCtx context)
        {
            switch (context.CurrOp)
            {
                case IOpCodeAluImm64 op:
                    context.EmitLdc_I(op.Imm);
                    break;

                case IOpCodeAluRs64 op:
                    context.EmitLdintzr(op.Rm);

                    switch (op.ShiftType)
                    {
                        case ShiftType.Lsl: context.EmitLsl(op.Shift); break;
                        case ShiftType.Lsr: context.EmitLsr(op.Shift); break;
                        case ShiftType.Asr: context.EmitAsr(op.Shift); break;
                        case ShiftType.Ror: context.EmitRor(op.Shift); break;
                    }
                    break;

                case IOpCodeAluRx64 op:
                    context.EmitLdintzr(op.Rm);
                    context.EmitCast(op.IntType);
                    context.EmitLsl(op.Shift);
                    break;
            }
        }

        public static void EmitDataStore(ILEmitterCtx context)  => EmitDataStore(context, false);
        public static void EmitDataStoreS(ILEmitterCtx context) => EmitDataStore(context, true);

        public static void EmitDataStore(ILEmitterCtx context, bool setFlags)
        {
            IOpCodeAlu64 op = (IOpCodeAlu64)context.CurrOp;

            if (setFlags || op is IOpCodeAluRs64)
            {
                context.EmitStintzr(op.Rd);
            }
            else
            {
                context.EmitStint(op.Rd);
            }
        }

        public static void EmitSetNzcv(ILEmitterCtx context)
        {
            context.Emit(OpCodes.Dup);
            context.Emit(OpCodes.Ldc_I4_1);
            context.Emit(OpCodes.And);
            context.EmitStflg((int)PState.VBit);

            context.Emit(OpCodes.Ldc_I4_1);
            context.Emit(OpCodes.Shr);
            context.Emit(OpCodes.Dup);
            context.Emit(OpCodes.Ldc_I4_1);
            context.Emit(OpCodes.And);
            context.EmitStflg((int)PState.CBit);

            context.Emit(OpCodes.Ldc_I4_1);
            context.Emit(OpCodes.Shr);
            context.Emit(OpCodes.Dup);
            context.Emit(OpCodes.Ldc_I4_1);
            context.Emit(OpCodes.And);
            context.EmitStflg((int)PState.ZBit);

            context.Emit(OpCodes.Ldc_I4_1);
            context.Emit(OpCodes.Shr);
            context.Emit(OpCodes.Ldc_I4_1);
            context.Emit(OpCodes.And);
            context.EmitStflg((int)PState.NBit);
        }
    }
}
