using System;
using System.Collections;
using System.Text;
using CommunityServer.Components;

namespace CommunityServer.Blogs.Components
{

    /// <summary>
    /// Allows overriding the default relevance sorting 
    /// </summary>
    public abstract class BlogRelevantPostScoringProvider
    {
        public abstract IList Score(IList weblogposts, int pageIndex, int pageSize);

        private static BlogRelevantPostScoringProvider _instance = null;

        public static BlogRelevantPostScoringProvider Instance()
        {
            if (_instance == null)
            {
                _instance = SingletonProviderHelper.LoadInstance("BlogRelevantPostScoringProvider", typeof(CSBlogPostScorer)) as BlogRelevantPostScoringProvider;
            }

            return _instance;
        }
    }

    public class CSBlogPostScorer : BlogRelevantPostScoringProvider
    {
        public override IList Score(IList weblogposts, int pageIndex, int pageSize)
        {
            return new BlogPostScorer(weblogposts, pageIndex, pageSize).Posts();
        }
    }

    /// <summary>
    /// Container object relevant sort results
    /// </summary>
    public class ScoredPostList   
    {
        public ScoredPostList(IList posts, int pageIndex, int pageSize, int totalRecords)
        {
            _posts = posts;
            _PageIndex = pageIndex;
            _PageSize = pageSize;
            _TotalRecords = totalRecords;
        }

        private int _PageIndex;
        private IList _posts;

        public int PageIndex
        {
            get { return _PageIndex; }
        }


        private int _PageSize;

        public int PageSize
        {
            get { return _PageSize; }
        }

        public IList Posts
        {
            get{ return _posts;}
        }

        private int _TotalRecords;

        public int TotalRecords
        {
            get { return _TotalRecords; }
        }


    }

    public class BlogPostScorer
    {
        #region cnstr

        public BlogPostScorer(IList weblogposts, int pageIndex, int pageSize)
        {
            Score(weblogposts, pageIndex, pageSize);
        }
        #endregion

        #region Public Methods/Properties

        public ArrayList Posts()
        {
            return _posts;
        }

        #endregion

        #region Private Members

        private ArrayList _posts = null;
        

        #endregion

        #region Private objects

        private class ScoredPost
        {
            public double Score;
            public WeblogPost Post;
            public int Index;

            public int DateRank;
            public int RatingsRank;
            public int CommentsRank;
            public int ViewsRank;
        }

        private enum SortByItem
        {
            Date,
            Ratings,
            Comments,
            Views
        }

        #endregion


        #region Sorting Helpers

        private class ScoreSorter : IComparer
        {
            public int Compare(object x, object y)
            {
                ScoredPost sx = x as ScoredPost;
                ScoredPost sy = y as ScoredPost;

                return sy.Score.CompareTo(sx.Score);
            }
        }

        private class RankSorter : IComparer
        {

            SortByItem _sbi;
            public RankSorter(SortByItem sbi)
            {
                _sbi = sbi;
            }

            public int Compare(object x, object y)
            {
                WeblogPost wx = (x as ScoredPost).Post;
                WeblogPost wy = (y as ScoredPost).Post;

                switch(_sbi)
                {
                    case SortByItem.Date:
                        return wx.PostDate.CompareTo(wy.PostDate);
                    case SortByItem.Ratings:
                        return wx.RatingSum.CompareTo(wy.RatingSum);
                    case SortByItem.Comments:
                        return wx.Replies.CompareTo(wy.Replies);
                    case SortByItem.Views:
                        return wx.Views.CompareTo(wy.Views);
                }

                return 0;
            }
        }

        #endregion

        #region Scoring 


        /// <summary>
        /// Takes the list of posts and sorts and scores them
        /// </summary>
        /// <param name="unScoredPosts"></param>
        private void Score(IList unScoredPosts, int pageIndex, int pageSize)
        {
            //We do not want to change the sort of the cached Arraylist
            //The initalial data requested will be re-used for every page in 
            //the list. Score is re-calulated whenever requested

            ArrayList list = new ArrayList(unScoredPosts.Count);

            int index = 0;
            foreach(WeblogPost post in unScoredPosts)
            {
                ScoredPost sp = new ScoredPost();
                sp.Post = post;
                sp.Index = index;
                index++;
                list.Add(sp);
            }

            //Sort by the values we care about and determine the rank.

            list.Sort(new RankSorter(SortByItem.Date));
            for(int i = 0; i<list.Count;i++)
            {
                ((ScoredPost)list[i]).DateRank = i + 1;
            }

            list.Sort(new RankSorter(SortByItem.Comments));
            for(int i = 0; i<list.Count;i++)
            {
                ((ScoredPost)list[i]).CommentsRank = i + 1;
            }

            list.Sort(new RankSorter(SortByItem.Views));
            for(int i = 0; i<list.Count;i++)
            {
                ((ScoredPost)list[i]).ViewsRank = i + 1;
            }

            list.Sort(new RankSorter(SortByItem.Ratings));
            for(int i = 0; i<list.Count;i++)
            {
                ((ScoredPost)list[i]).RatingsRank = i + 1;
            }

            WeblogConfiguration config = WeblogConfiguration.Instance(true);

            //Compute the score of the posts
            foreach(ScoredPost sp in list)
                ComputeScore(sp,config);

            //Re-sort based ont eh score
            list.Sort(new ScoreSorter());

            //Grab the posts needed for the current page. 
            int start = pageIndex * pageSize;
            int end = (1 + pageIndex) * pageSize;

            if(end > list.Count)
                end = list.Count;

            _posts = new ArrayList(pageSize);

            if(start >= 0)
            {
                
                for(int i = start; i<end; i++)
                {
                    ScoredPost sp = list[i] as ScoredPost;

                    //This will only happen when in debug mode
                    SetDebuggingHelp(sp,i,list.Count,config);

                    _posts.Add(sp.Post);
                }
            }
                

        }

        //This will be dynamic in the release
        void ComputeScore(ScoredPost post, WeblogConfiguration config)
        {
            post.Score = (  (post.DateRank * config.DateScoreFactor) + (post.RatingsRank * config.RatingScoreFactor) + (post.ViewsRank * config.ViewScoreFactor) + (post.CommentsRank * config.CommentScoreFactor));
        }

        

        [System.Diagnostics.Conditional("DEBUG")]
        void SetDebuggingHelp(ScoredPost sp, int i, int len, WeblogConfiguration config)
        {
            int[] ia = new int[len];
            int x = 0;
            for (int ix = len-1; ix >= 0; ix--)
            {
                ia[ix] = x;
                x++;
            }

            sp.Post.SetExtendedAttribute("PostScore",sp.Score.ToString());
            sp.Post.SetExtendedAttribute("PostRank",(i + 1).ToString());
            sp.Post.SetExtendedAttribute("PostIndex", sp.Index.ToString());
                    
            sp.Post.SetExtendedAttribute("DateRank", sp.DateRank.ToString());
            sp.Post.SetExtendedAttribute("CommentsRank", sp.CommentsRank.ToString());
            sp.Post.SetExtendedAttribute("ViewsRank", sp.ViewsRank.ToString());
            sp.Post.SetExtendedAttribute("RatingsRank", sp.RatingsRank.ToString());

            StringBuilder sb = new StringBuilder();

            sb.Append("<table border = \"1\" cellpadding=\"3\" cellspacing=\"3\">");
            sb.Append("<tr>");
            sb.Append("<th>Item</th><th>Value</th>");
            sb.Append("<th>Item</th><th>Value</th><th>Score</th>");
            sb.Append("</tr>");
            sb.Append("<tr>");
            sb.AppendFormat("<td>Rank</td><td>{0}</td>", i+1);
            sb.AppendFormat("<td>Date</td><td>{0}</td>",ia[sp.DateRank-1]+1);
            sb.AppendFormat("<td>{0} * {1} = {2}</td>", sp.DateRank, config.DateScoreFactor, sp.DateRank * config.DateScoreFactor); 
            sb.Append("</tr>");
            sb.Append("<tr>");
            sb.AppendFormat("<td>Score</td><td>{0}</td>",sp.Score);
            sb.AppendFormat("<td>Comments</td><td>{0}</td>", ia[sp.CommentsRank - 1] + 1);
            sb.AppendFormat("<td>{0} * {1} = {2}</td>", sp.CommentsRank, config.CommentScoreFactor, sp.CommentsRank * config.CommentScoreFactor); 
            sb.Append("</tr>");
            sb.Append("<tr>");
            sb.AppendFormat("<td>Index</td><td>{0}</td>",sp.Index);
            sb.AppendFormat("<td>Ratings</td><td>{0}</td>", ia[sp.RatingsRank - 1] + 1);
            sb.AppendFormat("<td>{0} * {1} = {2}</td>", sp.RatingsRank, config.RatingScoreFactor, sp.RatingsRank * config.RatingScoreFactor); 
            sb.Append("</tr>");
            sb.Append("<tr>");
            sb.Append("<td colspan=\"2\">&nbsp;</td>");
            sb.AppendFormat("<td>Views</td><td>{0}</td>", ia[sp.ViewsRank - 1] + 1);
            sb.AppendFormat("<td>{0} * {1} = {2}</td>", sp.ViewsRank, config.ViewScoreFactor, sp.ViewsRank * config.ViewScoreFactor);
            sb.AppendFormat("<tr><td colspan = \"4\" align=\"right\"><b>Total</b></td><td>{0}</td></tr>", sp.Score);
            sb.Append("</tr>");				    							    
            sb.Append("</table>");

            sp.Post.SetExtendedAttribute("debugScore",sb.ToString());
        }


        #endregion


    }
}