using System;
using System.Collections;
using System.Collections.Specialized;

namespace Icarus.Utilities.Data
{
	/// <summary>
	/// This is a caching class, which removes the least recently used items when there is no more need for them.
	/// </summary>
	public class Cache
	{
		private int mMaxElements;
		private IDictionary mCacheItems;

		/// <summary>
		/// Creates a new cache class.
		/// </summary>
		/// <param name="maxElements">The maximum number of elements the cache can have.</param>
		public Cache(int maxElements)
		{
			mMaxElements = maxElements;

			// MSDN documentation says if the number of elements is 10 or less, we want to use the ListDictionary class.
			if (maxElements <= 10)
			{
				mCacheItems = new ListDictionary();
			}
			else
			{
				mCacheItems = new Hashtable(maxElements);
			}
		}

		/// <summary>
		/// Adds a new element to the cache. Note that if the cache is already filled up, some element will be removed.
		/// </summary>
		/// <param name="key"></param>
		/// <param name="val"></param>
		public void Add(string key, object val)
		{
			if (this.Contains(key))
			{
				object eval = this[key];

				if (!val.Equals(val)) 
				{
					CacheElement elt = mCacheItems[key] as CacheElement;
					elt.pElement = val;
				}
			}
			else
			{
				if (!this.CanAddElement)
				{
					// Already at the maximum number of elements allowed...
					this.Remove(FindElementToRemove());
				}

				mCacheItems.Add(key, new CacheElement(val));
			}
		}

		/// <summary>
		/// Removes all elements from the cache.
		/// </summary>
		public void Clear() { mCacheItems.Clear(); }

		/// <summary>
		/// Determines whether the cache has an element with the specified key.
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public bool Contains(string key) { return mCacheItems.Contains(key); }

		/// <summary>
		/// Removes the object with the specified key from the cache.
		/// </summary>
		/// <param name="key"></param>
		public void Remove(string key) { mCacheItems.Remove(key); }

		/// <summary>
		/// Returns the element with the specified key from the cache.
		/// </summary>
		public object this[string key]
		{
			get
			{
				CacheElement elt = mCacheItems[key] as CacheElement;

				if (elt != null)
				{
					elt.pAccessTime = DateTime.Now;
				}

				return elt.pElement;
			}
		}

		/// <summary>
		/// Gets whether or not we can add another element without removing an existing one.
		/// </summary>
		public bool CanAddElement
		{
			get { return (mCacheItems.Count < mMaxElements); }
		}

		/// <summary>
		/// Returns an enumerator over the elements in the cache.
		/// </summary>
		/// <returns></returns>
		public IDictionaryEnumerator GetEnumerator()
		{
			return new CacheEnumerator(this);
		}

		#region Private Parts
		/// <summary>
		/// This function will be called whenever we hit the maximum number of elements allowed and will return the id of the element which we would like to remove. In the current implementation, it uses the LRU algorithm to determine which element to remove.
		/// </summary>
		/// <returns></returns>
		private string FindElementToRemove()
		{
			string lruKey = null;
			CacheElement lruElement = null;

			foreach (DictionaryEntry entry in mCacheItems) 
			{
				CacheElement elt = entry.Value as CacheElement;

				if (lruElement == null)
				{
					lruElement = elt;
				}
				else
				{
					// See if the current element is older than the one we have stored.
					if (elt.pAccessTime.CompareTo(lruElement) < 0)
					{
						lruElement = elt;
						lruKey = entry.Key as string;
					}
				}
			}

			return lruKey;
		}

		/// <summary>
		/// We want to keep track of access times to the elements of the cache, hence the need for a private class to encapsulate that data.
		/// </summary>
		private class CacheElement
		{
			public DateTime pAccessTime;
			public object pElement;

			public CacheElement(object elt)
			{
				pElement = elt;
				pAccessTime = DateTime.Now;
			}
		}

		/// <summary>
		/// The enumerator for the cache class.
		/// </summary>
		private class CacheEnumerator : IDictionaryEnumerator
		{
			private IDictionaryEnumerator enumerator;

			public CacheEnumerator(Cache c) 
			{
				enumerator = c.mCacheItems.GetEnumerator();
			}

			#region IDictionaryEnumerator Members

			public object Key
			{
				get
				{
					return enumerator.Key;
				}
			}

			public object Value
			{
				get
				{
					return (enumerator.Value as CacheElement).pElement;
				}
			}

			public DictionaryEntry Entry
			{
				get
				{
					DictionaryEntry entry = enumerator.Entry;
					entry.Value = this.Value;

					return entry;
				}
			}

			#endregion

			#region IEnumerator Members

			public void Reset()
			{
				enumerator.Reset();
			}

			public object Current
			{
				get
				{
					return this.Value;
				}
			}

			public bool MoveNext()
			{
				return enumerator.MoveNext();
			}

			#endregion
		}
		#endregion
	}
}
