using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using CustomCollections;

namespace Decal.Adapter.IDQueue;

/// <summary>
/// A scheduler: callers request actions. Multiple callers can request the same action.
/// -Each caller gets one action, then the other callers get a turn.
/// -If a new caller arrives, it is given priority in the turns list.
/// -Everytime a caller gets a turn, it selects its next available action in round-robin fashion.
/// -If all of a caller's actions are unavailable when it is that caller's turn, its turn is lost and it must wait in line again.
/// -If no caller can take a turn, the null action is returned.
/// Thus if multiple callers request the same action, it will be tried more often
/// since the attempt will occur during the turns of multiple callers.
/// </summary>
/// <typeparam name="CALLERTYPE"></typeparam>
/// <typeparam name="ACTIONTYPE"></typeparam>
[CLSCompliant(true)]
[ClassInterface(ClassInterfaceType.None)]
public abstract class FairRoundRobinScheduleQueue<CALLERTYPE, ACTIONTYPE>
{
	public class ActionRemovedEventArgs : EventArgs
	{
		private ACTIONTYPE mAction;

		public ACTIONTYPE Action => mAction;

		internal ActionRemovedEventArgs(ACTIONTYPE pAction)
		{
			mAction = pAction;
		}
	}

	private class ActionInfo : IEquatable<ActionInfo>
	{
		public ACTIONTYPE Action;

		public DateTime Expires;

		public int TryCount;

		public ActionInfo(ACTIONTYPE pAction, DateTime pExpires)
		{
			Action = pAction;
			Expires = pExpires;
		}

		public bool IsExpired()
		{
			return Expires < DateTime.Now;
		}

		public bool Equals(ActionInfo other)
		{
			ref ACTIONTYPE action = ref Action;
			object obj = other.Action;
			return action.Equals(obj);
		}

		public override bool Equals(object obj)
		{
			return Equals(obj as ActionInfo);
		}

		public override int GetHashCode()
		{
			return Action.GetHashCode();
		}
	}

	private HashedList<CALLERTYPE> Callers = new HashedList<CALLERTYPE>();

	private Dictionary<ACTIONTYPE, ActionInfo> ActInfos = new Dictionary<ACTIONTYPE, ActionInfo>();

	private Dictionary<CALLERTYPE, HashedList<ActionInfo>> CallerActions = new Dictionary<CALLERTYPE, HashedList<ActionInfo>>();

	private Dictionary<ACTIONTYPE, HashedList<CALLERTYPE>> ActionCallers = new Dictionary<ACTIONTYPE, HashedList<CALLERTYPE>>();

	private int MaximumTryCount;

	private ACTIONTYPE NullAction;

	public int CallerCount => Callers.Count;

	public int ActionCount => ActInfos.Count;

	public event EventHandler<ActionRemovedEventArgs> OnActionRemoved;

	/// <summary>
	///
	/// </summary>
	/// <param name="pMaximumTryCount">The most times an action can be attempted before it fails.</param>
	/// <param name="pNullAction">An ACTIONTYPE to return when no action is available.</param>
	protected FairRoundRobinScheduleQueue(int pMaximumTryCount, ACTIONTYPE pNullAction)
	{
		MaximumTryCount = pMaximumTryCount;
		NullAction = pNullAction;
	}

	protected abstract bool IsActionValidNow(ACTIONTYPE action);

	protected abstract bool IsActionPermanentlyInvalid(ACTIONTYPE action);

	private void RotateQueue<T>(HashedList<T> q)
	{
		if (q.Count > 0)
		{
			T item = q[0];
			q.RemoveAt(0);
			q.Add(item);
		}
	}

	public void ClearAll()
	{
		Callers.Clear();
		ActInfos.Clear();
		CallerActions.Clear();
		ActionCallers.Clear();
	}

	public void DeleteAction(ACTIONTYPE action)
	{
		if (!ActionCallers.ContainsKey(action) || !ActInfos.ContainsKey(action))
		{
			return;
		}
		ActionInfo item = ActInfos[action];
		ActInfos.Remove(action);
		HashedList<CALLERTYPE> hashedList = ActionCallers[action];
		ActionCallers.Remove(action);
		foreach (CALLERTYPE item2 in hashedList)
		{
			CallerActions[item2].Remove(item);
			if (CallerActions[item2].Count == 0)
			{
				CallerActions.Remove(item2);
				Callers.Remove(item2);
			}
		}
		if (this.OnActionRemoved != null)
		{
			this.OnActionRemoved(this, new ActionRemovedEventArgs(action));
		}
	}

	public void DeleteCaller(CALLERTYPE caller)
	{
		Callers.Remove(caller);
		if (!CallerActions.ContainsKey(caller))
		{
			return;
		}
		HashedList<ActionInfo> hashedList = CallerActions[caller];
		CallerActions.Remove(caller);
		HashedList<ActionInfo> hashedList2 = new HashedList<ActionInfo>();
		foreach (ActionInfo item in hashedList)
		{
			if (ActionCallers.ContainsKey(item.Action))
			{
				ActionCallers[item.Action].Remove(caller);
				if (ActionCallers[item.Action].Count == 0)
				{
					ActionCallers.Remove(item.Action);
					ActInfos.Remove(item.Action);
					hashedList2.Add(item);
				}
			}
		}
		if (this.OnActionRemoved == null)
		{
			return;
		}
		foreach (ActionInfo item2 in hashedList2)
		{
			this.OnActionRemoved(this, new ActionRemovedEventArgs(item2.Action));
		}
	}

	public ReadOnlyCollection<ACTIONTYPE> GetActionsForCaller(CALLERTYPE caller)
	{
		List<ACTIONTYPE> list = new List<ACTIONTYPE>();
		if (CallerActions.ContainsKey(caller))
		{
			foreach (ActionInfo item in CallerActions[caller])
			{
				list.Add(item.Action);
			}
		}
		return list.AsReadOnly();
	}

	public ReadOnlyCollection<CALLERTYPE> GetCallersForAction(ACTIONTYPE action)
	{
		if (ActionCallers.ContainsKey(action))
		{
			return ActionCallers[action].AsReadOnly();
		}
		return new List<CALLERTYPE>().AsReadOnly();
	}

	public void Enqueue(CALLERTYPE caller, ACTIONTYPE action, DateTime expiration)
	{
		ActionInfo actionInfo;
		if (ActInfos.ContainsKey(action))
		{
			actionInfo = ActInfos[action];
			if (actionInfo.Expires < expiration)
			{
				actionInfo.Expires = expiration;
			}
			actionInfo.TryCount = 0;
		}
		else
		{
			actionInfo = new ActionInfo(action, expiration);
			ActInfos[action] = actionInfo;
		}
		if (!CallerActions.ContainsKey(caller) || !CallerActions[caller].Contains(actionInfo))
		{
			if (!Callers.Contains(caller))
			{
				Callers.Insert(0, caller);
			}
			if (!CallerActions.ContainsKey(caller))
			{
				CallerActions.Add(caller, new HashedList<ActionInfo>());
			}
			CallerActions[caller].Add(actionInfo);
			if (!ActionCallers.ContainsKey(action))
			{
				ActionCallers.Add(action, new HashedList<CALLERTYPE>());
			}
			ActionCallers[action].Add(caller);
		}
	}

	public ACTIONTYPE GetNextAction(ref CALLERTYPE requester)
	{
		HashedList<ActionInfo> hashedList = new HashedList<ActionInfo>();
		ACTIONTYPE result = NullAction;
		for (int i = 0; i < Callers.Count; i++)
		{
			CALLERTYPE val = Callers[0];
			HashedList<ActionInfo> hashedList2 = CallerActions[val];
			bool flag = false;
			for (int j = 0; j < hashedList2.Count; j++)
			{
				ActionInfo actionInfo = hashedList2[0];
				if (hashedList.Contains(actionInfo))
				{
					RotateQueue(hashedList2);
					continue;
				}
				if (actionInfo.IsExpired())
				{
					hashedList.Add(actionInfo);
					RotateQueue(hashedList2);
					continue;
				}
				if (IsActionPermanentlyInvalid(actionInfo.Action))
				{
					hashedList.Add(actionInfo);
					RotateQueue(hashedList2);
					continue;
				}
				if (!IsActionValidNow(actionInfo.Action))
				{
					RotateQueue(hashedList2);
					continue;
				}
				flag = true;
				result = actionInfo.Action;
				requester = val;
				RotateQueue(hashedList2);
				actionInfo.TryCount++;
				if (actionInfo.TryCount > MaximumTryCount)
				{
					hashedList.Add(actionInfo);
				}
				break;
			}
			RotateQueue(Callers);
			if (flag)
			{
				break;
			}
		}
		foreach (ActionInfo item in hashedList)
		{
			DeleteAction(item.Action);
		}
		return result;
	}
}
