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

namespace ChocolArm64.Instruction
{
    static partial class AInstEmit
    {
        public static void Bfm(AILEmitterCtx Context)
        {
            AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;

            EmitBfmLoadRn(Context);

            Context.EmitLdintzr(Op.Rd);
            Context.EmitLdc_I(~Op.WMask & Op.TMask);

            Context.Emit(OpCodes.And);
            Context.Emit(OpCodes.Or);

            Context.EmitLdintzr(Op.Rd);
            Context.EmitLdc_I(~Op.TMask);

            Context.Emit(OpCodes.And);
            Context.Emit(OpCodes.Or);

            Context.EmitStintzr(Op.Rd);
        }

        public static void Sbfm(AILEmitterCtx Context)
        {
            AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;

            int BitsCount = Op.GetBitsCount();

            if (Op.Pos + 1 == BitsCount)
            {
                EmitSbfmShift(Context);
            }
            else if (Op.Pos < Op.Shift)
            {
                EmitSbfiz(Context);
            }
            else if (Op.Pos == 7 && Op.Shift == 0)
            {
                EmitSbfmCast(Context, OpCodes.Conv_I1);
            }
            else if (Op.Pos == 15 && Op.Shift == 0)
            {
                EmitSbfmCast(Context, OpCodes.Conv_I2);
            }
            else if (Op.Pos == 31 && Op.Shift == 0)
            {
                EmitSbfmCast(Context, OpCodes.Conv_I4);
            }
            else
            {
                EmitBfmLoadRn(Context);

                Context.EmitLdintzr(Op.Rn);

                Context.EmitLsl(BitsCount - 1 - Op.Pos);
                Context.EmitAsr(BitsCount - 1);

                Context.EmitLdc_I(~Op.TMask);

                Context.Emit(OpCodes.And);
                Context.Emit(OpCodes.Or);

                Context.EmitStintzr(Op.Rd);
            }
        }

        public static void Ubfm(AILEmitterCtx Context)
        {
            AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;

            if (Op.Pos + 1 == Op.GetBitsCount())
            {
                EmitUbfmShift(Context);
            }
            else if (Op.Pos < Op.Shift)
            {
                EmitUbfiz(Context);
            }
            else if (Op.Pos + 1 == Op.Shift)
            {
                EmitBfmLsl(Context);
            }
            else if (Op.Pos == 7 && Op.Shift == 0)
            {
                EmitUbfmCast(Context, OpCodes.Conv_U1);
            }
            else if (Op.Pos == 15 && Op.Shift == 0)
            {
                EmitUbfmCast(Context, OpCodes.Conv_U2);
            }
            else
            {
                EmitBfmLoadRn(Context);

                Context.EmitStintzr(Op.Rd);
            }
        }

        private static void EmitSbfiz(AILEmitterCtx Context) => EmitBfiz(Context, true);
        private static void EmitUbfiz(AILEmitterCtx Context) => EmitBfiz(Context, false);

        private static void EmitBfiz(AILEmitterCtx Context, bool Signed)
        {
            AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;

            int Width = Op.Pos + 1;

            Context.EmitLdintzr(Op.Rn);

            Context.EmitLsl(Op.GetBitsCount() - Width);

            if (Signed)
            {
                Context.EmitAsr(Op.Shift - Width);
            }
            else
            {
                Context.EmitLsr(Op.Shift - Width);
            }

            Context.EmitStintzr(Op.Rd);
        }

        private static void EmitSbfmCast(AILEmitterCtx Context, OpCode ILOp)
        {
            EmitBfmCast(Context, ILOp, true);
        }

        private static void EmitUbfmCast(AILEmitterCtx Context, OpCode ILOp)
        {
            EmitBfmCast(Context, ILOp, false);
        }

        private static void EmitBfmCast(AILEmitterCtx Context, OpCode ILOp, bool Signed)
        {
            AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;

            Context.EmitLdintzr(Op.Rn);

            Context.Emit(ILOp);

            if (Op.RegisterSize != ARegisterSize.Int32)
            {
                Context.Emit(Signed
                    ? OpCodes.Conv_I8
                    : OpCodes.Conv_U8);
            }

            Context.EmitStintzr(Op.Rd);
        }

        private static void EmitSbfmShift(AILEmitterCtx Context)
        {
            EmitBfmShift(Context, true);
        }

        private static void EmitUbfmShift(AILEmitterCtx Context)
        {
            EmitBfmShift(Context, false);
        }

        private static void EmitBfmShift(AILEmitterCtx Context, bool Signed)
        {
            AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;

            Context.EmitLdintzr(Op.Rn);
            Context.EmitLdc_I4(Op.Shift);

            Context.Emit(Signed
                ? OpCodes.Shr
                : OpCodes.Shr_Un);

            Context.EmitStintzr(Op.Rd);
        }

        private static void EmitBfmLsl(AILEmitterCtx Context)
        {
            AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;

            Context.EmitLdintzr(Op.Rn);

            Context.EmitLsl(Op.GetBitsCount() - Op.Shift);

            Context.EmitStintzr(Op.Rd);
        }

        private static void EmitBfmLoadRn(AILEmitterCtx Context)
        {
            AOpCodeBfm Op = (AOpCodeBfm)Context.CurrOp;

            Context.EmitLdintzr(Op.Rn);

            Context.EmitRor(Op.Shift);

            Context.EmitLdc_I(Op.WMask & Op.TMask);

            Context.Emit(OpCodes.And);
        }
    }
}