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

using static ChocolArm64.Instructions.InstEmit32Helper;
using static ChocolArm64.Instructions.InstEmitAluHelper;

namespace ChocolArm64.Instructions
{
    static partial class InstEmit32
    {
        public static void Add(ILEmitterCtx context)
        {
            IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;

            EmitAluLoadOpers(context, setCarry: false);

            context.Emit(OpCodes.Add);

            if (op.SetFlags)
            {
                context.EmitZnFlagCheck();

                EmitAddsCCheck(context);
                EmitAddsVCheck(context);
            }

            EmitAluStore(context);
        }

        public static void Cmp(ILEmitterCtx context)
        {
            IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;

            EmitAluLoadOpers(context, setCarry: false);

            context.Emit(OpCodes.Sub);

            context.EmitZnFlagCheck();

            EmitSubsCCheck(context);
            EmitSubsVCheck(context);

            context.Emit(OpCodes.Pop);
        }

        public static void Mov(ILEmitterCtx context)
        {
            IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;

            EmitAluLoadOper2(context);

            if (op.SetFlags)
            {
                context.EmitZnFlagCheck();
            }

            EmitAluStore(context);
        }

        public static void Sub(ILEmitterCtx context)
        {
            IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;

            EmitAluLoadOpers(context, setCarry: false);

            context.Emit(OpCodes.Sub);

            if (op.SetFlags)
            {
                context.EmitZnFlagCheck();

                EmitSubsCCheck(context);
                EmitSubsVCheck(context);
            }

            EmitAluStore(context);
        }

        private static void EmitAluStore(ILEmitterCtx context)
        {
            IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;

            if (op.Rd == RegisterAlias.Aarch32Pc)
            {
                if (op.SetFlags)
                {
                    // TODO: Load SPSR etc.

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

                    ILLabel lblThumb = new ILLabel();
                    ILLabel lblEnd   = new ILLabel();

                    context.Emit(OpCodes.Brtrue_S, lblThumb);

                    context.EmitLdc_I4(~3);

                    context.Emit(OpCodes.Br_S, lblEnd);

                    context.MarkLabel(lblThumb);

                    context.EmitLdc_I4(~1);

                    context.MarkLabel(lblEnd);

                    context.Emit(OpCodes.And);
                    context.Emit(OpCodes.Conv_U8);
                    context.Emit(OpCodes.Ret);
                }
                else
                {
                    EmitAluWritePc(context);
                }
            }
            else
            {
                context.EmitStint(GetRegisterAlias(context.Mode, op.Rd));
            }
        }

        private static void EmitAluWritePc(ILEmitterCtx context)
        {
            context.EmitStoreContext();

            if (IsThumb(context.CurrOp))
            {
                context.EmitLdc_I4(~1);

                context.Emit(OpCodes.And);
                context.Emit(OpCodes.Conv_U8);
                context.Emit(OpCodes.Ret);
            }
            else
            {
                EmitBxWritePc(context);
            }
        }
    }
}