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

using System;
using System.IO;
using System.Collections;
using System.Web.Caching;
using CommunityServer.Components;
using System.Security;

namespace CommunityServer.Discussions.Components {

    // *********************************************************************
    //  Forums
    //
    /// <summary>
    /// This class contains methods for working with the Forums.
    /// </summary>
    /// 
    // ********************************************************************/ 
    public sealed class Forums {

		#region CreateUpdateDelete
		public static int Update(Forum f)
		{
			return Sections.UpdateSection(f);
		}

		public static int Add(Forum f)
		{
			return Sections.AddSection(f);
		}

		public static void Delete(Forum f)
		{
			Sections.DeleteSection(f);

			ForumAttachmentSettings fas = ForumConfiguration.Instance().AttachmentSettings;

			try
			{
				fas.DeleteSectionFromDisk(f.SectionID);
			}
			catch(IOException ex)
			{ 
				new CSException(CSExceptionType.AccessDenied, "Delete: Permission to delete file on filesystem denied.", ex).Log(); 
			}
			catch(SecurityException ex)
			{ 
				new CSException(CSExceptionType.AccessDenied, "Delete: Permission to delete file on filesystem denied.", ex).Log(); 
			}
			catch { }
		}
		#endregion

        #region Mark All As Read
        public static void MarkAllForumsRead(int userID, int forumGroupID, int forumID, bool markThreadsRead) 
        {

            ForumDataProvider fdp = ForumDataProvider.Instance();
            fdp.MarkAllForumsRead(userID, forumGroupID, forumID, markThreadsRead);

        }
        #endregion

        #region GetForums

        public static ArrayList GetForums() 
        {
            return GetForums(false, true,true, false);
        }

        public static ArrayList GetForums(bool ignorePermissions, bool cacheable, bool toplevelForumsOnly, bool flush ) 
        {
            ArrayList forums = new ArrayList();
            Hashtable forumsTable;

            // Get the ForumGroups as a hashtable
            //
            forumsTable = GetForumsTable(ignorePermissions, cacheable, flush, false);

            // Convert the hashtable to an arraylist
            //
			foreach(Forum forum in forumsTable.Values) {
				if( toplevelForumsOnly ) {
					if( forum.ParentID == 0 )
						forums.Add( forum );
				}
				else
					forums.Add( forum );
			}

            // Sort the forum groups
            //
            forums.Sort(new SectionSorter());

            return forums;
        }

        internal static Hashtable GetForumsTable(bool ignorePermissions, bool cacheable, bool flush, bool ignoreLanguageFilter) {
            CSContext csContext = CSContext.Current;
            Hashtable unfilteredForums	= null;
            Hashtable forums;

			int settingsID = csContext.SiteSettings.SettingsID;
			string cacheKey = string.Format("Forums-Site:{0}",settingsID);
			string localKey = string.Format("ForumsForUser:{0}:{1}",ignorePermissions,ignoreLanguageFilter);

			if(flush)
			{
				CSCache.Remove(cacheKey);
				csContext.Items[localKey] = null;
			}

            #if DEBUG_NOCACHE
                cacheable = false;
            #endif

            // Have we already fetched for this request?
            //
			
            //If something is in the context with the current cache key, we have already processed and validated this request!
			unfilteredForums = csContext.Items[localKey] as Hashtable;

            //We do not need to revalidate this collection on the same request
            if((unfilteredForums != null))
                return unfilteredForums;
			else if(!cacheable)
				csContext.Items.Remove(cacheKey);
            
            //Is it safe to use the cached version?
			if ((!cacheable))
				CSCache.Remove(cacheKey);

            //If we find the forums in the cache, we need to revalidate them. DO NOT return the coolection unless
            //the call specifies ignorepermissions
            unfilteredForums = CSCache.Get(cacheKey) as Hashtable;

			// Get the raw forum groups
            //
            if ( unfilteredForums == null ) {
                unfilteredForums = ForumDataProvider.Instance().GetForums();

                // Dynamically add the special forum for private messages
                //
                unfilteredForums.Add( 0, PrivateForum() );

				// Process statics for forums
				unfilteredForums = ProcessStatisticsForSubForums(unfilteredForums);

                // Cache if we can
                //
                if (cacheable)
                    CSCache.Insert(cacheKey,unfilteredForums,CSCache.MinuteFactor * 15,CacheItemPriority.High);

            }

			if (!ignoreLanguageFilter && !ignorePermissions)
				unfilteredForums = Sections.FilterByLanguage(unfilteredForums, csContext);

            // Are we ignoring permissions?
            //
            if (ignorePermissions)
            {
                //Save us the logic look up later
                csContext[localKey] = unfilteredForums;
                return unfilteredForums;
            }

            // We need to create a new hashtable
            //
            forums = new Hashtable();
			User user = csContext.User;

            // Filter the list of forums to only show forums this user 
            // is allowed to see
            //
            foreach (Forum f in unfilteredForums.Values) {

                // The forum is added if the user can View, Read
                //
                
                if( Permissions.ValidatePermissions(f,Permission.View,user) )
					if(f.IsActive || user.IsForumAdministrator)
						forums.Add(f.SectionID, f);
				
            }

            // Insert into request cache
            //
            csContext[localKey] = forums;

            return forums;

        }
        #endregion

        #region Private Forum
        // *********************************************************************
        //  PrivateForum
        //
        /// <summary>
        /// Used to create and return the special private messages forum
        /// </summary>
        /// 
        // ***********************************************************************/
        private static Forum PrivateForum() 
        {
			// {ABDD286C-3ADE-4b86-BFC4-28B2B20A8BBF}
        	Guid privateGuid = new Guid(0xabdd286c, 0x3ade, 0x4b86, 0xbf, 0xc4, 0x28, 0xb2, 0xb2, 0xa, 0x8b, 0xbf );
			Forum f = new PrivateForum();

            // Set the forum name
            //
            f.Name = "Private Messages";
            f.IsActive = true;
            //f.IsPrivate = true;

			
			ForumPermission fp = new ForumPermission( CSContext.Current.SiteSettings.SettingsID, privateGuid, "Private Message Permission"
										,Permission.Announcement | Permission.CreatePoll | Permission.Delete | Permission.Edit | 
											Permission.Post | Permission.Read | Permission.Reply | Permission.View | Permission.Vote
										,Permission.LocalAttachment | Permission.Administer | Permission.EditOthers | Permission.Moderate | 
											Permission.Sticky | Permission.SystemAdmin
										,false );
			f.PermissionSet.Add( Roles.Everyone, fp );

            return f;
        }
        #endregion

        #region Forms By Group
        // *********************************************************************
        //  GetForumsByForumGroupID
        //
        /// <summary>
        /// Used to return a narrow collection of forums that belong to a given forum id.
        /// The username is provied for personalization, e.g. if the user has new
        /// posts in the forum. Does not return InActive forums.
        /// </summary>
        /// <param name="forumGroupID">Forum Group ID to retrieve forums for</param>
        /// 
        // ***********************************************************************/
        public static ArrayList GetForumsByForumGroupID (int forumGroupID) 
        {
            return GetForumsByForumGroupID(forumGroupID, true, false);
        }

		public static ArrayList GetForumsByForumGroupID (int forumGroupID, bool cacheable, bool ignorePermissions)
		{
			return GetForumsByForumGroupID(forumGroupID, cacheable,ignorePermissions,false);
		}

        public static ArrayList GetForumsByForumGroupID (int forumGroupID, bool cacheable, bool ignorePermissions, bool flush) {		
			return GetForumsByForumGroupID(forumGroupID, cacheable, ignorePermissions, flush, true);
        }

		public static ArrayList GetForumsByForumGroupID (int forumGroupID, bool cacheable, bool ignorePermissions, bool flush, bool returnAll) 
		{		
			ArrayList allForums = Forums.GetForums(ignorePermissions, cacheable, true, flush);
			ArrayList forums = new ArrayList();

			foreach (Forum f in allForums) 
			{
				if (returnAll)
				{
					if (f.GroupID == forumGroupID)
						forums.Add(f);
				}
				else
				{
					if ((f.GroupID == forumGroupID) && (f.IsActive == true))
						forums.Add(f);
				}
			}

			return forums;

		}

        #endregion

        #region Get Forum (single)
        // *********************************************************************
        //  GetForum
        //
        /// <summary>
        /// Returns information on a particular forum.
        /// </summary>
        /// <param name="forumID">The ID of the Forum that you are interested in.</param>
        /// <returns>A Forum object with information about the specified forum.</returns>
        /// <remarks>If the passed in ForumID is not found, a ForumNotFoundException exception is
        /// thrown.</remarks>
        /// 
        // ***********************************************************************/
        public static Forum GetForum (int forumID) 
        {
            return GetForum(forumID, true, false);
        }

        public static Forum GetForum (int forumID, bool cacheable, bool ignorePermissions) {
            
            return (Forum) GetForumsTable(ignorePermissions, cacheable,false,true)[forumID];
            
        }

        #endregion
        
        #region Get Forums By PostID
        public static Forum GetForumByPostID(int postID) 
        {
            return GetForumByPostID(postID, CSContext.Current.User.UserID);
        }

        public static Forum GetForumByPostID(int postID, int userID) {
            return GetForumByPostID(postID, userID, true);
        }

        public static Forum GetForumByPostID(int postID, int userID, bool cacheable) {

            Hashtable postToForumLookUpTable = null;
            CSContext csContext = CSContext.Current;
            string cacheKey = "PostToForumLookUpTable";
            Forum forum;

            // First check if we have the postid in our hashtable
            //
            if (cacheable)
                postToForumLookUpTable = CSCache.Get(cacheKey) as Hashtable;

            if (postToForumLookUpTable == null) {
                postToForumLookUpTable = new Hashtable();
                CSCache.Insert(cacheKey,postToForumLookUpTable,6); //this had no weight before
            }

            forum = postToForumLookUpTable[postID] as Forum;
            if (forum != null)
                return forum;

            // We don't have it in our lookup table
            //
            ForumDataProvider dp = ForumDataProvider.Instance();

            // Retrieve from the database
            //
            forum = Forums.GetForum( dp.GetForumIDByPostID(postID) );
            
            // Store in the hashtable
            //
            postToForumLookUpTable[postID] = forum;

            return forum;

        }
        #endregion


        #region Filter
        public static ArrayList FilterForumsByAccessControl(ArrayList forums, Permission permissions)
        {
            ArrayList filteredList = new ArrayList();

			for (int i = 0; i < forums.Count; i++) {
				if(ForumPermission.Validate(forums[i] as Forum,permissions, CSContext.Current.User, null)) {
					filteredList.Add(forums[i]);
				}

			}
            return filteredList;
        }

        // Because the forums are unordered, we need to track child posts and threads seperately from
        // the forum's posts and threads.  That way we're not added posts multiple times when summing
        // total posts for parent forums when the tree is more than two levels deep.  
        private struct ForumChildren
        {
            internal int ChildPosts;
            internal int ChildThreads;
        }

        private static Hashtable ProcessStatisticsForSubForums (Hashtable forums) 
        {
            SortedList forumList = new SortedList();

            // Get child posts and threads for each forum
            foreach (Forum f in forums.Values)
            {
                if ( f.ParentID != 0)
                {
                    forumList = RecurseTotalPostsAndThreads(f.ParentID, f.TotalPosts,  f.TotalThreads, forums, forumList);
                }
            }

            // Now that we have the total child posts and threads, add them to the totals for their parents.
            foreach (Forum f in forums.Values)
            {
                if (forumList.ContainsKey(f.SectionID))
                {
                    f.TotalPosts += ((ForumChildren)forumList[f.SectionID]).ChildPosts;
                    f.TotalThreads += ((ForumChildren)forumList[f.SectionID]).ChildThreads;
                }
            }
            return forums;
        }

        private static SortedList RecurseTotalPostsAndThreads(int parentID, int sectionPosts, int sectionThreads, Hashtable forums, SortedList forumList) 
        {
            Forum parent = (Forum)forums[parentID];
            ForumChildren fc;

            if (forumList.ContainsKey(parentID))
            {
                fc = (ForumChildren)forumList[parentID];
            }
            else
            {
                fc = new ForumChildren();
                forumList.Add(parentID, fc);
            }
           
            fc.ChildPosts += sectionPosts;
            fc.ChildThreads += sectionThreads;
            forumList[parentID] = fc;

            if (parent.ParentID != 0)
            {
                RecurseTotalPostsAndThreads(parent.ParentID, sectionPosts, sectionThreads, forums, forumList);
            }

            return forumList;

        }
    
        #endregion

        #region Special Requests
		public static ArrayList GetSpecialForums( ForumType forumType ) 
        {
			ArrayList forums = Forums.GetForums(true, false, true, false );
			ArrayList specialForums = new ArrayList();

			foreach( Forum forum in forums ) {
				if( forum.ForumType == forumType ) {
					specialForums.Add( forum );
				}
			}

			return specialForums;
		}

		/// <summary>
		/// Method to retrive the deleted forum
		/// </summary>
		/// <returns></returns>
		public static Forum GetDeleteForum( ) {
			string cacheKey = "DeletedForumKey";
			Forum deletedForum;

			deletedForum = CSCache.Get(cacheKey) as Forum;

			if (deletedForum != null) 
				return deletedForum;

			ArrayList specialForums = GetSpecialForums( ForumType.Deleted );
			
			if( specialForums != null && specialForums.Count > 0 ) {
				deletedForum = (Forum)specialForums[0];
				CSCache.Max(cacheKey, deletedForum);
				return deletedForum;
			}
			else 
				return null;
		}

		/// <summary>
		/// Method to retrieve the system reporting forum
		/// </summary>
		/// <returns></returns>
		public static Forum GetReportedForum() {
			string cacheKey = "ReportedForumKey";
			Forum reportedForum;

			reportedForum = CSCache.Get(cacheKey) as Forum;

			if (reportedForum != null) 
				return reportedForum;

			ArrayList specialForums = GetSpecialForums( ForumType.Reporting );
			
			if( specialForums != null && specialForums.Count > 0 ) {
				reportedForum = (Forum)specialForums[0];
				CSCache.Max(cacheKey, reportedForum);
				return reportedForum;
			}
			else 
				return null;
		}


        /// <summary>
        /// For a given section ID, it gets the topmost section that is its indirect parent.
        /// </summary>
        /// <remarks>It uses the collection of sections that was previously cached.</remarks>
        public static Section GetTopmostParentSection (int sectionID)
        {
            Section section = Forums.GetForum( sectionID, true, true );
            if (section == null)
                return null;
            
            // Do we have a parent section?
            //
            if (section.ParentID > 0)
            {
                // Lookup topmost parent section through a recursive call
                //
                return GetTopmostParentSection( section.ParentID );
            }

            // We are on top, so return this section.
            //
            return section;
        }

       #endregion
    }
}
