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

#pragma warning disable 0420

public partial class LockFreeDictionary<K, V> : IDictionary<K, V>
	where K : IEquatable<K>
	where V : IEquatable<V>
{
	static Int64 AtomicRead64(ref Int64 p)
	{
		if (IntPtr.Size == 4)
			return Interlocked.CompareExchange(ref p, 0, 0);

		/// The following is the same as 'return Thread.VolatileRead(ref p)'
		Int64 i64 = p;
		Thread.MemoryBarrier();
		return i64;
	}


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// 
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public partial class Config
	{
		const int DefaultCapacity = 35;

		internal int EntriesPerBlockPower;
		internal int EntriesPerBlock;
		internal int GlobalIndexMask;

		public Config(LockFreeDictionary<K, V> dict)
		{
			this.dict = dict;

			if (dict.m_options.HasFlag(Options.AttemptScaleToLargeObjectHeap))
			{
				/// attempt to place each EntryBlock in the large object heap
				int v = 85000 / 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[FreeListCount];
			for (int i = 0; i < FreeListCount; i++)
				free_lists[i] = new Freelist(this);

			m_buckets = new BucketListPrimary(this, DefaultCapacity);
		}

		internal LockFreeDictionary<K, V> dict;

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

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

		/// the number of entries in the dictionary is explicitly maintained
		public int m_count = 0;

		/// number of free lists determined by 2^P
		const int FreeListCountPower = 1;//0;
		const int FreeListCount = (1 << FreeListCountPower);
		const int FreeListCountMask = FreeListCount - 1;

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

		/// free list choosing index. don't care about atomicity
		volatile int i_free_list = 0;

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

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

		internal bool IsCurrent { get { return this == dict.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;
		}

		/// <summary>
		/// Get the block for the specified index. Allocates a new block, if necessary
		/// </summary>
		public EntryBlock GetBlock(int i_block)
		{
			EntryBlock eb = m_first_block;
			for (int i = 0; i < i_block; i++)
			{
				if (eb.next == null)
					Interlocked.CompareExchange(ref eb.next, new EntryBlock(this, i_block), null);
				eb = eb.next;	/// C# volatile
			}
			return eb;
		}

		EntryBlock.Entry _default_alloc_entry = new EntryBlock.Entry(-1, 0, default(K), default(V));
		EntryBlock.Entry _default_freelist_entry = new EntryBlock.Entry(0, 0, default(K), default(V));

		/// <summary>
		/// Get an unused slot, either from one of the freelists, or by allocating a new slot
		/// </summary>
		internal int GetUnusedIndex()
		{
			int gx;
			if (free_lists[++i_free_list & FreeListCountMask].TryGetFreeIndex(out gx))
			{
#if DEBUG
				Interlocked.Decrement(ref m_c_free);
#endif
			}
			else
				gx = Interlocked.Increment(ref m_gx_next) - 1;

#if DEBUG
			StoreEntry(gx, ref _default_alloc_entry);
#endif
			return gx;
		}

		internal void ReleaseIndex(int gx)
		{
#if DEBUG
			StoreEntry(gx, ref _default_freelist_entry);
#endif
			free_lists[++i_free_list & FreeListCountMask].AddIndexToFreeList(gx);
#if DEBUG
			Interlocked.Increment(ref m_c_free);
#endif
		}

		/// <summary>
		/// Release the entries on a (privately-held) chain of entries to the free list. The chain is privately held if:
		/// - it has never been published in a BucketHead -or-
		/// - disconnection from its BucketHead has been successfully transacted
		/// </summary>
		internal void ReleaseChain(int gx, int count)
		{
			Debug.Assert(count > 0);
			int i = 0;
			while (++i < count)
			{
				int gx_next = GetEntryNext(gx);
				ReleaseIndex(gx);
				gx = gx_next;
			}
			ReleaseIndex(gx);
		}

		/// <summary>
		/// Copy a chain of entries.
		/// </summary>
		/// <returns>
		/// <value>true</value> if the chain was copied, <value>false</value> if the chain was not copied because
		/// the bucket head became invalid. In this case, the caller can try again if desired.
		/// </returns>
		internal bool CopyChain(BucketList.BucketHead bh, int count, out int gx_head, out int gx_tail)
		{
			Debug.Assert(count > 0);

			int gx_prev = gx_head = gx_tail = default(int);
			int gx = bh.CapturedFirstSlotIndex;
			int j = 0;
			while (true)
			{
				EntryBlock.Entry e_src;
				GetEntryLoose(gx, out e_src);
				if (bh.IsExpired)
				{
					if (j > 0)
						ReleaseChain(gx_head, j);
					return false;
				}

				gx_tail = GetUnusedIndex();
				StoreEntryLoose(gx_tail, ref e_src);

				if (j == 0)
					gx_head = gx_tail;
				else
					SetEntryNext(gx_prev, gx_tail);

				if (++j == count)
					return true;

				gx = e_src.gx_next;
				gx_prev = gx_tail;
			}
		}

		public void GetEntryLoose(int gx, out EntryBlock.Entry e)
		{
			GetBlock(gx >> EntriesPerBlockPower).GetEntryLoose(gx & GlobalIndexMask, out e);
		}

		public void StoreEntryLoose(int gx, ref EntryBlock.Entry e)
		{
			GetBlock(gx >> EntriesPerBlockPower).StoreEntryLoose(gx & GlobalIndexMask, ref e);
		}

		public void SetEntryNext(int gx, int gx_next)
		{
			GetBlock(gx >> EntriesPerBlockPower).SetEntryNext(gx & GlobalIndexMask, gx_next);
		}

		public int GetEntryNext(int gx)
		{
			return GetBlock(gx >> EntriesPerBlockPower).GetEntryNext(gx & GlobalIndexMask);
		}

		public uint GetHashAndNext(int gx, out int gx_next)
		{
			return GetBlock(gx >> EntriesPerBlockPower).GetHashAndNext(gx & GlobalIndexMask, out gx_next);
		}

		public void GetKeyReadback(int gx, out K key)
		{
			GetBlock(gx >> EntriesPerBlockPower).GetKeyReadback(gx & GlobalIndexMask, out key);
		}

		public void SetEntryValueLoose(int gx, ref V v)
		{
			GetBlock(gx >> EntriesPerBlockPower).SetEntryValueLoose(gx & GlobalIndexMask, ref v);
		}

		public void SetEntryValueReadback(int gx, ref V v)
		{
			GetBlock(gx >> EntriesPerBlockPower).SetEntryValueReadback(gx & GlobalIndexMask, ref v);
		}
	};
};