//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
//     Copyright (c) Telligent Systems Corporation.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using System.Collections;

namespace CommunityServer.Components
{
	/// <summary>
	/// Manager class for PostCatories. This class can not be instantiated.
	/// </summary>
	public class PostCategories
	{
        //Do not allow the class to be instantiated
		private PostCategories(){}

        #region Create/Update/Delete

		public static string FormatName(string name)
		{
			if(CSContext.Current.SiteSettings.EnableCensorship)
			{
				name = Transforms.CensorPost(name);
			}
				
			return Globals.HtmlEncode(name);
		}
		
		private static void Format(PostCategory category)
		{

			if(CSContext.Current.SiteSettings.EnableCensorship)
			{
				category.Name = Transforms.CensorPost(category.Name);
				category.Description = Transforms.CensorPost(category.Description);
			}
				
			category.Name = Globals.HtmlEncode(category.Name);
			category.Description = HtmlScrubber.Clean(category.Description,true,true);
		}


        /// <summary>
        /// Creates a new PostCategory
        /// </summary>
        /// <param name="category"></param>
        public static void CreateCategory(PostCategory category)
        {
			Format(category);

            CommonDataProvider dp = CommonDataProvider.Instance();
             category.CategoryID = dp.CreateCategory(category);
            
            // Make sure the addition was successful
            if(category.CategoryID > 0)
            {
				//note this only updates the cache on the current node of the cluster
				//for cluster safety we need to flush the cache on the next request for data
				Hashtable categories = CSCache.Get(CacheKey(category.SectionID)) as Hashtable;
                if(categories != null)
                {
                    categories.Add(category.CategoryID,category);
                }
            }
            else if(category.CategoryID == -1)
                throw new CSException(CSExceptionType.CategoryDuplicate);
            else
                throw new CSException(CSExceptionType.UnknownError);

			Tags.ExpirePostsMatchingTagsCache();
			Tags.ExpireTagsCache();
        }

		
        /// <summary>
        /// Updates an existing PostCategory. It will also attempt to update the Cached version as well (not cluster safe)
        /// </summary>
        /// <param name="category"></param>
        public static void UpdateCategory(PostCategory category)
        {
			Format(category);

            CommonDataProvider dp = CommonDataProvider.Instance();
            if (!dp.UpdateCategory(category))
				throw new CSException(CSExceptionType.CategoryDuplicate);

            // Update it in the database

			//note this only updates the cache on the current node of the cluster
			//for cluster safety we need to flush the cache on the next request for data
			Hashtable categories = CSCache.Get(CacheKey(category.SectionID)) as Hashtable;
            if(categories != null)
            {
                if(categories.ContainsKey(category.CategoryID))
                {
                    try
                    {
                        categories[category.CategoryID] = category;
                    }
                    catch{}//not much to do here
                }
            }

			Tags.ExpirePostsMatchingTagsCache();
			Tags.ExpireTagsCache();

			CSEvents.CategoryUpdated(category);
        }



        /// <summary>
        /// Deletes in existing category. Will NOT update the cache
        /// </summary>
        public static void DeleteCategory(int categoryID, int forumID)
        {
			PostCategory category = PostCategories.GetCategory(categoryID, forumID);

			if (category != null)
			{
				CommonDataProvider dp = CommonDataProvider.Instance();
				dp.DeleteCategory(categoryID,forumID);
				//note this only removes the cache on the current node of the cluster
				//for cluster safety we need to flush the cache on the next request for data
				CSCache.Remove(CacheKey(forumID));

				Tags.ExpirePostsMatchingTagsCache();
				Tags.ExpireTagsCache();

				CSEvents.CategoryRemoved(category);
			}
        }
        #endregion

        #region Get By CategoryID
        /// <summary>
        /// Gets the specified category by CategoryID. CategoryType is required for the 
        /// cache look up. Will only return "allowed/published" categories.
		/// These are returned from the in memory hash table (if present)
		/// </summary>
        public static PostCategory GetCategory(int categoryID, int forumID)
        {
            return GetCategory(categoryID, forumID, false);
        }

        /// <summary>
        /// Gets the specified category by CategoryID. CategoryType is required for the 
        /// cache look up. Will filter "allowed/published" categories.
		/// These are returned from the in memory hash table (if present)
		/// </summary>
        public static PostCategory GetCategory(int categoryID, int forumID, bool allowDisabled)
        {
            Hashtable categories = GetCategoriesTable(forumID, true, allowDisabled);
            return (PostCategory)categories[categoryID];
        }

		/// <summary>
		///	Gets the specified category by CategoryID.  By passing flush = true, it will get the lastes
		///	list of categories from the database before the lookup. (Cluster safe)
		/// </summary>
		public static PostCategory GetCategory(int categoryID, int forumID, bool allowDisabled, bool flush)
		{
			if(flush)
				CSCache.Remove(CacheKey(forumID));
			return GetCategory(categoryID, forumID, allowDisabled);
		}
        #endregion

        #region Get By CategoryName
        /// <summary>
        /// Gets the specified category by CategoryID. CategoryType is required for the 
        /// cache look up. Will only return "allowed/published" categories.
        /// </summary>
        public static PostCategory GetCategory(string name, int forumID)
        {
            return GetCategory(name, forumID, false);
        }

        /// <summary>
        /// Gets the specified category by CategoryID. CategoryType is required for the 
        /// cache look up. Will filter "allowed/published" categories.
		/// These are returned from the in memory hash table (if present)
		/// </summary>
        public static PostCategory GetCategory(string name, int forumID, bool allowDisabled)
        {
            Hashtable categories = GetCategoriesTable(forumID, true, allowDisabled);
            PostCategory pc  = null;
            foreach(int key in categories.Keys)
            {
                pc = categories[key] as PostCategory;
                if(pc != null && string.Compare(pc.Name, name, false) == 0)
                    return pc;
            }

			return pc;
        }
        #endregion

		#region Get By CategoryName (not section-specific)

		public static ArrayList GetCategoriesInSections(string name, int[] sectionIDs)
		{
			return CommonDataProvider.Instance().GetCategoriesInSections(name, sectionIDs);
		}

		public static ArrayList GetCategoriesInGroup(string name, int groupID)
		{
			return GetCategoriesInGroups(name, new int[] { groupID });
		}

		public static ArrayList GetCategoriesInGroups(string name, int[] groupIDs)
		{
			return CommonDataProvider.Instance().GetCategoriesInGroups(name, groupIDs);
		}

		public static ArrayList GetCategories(string name, ApplicationType applicationType)
		{
			return CommonDataProvider.Instance().GetCategories(name, applicationType);
		}

		public static ArrayList GetCategories(string name)
		{
			return CommonDataProvider.Instance().GetCategories(name);
		}

		#endregion

		#region Get Without Posts

		public static ArrayList GetEmptyCategoriesInSections(int sectionID)
		{
			return GetEmptyCategoriesInSections(new int[] { sectionID });
		}

		public static ArrayList GetEmptyCategoriesInSections(int[] sectionIDs)
		{
			return CommonDataProvider.Instance().GetEmptyCategoriesInSections(sectionIDs);
		}

		public static ArrayList GetEmptyCategoriesInGroup(int groupID)
		{
			return GetEmptyCategoriesInGroups(new int[] { groupID });
		}

		public static ArrayList GetEmptyCategoriesInGroups(int[] groupIDs)
		{
			return CommonDataProvider.Instance().GetEmptyCategoriesInGroups(groupIDs);
		}

		public static ArrayList GetEmptyCategories(ApplicationType applicationType)
		{
			return CommonDataProvider.Instance().GetEmptyCategories(applicationType);
		}

		public static ArrayList GetEmptyCategories()
		{
			return CommonDataProvider.Instance().GetEmptyCategories();
		}

		#endregion

        #region Get All
        /// <summary>
        /// Returns all of the allowed categories
		/// These are returned from the in memory hash table (if present)
		/// </summary>
        public static ArrayList GetCategories(int forumID)
        {
            return GetCategories(forumID, true, false);
        }
		public static ArrayList GetCategories(int forumID, int parentID)
		{
			return GetCategories(forumID, true, false, parentID);
		}

        /// <summary>
        /// Returns all of the categories from the cache. Can be filtered by allowed categories
		/// These are returned from the in memory hash table (if present)
		/// </summary>
        public static ArrayList GetCategories(int forumID, bool allowDisabled)
        {
            return GetCategories(forumID, true, allowDisabled);
        }
		public static ArrayList GetCategories(int forumID, bool allowDisabled, int parentID)
		{
			return GetCategories(forumID, true, allowDisabled, parentID);
		}

        /// <summary>
        /// Returns all of the categories (cache optional). Can be filtered by allowed categories
        /// Passing false to cacheable will allow the server to re-load the database values but not update the cache 
        /// (use flush with cacheable true to update the cache)
        /// </summary>
        public static ArrayList GetCategories(int forumID, bool cacheable, bool allowDisabled)
        {
        	return GetCategories(forumID, cacheable, allowDisabled, 0);
        }

		/// <summary>
		/// Returns all of the categories (cache optional). Can be filtered by allowed categories and parentID
		/// Passing false to cacheable will allow the server to re-load the database values but not update the cache
		/// (use flush with cacheable true to update the cache)
		/// </summary>
		public static ArrayList GetCategories(int forumID, bool cacheable, bool allowDisabled, int parentID)
        {
            ArrayList categories = new ArrayList();
            Hashtable categoryTable;

            // Get the Albums as a hashtable
            categoryTable = GetCategoriesTable(forumID, cacheable, allowDisabled);

            // Convert the hashtable to an arraylist
            foreach(PostCategory category in categoryTable.Values)
                if((category.ParentID == parentID) || (parentID == -1))
                    categories.Add( category );

            // Sort the categories
            categories.Sort();

            return categories;
        }
		/// <summary>
		/// Returns all of the categories (cache optional). Can be filtered by allowed categories and parentID
		/// Passing false to cacheable will cause the server to re-load the database values but not update the cache
		/// (use flush true with cacheable true to update the cache)
		/// </summary>
		public static ArrayList GetCategories(int forumID, bool cacheable, bool allowDisabled, int parentID, bool flush)
		{
			if(flush)
				CSCache.Remove(CacheKey(forumID));
			return GetCategories(forumID, cacheable, allowDisabled, parentID);
		}
		#endregion

        #region CacheKey
        /// <summary>
        /// Categories are cached by type and sectionID. Generates consistent key
        /// </summary>
        private static string CacheKey(int sectionID)
        {
            return string.Format("CategoriesCacheKey:{0}",sectionID);
        }

		public static void ExpireCategoryCache()
		{
			CSCache.RemoveByPattern("CategoriesCacheKey:.*");
		}

		public static void ExpireCategoryCache(int sectionID)
		{
			CSCache.Remove(CacheKey(sectionID));
		}

        #endregion

        #region Internal Manager
        /// <summary>
        /// Returns a list of categories. Caching and Disabled are optional.
		/// Passing false to cacheable will allow the server to re-load the database values
		/// </summary>
        private static Hashtable GetCategoriesTable(int forumID, bool cacheable, bool allowDisabled)
        {
            string cacheKey = CacheKey(forumID);

            #if DEBUG_NOCACHE
                        cacheable = false;
            #endif

            Hashtable categories = CSCache.Get(cacheKey) as Hashtable;
            Hashtable filtered = new Hashtable();
                       
            // Ensure it's not in the cache
            if ((!cacheable) && (categories != null))
                CSCache.Remove(cacheKey);

            // Get the categories
            if(categories == null)
            {

                // This gets all categories regardless of type
                CommonDataProvider dp = CommonDataProvider.Instance();
                categories =  dp.GetCategories(forumID);

                // Cache if we can
                if (cacheable)
                    CSCache.Insert(cacheKey, categories, CSCache.MinuteFactor);
                    
            } 

            // Filter for the categories requested
            foreach (PostCategory category in categories.Values) {
                    filtered.Add(category.CategoryID, category);
            }


            // Should we return all of them?
            if(allowDisabled == true)
                return filtered;

            // Filter out the disabled categories
            Hashtable enabledCategories = new Hashtable();
            foreach(PostCategory category in filtered.Values)
                if(category.IsEnabled == true)
                    enabledCategories.Add(category.CategoryID, category);
            return enabledCategories;
        }
        #endregion

		#region Auto Naming

		/// <summary>
		/// Searches the list for the next available name based on the pattern string
		/// </summary>
		public static string NextCategoryName(ArrayList categories , string baseName)
		{
			string newname = baseName;
			if(!HasCategory(categories, newname))
				return newname;
			
			int i = 2;
			newname = string.Concat(baseName, " ({0})");
			while(HasCategory(categories, string.Format(newname,i)))
				i++;

			return string.Format(newname,i);
		}

		/// <summary>
		/// Checks to see if a category with that name already exists in the list
		/// </summary>
		public static bool HasCategory(ArrayList categories, string name)
		{
			foreach(PostCategory category in categories)
				if(string.Compare(category.Name, name, true) == 0)
					return true;

			return false;
		}

		#endregion
	}
}
