Cores/Nds/ARMv4.cs
namespace Emulotl.Nds;

public sealed class ARMv4 : ARM
{
	public ARMv4( NDS nds ) : base( 1, nds ) { }

	public override void FillPipeline()
	{
		SetupCodeMem( R[15] );

		if ( (CPSR & 0x20) != 0 )
		{
			NextInstr[0] = CodeRead16( R[15] - 2 );
			NextInstr[1] = CodeRead16( R[15] );
		}
		else
		{
			NextInstr[0] = CodeRead32( R[15] - 4 );
			NextInstr[1] = CodeRead32( R[15] );
		}
	}

	public override void JumpTo( uint addr, bool restorecpsr = false )
	{
		if ( restorecpsr )
		{
			RestoreCPSR();

			if ( (CPSR & 0x20) != 0 ) addr |= 0x1;
			else addr &= ~0x1u;
		}

		CodeRegion = addr >> 24;
		CodeCycles = (int)(addr >> 15);
		SetupCodeMem( addr );

		if ( (addr & 0x1) != 0 )
		{
			addr &= ~0x1u;
			R[15] = addr + 2;

			NextInstr[0] = CodeRead16( addr );
			NextInstr[1] = CodeRead16( addr + 2 );
			Cycles += NDS.ARM7MemTimings[CodeCycles, 0] + NDS.ARM7MemTimings[CodeCycles, 1];

			CPSR |= 0x20;
		}
		else
		{
			addr &= ~0x3u;
			R[15] = addr + 4;

			NextInstr[0] = CodeRead32( addr );
			NextInstr[1] = CodeRead32( addr + 4 );
			Cycles += NDS.ARM7MemTimings[CodeCycles, 2] + NDS.ARM7MemTimings[CodeCycles, 3];

			CPSR &= ~0x20u;
		}
	}

	public void Execute()
	{
		if ( Halted != 0 )
		{
			if ( Halted == 2 )
			{
				Halted = 0;
			}
			else if ( NDS.HaltInterrupted( 1 ) )
			{
				Halted = 0;
				if ( (NDS.IME[1] & 0x1) != 0 )
					TriggerIRQ();
			}
			else
			{
				NDS.ARM7Timestamp = NDS.ARM7Target;
				return;
			}
		}

		while ( NDS.ARM7Timestamp < NDS.ARM7Target )
		{
			if ( (CPSR & 0x20) != 0 )
			{
				R[15] += 2;
				CurInstr = NextInstr[0];
				NextInstr[0] = NextInstr[1];
				NextInstr[1] = CodeRead16( R[15] );

				uint icode = CurInstr >> 6;
				ARMInterpreter.THUMBInstrTable[(int)icode]( this );
			}
			else
			{
				R[15] += 4;
				CurInstr = NextInstr[0];
				NextInstr[0] = NextInstr[1];
				NextInstr[1] = CodeRead32( R[15] );

				if ( CheckCondition( CurInstr >> 28 ) )
				{
					uint icode = ((CurInstr >> 4) & 0xF) | ((CurInstr >> 16) & 0xFF0);
					ARMInterpreter.ARMInstrTable[(int)icode]( this );
				}
				else
				{
					AddCycles_C();
				}
			}

			if ( Halted != 0 )
			{
				if ( Halted == 1 && NDS.ARM7Timestamp < NDS.ARM7Target )
					NDS.ARM7Timestamp = NDS.ARM7Target;
				break;
			}

			if ( IRQ != 0 )
				TriggerIRQ();

			NDS.ARM7Timestamp += Cycles;
			Cycles = 0;
		}

		if ( Halted == 2 )
			Halted = 0;
	}

	public ushort CodeRead16( uint addr )
	{
		if ( (addr & 0xFF800000) != CodeMemBase )
			SetupCodeMem( addr );
		if ( CodeMem.Mem != null )
		{
			uint o = CodeMem.Offset + (addr & CodeMem.Mask);
			return (ushort)(CodeMem.Mem[o] | (CodeMem.Mem[o + 1] << 8));
		}
		return BusRead16( addr );
	}

	public uint CodeRead32( uint addr )
	{
		if ( (addr & 0xFF800000) != CodeMemBase )
			SetupCodeMem( addr );
		if ( CodeMem.Mem != null )
		{
			uint o = CodeMem.Offset + (addr & CodeMem.Mask);
			return (uint)(CodeMem.Mem[o] | (CodeMem.Mem[o + 1] << 8) | (CodeMem.Mem[o + 2] << 16) | (CodeMem.Mem[o + 3] << 24));
		}
		return BusRead32( addr );
	}

	public override void DataRead8( uint addr, ref uint val )
	{
		val = BusRead8( addr );
		DataRegion = addr;
		DataCycles = NDS.ARM7MemTimings[addr >> 15, 0];
	}

	private static bool IsMainRam( uint addr ) => (addr & 0xFF000000) == 0x02000000;
	private static ushort RdRam16( byte[] m, uint o ) => (ushort)(m[o] | (m[o + 1] << 8));
	private static uint RdRam32( byte[] m, uint o ) => (uint)(m[o] | (m[o + 1] << 8) | (m[o + 2] << 16) | (m[o + 3] << 24));
	private static void WrRam16( byte[] m, uint o, ushort v ) { m[o] = (byte)v; m[o + 1] = (byte)(v >> 8); }
	private static void WrRam32( byte[] m, uint o, uint v ) { m[o] = (byte)v; m[o + 1] = (byte)(v >> 8); m[o + 2] = (byte)(v >> 16); m[o + 3] = (byte)(v >> 24); }

	public override void DataRead16( uint addr, ref uint val )
	{
		addr &= ~1u;
		if ( IsMainRam( addr ) ) val = RdRam16( NDS.MainRAM, addr & NDS.MainRAMMask );
		else val = BusRead16( addr );
		DataRegion = addr;
		DataCycles = NDS.ARM7MemTimings[addr >> 15, 0];
	}

	public override void DataRead32( uint addr, ref uint val )
	{
		addr &= ~3u;
		if ( IsMainRam( addr ) ) val = RdRam32( NDS.MainRAM, addr & NDS.MainRAMMask );
		else val = BusRead32( addr );
		DataRegion = addr;
		DataCycles = NDS.ARM7MemTimings[addr >> 15, 2];
	}

	public override void DataRead32S( uint addr, ref uint val )
	{
		addr &= ~3u;
		if ( IsMainRam( addr ) ) val = RdRam32( NDS.MainRAM, addr & NDS.MainRAMMask );
		else val = BusRead32( addr );
		DataCycles += NDS.ARM7MemTimings[addr >> 15, 3];
	}

	public override void DataWrite8( uint addr, byte val )
	{
		BusWrite8( addr, val );
		DataRegion = addr;
		DataCycles = NDS.ARM7MemTimings[addr >> 15, 0];
	}

	public override void DataWrite16( uint addr, ushort val )
	{
		addr &= ~1u;
		if ( IsMainRam( addr ) ) WrRam16( NDS.MainRAM, addr & NDS.MainRAMMask, val );
		else BusWrite16( addr, val );
		DataRegion = addr;
		DataCycles = NDS.ARM7MemTimings[addr >> 15, 0];
	}

	public override void DataWrite32( uint addr, uint val )
	{
		addr &= ~3u;
		if ( IsMainRam( addr ) ) WrRam32( NDS.MainRAM, addr & NDS.MainRAMMask, val );
		else BusWrite32( addr, val );
		DataRegion = addr;
		DataCycles = NDS.ARM7MemTimings[addr >> 15, 2];
	}

	public override void DataWrite32S( uint addr, uint val )
	{
		addr &= ~3u;
		if ( IsMainRam( addr ) ) WrRam32( NDS.MainRAM, addr & NDS.MainRAMMask, val );
		else BusWrite32( addr, val );
		DataCycles += NDS.ARM7MemTimings[addr >> 15, 3];
	}

	public override void AddCycles_C()
	{
		Cycles += NDS.ARM7MemTimings[CodeCycles, (CPSR & 0x20) != 0 ? 1 : 3];
	}

	public override void AddCycles_CI( int num )
	{
		Cycles += NDS.ARM7MemTimings[CodeCycles, (CPSR & 0x20) != 0 ? 0 : 2] + num;
	}

	public override void AddCycles_CDI()
	{
		int numC = NDS.ARM7MemTimings[CodeCycles, (CPSR & 0x20) != 0 ? 0 : 2];
		int numD = DataCycles;

		if ( (DataRegion >> 24) == 0x02 )
		{
			if ( CodeRegion == 0x02 )
				Cycles += numC + numD;
			else
			{
				numC++;
				Cycles += Math.Max( numC + numD - 3, Math.Max( numC, numD ) );
			}
		}
		else if ( CodeRegion == 0x02 )
		{
			numD++;
			Cycles += Math.Max( numC + numD - 3, Math.Max( numC, numD ) );
		}
		else
		{
			Cycles += numC + numD + 1;
		}
	}

	public override void AddCycles_CD()
	{
		int numC = NDS.ARM7MemTimings[CodeCycles, (CPSR & 0x20) != 0 ? 0 : 2];
		int numD = DataCycles;

		if ( (DataRegion >> 24) == 0x02 )
		{
			if ( CodeRegion == 0x02 )
				Cycles += numC + numD;
			else
				Cycles += Math.Max( numC + numD - 3, Math.Max( numC, numD ) );
		}
		else if ( CodeRegion == 0x02 )
		{
			Cycles += Math.Max( numC + numD - 3, Math.Max( numC, numD ) );
		}
		else
		{
			Cycles += numC + numD;
		}
	}

	protected override byte BusRead8( uint addr ) => NDS.ARM7Read8( addr );
	protected override ushort BusRead16( uint addr ) => NDS.ARM7Read16( addr );
	protected override uint BusRead32( uint addr ) => NDS.ARM7Read32( addr );
	protected override void BusWrite8( uint addr, byte val ) => NDS.ARM7Write8( addr, val );
	protected override void BusWrite16( uint addr, ushort val ) => NDS.ARM7Write16( addr, val );
	protected override void BusWrite32( uint addr, uint val ) => NDS.ARM7Write32( addr, val );
}