# Schema as of Fri Jun 29 21:58:21 EDT 2007 (schema version 45)
#
#  id                  :integer(11)   not null
#  body                :text          
#  created_by          :integer(11)   
#  created_at          :datetime      
#  topic_id            :integer(11)   default(0)
#  root_id             :integer(11)   default(0)
#  parent_id           :integer(11)   default(0)
#  lft                 :integer(11)   default(0)
#  rgt                 :integer(11)   default(0)
#  depth               :integer(11)   default(0)
#  removed             :boolean(1)    
#  word_count          :integer(11)   
#  reading_count       :integer(11)   default(0)
#  avg_rating          :float         
#


# Comments that are on a topic
class Comment < ActiveRecord::Base
  acts_as_threaded

  add_index :fields => ['body'], :conditions => "comments.removed = 0 && comments.private = 0" # don't show removed comments, or comments from private topics

  #acts_as_nested_set
  # TODO strip out any unsafe characters
  # TODO use h() to show body content
  # alias :children :direct_children

  belongs_to :topic
  belongs_to :user,    :class_name => 'User', :foreign_key => 'created_by'
  belongs_to :parent,  :class_name => 'Comment', :foreign_key => 'parent_id'
  
  # has_many   :ratings, :class_name =>  'CommentRating', :foreign_key => 'subject_id', :include => :user do
  has_many   :ratings, :class_name =>  'CommentRating', :foreign_key => 'subject_id' do
       def for_user(user)
         # don't do anything if this is this users comment
         return nil unless RatingSpecification::user_not_self_rating(proxy_owner, user)

         # don't do anything for non-participants/guests
         return nil unless RatingSpecification::user_is_a_participant_or_guest(proxy_owner, user)

         # otherwise, grab the existing rating, or build a new one
         # detect{|r|r.participant_id == user.id } || build(:user => user) 
         # find(:first, :conditions => ['participant_id = ?', user.id]) || build(:user => user) 
         user.comment_ratings.detect{|r|r.subject == proxy_owner } || build(:user => user) 
       end
  
       def average_value_excluding_77s
         average(:value, :conditions => 'value != 77')
       end
     end

  has_many   :readings,
             :as => :readable,
             :dependent => :delete_all
  
  extend ReadingFinderExtension
             
    
  validates_length_of :body, :within => 2 .. 5000
  validates_presence_of :topic
  validate :topic_is_open_for_comment
  
  before_validation :count_words
  
  def topic_is_open_for_comment
    errors.add('topic', "cannot be commented on") unless topic.commentable?    
  end
  
  def commentable?
    !hidden? && topic.commentable?
  end
  
  # can this comment be rated -- depend on the topic it is attached to and whether this comment is hidden or not
  def rateable?
    commentable?
  end
  
  def subject # TODO write test for this method
    parent_id == 0 ? topic : Comment.find(parent_id)
  end
  
  def hidden? # TODO replace this with a boolean field in the DB
    false
  end
  
  def count_words
    self.word_count = (body || '').split.length
  end
  
  # Recalculate the average rating and update the avg_rating field.
  #
  # Until there is more than 1 rating, the average stays at zero.
  def update_average_rating_cache
    update_attribute(:avg_rating, ratings.count > 1 ? ratings.average_value : 0)
  end

  # Returns a set of only this entry's immediate children  
  def children
    return [] unless has_children?
    self.class.find(:all, :conditions => "#{scope_condition} AND #{parent_column} = #{self.id}")
    # self.class.find(:all, 
    #   :conditions => "#{scope_condition} AND #{parent_column} = #{self.id}", 
    #   :include => [:user, [:ratings => [:user]]])
  end
  
  def children_ids
    return [] unless has_children?
    self.class.connection.select_all("SELECT id FROM #{self.class.table_name} WHERE #{scope_condition} AND #{parent_column} = #{self.id}")
  end
  
  def has_children?
    if self.rgt - self.lft == 1
      logger.info("this comment has no children -- left: #{self.lft}, right: #{self.rgt}")
      false
    else
      return true
    end
  end
  
  class << self
    
    include SphincterWillPaginate
    
    # TODO hook up these stats gatherers to a slider (or even the measuremap dateslider)
    # to get better parsing of data over time    
    
    # Find the comment that has drawn the most ratings
    #
    # Returns a Comment or Nil if none found
    def find_most_rated
      find(rating_klass.most_rated_subject_id) rescue nil
    end
  
    # Find the comment that has the highest mean rating.
    #
    # Returns a Comment or Nil if none found
    def find_best_mean_rated
      find(rating_klass.best_average_rated_subject_id) rescue nil
    end
  
    # Find the comment that has the most direct replies.
    #
    # Returns a Comment or Nil if none found
    def find_most_replied
      find(self.count(:group => :parent_id, 
         :conditions => 'root_id != parent_id AND parent_id != 0',
         :limit => 1, :order => 'count_all DESC'
         ).first.first) rescue nil
    end
    
    # returns the number of comments that have not been removed
    def count_visible
      count(:conditions => 'removed != 1')
    end
    
    def find_three_most_read  
      find(:all, :limit => 3, :order => 'reading_count DESC', :conditions => 'reading_count > 0', :include => :user)
    end
    
    def find_three_best_avg_rating
      find(:all, :limit => 3, :order => "#{Comment.table_name}.avg_rating DESC", :conditions => "#{Comment.table_name}.avg_rating > 0", :include => :user)
    end
    
    def find_three_newest
      self.find(:all, :limit => 3, :order => "#{Comment.table_name}.created_at DESC", :include => :user)
    end
    
    def rating_klass
      reflect_on_association(:ratings).klass
    end
    
    def all_parent_children_ids(comment_ids = [])
      return [] if comment_ids.empty?
      parent_children_ids = {}
      self.connection.select_all("SELECT id, parent_id FROM #{self.table_name} WHERE id IN (#{comment_ids * ', '})").each do |row|
        parent_children_ids[row['parent_id'].to_i] ||= []
        parent_children_ids[row['parent_id'].to_i] << row['id'].to_i
      end
      return parent_children_ids
    end
    
    include RuportQueryOverArConnection
    def report(topic_id=nil)
      where = " WHERE topic_id = #{topic_id}" if topic_id
      report_raw "SELECT created_at, id, parent_id, parent_id, topic_id, body, created_by as `user_id` FROM #{table_name} #{where} ORDER BY topic_id, root_id, lft ASC"
    end
    
    
    # Return a list of the 5 best best comments based on some parameters
    #
    # Eg. Based on average rating:
    #
    #     Comment.list_for(:ratings)
    #
    # Scope list to a topic and within a time window:
    #
    #     Comment.list_for(:ratings, {:topic => 1, :max_age => 24})
    #
    # Return the inverse/compliment list:
    #
    #     Comment.list_for(:ratings, {:reverse => true})
    def list_for(what, options={})
      case what
      when :readings
        query_helper = QueryHelper.new(options, :base => ['readable_type = ?', self.to_s], :topic => 'comments.topic_id', :age => 'readings.created_at')
        # scopes to the age of the Reading, not of the Comment
        # 
        # When sorting in reverse, Comments with to reading are ignored because
        # without any reading, there is nothing to count, or scope
        counts = Reading.count(:group => :readable_id,           # returns an array of id, count pairs
          :joins => 'INNER JOIN comments ON comments.id = readings.readable_id', # needed for topic condition
          :conditions => query_helper.conditions,
          :limit => 5, 
          :order => "count_all #{query_helper.sort_order}"
        )
        counts.collect{|id, count| [Comment.find(id), count]}
      
      when :rated # how many times rated
        query_helper = QueryHelper.new(options, :topic => 'comments.topic_id', :age => 'ratings.created_on')
        counts = CommentRating.count(:group => :subject_id, 
          :joins => 'INNER JOIN comments ON comments.id = ratings.subject_id', # needed for topic condition
          :conditions => query_helper.conditions,
          :limit => 5,
          :order => "count_all #{query_helper.sort_order}")
        counts.collect{|id, count| [Comment.find(id), count]}
      
      when :ratings # how well rated (avg)
        query_helper = QueryHelper.new(options, :base => ['ratings.value != 77'], :topic => 'comments.topic_id', :age => 'ratings.created_on')
        
        CommentRating.average(:value, :group => :subject, 
          :joins => 'INNER JOIN comments ON comments.id = ratings.subject_id', # needed for topic condition
          :conditions => query_helper.conditions,
          :limit => 5,
          :order => "avg_value #{query_helper.sort_order}")
      
      when :commented_on # most direct replies
        qh = QueryHelper.new(options, :base => ['root_id != parent_id AND parent_id != 0'], :topic => 'comments.topic_id', :age => 'comments.created_at')
        counts = self.count(:group => :parent_id, 
           :conditions => qh.conditions,
           :limit => 5, :order => "count_all #{qh.sort_order}"
           )
        counts.collect{|id, count| [Comment.find(id), count]}
        
      when :word_count 
        qh = QueryHelper.new(options, :topic => 'comments.topic_id', :age => 'comments.created_at')
        records = self.find(:all,
           :conditions => qh.conditions,
           :limit => 5, :order => "word_count #{qh.sort_order}"
           )
        records.collect{|rec| [rec, rec.word_count]}
      else
        raise ArgumentError, "unrecognized list '#{what}' for #{self}"
      end # case
    end
  end
end
