using System;
using System.Collections;
using System.Web;
using System.Web.Caching;
using System.Threading;
using System.Diagnostics;

namespace Orciid.Core
{

	/// <summary>
	/// Base class for cachable objects
	/// </summary>
	/// <remarks>
	/// This class implements static methods to be used to cache object instances
	/// of classes derived from this class. If the method is not called,
	/// no caching is performed. Objects stored in the cache are identified by a string class
	/// identifier and an internal identifier. In addition to storing individual objects, a list
	/// of all available internal identifiers of a certain class can also be stored and queried.
	/// This is useful if a list of all instances of a certain class is required, but not all
	/// instances are currently cached.  Items are cached on a per-thread basis, so once a thread
	/// is finishing execution, it must call the <c>Clear()</c> method to release all items for the
	/// thread.
	/// </remarks>
	public abstract class CachableObject:
		ModifiableObject
	{
		// the per-thread cache is used if HttpContext.Current is not available.
		// you can use ResetThreadCache() to flush this cache.
		[ThreadStatic]
		private static IDictionary threadcache = null;

		// this will return a cache for the current web request, or the global cache if
		// this is not running within a web application
		private static IDictionary cache
		{
			get
			{
				HttpContext c = HttpContext.Current;
				if (c == null || c.Items == null)
				{
					if (threadcache == null)
						threadcache = new Hashtable();
					return threadcache;
				}
				else
					return c.Items;
			}
		}

		/// <summary>
		/// Reset thread cache
		/// </summary>
		/// <remarks>
		/// This method clears the cache for the current thread.  In a web application,
		/// this method should be called after a page is done processing, in case the same
		/// thread is used to process another page.
		/// </remarks>
		public static void ResetThreadCache()
		{
			threadcache = null;
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// This constructor has no functionality, it only calls the base class' constructor.
		/// </remarks>
		public CachableObject():
			base()
		{
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <remarks>
		/// This constructor has no functionality, it only calls the base class' constructor.
		/// </remarks>
		/// <param name="init">dummy parameter, should be <c>false</c></param>
		public CachableObject(bool init):
			base(init)
		{
		}

		private static string BuildObjectItemID(string itemid)
		{
			return String.Format("ORCIID:{0}", itemid);
		}

		private static string BuildObjectID(string classid, int id)
		{
			return String.Format("ORCIID:{0}:{1}", classid, id);
		}

		private static string BuildObjectID(CachableObject obj)
		{
			return BuildObjectID(obj.GetClassIdentifier(), obj.GetID());
		}
		
		private static string BuildClassID(string classid)
		{
			return String.Format("ORCIID:{0}", classid);
		}

		/// <summary>
		/// Retrieve object from cache
		/// </summary>
		/// <remarks>
		/// This method checks if an object of a given class with a given internal identifier
		/// is currently cached. If the object is cached, it is returned, otherwise this
		/// method returns <c>null</c>.
		/// </remarks>
		/// <param name="classid">A string identifier of the class the requested object is an instance of.</param>
		/// <param name="id">The internal identifier of the requested object.</param>
		/// <returns>The requested object, or <c>null</c> if the object is not in the cache
		/// or caching is not activated.</returns>
		protected static CachableObject GetFromCache(string classid, int id)
		{
			return (cache == null ? null : (CachableObject)cache[BuildObjectID(classid, id)]);
		}

		/// <summary>
		/// Retrieve object from cache
		/// </summary>
		/// <remarks>
		/// This method checks if an object with a specified item identifier is currently cached,
		/// if so, it is returned, otherwise <c>null</c> is returned.
		/// </remarks>
		/// <param name="itemid">A string identifier</param>
		/// <returns>The requested object, or <c>null</c> if the object is not in the cache
		/// or caching is not activated.</returns>
		protected static object GetObjectItemFromCache(string itemid)
		{
			return (cache == null ? null : cache[BuildObjectItemID(itemid)]);
		}

		/// <summary>
		/// Add object to cache
		/// </summary>
		/// <remarks>
		/// This method adds an object to the cache. If caching is not activated, no
		/// action is performed. If an object with the same internal identifier is already 
		/// in the cache, the old copy is replaced.
		/// </remarks>
		/// <param name="obj">The object to cache.</param>
		protected static void AddToCache(CachableObject obj)
		{
			if (obj != null && cache != null)
				cache[BuildObjectID(obj)] = obj;
		}

		/// <summary>
		/// Add object to cache
		/// </summary>
		/// <remarks>
		/// This method adds an object to the cache. If caching is not activated, no
		/// action is performed. If the newentry parameter is <c>true</c>, the internal
		/// identifier is added to the cached list of internal identifiers for the specified
		/// class. If an object with the same internal identifier is already 
		/// in the cache, the old copy is replaced.
		/// </remarks>
		/// <param name="obj">The object to cache.</param>
		/// <param name="newentry">This parameter must be <c>true</c> if the object has been newly
		/// created, <c>false</c> otherwise.</param>
		protected static void AddToCache(CachableObject obj, bool newentry)
		{
			AddToCache(obj);
			if (newentry)
				AddIDToCache(obj.GetClassIdentifier(), obj.GetID());
		}

		/// <summary>
		/// Add object to cache
		/// </summary>
		/// <remarks>
		/// This method adds an object to the cache. If caching is not activated, no
		/// action is performed. If an object with the same identifier is already 
		/// in the cache, the old copy is replaced.
		/// </remarks>
		/// <param name="item">The object to cache</param>
		/// <param name="itemid">The identifier of the object to cache</param>
		protected static void AddObjectItemToCache(string itemid, object item)
		{
			if (cache != null && itemid != null && itemid.Length > 0 && item != null)
				cache[BuildObjectItemID(itemid)] = item;
		}

		/// <summary>
		/// Remove object from cache
		/// </summary>
		/// <remarks>
		/// This method removes an object from the cache. This is only necessary 
		/// if an object has changed e.g. on 
		/// another server and needs to be recreated from the database on this server.
		/// </remarks>
		/// <param name="itemid">The identifier of the object to remove from the cache.</param>
		protected static void RemoveObjectItemFromCache(string itemid)
		{
			if (cache != null && itemid != null && itemid.Length > 0)
				cache.Remove(BuildObjectItemID(itemid));
		}

		/// <summary>
		/// Remove object from cache
		/// </summary>
		/// <remarks>
		/// This method removes an object from the cache. This is only necessary 
		/// if an object has changed e.g. on 
		/// another server and needs to be recreated from the database on this server.
		/// </remarks>
		/// <param name="obj">The object to remove from the cache.</param>
		protected static void RemoveFromCache(CachableObject obj)
		{
			if (obj != null && cache != null)
				cache.Remove(BuildObjectID(obj));
		}

		/// <summary>
		/// Remove object from cache
		/// </summary>
		/// <remarks>
		/// This method removes an object from the cache. This is only necessary if an object
		/// is deleted and should no longer be available, or if an object has changed e.g. on 
		/// another server and needs to be recreated from the database on this server. 
		/// If the delentry parameter is <c>true</c>, the internal
		/// identifier is removed from the cached list of internal identifiers for the specified
		/// class.
		/// </remarks>
		/// <param name="obj">The object to remove from the cache.</param>
		/// <param name="delentry">This parameter must be <c>true</c> if the object has been 
		/// permanently removed, <c>false</c> otherwise.</param>
		protected static void RemoveFromCache(CachableObject obj, bool delentry)
		{
			RemoveFromCache(obj);
			if (delentry)
				RemoveIDFromCache(obj.GetClassIdentifier(), obj.GetID());
		}

		/// <summary>
		/// Get list of internal identifiers for specified class
		/// </summary>
		/// <remarks>
		/// A complete list of internal identifiers for all objects of a specified class
		/// can be cached. This method returns such a list for the specified class.
		/// </remarks>
		/// <param name="classid">The class identifier for which to return internal 
		/// object identifiers</param>
		/// <returns>An <see cref="ArrayList"/> of internal object identifiers, or <c>null</c>
		/// if that list is not currently cached or if caching is not active.</returns>
		protected static ArrayList GetIDsFromCache(string classid)
		{
			return (cache == null ? null : (ArrayList)cache[BuildClassID(classid) + ":IDs"]);
		}

		/// <summary>
		/// Set list of internal identifiers for specified class
		/// </summary>
		/// <remarks>
		/// A complete list of internal identifiers for all objects of a specified class
		/// can be cached. This method sets such a list for the specified class. Any previously
		/// set list is replaced.
		/// </remarks>
		/// <param name="classid">A string identifier of the class for the internal identifier list</param>
		/// <param name="ids">An <see cref="ArrayList"/> of internal identifiers</param>
		protected static void AddIDsToCache(string classid, ArrayList ids)
		{
			if (ids != null && cache != null)
				cache[BuildClassID(classid) + ":IDs"] = ids;
		}

		/// <summary>
		/// Add an internal identifier for a specified class
		/// </summary>
		/// <remarks>
		/// A complete list of internal identifiers for all objects of a specified class
		/// can be cached. This method adds one internal identifier to such a list 
		/// for the specified class. 
		/// </remarks>
		/// <param name="classid">A string identifier of the class for the internal identifier</param>
		/// <param name="id">The internal identifier to add</param>
		protected static void AddIDToCache(string classid, int id)
		{
			ArrayList list = GetIDsFromCache(classid);
			if (list != null && !list.Contains(id))
			{
				list.Add(id);
				AddIDsToCache(classid, list);
			}
		}

		/// <summary>
		/// Remove an internal identifier for a specified class
		/// </summary>
		/// <remarks>
		/// A complete list of internal identifiers for all objects of a specified class
		/// can be cached. This method removes one internal identifier from such a list 
		/// for the specified class. 
		/// </remarks>
		/// <param name="classid">A string identifier of the class for the internal identifier</param>
		/// <param name="id">The internal identifier to remove</param>
		protected static void RemoveIDFromCache(string classid, int id)
		{
			ArrayList list = GetIDsFromCache(classid);
			if (list != null && list.Contains(id))
			{
				list.Remove(id);
				AddIDsToCache(classid, list);
			}
		}

		/// <summary>
		/// Return the class identifier
		/// </summary>
		/// <remarks>
		/// Every class derived from this class must implement this method. It must return
		/// a string that is unique in the system. The suggested format is <c>Namespace.Class</c>.
		/// </remarks>
		/// <returns>A unique string identifier for this class</returns>
		protected abstract string GetClassIdentifier();

		/// <summary>
		/// Display the current cache contents
		/// </summary>
		/// <remarks>
		/// This method prints the keys of all cached items for the current thread to the Debug 
		/// console.
		/// </remarks>
		public static void Dump()
		{
			string threadid = AppDomain.GetCurrentThreadId().ToString() + ":";
			foreach (DictionaryEntry item in cache)
				if (item.Key is string && ((string)item.Key).StartsWith(threadid))
					Debug.WriteLine(item.Key);
		}

		/// <summary>
		/// Clear the cache
		/// </summary>
		/// <remarks>
		/// This method removes all items for the current thread from the cache.
		/// </remarks>
		public static void Clear()
		{
			ArrayList todelete = new ArrayList();
			string threadid = AppDomain.GetCurrentThreadId().ToString() + ":";
			foreach (DictionaryEntry item in cache)
				if (item.Key is string && ((string)item.Key).StartsWith(threadid))
					todelete.Add(item.Key);
			foreach (string key in todelete)
				cache.Remove(key);
		}
	}
}