using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace CustomCollections;

/// <summary>
/// A doubly-linked list with a Dictionary index. Duplicate items are not allowed.
/// -Add is O(1)
/// -Contains is O(1)
/// -Remove is O(1)
/// -Get/set by index is O(n)
/// -Insert is O(n)
/// -RemoveAt is O(n)
/// Additionally, a cached pointer (with associated index) is kept pointing to the last used index item.
/// When looking up an item by index, the list is walked from the head, tail, or cached index pointer.
/// Thus, doing multiple operations in index order is O(1) even without an enumerator.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class HashedList<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable
{
	private class ListNode
	{
		public ListNode Previous;

		public ListNode Next;

		public T Value;

		public ListNode(T v, ListNode prev, ListNode nxt)
		{
			Previous = prev;
			Next = nxt;
			Value = v;
		}
	}

	private ListNode Head;

	private ListNode Tail;

	private Dictionary<T, ListNode> HashIndex = new Dictionary<T, ListNode>();

	private int ChangeMarker;

	private bool IndexCacheValid;

	private int IndexCache_Index;

	private ListNode IndexCache_Value;

	public T this[int index]
	{
		get
		{
			if (index >= HashIndex.Count || index < 0)
			{
				throw new ArgumentOutOfRangeException();
			}
			return RunToIndex(index).Value;
		}
		set
		{
			if (index >= HashIndex.Count || index < 0)
			{
				throw new ArgumentOutOfRangeException();
			}
			if (HashIndex.ContainsKey(value))
			{
				throw new ArgumentException("Duplicate value");
			}
			ListNode listNode = RunToIndex(index);
			HashIndex.Remove(listNode.Value);
			listNode.Value = value;
			HashIndex.Add(value, listNode);
			ChangeMarker++;
		}
	}

	public int Count => HashIndex.Count;

	public bool IsReadOnly => false;

	public HashedList()
	{
	}

	public HashedList(T[] arr)
	{
		foreach (T item in arr)
		{
			Add(item);
		}
	}

	/// <summary>
	/// This method gets the node corresponding to a particular index. To get there,
	/// the list is traversed from the head, tail, or cached index pointer (if valid).
	/// </summary>
	/// <param name="ind"></param>
	/// <returns></returns>
	private ListNode RunToIndex(int ind)
	{
		int num = HashIndex.Count / 2;
		if (IndexCacheValid)
		{
			if (ind > IndexCache_Index)
			{
				if (ind - IndexCache_Index < num)
				{
					ListNode listNode = IndexCache_Value;
					for (int i = IndexCache_Index; i < ind; i++)
					{
						listNode = listNode.Next;
					}
					IndexCache_Index = ind;
					IndexCache_Value = listNode;
					IndexCacheValid = true;
					return listNode;
				}
			}
			else if (IndexCache_Index - ind < num)
			{
				ListNode listNode2 = IndexCache_Value;
				for (int num2 = IndexCache_Index; num2 > ind; num2--)
				{
					listNode2 = listNode2.Previous;
				}
				IndexCache_Index = ind;
				IndexCache_Value = listNode2;
				IndexCacheValid = true;
				return listNode2;
			}
		}
		if (ind < num)
		{
			ListNode listNode3 = Head;
			for (int j = 0; j < ind; j++)
			{
				listNode3 = listNode3.Next;
			}
			if (listNode3 == null)
			{
				throw new Exception();
			}
			IndexCache_Index = ind;
			IndexCache_Value = listNode3;
			IndexCacheValid = true;
			return listNode3;
		}
		ListNode listNode4 = Tail;
		for (int num3 = HashIndex.Count - 1; num3 > ind; num3--)
		{
			listNode4 = listNode4.Previous;
		}
		if (listNode4 == null)
		{
			throw new Exception();
		}
		IndexCache_Index = ind;
		IndexCache_Value = listNode4;
		IndexCacheValid = true;
		return listNode4;
	}

	private void RemoveNode(ListNode n)
	{
		if (n.Previous == null)
		{
			Head = n.Next;
		}
		else
		{
			n.Previous.Next = n.Next;
		}
		if (n.Next == null)
		{
			Tail = n.Previous;
		}
		else
		{
			n.Next.Previous = n.Previous;
		}
		HashIndex.Remove(n.Value);
	}

	public ReadOnlyCollection<T> AsReadOnly()
	{
		return new ReadOnlyCollection<T>(this);
	}

	public bool GetIfContains(T matchval, ref T outval)
	{
		if (HashIndex.ContainsKey(matchval))
		{
			outval = HashIndex[matchval].Value;
			return true;
		}
		return false;
	}

	public int IndexOf(T item)
	{
		if (HashIndex.ContainsKey(item))
		{
			ListNode listNode = HashIndex[item];
			ListNode listNode2 = Head;
			for (int i = 0; i < HashIndex.Count; i++)
			{
				if (listNode2 == listNode)
				{
					return i;
				}
				listNode2 = listNode2.Next;
			}
			return -1;
		}
		return -1;
	}

	public void Insert(int index, T item)
	{
		if (index > HashIndex.Count || index < 0)
		{
			throw new ArgumentOutOfRangeException();
		}
		if (HashIndex.ContainsKey(item))
		{
			throw new ArgumentException("Duplicate value");
		}
		ListNode listNode;
		if (index == HashIndex.Count)
		{
			Add(item);
			listNode = HashIndex[item];
		}
		else if (index == 0)
		{
			listNode = new ListNode(item, null, Head);
			Head.Previous = listNode;
			Head = listNode;
			HashIndex.Add(item, listNode);
		}
		else
		{
			ListNode listNode2 = RunToIndex(index - 1);
			listNode = (listNode2.Next = new ListNode(item, listNode2, listNode2.Next));
			listNode.Next.Previous = listNode;
			HashIndex.Add(item, listNode);
		}
		ChangeMarker++;
		IndexCache_Index = index;
		IndexCache_Value = listNode;
		IndexCacheValid = true;
	}

	public void RemoveAt(int index)
	{
		if (index >= HashIndex.Count || index < 0)
		{
			throw new ArgumentOutOfRangeException();
		}
		ListNode n = RunToIndex(index);
		RemoveNode(n);
		ChangeMarker++;
		IndexCacheValid = false;
	}

	public void Add(T item)
	{
		ListNode listNode;
		if (HashIndex.Count == 0)
		{
			listNode = (Tail = (Head = new ListNode(item, null, null)));
		}
		else
		{
			if (HashIndex.ContainsKey(item))
			{
				throw new ArgumentException("Duplicate value");
			}
			listNode = new ListNode(item, Tail, null);
			Tail.Next = listNode;
			Tail = listNode;
		}
		HashIndex.Add(item, listNode);
		ChangeMarker++;
		IndexCacheValid = false;
	}

	public void Clear()
	{
		Head = null;
		Tail = null;
		HashIndex.Clear();
		ChangeMarker++;
		IndexCacheValid = false;
	}

	public bool Contains(T item)
	{
		return HashIndex.ContainsKey(item);
	}

	public bool Remove(T item)
	{
		if (!HashIndex.ContainsKey(item))
		{
			return false;
		}
		RemoveNode(HashIndex[item]);
		ChangeMarker++;
		IndexCacheValid = false;
		return true;
	}

	public void CopyTo(T[] array, int arrayIndex)
	{
		int num = arrayIndex + HashIndex.Count;
		IEnumerator<T> enumerator = GetEnumerator();
		for (int i = arrayIndex; i < num; i++)
		{
			enumerator.MoveNext();
			array[i] = enumerator.Current;
		}
	}

	public T[] ToArray()
	{
		T[] array = new T[HashIndex.Count];
		CopyTo(array, 0);
		return array;
	}

	public IEnumerator<T> GetEnumerator()
	{
		int startchangemarker = ChangeMarker;
		for (ListNode cur = Head; cur != null; cur = cur.Next)
		{
			yield return cur.Value;
			if (ChangeMarker != startchangemarker)
			{
				throw new Exception("Collection modified during enumeration");
			}
		}
	}

	IEnumerator IEnumerable.GetEnumerator()
	{
		return GetEnumerator();
	}
}
