Cores/Nds/NDS.Math.cs
namespace Emulotl.Nds;

public sealed partial class NDS
{
	public ushort DivCnt;
	public uint[] DivNumerator = new uint[2];
	public uint[] DivDenominator = new uint[2];
	public uint[] DivQuotient = new uint[2];
	public uint[] DivRemainder = new uint[2];

	public ushort SqrtCnt;
	public uint[] SqrtVal = new uint[2];
	public uint SqrtRes;

	private void ResetMath()
	{
		DivCnt = 0;
		SqrtCnt = 0;
		Array.Clear( DivNumerator );
		Array.Clear( DivDenominator );
		Array.Clear( DivQuotient );
		Array.Clear( DivRemainder );
		Array.Clear( SqrtVal );
		SqrtRes = 0;
	}

	private long DivNum64() => (long)(((ulong)DivNumerator[1] << 32) | DivNumerator[0]);
	private long DivDen64() => (long)(((ulong)DivDenominator[1] << 32) | DivDenominator[0]);

	private void SetQuotient( long v )
	{
		DivQuotient[0] = (uint)v;
		DivQuotient[1] = (uint)((ulong)v >> 32);
	}

	private void SetRemainder( long v )
	{
		DivRemainder[0] = (uint)v;
		DivRemainder[1] = (uint)((ulong)v >> 32);
	}

	public void StartDiv()
	{
		CancelEvent( SysEvent.Div );
		DivCnt |= 0x8000;
		ScheduleEvent( SysEvent.Div, false, (DivCnt & 0x3) == 0 ? 18 : 34, OnDivDone );
	}

	private void OnDivDone( uint param ) => DivDone();

	private void DivDone()
	{
		DivCnt &= 0x3FFF;

		switch ( DivCnt & 0x0003 )
		{
			case 0x0000:
				{
					int num = (int)DivNumerator[0];
					int den = (int)DivDenominator[0];
					if ( den == 0 )
					{
						DivQuotient[0] = (uint)(num < 0 ? 1 : -1);
						DivQuotient[1] = (uint)(num < 0 ? -1 : 0);
						SetRemainder( num );
					}
					else if ( num == int.MinValue && den == -1 )
					{
						SetQuotient( 0x80000000L );
					}
					else
					{
						SetQuotient( num / den );
						SetRemainder( num % den );
					}
					break;
				}

			case 0x0001:
			case 0x0003:
				{
					long num = DivNum64();
					int den = (int)DivDenominator[0];
					if ( den == 0 )
					{
						SetQuotient( num < 0 ? 1 : -1 );
						SetRemainder( num );
					}
					else if ( num == long.MinValue && den == -1 )
					{
						SetQuotient( long.MinValue );
						SetRemainder( 0 );
					}
					else
					{
						SetQuotient( num / den );
						SetRemainder( num % den );
					}
					break;
				}

			case 0x0002:
				{
					long num = DivNum64();
					long den = DivDen64();
					if ( den == 0 )
					{
						SetQuotient( num < 0 ? 1 : -1 );
						SetRemainder( num );
					}
					else if ( num == long.MinValue && den == -1 )
					{
						SetQuotient( long.MinValue );
						SetRemainder( 0 );
					}
					else
					{
						SetQuotient( num / den );
						SetRemainder( num % den );
					}
					break;
				}
		}

		if ( (DivDenominator[0] | DivDenominator[1]) == 0 )
			DivCnt |= 0x4000;
	}

	public void StartSqrt()
	{
		CancelEvent( SysEvent.Sqrt );
		SqrtCnt |= 0x8000;
		ScheduleEvent( SysEvent.Sqrt, false, 13, OnSqrtDone );
	}

	private void OnSqrtDone( uint param ) => SqrtDone();

	private void SqrtDone()
	{
		SqrtCnt &= 0x7FFF;

		ulong val;
		uint res = 0;
		ulong rem = 0;
		int nbits, topshift;

		if ( (SqrtCnt & 0x0001) != 0 )
		{
			val = ((ulong)SqrtVal[1] << 32) | SqrtVal[0];
			nbits = 32;
			topshift = 62;
		}
		else
		{
			val = SqrtVal[0];
			nbits = 16;
			topshift = 30;
		}

		for ( int i = 0; i < nbits; i++ )
		{
			rem = (rem << 2) + ((val >> topshift) & 0x3);
			val <<= 2;
			res <<= 1;

			uint prod = (res << 1) + 1;
			if ( rem >= prod )
			{
				rem -= prod;
				res++;
			}
		}

		SqrtRes = res;
	}
}