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

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

namespace CommunityServer.Discussions.Components {

    // *********************************************************************
    //  Posts
    //
    /// <summary>
    /// This class contains methods for working with an individual post.  There are methods to
    /// Add a New Post, Update an Existing Post, retrieve a single post, etc.
    /// </summary>
    // ***********************************************************************/
    public class Posts {

        #region GetPost
        // *********************************************************************
        //  GetPost
        //
        /// <summary>
        /// Returns information about a particular post.
        /// </summary>
        /// <param name="PostID">The ID of the Post to return.</param>
        /// <returns>A Post object with the spcified Post's information.</returns>
        /// <remarks>This method returns information about a particular post.  If the post specified is
        /// not found, a PostNotFoundException exception is thrown.  If you need more detailed
        /// information, such as the PostID of the next/prev posts in the thread, or if the current user
        /// has email tracking enabled for the thread the post appears in, use the GetPostDetails
        /// method.<seealso cref="GetPostDetails"/></remarks>
        /// 
        // ***********************************************************************/
        public static ForumPost GetPost(int postID, int userID) {
            return Posts.GetPost( postID, userID, false, false );
        }

        public static ForumPost GetPost(int postID, int userID, bool trackViews) {
            return Posts.GetPost( postID, userID, trackViews, false );
        }

		public static ForumPost GetPost(int postID, int userID, bool trackViews, bool markRead) 
		{
			return GetPost(postID, userID, trackViews, markRead, true);
		}

		public static ForumPost GetPost(int postID, int userID, bool trackViews, bool markRead, bool includeCategories)
		{
        	CSContext csContext = CSContext.Current;

            // We only want to call this code once per request
            // LN 6/22/04: Added one more cond. to get the post from CSContext.Items
            // only when we don't want to track views, which is 
            // anywhere (?!) but PostFlatView control. :)
			string key = "Post" + postID + ":" + includeCategories;

            if (csContext.Items[key] != null && !trackViews) {
                return (ForumPost) csContext.Items[key];
            } else {
                ForumPost post;

                // Create Instance of the CommonDataProvider
                ForumDataProvider dp = ForumDataProvider.Instance();

                post = dp.GetPost( postID, userID, trackViews, markRead, includeCategories );

                // Store in context of current request
                csContext.Items[key] = post;

                return post;
            }
        }
        #endregion

		public static ArrayList GetPostCategories(int postID)
		{
			return ForumDataProvider.Instance().GetPostCategories(postID);
		}

        // *********************************************************************
        //  MarkPostAsRead
        //
        /// <summary>
        /// Given a post id, marks it as read in the database for a user.
        /// </summary>
        /// <param name="postID">Id of post to mark as read</param>
        /// <param name="username">Mark read for this user</param>
        /// 
        // ********************************************************************/ 
        public static void MarkPostAsRead(int postID, string username) {
            // Create Instance of the CommonDataProvider
            ForumDataProvider dp = ForumDataProvider.Instance();

            dp.MarkPostAsRead(postID, username);
        }
        
        // *********************************************************************
        //  GetTop25NewPosts
        //
        /// <summary>
        /// This method returns the top 25 new posts.  These are the 25 posts
        /// most recently posted to on the boards.
        /// </summary>
        /// <param name="PostID">Specifies the PostID of a post that belongs to the thread that we are 
        /// interested in grabbing the messages from.</param>
        /// <returns>A PostCollection containing the posts in the thread.</returns>
        /// 
        // ********************************************************************/
        public static PostSet GetTopNPopularPosts(string username, int postCount, int days)
        {
			return ForumDataProvider.Instance().GetTopNPopularPosts(username, postCount, days);
		}

        public static PostSet GetTopNNewPosts(string username, int postCount)
        {
            return ForumDataProvider.Instance().GetTopNNewPosts(username, postCount);
        }
	
        // *********************************************************************
        //  GetPosts
        //
        /// <summary>
        /// This method returns a listing of the messages in a given thread using paging.
        /// </summary>
        /// <param name="PostID">Specifies the PostID of a post that belongs to the thread that we are 
        /// interested in grabbing the messages from.</param>
        /// <returns>A PostCollection containing the posts in the thread.</returns>
        /// 
        // ********************************************************************/ 
		public static PostSet GetPosts(int postID, int pageIndex, int pageSize, int sortBy, int sortOrder, bool includeCategories)
        {
            PostSet postSet = null;
            string key = "Forum-Posts::P:{0}-PI:{1}-PS:{2}-SB:{3}-SO:{4}-C:{5}";
            string postCollectionKey = string.Format(key,postID,pageIndex,pageSize, sortBy, sortOrder, includeCategories);

            CSContext csContext = CSContext.Current;
            postSet = csContext.Items[key] as PostSet;

            if(postSet == null)
                postSet = CSCache.Get(postCollectionKey) as PostSet;
            

            if (postSet == null) {
                // Create Instance of the CommonDataProvider
                ForumDataProvider dp = ForumDataProvider.Instance();

                postSet = dp.GetPosts(postID, pageIndex, pageSize, sortBy, sortOrder, CSContext.Current.User.UserID, true, includeCategories);

                csContext.Items[key] = postSet;

                if(pageIndex == 0)
                    CSCache.Insert(postCollectionKey,postSet,6);
            }

            return postSet;
        }

		public static PostSet GetPosts(int postID, int pageIndex, int pageSize, int sortBy, int sortOrder)
		{
			return GetPosts(postID, pageIndex, pageSize, sortBy, sortOrder, true);
		}
	
		// *********************************************************************
		//  GetPosts_RSSThreadless
		//
		/// <summary>
		/// This method returns the most recent X number of posts from ALL threads for a RSS feed.
		/// </summary>
		/// <returns>A PostCollection containing the posts in the thread.</returns>
		/// 
		// ********************************************************************/ 
		public static PostSet GetPosts_RSSThreadless(int sectionID, int pageSize)
		{
			PostSet postSet = null;
			string key = "Forum-Posts-RSSThreadless::SID:{0}-PS:{1}";
			string postCollectionKey = string.Format(key, sectionID, pageSize);

			// Attempt to retrieve from Cache
			postSet = CSCache.Get(postCollectionKey) as PostSet;

			if (postSet == null) 
			{
				// Create Instance of the CommonDataProvider
				ForumDataProvider dp = ForumDataProvider.Instance();

				postSet = dp.GetPosts_RSSThreadless(sectionID, pageSize);

				CSCache.Insert(postCollectionKey ,postSet, 6);
			}

			return postSet;
		}
	
		/// <summary>
		/// This command clears the PostSet cache for a particular post collection.
		/// </summary>
		public static void ClearPosts(int postID, int pageIndex, int pageSize, int sortBy, int sortOrder)
		{
			string key = "Forum-Posts::P:{0}-PI:{1}-PS:{2}-SB:{3}-SO:{4}";
			string postCollectionKey = string.Format(key,postID,pageIndex,pageSize, sortBy, sortOrder);
			CSCache.Remove(postCollectionKey);
		}
	
		#region Removed GetAllMessages Method
        // *********************************************************************
        //  GetAllMessages
        //
        // <summary>
        // This method returns all of the messages for a particular forum 
        // (specified by ForumID) and returns the messages in a particular
        // format (specified by ForumView).
        // </summary>
        // <param name="ForumID">The ID of the Forum whose posts you are interested in retrieving.</param>
        // <param name="ForumView">How to view the posts.  The three options are: Flat, Mixed, and Threaded.</param>
        // <param name="PagesBack">How many pages back of posts to view.  Each forum has a 
        // parameter indicating how many days worth of posts to show per page.</param>
        // <returns>A PostCollection object containing the posts for the particular forum that fall within
        // the particular page specified by PagesBack.</returns>
        // 
        // ********************************************************************/ 
        //public static PostSet GetAllMessages(int forumID, ViewOptions forumView, int pagesBack) {
            // Create Instance of the CommonDataProvider
            //ForumDataProvider dp = ForumDataProvider.Instance();

            // make sure ForumView is set
            //if (forumView == ViewOptions.NotSet)
                //forumView = (ViewOptions) Globals.DefaultForumView;

            //return dp.GetAllMessages(forumID, forumView, pagesBack);			
        //}
		#endregion


        // *********************************************************************
        //  GetTotalPostCount
        //
        /// <summary>
        /// Returns the total count of all posts in the system
        /// </summary>
        /// <returns>A count of the total posts</returns>
        /// 
        // ********************************************************************/ 
        public static int GetTotalPostCount() 
		{
            // Create Instance of the CommonDataProvider
            ForumDataProvider dp = ForumDataProvider.Instance();

            return dp.GetTotalPostCount();

        }
		

		// *********************************************************************
		//  GetPageIndex
		//
		/// <summary>
		/// Returns the thread page number for the supplied post based on current paging settings
		/// </summary>
		/// <returns>The thread page number</returns>
		/// 
		// ********************************************************************/ 
		public static int GetPageIndex(int postID, int pageSize, int sortBy, int sortOrder)
		{
			int pageIndex;
			string key = "Post-Pages::P:{0}-PS:{1}-SB:{2}-SO:{3}";
			string pageIndexKey = string.Format(key,postID,pageSize, sortBy, sortOrder);

			// Attempt to retrieve from Cache
			if (CSCache.Get(pageIndexKey) == null)
			{
				// Create Instance of the CommonDataProvider
				ForumDataProvider dp = ForumDataProvider.Instance();

				pageIndex = dp.GetPageIndex(postID, pageSize, sortBy, sortOrder);

				CSCache.Insert(pageIndexKey,pageIndex,6);
			}
			else
			{
				pageIndex = Convert.ToInt32(CSCache.Get(pageIndexKey));
			}

			return pageIndex;
		}


        // *********************************************************************
        //  AddPost
        //
        /// <summary>
        /// This method Adds a new post and returns a Post object containing information about the
        /// newly added post.
        /// </summary>
        /// <param name="PostToAdd">A Post object containing information about the post to add.
        /// This Post object need only have the following properties set: Subject, Body, Username,
        /// and ParentID or ForumID.  If the post is a new post, set ForumID; if it is a reply to
        /// an existing post, set the ParentID to the ID of the Post that is being replied to.</param>
        /// <returns>A Post object containing information about the newly added post.</returns>
        /// <remarks>The Post object being returned by the AddPost method indicates the PostID of the
        /// newly added post and specifies if the post is approved for viewing or not.</remarks>
        /// 
        // ********************************************************************/ 
        public static ForumPost AddPost(ForumPost post) {

			return AddPost (post, CSContext.Current.User);
        }

		internal static void FormatPost (ForumPost post)
		{
			CSContext csContext = CSContext.Current;

			if (csContext.SiteSettings.EnableTrackPostsByIP && csContext.IsWebRequest)
				if (csContext.Context.Request.UserHostAddress != null)
					post.UserHostAddress = csContext.Context.Request.UserHostAddress;

			
			post.Subject = Globals.HtmlEncode(post.Subject);
			post.FormattedBody = post.Body;

			CSEvents.PrePost(post,ObjectState.Create,ApplicationType.Forum);
			CSEvents.BeforePost(post,ObjectState.Create,ApplicationType.Forum);
		}

		public static ForumPost AddPost (ForumPost post, User postAuthor, System.Guid attachmentID, string attachmentFilename)
		{
			post = AddPost(post, postAuthor);
			if(post.PostID > 0)
			{
				ForumAttachmentSettings fas = ForumConfiguration.Instance().AttachmentSettings;
				PostAttachment pa = PostAttachments.ToggleTemporary(attachmentID, attachmentFilename, !fas.EnableDataStoreStorage, post.PostID, post.SectionID, postAuthor.UserID);
				if (fas.EnableFileSystemStorage)
					fas.SaveAttachmentToDisk(pa, post.SectionID, post.PostID);

				post.AttachmentFilename = pa.FileName;
				UpdatePost(post, postAuthor.UserID);
			}

			return post;
		}

        public static ForumPost AddPost (ForumPost post, User postAuthor) {

			FormatPost(post);

            // Create Instance of the CommonDataProvider
            //
            ForumDataProvider dp = ForumDataProvider.Instance();

            ForumPost newPost = dp.AddPost(post, postAuthor.UserID);

			// Update user's last post datetime
			//
			Users.UpdateUserLastPostDate(postAuthor);

			if (post.Categories != null)
			{
				ForumDataProvider.Instance().UpdatePostCategories(newPost.SectionID, newPost.PostID, post.Categories);
				PostCategories.ExpireCategoryCache(newPost.SectionID);
			}

            // Send forum tracking mail
            //
            if (newPost.IsApproved)
                ForumEmails.ForumTracking(newPost);
            else 
                ForumEmails.NotifyModerators(newPost);

			ExpirePostCache(newPost);
            
			CSEvents.AfterPost(newPost,ObjectState.Create,ApplicationType.Forum);

            return newPost;
        }
        
        // *********************************************************************
        //  UpdatePost
        //
        /// <summary>
        /// This method updates a post (called from the admin/moderator editing the post).
        /// </summary>
        /// <param name="post">Changes needing to be made to a particular post.  The PostID
        /// represents to post to update.</param>
        /// 
        // ********************************************************************/ 
        public static void UpdatePost(ForumPost post, int editedBy) {

			post.Subject = Globals.HtmlEncode(post.Subject);
			post.FormattedBody = post.Body;

			CSEvents.PrePost(post,ObjectState.Update,ApplicationType.Forum);
			CSEvents.BeforePost(post,ObjectState.Update,ApplicationType.Forum);

            // Create Instance of the CommonDataProvider
            ForumDataProvider dp = ForumDataProvider.Instance();
            dp.UpdatePost(post, editedBy);

			if (post.Categories != null)
			{
				ForumDataProvider.Instance().UpdatePostCategories(post.SectionID, post.PostID, post.Categories);
				PostCategories.ExpireCategoryCache(post.SectionID);
			}

            ExpirePostCache(post);

			CSEvents.AfterPost(post,ObjectState.Update,ApplicationType.Forum);
        } 

		public static void ExpirePostCache(ForumPost post)
		{
			CSCache.RemoveByPattern(string.Format("Forum-Threads::S:{0}",post.SectionID));

            //This will only work for post's at level 1 or 2. need to figure out clean way to find a post's root part
            CSCache.RemoveByPattern(string.Format("Forum-Posts::P:{0}",post.PostID));
            if(post.PostLevel > 1)
                CSCache.RemoveByPattern(string.Format("Forum-Posts::P:{0}",post.ParentID));
		}

		public static void UpdatePost (ForumPost post, System.Guid tempID, string filename, int userID)
		{
			ForumAttachmentSettings fas = ForumConfiguration.Instance().AttachmentSettings;

			// clean up old attachment, if it exists
			try
			{
				fas.DeleteAttachmentFromDisk(post.PostID);
			}
			catch(IOException e)
			{ 
				new CSException(CSExceptionType.AccessDenied, "AddAttachment: Permission to delete file on filesystem denied.", e).Log(); 
			}
			catch(System.Security.SecurityException e)
			{ 
				new CSException(CSExceptionType.AccessDenied, "AddAttachment: Permission to delete file on filesystem denied.", e).Log(); 
			}
			catch { }

			PostAttachment pa = PostAttachments.ToggleTemporary(tempID, filename, !fas.EnableDataStoreStorage, post.PostID, post.SectionID, userID);
			if (fas.EnableFileSystemStorage)
				fas.SaveAttachmentToDisk(pa, post.SectionID, post.PostID);

			post.AttachmentFilename = pa.FileName;
			
			UpdatePost(post, userID);
		}

		public static void DeleteAttachment (ForumPost post)
		{
			CommonDataProvider.Instance().DeletePostAttachment(post.PostID);

			ForumAttachmentSettings fas = ForumConfiguration.Instance().AttachmentSettings;

			// clean up old attachment, if it exists
			try
			{
				fas.DeleteAttachmentFromDisk(post.PostID);
			}
			catch(IOException e)
			{ 
				new CSException(CSExceptionType.AccessDenied, "AddAttachment: Permission to delete file on filesystem denied.", e).Log(); 
			}
			catch(System.Security.SecurityException e)
			{ 
				new CSException(CSExceptionType.AccessDenied, "AddAttachment: Permission to delete file on filesystem denied.", e).Log(); 
			}
			catch { }
		}

        public static PostAttachment GetAttachment (int postID) {
            // Create Instance of the CommonDataProvider
            CommonDataProvider dp = CommonDataProvider.Instance();

            return dp.GetPostAttachment(postID,false);
        }

    }
}
