Emulator/GbaTiming.cs
namespace sGBA;

public sealed class GbaTimingEvent
{
	public GbaTimingEvent( Action<long> callback, int priority, string name )
	{
		Callback = callback;
		Priority = priority;
		Name = name;
	}

	public Action<long> Callback { get; }
	public int Priority { get; }
	public string Name { get; }

	public long When { get; internal set; }
	public bool Scheduled { get; internal set; }
	internal GbaTimingEvent Next;
}

public sealed class GbaTiming
{
	private GbaTimingEvent _root;

	public long NextEvent => _root?.When ?? long.MaxValue;

	public void Schedule( GbaTimingEvent ev, long when )
	{
		if ( ev.Scheduled )
			Deschedule( ev );

		ev.When = when;
		ev.Scheduled = true;

		GbaTimingEvent previous = null;
		GbaTimingEvent next = _root;
		while ( next != null )
		{
			if ( next.When > when || (next.When == when && next.Priority > ev.Priority) )
				break;

			previous = next;
			next = next.Next;
		}

		ev.Next = next;
		if ( previous == null )
			_root = ev;
		else
			previous.Next = ev;
	}

	public void Deschedule( GbaTimingEvent ev )
	{
		if ( !ev.Scheduled )
			return;

		GbaTimingEvent previous = null;
		GbaTimingEvent node = _root;
		while ( node != null )
		{
			if ( ReferenceEquals( node, ev ) )
			{
				if ( previous == null )
					_root = node.Next;
				else
					previous.Next = node.Next;
				break;
			}

			previous = node;
			node = node.Next;
		}

		ev.Next = null;
		ev.Scheduled = false;
	}

	public bool IsScheduled( GbaTimingEvent ev ) => ev.Scheduled;

	public long Tick( long currentCycle )
	{
		while ( _root != null && _root.When <= currentCycle )
		{
			GbaTimingEvent ev = _root;
			_root = ev.Next;
			ev.Next = null;
			ev.Scheduled = false;

			ev.Callback( currentCycle - ev.When );
		}

		return NextEvent;
	}

	public void Clear()
	{
		GbaTimingEvent node = _root;
		while ( node != null )
		{
			GbaTimingEvent next = node.Next;
			node.Next = null;
			node.Scheduled = false;
			node = next;
		}
		_root = null;
	}
}