using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Linq;

#pragma warning disable 0420

public partial class LockFreeDictionary<K, V> : IDictionary<K, V>
	where K : IEquatable<K>
	where V : IEquatable<V>
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public partial class Config
	{
		const int DefaultCapacity = 35;
		readonly int EntriesPerBlockPower;
		readonly int EntriesPerBlock;
		readonly int GlobalIndexMask;

		public Config(LockFreeDictionary<K, V> dict)
		{
			this.d = dict;
			if (d.m_options.HasFlag(Options.AttemptScaleToLargeObjectHeap))
			{
				/// attempt to place each EntryBlock in the large object heap
				int v = 85000 / Config.EntryBlock.Entry.StructureSize;
				int r = 1;
				while ((v >>= 1) > 0)
					r++;
				EntriesPerBlockPower = r;
			}
			else
				EntriesPerBlockPower = 10;

			EntriesPerBlock = 1 << EntriesPerBlockPower;
			GlobalIndexMask = EntriesPerBlock - 1;

			m_first_block = new EntryBlock(this, 0);

			free_lists = new Freelist[dict.c_processors * 2];
			for (int i = 0; i < free_lists.Length; i++)
				free_lists[i] = new Freelist(this);

			m_buckets = new BucketListPrimary(this, DefaultCapacity);
		}

		internal readonly LockFreeDictionary<K, V> d;

		/// each EntryBlock stores a fixed number of entries
		readonly EntryBlock m_first_block;

		/// entries have a global index across all EntryBlocks
		int m_gx_next;

		/// a pair of bucket arrays that can be atomically swapped
		internal volatile BucketList m_buckets;

		/// the number of entries in the dictionary is maintained with interlocked operations
		internal int m_count = 0;

		///  a small number of free lists
		readonly Freelist[] free_lists;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

		internal bool IsCurrent { get { return this == d.m_config; } }

		internal BucketList ChangeBuckets(BucketList bl_old, BucketList bl_new)
		{
			BucketList bl_cur = Interlocked.CompareExchange(ref m_buckets, bl_new, bl_old);
			return bl_cur == bl_old ? bl_new : bl_cur;
		}

		internal void CheckAssist(ref int mtid)
		{
			Config.BucketListResize blr = m_buckets as Config.BucketListResize;
			if (blr != null)
				blr._check_assist(ref mtid);
		}

		internal void RequestBucketResize(ref int mtid)
		{
			Config.BucketListPrimary blp = m_buckets as Config.BucketListPrimary;
			if (blp != null)
				blp.InitiateBucketResize(ref mtid);
		}

		internal void IncrementCountAndCheck(ref int mtid)
		{
			int c = Interlocked.Increment(ref m_count);
			if (c > m_buckets.BucketCount)
				RequestBucketResize(ref mtid);
		}

		internal IEnumerator<KeyValuePair<K, V>> EnumerateKeyValuePairs()
		{
			int mtid = -1;
			for (int i = 0; i < m_gx_next; i++)
			{
				EntryBlock.Entry e;
				GetEntryHot(i, out e);
				if (e.hash != EntryBlock.Entry.FreelistHash)
				{
					int gx;
					BucketList.BucketHead bh;
					do
						m_buckets.GetBucketHead(ref mtid, e.hash, out bh);	/// ignore config change
					while (!bh.TryFindKey(e.hash, e.key, out gx));
					if (gx != -1)
						yield return new KeyValuePair<K, V>(e.key, e.value);
				}
			}
		}

		/// <summary>
		/// Get an unused entry index, either from one of the freelists, or by allocating a new one
		/// </summary>
		internal int GetUnusedIndex(ref int mtid)
		{
			if (mtid == -1)
				mtid = Thread.CurrentThread.ManagedThreadId % free_lists.Length;
			int gx;
			if (!free_lists[mtid].TryGetFreeIndex(out gx))
			{
				if ((uint)m_gx_next >= 0xFFFFFFF0)
					throw new OverflowException("The dictionary already contains the amximum number of elements.");
				gx = Interlocked.Increment(ref m_gx_next) - 1;
			}
#if DEBUG
			StoreEntryHot(gx, ref EntryBlock.Entry.AllocDefault);
#endif
			return gx;
		}

		/// <summary>
		/// Add an entry index to one of the the freelists
		/// </summary>
		internal void ReleaseIndex(ref int mtid, int gx)
		{
#if DEBUG
			StoreEntryHot(gx, ref EntryBlock.Entry.FreelistDefault);
#endif
			if (mtid == -1)
				mtid = Thread.CurrentThread.ManagedThreadId % free_lists.Length;
			free_lists[mtid].AddIndexToFreeList(gx);
		}

		/// <summary>
		/// Get the block for the specified index. Allocates a new block, if necessary
		/// </summary>
		EntryBlock GetBlock(int gx)
		{
			EntryBlock eb = m_first_block;
			for (int i = EntriesPerBlock; i <= gx; i += EntriesPerBlock)
			{
				EntryBlock next = eb.next;
				Thread.MemoryBarrier();
				if (next == null)
				{
					next = new EntryBlock(this, i >> EntriesPerBlockPower);
					eb = Interlocked.CompareExchange(ref eb.next, next, null) ?? next;
				}
				else
					eb = next;
			}
			return eb;
		}

		internal void StoreEntryHot(int gx, ref EntryBlock.Entry e)
		{
			(gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.StoreEntryHot(gx & GlobalIndexMask, ref e);
		}

		internal void SetEntryNext(int gx, int gx_next)
		{
			(gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.SetEntryNext(gx & GlobalIndexMask, gx_next);
		}

		internal void SetFreelistNext(int gx, int gx_next)
		{
			(gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.SetFreelistNext(gx & GlobalIndexMask, gx_next);
		}

		internal void SetEntryHash(int gx, uint h)
		{
			(gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.SetEntryHash(gx & GlobalIndexMask, h);
		}

		internal int TransactEntryNext(int gx, int gx_next, int gx_compare)
		{
			return (gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.TransactEntryNext(gx & GlobalIndexMask, gx_next, gx_compare);
		}

		internal int GetNextHot(int gx)
		{
			return (gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.GetNextHot(gx & GlobalIndexMask);
		}

		internal uint GetHashAndNextHot(int gx, out int gx_next)
		{
			return (gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.GetHashAndNextHot(gx & GlobalIndexMask, out gx_next);
		}

		internal void GetEntryHot(int gx, out EntryBlock.Entry e)
		{
			(gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.GetEntryHot(gx & GlobalIndexMask, out e);
		}

		internal void GetKeyHot(int gx, out K key)
		{
			(gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.GetKeyHot(gx & GlobalIndexMask, out key);
		}

		internal void GetValueHot(int gx, out V v)
		{
			(gx < EntriesPerBlock ? m_first_block : GetBlock(gx))
				.GetValueHot(gx & GlobalIndexMask, out v);
		}
	};
};