Emulator/GbaBios.Math.cs
namespace sGBA;

public partial class GbaBios
{
	private void Div()
	{
		int num = (int)Gba.Cpu.Registers[0];
		int den = (int)Gba.Cpu.Registers[1];
		if ( den == 0 )
		{
			Gba.Cpu.Registers[0] = (uint)(num < 0 ? -1 : 1);
			Gba.Cpu.Registers[1] = (uint)num;
			Gba.Cpu.Registers[3] = 1;
		}
		else if ( den == -1 && num == int.MinValue )
		{
			Gba.Cpu.Registers[0] = 0x80000000u;
			Gba.Cpu.Registers[1] = 0;
			Gba.Cpu.Registers[3] = 0x80000000u;
		}
		else
		{
			int result = num / den;
			int remainder = num % den;
			Gba.Cpu.Registers[0] = (uint)result;
			Gba.Cpu.Registers[1] = (uint)remainder;
			Gba.Cpu.Registers[3] = result < 0 ? (uint)-result : (uint)result;
		}

		int loops = Clz32( (uint)den ) - Clz32( (uint)num );
		if ( loops < 1 ) loops = 1;
		BiosStall = 4 + 13 * loops + 7;
	}

	private void DivArm()
	{
		(Gba.Cpu.Registers[1], Gba.Cpu.Registers[0]) = (Gba.Cpu.Registers[0], Gba.Cpu.Registers[1]);
		Div();
	}

	private void Sqrt()
	{
		uint val = Gba.Cpu.Registers[0];
		Gba.Cpu.Registers[0] = SqrtWithCycles( val, out int cycles );
		BiosStall = cycles;
	}

	private static uint SqrtWithCycles( uint x, out int cycles )
	{
		if ( x == 0 ) { cycles = 53; return 0; }

		int currentCycles = 15;
		uint upper = x;
		uint bound = 1;

		while ( bound < upper )
		{
			upper >>= 1;
			bound <<= 1;
			currentCycles += 6;
		}

		while ( true )
		{
			currentCycles += 6;
			upper = x;
			uint accum = 0;
			uint lower = bound;

			while ( true )
			{
				currentCycles += 5;
				uint oldLower = lower;
				if ( lower <= upper >> 1 ) lower <<= 1;
				if ( oldLower >= upper >> 1 ) break;
			}

			while ( true )
			{
				currentCycles += 8;
				accum <<= 1;
				if ( upper >= lower )
				{
					++accum;
					upper -= lower;
				}
				if ( lower == bound ) break;
				lower >>= 1;
			}

			uint oldBound = bound;
			bound += accum;
			bound >>= 1;
			if ( bound >= oldBound )
			{
				bound = oldBound;
				break;
			}
		}

		cycles = currentCycles;
		return bound;
	}

	private void ArcTan()
	{
		int i = (int)Gba.Cpu.Registers[0];
		short result = ArcTanCore( i, out int a, out int b, out int cycles );
		Gba.Cpu.Registers[0] = (uint)result;
		Gba.Cpu.Registers[1] = (uint)a;
		Gba.Cpu.Registers[3] = (uint)b;
		BiosStall = cycles;
	}

	private short ArcTanCore( int i, out int a, out int b, out int cycles )
	{
		int currentCycles = 37;
		currentCycles += MulWait( i * i );
		a = -((i * i) >> 14);
		currentCycles += MulWait( 0xA9 * a );
		b = ((0xA9 * a) >> 14) + 0x390;
		currentCycles += MulWait( b * a );
		b = ((b * a) >> 14) + 0x91C;
		currentCycles += MulWait( b * a );
		b = ((b * a) >> 14) + 0xFB6;
		currentCycles += MulWait( b * a );
		b = ((b * a) >> 14) + 0x16AA;
		currentCycles += MulWait( b * a );
		b = ((b * a) >> 14) + 0x2081;
		currentCycles += MulWait( b * a );
		b = ((b * a) >> 14) + 0x3651;
		currentCycles += MulWait( b * a );
		b = ((b * a) >> 14) + 0xA2F9;
		cycles = currentCycles;
		return (short)((i * b) >> 16);
	}

	private void ArcTan2()
	{
		int x = (int)Gba.Cpu.Registers[0];
		int y = (int)Gba.Cpu.Registers[1];
		short angle;

		if ( y == 0 )
		{
			BiosStall = 11;
			angle = (short)(x >= 0 ? 0 : unchecked((short)0x8000));
		}
		else if ( x == 0 )
		{
			BiosStall = 11;
			angle = (short)(y >= 0 ? 0x4000 : unchecked((short)0xC000));
		}
		else if ( y >= 0 )
		{
			if ( x >= 0 )
			{
				if ( x >= y )
				{
					angle = ArcTanCore( (y << 14) / x, out int a, out _, out int cycles );
					Gba.Cpu.Registers[1] = (uint)a;
					BiosStall = cycles;
				}
				else
				{
					angle = (short)(0x4000 - ArcTanCore( (x << 14) / y, out int a, out _, out int cycles ));
					Gba.Cpu.Registers[1] = (uint)a;
					BiosStall = cycles;
				}
			}
			else if ( -x >= y )
			{
				angle = (short)(ArcTanCore( (y << 14) / x, out int a, out _, out int cycles ) + 0x8000);
				Gba.Cpu.Registers[1] = (uint)a;
				BiosStall = cycles;
			}
			else
			{
				angle = (short)(0x4000 - ArcTanCore( (x << 14) / y, out int a, out _, out int cycles ));
				Gba.Cpu.Registers[1] = (uint)a;
				BiosStall = cycles;
			}
		}
		else
		{
			if ( x <= 0 )
			{
				if ( -x > -y )
				{
					angle = (short)(ArcTanCore( (y << 14) / x, out int a, out _, out int cycles ) + 0x8000);
					Gba.Cpu.Registers[1] = (uint)a;
					BiosStall = cycles;
				}
				else
				{
					angle = (short)(unchecked((short)0xC000) - ArcTanCore( (x << 14) / y, out int a, out _, out int cycles ));
					Gba.Cpu.Registers[1] = (uint)a;
					BiosStall = cycles;
				}
			}
			else if ( x >= -y )
			{
				angle = (short)(ArcTanCore( (y << 14) / x, out int a, out _, out int cycles ) + 0x10000);
				Gba.Cpu.Registers[1] = (uint)a;
				BiosStall = cycles;
			}
			else
			{
				angle = (short)(unchecked((short)0xC000) - ArcTanCore( (x << 14) / y, out int a, out _, out int cycles ));
				Gba.Cpu.Registers[1] = (uint)a;
				BiosStall = cycles;
			}
		}

		Gba.Cpu.Registers[0] = (ushort)angle;
		Gba.Cpu.Registers[3] = 0x170;
	}

	private static int Clz32( uint value )
	{
		if ( value == 0 ) return 32;
		int n = 0;
		if ( (value & 0xFFFF0000) == 0 ) { n += 16; value <<= 16; }
		if ( (value & 0xFF000000) == 0 ) { n += 8; value <<= 8; }
		if ( (value & 0xF0000000) == 0 ) { n += 4; value <<= 4; }
		if ( (value & 0xC0000000) == 0 ) { n += 2; value <<= 2; }
		if ( (value & 0x80000000) == 0 ) { n += 1; }
		return n;
	}

	private static int MulWait( int r )
	{
		if ( (r & unchecked((int)0xFFFFFF00)) == unchecked((int)0xFFFFFF00) || (r & 0xFFFFFF00) == 0 ) return 1;
		if ( (r & unchecked((int)0xFFFF0000)) == unchecked((int)0xFFFF0000) || (r & 0xFFFF0000) == 0 ) return 2;
		if ( (r & unchecked((int)0xFF000000)) == unchecked((int)0xFF000000) || (r & 0xFF000000) == 0 ) return 3;
		return 4;
	}
}