using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Decal.Adapter.Support;
using Decal.Adapter.Wrappers;
using Decal.Interop.Core;

namespace Decal.Adapter.IDQueue;

/// <summary>
/// An IDQueue that is fair with respect to plugins and round-robin with respect to ID requests.
/// </summary>
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IIdentifyFilter))]
[ProgId("DecalAdapter.FairIDQueue")]
[Guid("5CD85A12-3DED-48E7-B440-B41E2A1452D9")]
[CLSCompliant(true)]
public class FairIDQueue : FairRoundRobinScheduleQueue<Assembly, int>, IIdentifyFilter
{
	private static int iVal = 1126270821;

	private const int REQUEST_INTERVAL = 600;

	private const int MAX_TRY_COUNT = 3;

	private const int NULL_ACTION_ID = -1;

	private Timer FireTimer = new Timer();

	private DateTime LastFireTime = DateTime.MinValue;

	private uint CurrentTimerFrame;

	private uint LastTimerFireFrame;

	private int UserRequestedID = -1;

	private int UserRequestedAttempts;

	private EventHandler<UserIDRequestProcessedEventArgs> mUserIDRequestProcessed;

	[CLSCompliant(false)]
	public event EventHandler<UserIDRequestProcessedEventArgs> UserIDRequestProcessed
	{
		add
		{
			mUserIDRequestProcessed = (EventHandler<UserIDRequestProcessedEventArgs>)Delegate.Combine(mUserIDRequestProcessed, value);
		}
		remove
		{
			mUserIDRequestProcessed = (EventHandler<UserIDRequestProcessedEventArgs>)Delegate.Remove(mUserIDRequestProcessed, value);
		}
	}

	internal FairIDQueue()
		: base(3, -1)
	{
		FireTimer.Interval = 600;
		FireTimer.Tick += FireTimer_Tick;
		CoreManager.Current.Actions.Underlying.SetIDFilter(this);
		CoreManager.Current.MessageProcessed += Current_MessageProcessed;
		CoreManager.Current.RenderFrame += Current_RenderFrame;
	}

	private void Current_RenderFrame(object sender, EventArgs e)
	{
		CurrentTimerFrame++;
	}

	private void Current_MessageProcessed(object sender, MessageProcessedEventArgs e)
	{
		if (e.Message.Type == 63408 && e.Message.Value<int>("event") == 201)
		{
			int num = e.Message.Value<int>("object");
			DeleteAction(num);
			if (UserRequestedID == num)
			{
				UserRequestedID = -1;
				Util.SafeFireEvent(this, mUserIDRequestProcessed, new UserIDRequestProcessedEventArgs(num));
			}
		}
	}

	internal void Start()
	{
		LastFireTime = DateTime.MinValue;
		FireTimer.Enabled = true;
	}

	internal void Stop()
	{
		FireTimer.Enabled = false;
		ClearAll();
		UserRequestedID = -1;
	}

	private void FireTimer_Tick(object sender, EventArgs e)
	{
		if (FireTimer.Interval != 600)
		{
			FireTimer.Enabled = false;
			FireTimer.Interval = 600;
			FireTimer.Enabled = true;
		}
		if (CurrentTimerFrame != LastTimerFireFrame)
		{
			LastTimerFireFrame = CurrentTimerFrame;
			FireNow();
		}
	}

	private void FireNow()
	{
		Assembly requester = null;
		int num = -1;
		if (UserRequestedID != -1)
		{
			num = UserRequestedID;
			UserRequestedAttempts++;
			if (UserRequestedAttempts > 3)
			{
				UserRequestedID = -1;
			}
		}
		if (num == -1)
		{
			num = GetNextAction(ref requester);
		}
		if (num != -1)
		{
			LastFireTime = DateTime.Now;
			int num2 = num ^ iVal;
			CoreManager.Current.Actions.Underlying.CallerRefInstanceInternal = num2;
			iVal = (num2 << 5) ^ (num2 >> 13) ^ iVal;
		}
	}

	protected override bool IsActionValidNow(int action)
	{
		WorldObject worldObject = CoreManager.Current.WorldFilter[action];
		if (worldObject == null)
		{
			return true;
		}
		if (worldObject.Container == 0)
		{
			WorldObject worldObject2 = CoreManager.Current.WorldFilter[CoreManager.Current.CharacterFilter.Id];
			if (worldObject2 != null && worldObject.Coordinates().DistanceToCoords(worldObject2.Coordinates()) > 0.375)
			{
				return false;
			}
		}
		return true;
	}

	protected override bool IsActionPermanentlyInvalid(int action)
	{
		return !CoreManager.Current.Actions.IsValidObject(action);
	}

	/// <summary>
	/// Adds a request to the queue.
	/// </summary>
	/// <param name="lObjectID">The game object ID to identify.</param>
	public void AddToQueue(int lObjectID)
	{
		AddToQueue(lObjectID, DateTime.MaxValue);
	}

	/// <summary>
	/// Adds a request to the queue with a timeout time.
	/// Note: if something else requests this ID while this request is still pending,
	/// the later of the two timeouts will prevail.
	/// </summary>
	/// <param name="lObjectID">The game object ID to identify.</param>
	/// <param name="pTimeout"></param>
	public void AddToQueue(int lObjectID, DateTime pTimeout)
	{
		Assembly assembly = Assembly.GetCallingAssembly();
		if (assembly == null)
		{
			assembly = Assembly.GetExecutingAssembly();
		}
		AddToQueueForCaller(lObjectID, assembly, pTimeout);
	}

	internal void AddToQueueForCaller(int lObjectID, Assembly pCaller, DateTime pTimeout)
	{
		try
		{
			if (FireTimer.Enabled && lObjectID != -1)
			{
				Enqueue(pCaller, lObjectID, DateTime.MaxValue);
				if ((DateTime.Now - LastFireTime).TotalMilliseconds > 700.0)
				{
					FireTimer.Enabled = false;
					FireTimer.Interval = 1;
					FireTimer.Enabled = true;
				}
			}
		}
		catch (Exception ex)
		{
			Util.WriteLine("Queue exception in addtoqueue: " + ex.ToString());
		}
	}

	/// <summary>
	/// Send an ID request which bypasses the queue.
	/// This is used when the user manually requests an ID. Only one object
	/// at a time can be pending here.
	/// </summary>
	/// <param name="lObjectID">The game object ID to identify.</param>
	[CLSCompliant(false)]
	[EditorBrowsable(EditorBrowsableState.Never)]
	public void ShortcircuitID(int lObjectID)
	{
		if (FireTimer.Enabled && lObjectID != -1)
		{
			UserRequestedID = lObjectID;
			UserRequestedAttempts = 0;
			if ((DateTime.Now - LastFireTime).TotalMilliseconds > 700.0)
			{
				LastFireTime = DateTime.Now;
			}
		}
	}
}
