using Ryujinx.Cpu.LightningJit.CodeGen;
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
using System;
using System.Diagnostics;

namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
{
    static class InstEmitAlu
    {
        private const uint Imm12Limit = 0x1000;

        public static void AdcI(CodeGenContext context, uint rd, uint rn, uint imm, bool s)
        {
            EmitI(context, s ? context.Arm64Assembler.Adcs : context.Arm64Assembler.Adc, rd, rn, imm, s);
        }

        public static void AdcR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitR(context, s ? context.Arm64Assembler.Adcs : context.Arm64Assembler.Adc, rd, rn, rm, sType, imm5, s);
        }

        public static void AdcRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitRr(context, s ? context.Arm64Assembler.Adcs : context.Arm64Assembler.Adc, rd, rn, rm, sType, rs, s);
        }

        public static void AddI(CodeGenContext context, uint rd, uint rn, uint imm, bool s)
        {
            EmitArithmeticI(context, s ? context.Arm64Assembler.Adds : context.Arm64Assembler.Add, rd, rn, imm, s);
        }

        public static void AddR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitArithmeticR(context, s ? context.Arm64Assembler.Adds : context.Arm64Assembler.Add, rd, rn, rm, sType, imm5, s);
        }

        public static void AddRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitRr(context, s ? context.Arm64Assembler.Adds : context.Arm64Assembler.Add, rd, rn, rm, sType, rs, s);
        }

        public static void Adr(CodeGenContext context, uint rd, uint imm, bool add)
        {
            Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);

            uint pc = context.Pc & ~3u;

            if (add)
            {
                pc += imm;
            }
            else
            {
                pc -= imm;
            }

            context.Arm64Assembler.Mov(rdOperand, pc);
        }

        public static void AndI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s)
        {
            EmitLogicalI(context, s ? context.Arm64Assembler.Ands : context.Arm64Assembler.And, rd, rn, imm, immRotated, s);
        }

        public static void AndR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitLogicalR(context, s ? context.Arm64Assembler.Ands : context.Arm64Assembler.And, rd, rn, rm, sType, imm5, s);
        }

        public static void AndRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitLogicalRr(context, s ? context.Arm64Assembler.Ands : context.Arm64Assembler.And, rd, rn, rm, sType, rs, s);
        }

        public static void BicI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s)
        {
            if (!s && CodeGenCommon.TryEncodeBitMask(OperandType.I32, ~imm, out _, out _, out _))
            {
                AndI(context, rd, rn, ~imm, immRotated, s);
            }
            else
            {
                EmitLogicalI(context, s ? context.Arm64Assembler.Bics : context.Arm64Assembler.Bic, rd, rn, imm, immRotated, s, immForm: false);
            }
        }

        public static void BicR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitLogicalR(context, s ? context.Arm64Assembler.Bics : context.Arm64Assembler.Bic, rd, rn, rm, sType, imm5, s);
        }

        public static void BicRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitLogicalRr(context, s ? context.Arm64Assembler.Bics : context.Arm64Assembler.Bic, rd, rn, rm, sType, rs, s);
        }

        public static void CmnI(CodeGenContext context, uint rn, uint imm)
        {
            EmitCompareI(context, context.Arm64Assembler.Cmn, rn, imm);
        }

        public static void CmnR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5)
        {
            EmitCompareR(context, context.Arm64Assembler.Cmn, rn, rm, sType, imm5);
        }

        public static void CmnRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs)
        {
            EmitCompareRr(context, context.Arm64Assembler.Cmn, rn, rm, sType, rs);
        }

        public static void CmpI(CodeGenContext context, uint rn, uint imm)
        {
            EmitCompareI(context, context.Arm64Assembler.Cmp, rn, imm);
        }

        public static void CmpR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5)
        {
            EmitCompareR(context, context.Arm64Assembler.Cmp, rn, rm, sType, imm5);
        }

        public static void CmpRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs)
        {
            EmitCompareRr(context, context.Arm64Assembler.Cmp, rn, rm, sType, rs);
        }

        public static void EorI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s)
        {
            EmitLogicalI(context, s ? context.Arm64Assembler.Eors : context.Arm64Assembler.Eor, rd, rn, imm, immRotated, s);
        }

        public static void EorR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitLogicalR(context, s ? context.Arm64Assembler.Eors : context.Arm64Assembler.Eor, rd, rn, rm, sType, imm5, s);
        }

        public static void EorRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitLogicalRr(context, s ? context.Arm64Assembler.Eors : context.Arm64Assembler.Eor, rd, rn, rm, sType, rs, s);
        }

        public static void OrnI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s)
        {
            if (!s && CodeGenCommon.TryEncodeBitMask(OperandType.I32, ~imm, out _, out _, out _))
            {
                OrrI(context, rd, rn, ~imm, immRotated, s);
            }
            else
            {
                EmitLogicalI(context, s ? context.Arm64Assembler.Orns : context.Arm64Assembler.Orn, rd, rn, imm, immRotated, s, immForm: false);
            }
        }

        public static void OrnR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitLogicalR(context, s ? context.Arm64Assembler.Orns : context.Arm64Assembler.Orn, rd, rn, rm, sType, imm5, s);
        }

        public static void OrrI(CodeGenContext context, uint rd, uint rn, uint imm, bool immRotated, bool s)
        {
            EmitLogicalI(context, s ? context.Arm64Assembler.Orrs : context.Arm64Assembler.Orr, rd, rn, imm, immRotated, s);
        }

        public static void OrrR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitLogicalR(context, s ? context.Arm64Assembler.Orrs : context.Arm64Assembler.Orr, rd, rn, rm, sType, imm5, s);
        }

        public static void OrrRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitLogicalRr(context, s ? context.Arm64Assembler.Orrs : context.Arm64Assembler.Orr, rd, rn, rm, sType, rs, s);
        }

        public static void RsbI(CodeGenContext context, uint rd, uint rn, uint imm, bool s)
        {
            if (imm == 0)
            {
                Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
                Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);

                if (s)
                {
                    context.Arm64Assembler.Negs(rdOperand, rnOperand);
                    context.SetNzcvModified();
                }
                else
                {
                    context.Arm64Assembler.Neg(rdOperand, rnOperand);
                }
            }
            else
            {
                EmitI(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, imm, s, reverse: true);
            }
        }

        public static void RsbR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitR(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, imm5, s, reverse: true);
        }

        public static void RsbRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitRr(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, rs, s, reverse: true);
        }

        public static void RscI(CodeGenContext context, uint rd, uint rn, uint imm, bool s)
        {
            EmitI(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, imm, s, reverse: true);
        }

        public static void RscR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitR(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, imm5, s, reverse: true);
        }

        public static void RscRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitRr(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, rs, s, reverse: true);
        }

        public static void SbcI(CodeGenContext context, uint rd, uint rn, uint imm, bool s)
        {
            EmitI(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, imm, s);
        }

        public static void SbcR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitR(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, imm5, s);
        }

        public static void SbcRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitRr(context, s ? context.Arm64Assembler.Sbcs : context.Arm64Assembler.Sbc, rd, rn, rm, sType, rs, s);
        }

        public static void SubI(CodeGenContext context, uint rd, uint rn, uint imm, bool s)
        {
            EmitArithmeticI(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, imm, s);
        }

        public static void SubR(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            EmitArithmeticR(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, imm5, s);
        }

        public static void SubRr(CodeGenContext context, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            EmitRr(context, s ? context.Arm64Assembler.Subs : context.Arm64Assembler.Sub, rd, rn, rm, sType, rs, s);
        }

        public static void TeqI(CodeGenContext context, uint rn, uint imm, bool immRotated)
        {
            EmitLogicalI(context, (rnOperand, rmOperand) => EmitTeq(context, rnOperand, rmOperand), rn, imm, immRotated);
        }

        public static void TeqR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5)
        {
            EmitLogicalR(context, (rnOperand, rmOperand) => EmitTeq(context, rnOperand, rmOperand), rn, rm, sType, imm5);
        }

        public static void TeqRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs)
        {
            EmitLogicalRr(context, (rnOperand, rmOperand) => EmitTeq(context, rnOperand, rmOperand), rn, rm, sType, rs);
        }

        public static void TstI(CodeGenContext context, uint rn, uint imm, bool immRotated)
        {
            EmitLogicalI(context, context.Arm64Assembler.Tst, rn, imm, immRotated);
        }

        public static void TstR(CodeGenContext context, uint rn, uint rm, uint sType, uint imm5)
        {
            EmitLogicalR(context, context.Arm64Assembler.Tst, rn, rm, sType, imm5);
        }

        public static void TstRr(CodeGenContext context, uint rn, uint rm, uint sType, uint rs)
        {
            EmitLogicalRr(context, context.Arm64Assembler.Tst, rn, rm, sType, rs);
        }

        private static void EmitArithmeticI(CodeGenContext context, Action<Operand, Operand, Operand> action, uint rd, uint rn, uint imm, bool s)
        {
            Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);

            if (imm < Imm12Limit)
            {
                Operand rmOperand = new(OperandKind.Constant, OperandType.I32, imm);

                action(rdOperand, rnOperand, rmOperand);
            }
            else
            {
                using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                context.Arm64Assembler.Mov(tempRegister.Operand, imm);

                action(rdOperand, rnOperand, tempRegister.Operand);
            }

            if (s)
            {
                context.SetNzcvModified();
            }
        }

        private static void EmitArithmeticR(
            CodeGenContext context,
            Action<Operand, Operand, Operand, ArmShiftType, int> action,
            uint rd,
            uint rn,
            uint rm,
            uint sType,
            uint imm5,
            bool s)
        {
            Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
            Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);

            if (CanShiftArithmetic(sType, imm5))
            {
                action(rdOperand, rnOperand, rmOperand, (ArmShiftType)sType, (int)imm5);
            }
            else
            {
                using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType);

                action(rdOperand, rnOperand, rmOperand, ArmShiftType.Lsl, 0);
            }

            if (s)
            {
                context.SetNzcvModified();
            }
        }

        private static void EmitCompareI(CodeGenContext context, Action<Operand, Operand> action, uint rn, uint imm)
        {
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);

            if (imm < Imm12Limit)
            {
                Operand rmOperand = new(OperandKind.Constant, OperandType.I32, imm);

                action(rnOperand, rmOperand);
            }
            else
            {
                using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                context.Arm64Assembler.Mov(tempRegister.Operand, imm);

                action(rnOperand, tempRegister.Operand);
            }

            context.SetNzcvModified();
        }

        private static void EmitCompareR(
            CodeGenContext context,
            Action<Operand, Operand, ArmShiftType, int> action,
            uint rn,
            uint rm,
            uint sType,
            uint imm5)
        {
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
            Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);

            if (CanShiftArithmetic(sType, imm5))
            {
                action(rnOperand, rmOperand, (ArmShiftType)sType, (int)imm5);
            }
            else
            {
                using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType);

                action(rnOperand, rmOperand, ArmShiftType.Lsl, 0);
            }

            context.SetNzcvModified();
        }

        private static void EmitCompareRr(CodeGenContext context, Action<Operand, Operand> action, uint rn, uint rm, uint sType, uint rs)
        {
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
            Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
            Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType);

            action(rnOperand, rmOperand);

            context.SetNzcvModified();
        }

        private static void EmitI(CodeGenContext context, Action<Operand, Operand, Operand> action, uint rd, uint rn, uint imm, bool s, bool reverse = false)
        {
            Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            context.Arm64Assembler.Mov(tempRegister.Operand, imm);

            if (reverse)
            {
                action(rdOperand, tempRegister.Operand, rnOperand);
            }
            else
            {
                action(rdOperand, rnOperand, tempRegister.Operand);
            }

            if (s)
            {
                context.SetNzcvModified();
            }
        }

        private static void EmitR(CodeGenContext context, Action<Operand, Operand, Operand> action, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s, bool reverse = false)
        {
            Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
            Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType);

            if (reverse)
            {
                action(rdOperand, rmOperand, rnOperand);
            }
            else
            {
                action(rdOperand, rnOperand, rmOperand);
            }

            if (s)
            {
                context.SetNzcvModified();
            }
        }

        private static void EmitRr(CodeGenContext context, Action<Operand, Operand, Operand> action, uint rd, uint rn, uint rm, uint sType, uint rs, bool s, bool reverse = false)
        {
            Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
            Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
            Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType);

            if (reverse)
            {
                action(rdOperand, rmOperand, rnOperand);
            }
            else
            {
                action(rdOperand, rnOperand, rmOperand);
            }

            if (s)
            {
                context.SetNzcvModified();
            }
        }

        private static void EmitLogicalI(CodeGenContext context, Action<Operand, Operand> action, uint rn, uint imm, bool immRotated)
        {
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);

            using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand);

            if (immRotated)
            {
                if ((imm & (1u << 31)) != 0)
                {
                    context.Arm64Assembler.Orr(flagsRegister.Operand, flagsRegister.Operand, InstEmitCommon.Const(2));
                }
                else
                {
                    context.Arm64Assembler.Bfc(flagsRegister.Operand, 1, 1);
                }
            }

            if (CodeGenCommon.TryEncodeBitMask(OperandType.I32, imm, out _, out _, out _))
            {
                action(rnOperand, InstEmitCommon.Const((int)imm));
            }
            else
            {
                using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                context.Arm64Assembler.Mov(tempRegister.Operand, imm);

                action(rnOperand, tempRegister.Operand);
            }

            InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand);

            context.SetNzcvModified();
        }

        private static void EmitLogicalI(
            CodeGenContext context,
            Action<Operand, Operand, Operand> action,
            uint rd,
            uint rn,
            uint imm,
            bool immRotated,
            bool s,
            bool immForm = true)
        {
            Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);

            ScopedRegister flagsRegister = default;

            if (s)
            {
                flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand);

                if (immRotated)
                {
                    if ((imm & (1u << 31)) != 0)
                    {
                        context.Arm64Assembler.Orr(flagsRegister.Operand, flagsRegister.Operand, InstEmitCommon.Const(2));
                    }
                    else
                    {
                        context.Arm64Assembler.Bfc(flagsRegister.Operand, 1, 1);
                    }
                }
            }

            if (imm == 0 || (immForm && CodeGenCommon.TryEncodeBitMask(OperandType.I32, imm, out _, out _, out _)))
            {
                action(rdOperand, rnOperand, InstEmitCommon.Const((int)imm));
            }
            else
            {
                using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                context.Arm64Assembler.Mov(tempRegister.Operand, imm);

                action(rdOperand, rnOperand, tempRegister.Operand);
            }

            if (s)
            {
                InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand);

                flagsRegister.Dispose();

                context.SetNzcvModified();
            }
        }

        private static void EmitLogicalR(CodeGenContext context, Action<Operand, Operand> action, uint rn, uint rm, uint sType, uint imm5)
        {
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
            Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
            using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand);

            rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, flagsRegister.Operand);

            action(rnOperand, rmOperand);

            InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand);

            context.SetNzcvModified();
        }

        private static void EmitLogicalR(CodeGenContext context, Action<Operand, Operand, Operand, ArmShiftType, int> action, uint rd, uint rn, uint rm, uint sType, uint imm5, bool s)
        {
            Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
            Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);

            if (CanShift(sType, imm5) && !s)
            {
                action(rdOperand, rnOperand, rmOperand, (ArmShiftType)sType, (int)imm5);
            }
            else
            {
                using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
                ScopedRegister flagsRegister = default;

                if (s)
                {
                    flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                    InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand);

                    rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, flagsRegister.Operand);
                }
                else
                {
                    rmOperand = GetMShiftedByImmediate(context, tempRegister.Operand, rmOperand, imm5, sType, null);
                }

                action(rdOperand, rnOperand, rmOperand, ArmShiftType.Lsl, 0);

                if (s)
                {
                    InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand);

                    flagsRegister.Dispose();

                    context.SetNzcvModified();
                }
            }
        }

        private static void EmitLogicalRr(CodeGenContext context, Action<Operand, Operand> action, uint rn, uint rm, uint sType, uint rs)
        {
            Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
            Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
            Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
            using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand);

            rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType, flagsRegister.Operand);

            action(rnOperand, rmOperand);

            InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand);

            context.SetNzcvModified();
        }

        private static void EmitLogicalRr(CodeGenContext context, Action<Operand, Operand, Operand> action, uint rd, uint rn, uint rm, uint sType, uint rs, bool s)
        {
            if (s)
            {
                Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
                Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
                Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
                Operand rsOperand = InstEmitCommon.GetInputGpr(context, rs);

                using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
                using ScopedRegister flagsRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                InstEmitCommon.GetCurrentFlags(context, flagsRegister.Operand);

                rmOperand = GetMShiftedByReg(context, tempRegister.Operand, rmOperand, rsOperand, sType, flagsRegister.Operand);

                action(rdOperand, rnOperand, rmOperand);

                InstEmitCommon.RestoreCvFlags(context, flagsRegister.Operand);

                context.SetNzcvModified();
            }
            else
            {
                EmitRr(context, action, rd, rn, rm, sType, rs, s);
            }
        }

        public static bool CanShiftArithmetic(uint sType, uint imm5)
        {
            // We can't encode ROR or RRX.

            return sType != 3 && (sType == 0 || imm5 != 0);
        }

        public static bool CanShift(uint sType, uint imm5)
        {
            // We can encode all shift types directly, except RRX.

            return imm5 != 0 || sType == 0;
        }

        public static Operand GetMShiftedByImmediate(CodeGenContext context, Operand dest, Operand m, uint imm, uint sType, Operand? carryOut = null)
        {
            int shift = (int)imm;

            if (shift == 0)
            {
                switch ((ArmShiftType)sType)
                {
                    case ArmShiftType.Lsr:
                    case ArmShiftType.Asr:
                        shift = 32;
                        break;
                    case ArmShiftType.Ror:
                        shift = 1;
                        break;
                }
            }

            if (shift != 0)
            {
                switch ((ArmShiftType)sType)
                {
                    case ArmShiftType.Lsl:
                        m = GetLslC(context, dest, m, carryOut, shift);
                        break;
                    case ArmShiftType.Lsr:
                        m = GetLsrC(context, dest, m, carryOut, shift);
                        break;
                    case ArmShiftType.Asr:
                        m = GetAsrC(context, dest, m, carryOut, shift);
                        break;
                    case ArmShiftType.Ror:
                        if (imm != 0)
                        {
                            m = GetRorC(context, dest, m, carryOut, shift);
                        }
                        else
                        {
                            m = GetRrxC(context, dest, m, carryOut);
                        }
                        break;
                }
            }

            return m;
        }

        public static Operand GetMShiftedByReg(CodeGenContext context, Operand dest, Operand m, Operand s, uint sType, Operand? carryOut = null)
        {
            Operand shiftResult = m;

            switch ((ArmShiftType)sType)
            {
                case ArmShiftType.Lsl:
                    shiftResult = EmitLslC(context, dest, m, carryOut, s);
                    break;
                case ArmShiftType.Lsr:
                    shiftResult = EmitLsrC(context, dest, m, carryOut, s);
                    break;
                case ArmShiftType.Asr:
                    shiftResult = EmitAsrC(context, dest, m, carryOut, s);
                    break;
                case ArmShiftType.Ror:
                    shiftResult = EmitRorC(context, dest, m, carryOut, s);
                    break;
            }

            return shiftResult;
        }

        private static void EmitIfHelper(CodeGenContext context, Operand boolValue, Action action, bool expected = true)
        {
            Debug.Assert(boolValue.Type == OperandType.I32);

            int branchInstructionPointer = context.CodeWriter.InstructionPointer;

            if (expected)
            {
                context.Arm64Assembler.Cbnz(boolValue, 0);
            }
            else
            {
                context.Arm64Assembler.Cbz(boolValue, 0);
            }

            action();

            int offset = context.CodeWriter.InstructionPointer - branchInstructionPointer;
            Debug.Assert(offset >= 0);
            Debug.Assert((offset << 13) >> 13 == offset);
            uint branchInst = context.CodeWriter.ReadInstructionAt(branchInstructionPointer);
            context.CodeWriter.WriteInstructionAt(branchInstructionPointer, branchInst | (uint)(offset << 5));
        }

        private static Operand EmitLslC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift)
        {
            Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            Operand mask = tempRegister.Operand;
            context.Arm64Assembler.Uxtb(mask, shift);
            context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(32));
            context.Arm64Assembler.Asr(mask, mask, InstEmitCommon.Const(31));

            Operand dest64 = new(OperandKind.Register, OperandType.I64, dest.Value);

            if (carryOut.HasValue)
            {
                context.Arm64Assembler.Lslv(dest64, m, shift);
            }
            else
            {
                context.Arm64Assembler.Lslv(dest, m, shift);
            }

            // If shift >= 32, force the result to 0.
            context.Arm64Assembler.And(dest, dest, mask);

            if (carryOut.HasValue)
            {
                EmitIfHelper(context, shift, () =>
                {
                    using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                    context.Arm64Assembler.Uxtb(mask, shift);
                    context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(33));
                    context.Arm64Assembler.Lsr(mask, mask, InstEmitCommon.Const(31));
                    context.Arm64Assembler.Lsr(tempRegister.Operand, dest64, InstEmitCommon.Const(32));
                    context.Arm64Assembler.And(tempRegister.Operand, tempRegister.Operand, mask);

                    UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value);
                }, false);
            }

            return dest;
        }

        private static Operand GetLslC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift)
        {
            Debug.Assert(m.Type == OperandType.I32);

            if ((uint)shift > 32)
            {
                return GetShiftByMoreThan32(context, carryOut);
            }
            else if (shift == 32)
            {
                if (carryOut.HasValue)
                {
                    SetCarryMLsb(context, m, carryOut.Value);
                }

                return InstEmitCommon.Const(0);
            }
            else
            {
                if (carryOut.HasValue)
                {
                    using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                    context.Arm64Assembler.Lsr(tempRegister.Operand, m, InstEmitCommon.Const(32 - shift));

                    UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value);
                }

                context.Arm64Assembler.Lsl(dest, m, InstEmitCommon.Const(shift));

                return dest;
            }
        }

        private static Operand EmitLsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift)
        {
            Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            Operand mask = tempRegister.Operand;
            context.Arm64Assembler.Uxtb(mask, shift);
            context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(32));
            context.Arm64Assembler.Asr(mask, mask, InstEmitCommon.Const(31));

            context.Arm64Assembler.Lsrv(dest, m, shift);

            // If shift >= 32, force the result to 0.
            context.Arm64Assembler.And(dest, dest, mask);

            if (carryOut.HasValue)
            {
                EmitIfHelper(context, shift, () =>
                {
                    using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                    context.Arm64Assembler.Uxtb(mask, shift);
                    context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(33));
                    context.Arm64Assembler.Lsr(mask, mask, InstEmitCommon.Const(31));
                    context.Arm64Assembler.Sub(tempRegister.Operand, shift, InstEmitCommon.Const(1));
                    context.Arm64Assembler.Lsrv(tempRegister.Operand, m, tempRegister.Operand);
                    context.Arm64Assembler.And(tempRegister.Operand, tempRegister.Operand, mask);

                    UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value);
                }, false);
            }

            return dest;
        }

        public static Operand GetLsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift)
        {
            Debug.Assert(m.Type == OperandType.I32);

            if ((uint)shift > 32)
            {
                return GetShiftByMoreThan32(context, carryOut);
            }
            else if (shift == 32)
            {
                if (carryOut.HasValue)
                {
                    SetCarryMMsb(context, m, carryOut.Value);
                }

                return InstEmitCommon.Const(0);
            }
            else
            {
                if (carryOut.HasValue)
                {
                    SetCarryMShrOut(context, m, shift, carryOut.Value);
                }

                context.Arm64Assembler.Lsr(dest, m, InstEmitCommon.Const(shift));

                return dest;
            }
        }

        private static Operand GetShiftByMoreThan32(CodeGenContext context, Operand? carryOut)
        {
            if (carryOut.HasValue)
            {
                // Clear carry flag.

                context.Arm64Assembler.Bfc(carryOut.Value, 1, 1);
            }

            return InstEmitCommon.Const(0);
        }

        private static Operand EmitAsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift)
        {
            Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            Operand mask = tempRegister.Operand;
            context.Arm64Assembler.Uxtb(mask, shift);
            context.Arm64Assembler.Sub(mask, mask, InstEmitCommon.Const(31));
            context.Arm64Assembler.Orn(mask, shift, mask, ArmShiftType.Asr, 31);

            context.Arm64Assembler.Asrv(dest, m, mask);

            if (carryOut.HasValue)
            {
                EmitIfHelper(context, shift, () =>
                {
                    // If shift >= 32, carry should be equal to the MSB of Rm.

                    using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

                    context.Arm64Assembler.Sub(tempRegister.Operand, mask, InstEmitCommon.Const(1));
                    context.Arm64Assembler.Orr(tempRegister.Operand, tempRegister.Operand, mask, ArmShiftType.Asr, 31);
                    context.Arm64Assembler.Lsrv(tempRegister.Operand, m, tempRegister.Operand);

                    UpdateCarryFlag(context, tempRegister.Operand, carryOut.Value);
                }, false);
            }

            return dest;
        }

        private static Operand GetAsrC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift)
        {
            Debug.Assert(m.Type == OperandType.I32);

            if ((uint)shift >= 32)
            {
                context.Arm64Assembler.Asr(dest, m, InstEmitCommon.Const(31));

                if (carryOut.HasValue)
                {
                    SetCarryMLsb(context, dest, carryOut.Value);
                }

                return dest;
            }
            else
            {
                if (carryOut.HasValue)
                {
                    SetCarryMShrOut(context, m, shift, carryOut.Value);
                }

                context.Arm64Assembler.Asr(dest, m, InstEmitCommon.Const(shift));

                return dest;
            }
        }

        private static Operand EmitRorC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, Operand shift)
        {
            Debug.Assert(m.Type == OperandType.I32 && shift.Type == OperandType.I32);

            context.Arm64Assembler.Rorv(dest, m, shift);

            if (carryOut.HasValue)
            {
                EmitIfHelper(context, shift, () =>
                {
                    SetCarryMMsb(context, m, carryOut.Value);
                }, false);
            }

            return dest;
        }

        private static Operand GetRorC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut, int shift)
        {
            Debug.Assert(m.Type == OperandType.I32);

            shift &= 0x1f;

            context.Arm64Assembler.Ror(dest, m, InstEmitCommon.Const(shift));

            if (carryOut.HasValue)
            {
                SetCarryMMsb(context, dest, carryOut.Value);
            }

            return dest;
        }

        private static Operand GetRrxC(CodeGenContext context, Operand dest, Operand m, Operand? carryOut)
        {
            Debug.Assert(m.Type == OperandType.I32);

            // Rotate right by 1 with carry.

            if (carryOut.HasValue)
            {
                SetCarryMLsb(context, m, carryOut.Value);
            }

            context.Arm64Assembler.Mov(dest, m);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            context.Arm64Assembler.MrsNzcv(tempRegister.Operand);
            context.Arm64Assembler.Bfxil(dest, tempRegister.Operand, 29, 1);
            context.Arm64Assembler.Ror(dest, dest, InstEmitCommon.Const(1));

            return dest;
        }

        private static void SetCarryMLsb(CodeGenContext context, Operand m, Operand carryOut)
        {
            Debug.Assert(m.Type == OperandType.I32);

            UpdateCarryFlag(context, m, carryOut);
        }

        private static void SetCarryMMsb(CodeGenContext context, Operand m, Operand carryOut)
        {
            Debug.Assert(m.Type == OperandType.I32);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            context.Arm64Assembler.Lsr(tempRegister.Operand, m, InstEmitCommon.Const(31));

            UpdateCarryFlag(context, tempRegister.Operand, carryOut);
        }

        private static void SetCarryMShrOut(CodeGenContext context, Operand m, int shift, Operand carryOut)
        {
            Debug.Assert(m.Type == OperandType.I32);

            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            context.Arm64Assembler.Lsr(tempRegister.Operand, m, InstEmitCommon.Const(shift - 1));

            UpdateCarryFlag(context, tempRegister.Operand, carryOut);
        }

        private static void UpdateCarryFlag(CodeGenContext context, Operand value, Operand carryOut)
        {
            context.Arm64Assembler.Bfi(carryOut, value, 1, 1);
        }

        private static void EmitTeq(CodeGenContext context, Operand rnOperand, Operand rmOperand)
        {
            using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();

            context.Arm64Assembler.Eors(tempRegister.Operand, rnOperand, rmOperand);
        }
    }
}
