using System;

namespace SharpQuake
{
	internal static class Cache
	{
		public static void Init(int capacity)
		{
			Cache._Capacity = capacity;
			Cache._BytesAllocated = 0;
			Cache._Head = new Cache.CacheEntry(true);
			cmd.Add("flush", new xcommand_t(Cache.Flush));
		}

		public static object Check(cache_user_t c)
		{
			Cache.CacheEntry cacheEntry = (Cache.CacheEntry)c;
			if (cacheEntry == null || cacheEntry.data == null)
			{
				return null;
			}
			cacheEntry.RemoveFromLRU();
			cacheEntry.LRUInstertAfter(Cache._Head);
			return cacheEntry.data;
		}

		public static cache_user_t Alloc(int size, string name)
		{
			if (size <= 0)
			{
				sys.Error("Cache_Alloc: size {0}", new object[]
				{
					size
				});
			}
			size = (size + 15 & -16);
			Cache.CacheEntry cacheEntry;
			for (;;)
			{
				cacheEntry = Cache.TryAlloc(size);
				if (cacheEntry != null)
				{
					break;
				}
				if (Cache._Head.LruPrev == Cache._Head)
				{
					sys.Error("Cache_Alloc: out of memory", Array.Empty<object>());
				}
				Cache.Free(Cache._Head.LruPrev);
			}
			Cache.Check(cacheEntry);
			return cacheEntry;
		}

		public static void Report()
		{
			Con.DPrint("{0,4:F1} megabyte data cache, used {1,4:F1} megabyte\n", new object[]
			{
				(float)Cache._Capacity / 1048576f,
				(float)Cache._BytesAllocated / 1048576f
			});
		}

		private static void Flush()
		{
			while (Cache._Head.Next != Cache._Head)
			{
				Cache.Free(Cache._Head.Next);
			}
		}

		private static void Free(cache_user_t c)
		{
			if (c.data == null)
			{
				sys.Error("Cache_Free: not allocated", Array.Empty<object>());
			}
			((Cache.CacheEntry)c).Remove();
		}

		private static Cache.CacheEntry TryAlloc(int size)
		{
			if (Cache._BytesAllocated + size > Cache._Capacity)
			{
				return null;
			}
			Cache.CacheEntry cacheEntry = new Cache.CacheEntry(size);
			Cache._Head.InsertBefore(cacheEntry);
			cacheEntry.LRUInstertAfter(Cache._Head);
			return cacheEntry;
		}

		private static Cache.CacheEntry _Head;

		private static int _Capacity;

		private static int _BytesAllocated;

		private class CacheEntry : cache_user_t
		{
			public Cache.CacheEntry Next
			{
				get
				{
					return this._Next;
				}
			}

			public Cache.CacheEntry Prev
			{
				get
				{
					return this._Prev;
				}
			}

			public Cache.CacheEntry LruPrev
			{
				get
				{
					return this._LruPrev;
				}
			}

			public Cache.CacheEntry LruNext
			{
				get
				{
					return this._LruNext;
				}
			}

			public void RemoveFromLRU()
			{
				if (this._LruNext == null || this._LruPrev == null)
				{
					sys.Error("Cache_UnlinkLRU: NULL link", Array.Empty<object>());
				}
				this._LruNext._LruPrev = this._LruPrev;
				this._LruPrev._LruNext = this._LruNext;
				this._LruPrev = (this._LruNext = null);
			}

			public void LRUInstertAfter(Cache.CacheEntry prev)
			{
				if (this._LruNext != null || this._LruPrev != null)
				{
					sys.Error("Cache_MakeLRU: active link", Array.Empty<object>());
				}
				prev._LruNext._LruPrev = this;
				this._LruNext = prev._LruNext;
				this._LruPrev = prev;
				prev._LruNext = this;
			}

			public void InsertBefore(Cache.CacheEntry next)
			{
				this._Next = next;
				if (next._Prev != null)
				{
					this._Prev = next._Prev;
				}
				else
				{
					this._Prev = next;
				}
				if (next._Prev != null)
				{
					next._Prev._Next = this;
				}
				else
				{
					next._Prev = this;
				}
				next._Prev = this;
				if (next._Next == null)
				{
					next._Next = this;
				}
			}

			public void Remove()
			{
				this._Prev._Next = this._Next;
				this._Next._Prev = this._Prev;
				this._Next = (this._Prev = null);
				this.data = null;
				Cache._BytesAllocated -= this._Size;
				this._Size = 0;
				this.RemoveFromLRU();
			}

			public CacheEntry(bool isHead = false)
			{
				if (isHead)
				{
					this._Next = this;
					this._Prev = this;
					this._LruNext = this;
					this._LruPrev = this;
				}
			}

			public CacheEntry(int size)
			{
				this._Size = size;
				Cache._BytesAllocated += this._Size;
			}

			~CacheEntry()
			{
				Cache._BytesAllocated -= this._Size;
			}

			private Cache.CacheEntry _Prev;

			private Cache.CacheEntry _Next;

			private Cache.CacheEntry _LruPrev;

			private Cache.CacheEntry _LruNext;

			private int _Size;
		}
	}
}
