Cores/Nds/ARMInterpreter_LoadStore.cs
using System.Numerics;

namespace Emulotl.Nds;

public static partial class ARMInterpreter
{
	private static int RnIdx( ARM cpu ) => (int)((cpu.CurInstr >> 16) & 0xF);
	private static int RdIdx( ARM cpu ) => (int)((cpu.CurInstr >> 12) & 0xF);
	private static bool Wb( ARM cpu ) => (cpu.CurInstr & (1 << 21)) != 0;

	private static uint OffImm( ARM cpu )
	{
		uint offset = cpu.CurInstr & 0xFFF;
		if ( (cpu.CurInstr & (1 << 23)) == 0 ) offset = 0u - offset;
		return offset;
	}

	private static uint OffReg( ARM cpu, int mode )
	{
		uint offset = cpu.R[cpu.CurInstr & 0xF];
		uint shift = (cpu.CurInstr >> 7) & 0x1F;
		Shift( cpu, ref offset, shift, mode, false, false );
		if ( (cpu.CurInstr & (1 << 23)) == 0 ) offset = 0u - offset;
		return offset;
	}

	private static uint OffHDImm( ARM cpu )
	{
		uint offset = (cpu.CurInstr & 0xF) | ((cpu.CurInstr >> 4) & 0xF0);
		if ( (cpu.CurInstr & (1 << 23)) == 0 ) offset = 0u - offset;
		return offset;
	}

	private static uint OffHDReg( ARM cpu )
	{
		uint offset = cpu.R[cpu.CurInstr & 0xF];
		if ( (cpu.CurInstr & (1 << 23)) == 0 ) offset = 0u - offset;
		return offset;
	}

	private static void Op_STR( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		uint sv = cpu.R[RdIdx( cpu )];
		if ( RdIdx( cpu ) == 15 ) sv += 4;
		cpu.DataWrite32( offset, sv );
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		cpu.AddCycles_CD();
	}

	private static void Op_STR_POST( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		uint sv = cpu.R[RdIdx( cpu )];
		if ( RdIdx( cpu ) == 15 ) sv += 4;
		cpu.DataWrite32( addr, sv );
		cpu.R[rn] += offset;
		cpu.AddCycles_CD();
	}

	private static void Op_STRB( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		cpu.DataWrite8( offset, (byte)cpu.R[RdIdx( cpu )] );
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		cpu.AddCycles_CD();
	}

	private static void Op_STRB_POST( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		cpu.DataWrite8( addr, (byte)cpu.R[RdIdx( cpu )] );
		cpu.R[rn] += offset;
		cpu.AddCycles_CD();
	}

	private static void Op_LDR( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		uint val = 0;
		cpu.DataRead32( offset, ref val );
		val = ARM.ROR( val, (offset & 0x3) << 3 );
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		cpu.AddCycles_CDI();
		int rd = RdIdx( cpu );
		if ( rd == 15 )
		{
			if ( cpu.Num == 1 ) val &= ~0x1u;
			cpu.JumpTo( val );
		}
		else cpu.R[rd] = val;
	}

	private static void Op_LDR_POST( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		uint val = 0;
		cpu.DataRead32( addr, ref val );
		val = ARM.ROR( val, (addr & 0x3) << 3 );
		cpu.R[rn] += offset;
		cpu.AddCycles_CDI();
		int rd = RdIdx( cpu );
		if ( rd == 15 )
		{
			if ( cpu.Num == 1 ) val &= ~0x1u;
			cpu.JumpTo( val );
		}
		else cpu.R[rd] = val;
	}

	private static void Op_LDRB( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		uint val = 0;
		cpu.DataRead8( offset, ref val );
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		cpu.AddCycles_CDI();
		cpu.R[RdIdx( cpu )] = val;
	}

	private static void Op_LDRB_POST( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		uint val = 0;
		cpu.DataRead8( addr, ref val );
		cpu.R[rn] += offset;
		cpu.AddCycles_CDI();
		cpu.R[RdIdx( cpu )] = val;
	}

	private static void Op_STRH( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		cpu.DataWrite16( offset, (ushort)cpu.R[RdIdx( cpu )] );
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		cpu.AddCycles_CD();
	}

	private static void Op_STRH_POST( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		cpu.DataWrite16( addr, (ushort)cpu.R[RdIdx( cpu )] );
		cpu.R[rn] += offset;
		cpu.AddCycles_CD();
	}

	private static void Op_LDRH( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		uint val = 0;
		cpu.DataRead16( offset, ref val );
		cpu.R[RdIdx( cpu )] = val;
		cpu.AddCycles_CDI();
	}

	private static void Op_LDRH_POST( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		cpu.R[rn] += offset;
		uint val = 0;
		cpu.DataRead16( addr, ref val );
		cpu.R[RdIdx( cpu )] = val;
		cpu.AddCycles_CDI();
	}

	private static void Op_LDRSB( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		uint val = 0;
		cpu.DataRead8( offset, ref val );
		cpu.R[RdIdx( cpu )] = (uint)(sbyte)val;
		cpu.AddCycles_CDI();
	}

	private static void Op_LDRSB_POST( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		cpu.R[rn] += offset;
		uint val = 0;
		cpu.DataRead8( addr, ref val );
		cpu.R[RdIdx( cpu )] = (uint)(sbyte)val;
		cpu.AddCycles_CDI();
	}

	private static void Op_LDRSH( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		uint val = 0;
		cpu.DataRead16( offset, ref val );
		cpu.R[RdIdx( cpu )] = (uint)(short)val;
		cpu.AddCycles_CDI();
	}

	private static void Op_LDRSH_POST( ARM cpu, uint offset )
	{
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		cpu.R[rn] += offset;
		uint val = 0;
		cpu.DataRead16( addr, ref val );
		cpu.R[RdIdx( cpu )] = (uint)(short)val;
		cpu.AddCycles_CDI();
	}

	private static void Op_LDRD( ARM cpu, uint offset )
	{
		if ( cpu.Num != 0 ) return;
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		int r = RdIdx( cpu );
		if ( (r & 1) != 0 ) r--;
		uint v0 = 0, v1 = 0;
		cpu.DataRead32( offset, ref v0 );
		cpu.DataRead32S( offset + 4, ref v1 );
		cpu.R[r] = v0;
		cpu.R[r + 1] = v1;
		cpu.AddCycles_CDI();
	}

	private static void Op_LDRD_POST( ARM cpu, uint offset )
	{
		if ( cpu.Num != 0 ) return;
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		cpu.R[rn] += offset;
		int r = RdIdx( cpu );
		if ( (r & 1) != 0 ) r--;
		uint v0 = 0, v1 = 0;
		cpu.DataRead32( addr, ref v0 );
		cpu.DataRead32S( addr + 4, ref v1 );
		cpu.R[r] = v0;
		cpu.R[r + 1] = v1;
		cpu.AddCycles_CDI();
	}

	private static void Op_STRD( ARM cpu, uint offset )
	{
		if ( cpu.Num != 0 ) return;
		int rn = RnIdx( cpu );
		offset += cpu.R[rn];
		if ( Wb( cpu ) ) cpu.R[rn] = offset;
		int r = RdIdx( cpu );
		if ( (r & 1) != 0 ) r--;
		cpu.DataWrite32( offset, cpu.R[r] );
		cpu.DataWrite32S( offset + 4, cpu.R[r + 1] );
		cpu.AddCycles_CD();
	}

	private static void Op_STRD_POST( ARM cpu, uint offset )
	{
		if ( cpu.Num != 0 ) return;
		int rn = RnIdx( cpu );
		uint addr = cpu.R[rn];
		cpu.R[rn] += offset;
		int r = RdIdx( cpu );
		if ( (r & 1) != 0 ) r--;
		cpu.DataWrite32( addr, cpu.R[r] );
		cpu.DataWrite32S( addr + 4, cpu.R[r + 1] );
		cpu.AddCycles_CD();
	}

	public static void A_SWP( ARM cpu )
	{
		uint @base = cpu.R[(cpu.CurInstr >> 16) & 0xF];
		uint rm = cpu.R[cpu.CurInstr & 0xF];

		uint val = 0;
		cpu.DataRead32( @base, ref val );
		cpu.R[(cpu.CurInstr >> 12) & 0xF] = ARM.ROR( val, 8 * (@base & 0x3) );

		int numD = cpu.DataCycles;
		cpu.DataWrite32( @base, rm );
		cpu.DataCycles += numD;

		cpu.AddCycles_CDI();
	}

	public static void A_SWPB( ARM cpu )
	{
		uint @base = cpu.R[(cpu.CurInstr >> 16) & 0xF];
		uint rm = cpu.R[cpu.CurInstr & 0xF] & 0xFF;

		uint val = 0;
		cpu.DataRead8( @base, ref val );
		cpu.R[(cpu.CurInstr >> 12) & 0xF] = val;

		int numD = cpu.DataCycles;
		cpu.DataWrite8( @base, (byte)rm );
		cpu.DataCycles += numD;

		cpu.AddCycles_CDI();
	}

	public static void A_LDM( ARM cpu )
	{
		uint baseid = (cpu.CurInstr >> 16) & 0xF;
		uint @base = cpu.R[baseid];
		uint wbbase = 0;
		bool preinc = (cpu.CurInstr & (1 << 24)) != 0;
		bool first = true;

		if ( (cpu.CurInstr & (1 << 23)) == 0 )
		{
			@base -= 4 * (uint)BitOperations.PopCount( cpu.CurInstr & 0xFFFF );
			if ( (cpu.CurInstr & (1 << 21)) != 0 )
				wbbase = @base;
			preinc = !preinc;
		}

		if ( (cpu.CurInstr & (1 << 22)) != 0 && (cpu.CurInstr & (1 << 15)) == 0 )
			cpu.UpdateMode( cpu.CPSR, (cpu.CPSR & ~0x1Fu) | 0x10, true );

		for ( int i = 0; i < 15; i++ )
		{
			if ( (cpu.CurInstr & (1 << i)) != 0 )
			{
				if ( preinc ) @base += 4;
				uint v = 0;
				if ( first ) cpu.DataRead32( @base, ref v );
				else cpu.DataRead32S( @base, ref v );
				cpu.R[i] = v;
				first = false;
				if ( !preinc ) @base += 4;
			}
		}

		uint pc = 0;
		if ( (cpu.CurInstr & (1 << 15)) != 0 )
		{
			if ( preinc ) @base += 4;
			if ( first ) cpu.DataRead32( @base, ref pc );
			else cpu.DataRead32S( @base, ref pc );
			if ( !preinc ) @base += 4;

			if ( cpu.Num == 1 ) pc &= ~0x1u;
		}

		if ( (cpu.CurInstr & (1 << 21)) != 0 )
		{
			if ( (cpu.CurInstr & (1 << 23)) != 0 )
				wbbase = @base;

			if ( (cpu.CurInstr & (1 << (int)baseid)) != 0 )
			{
				if ( cpu.Num == 0 )
				{
					uint rlist = cpu.CurInstr & 0xFFFF;
					if ( (rlist & ~(1u << (int)baseid)) == 0 || (rlist & ~((2u << (int)baseid) - 1)) != 0 )
						cpu.R[baseid] = wbbase;
				}
			}
			else cpu.R[baseid] = wbbase;
		}

		if ( (cpu.CurInstr & (1 << 22)) != 0 && (cpu.CurInstr & (1 << 15)) == 0 )
			cpu.UpdateMode( (cpu.CPSR & ~0x1Fu) | 0x10, cpu.CPSR, true );

		if ( (cpu.CurInstr & (1 << 15)) != 0 )
			cpu.JumpTo( pc, (cpu.CurInstr & (1 << 22)) != 0 );

		cpu.AddCycles_CDI();
	}

	public static void A_STM( ARM cpu )
	{
		uint baseid = (cpu.CurInstr >> 16) & 0xF;
		uint @base = cpu.R[baseid];
		uint oldbase = @base;
		bool preinc = (cpu.CurInstr & (1 << 24)) != 0;
		bool first = true;

		if ( (cpu.CurInstr & (1 << 23)) == 0 )
		{
			@base -= 4 * (uint)BitOperations.PopCount( cpu.CurInstr & 0xFFFF );
			if ( (cpu.CurInstr & (1 << 21)) != 0 )
				cpu.R[baseid] = @base;
			preinc = !preinc;
		}

		bool isbanked = false;
		if ( (cpu.CurInstr & (1 << 22)) != 0 )
		{
			uint mode = cpu.CPSR & 0x1F;
			if ( mode == 0x11 )
				isbanked = baseid >= 8 && baseid < 15;
			else if ( mode != 0x10 && mode != 0x1F )
				isbanked = baseid >= 13 && baseid < 15;

			cpu.UpdateMode( cpu.CPSR, (cpu.CPSR & ~0x1Fu) | 0x10, true );
		}

		for ( int i = 0; i < 16; i++ )
		{
			if ( (cpu.CurInstr & (1 << i)) != 0 )
			{
				if ( preinc ) @base += 4;

				if ( i == baseid && !isbanked )
				{
					uint val = (cpu.Num == 0 || (cpu.CurInstr & ((1 << i) - 1)) == 0) ? oldbase : @base;
					if ( first ) cpu.DataWrite32( @base, val );
					else cpu.DataWrite32S( @base, val );
				}
				else
				{
					if ( first ) cpu.DataWrite32( @base, cpu.R[i] );
					else cpu.DataWrite32S( @base, cpu.R[i] );
				}

				first = false;

				if ( !preinc ) @base += 4;
			}
		}

		if ( (cpu.CurInstr & (1 << 22)) != 0 )
			cpu.UpdateMode( (cpu.CPSR & ~0x1Fu) | 0x10, cpu.CPSR, true );

		if ( (cpu.CurInstr & (1 << 23)) != 0 && (cpu.CurInstr & (1 << 21)) != 0 )
			cpu.R[baseid] = @base;

		cpu.AddCycles_CD();
	}

	private static Action<ARM, uint> WordBody( int l, int b, int p )
	{
		if ( l == 0 )
			return b == 0 ? (p == 1 ? Op_STR : Op_STR_POST) : (p == 1 ? Op_STRB : Op_STRB_POST);
		return b == 0 ? (p == 1 ? Op_LDR : Op_LDR_POST) : (p == 1 ? Op_LDRB : Op_LDRB_POST);
	}

	private static Action<ARM, uint> HalfwordBody( int sh, int l, int p )
	{
		if ( sh == 1 )
			return l == 0 ? (p == 1 ? Op_STRH : Op_STRH_POST) : (p == 1 ? Op_LDRH : Op_LDRH_POST);
		if ( sh == 2 )
			return l == 0 ? (p == 1 ? Op_LDRD : Op_LDRD_POST) : (p == 1 ? Op_LDRSB : Op_LDRSB_POST);
		return l == 0 ? (p == 1 ? Op_STRD : Op_STRD_POST) : (p == 1 ? Op_LDRSH : Op_LDRSH_POST);
	}

	private static void BuildLoadStoreTable()
	{
		for ( int h = 0x40; h <= 0x7F; h++ )
		{
			int i = (h >> 5) & 1;
			int p = (h >> 4) & 1;
			int b = (h >> 2) & 1;
			int l = h & 1;
			Action<ARM, uint> body = WordBody( l, b, p );

			if ( i == 0 )
			{
				for ( int low = 0; low < 16; low++ )
					ARMInstrTable[(h << 4) | low] = MakeLsImm( body );
			}
			else
			{
				for ( int low = 0; low < 16; low += 2 )
				{
					int mode = (low >> 1) & 3;
					ARMInstrTable[(h << 4) | low] = MakeLsReg( body, mode );
				}
			}
		}

		(int low, int sh)[] hd = [(0xB, 1), (0xD, 2), (0xF, 3)];
		for ( int h = 0; h <= 0x1F; h++ )
		{
			int p = (h >> 4) & 1;
			int immOff = (h >> 2) & 1;
			int l = h & 1;

			foreach ( var (low, sh) in hd )
			{
				Action<ARM, uint> body = HalfwordBody( sh, l, p );
				ARMInstrTable[(h << 4) | low] = immOff == 1 ? MakeHdImm( body ) : MakeHdReg( body );
			}
		}

		ARMInstrTable[0x109] = A_SWP;
		ARMInstrTable[0x149] = A_SWPB;

		for ( int h = 0x80; h <= 0x9F; h++ )
		{
			InstrFunc fn = (h & 1) == 1 ? A_LDM : A_STM;
			for ( int low = 0; low < 16; low++ )
				ARMInstrTable[(h << 4) | low] = fn;
		}
	}

	private static InstrFunc MakeLsImm( Action<ARM, uint> body ) => cpu => body( cpu, OffImm( cpu ) );
	private static InstrFunc MakeLsReg( Action<ARM, uint> body, int mode ) => cpu => body( cpu, OffReg( cpu, mode ) );
	private static InstrFunc MakeHdImm( Action<ARM, uint> body ) => cpu => body( cpu, OffHDImm( cpu ) );
	private static InstrFunc MakeHdReg( Action<ARM, uint> body ) => cpu => body( cpu, OffHDReg( cpu ) );
}