# Schema as of Fri Jun 29 21:58:21 EDT 2007 (schema version 45)
#
#  id                  :integer(11)   not null
#  title               :string(255)   
#  introduction        :text          
#  body_copy           :text          
#  start_date          :date          
#  expert_id           :integer(11)   
#  approved            :binary        
#  continue_reading    :string(255)   default(Read the full piece)
#  end_date            :date          not null
#  private             :boolean(1)    
#

# Topics intros can always be seen, but some topics can be set so that the body cannot
# be read by a Participant (Expert and Admin can always see).
#
# A Topic can be made private, in which case the topic intro is not even visible to users who
# do not have access. Private topics do not show up in searches, archives unless the current 
# user has access. RSS, since it doesn't use sessions will never show private topics.
class Topic < ActiveRecord::Base
  belongs_to :expert
  
  has_many :comments, :dependent => :destroy do
    def most_recent()
      find(:first, :order => "#{Comment.table_name}.created_at DESC", :include => :user)
    end
    def total_words
      sum(:word_count)
    end
    def thread_starters
      find(:all, :conditions => "parent_id = 0", :order => "#{Comment.table_name}.created_at DESC", :include => :user)
    end
    def find_three_best_avg_rating # TODO figure out how to DRY this up (its a repeat from Comment)
      find(proxy_owner.comment_ratings.best_average_rated_subject_ids, :include => :user)
    end
  end
  
  has_many :support_materials,  :dependent => :destroy

  has_many :ratings,  :dependent => :destroy, :class_name => 'TopicRating', :foreign_key => 'subject_id',
   :extend => RatingForUserAssociationExtension
  
  has_many :comment_ratings, :through => :comments, :source => :ratings, :extend => RatingForUserAssociationExtension
  has_many :support_material_informative_ratings, :through => :support_materials, :source => :informative_ratings
  has_many :support_material_convincing_ratings,  :through => :support_materials, :source => :convincing_ratings
  
  has_many :support_material_readings, :through => :support_materials, :source => :readings
  has_many :comment_readings, :through => :comments, :source => :readings
  
  has_many :access_passes,             :dependent => :destroy
  has_many :whitelist_filter_items,    :dependent => :destroy
  has_many :mask_filter_items,         :dependent => :destroy
  has_many :registration_filter_items, :dependent => :destroy
  has_many :users,                  :through   => :access_passes

  has_many :readings, :as => :readable, :dependent => :delete_all # TODO test
  has_many :reading_users, :through => :readings, :source => :user, :uniq => true # TODO test
  has_many :reading_participants, :through => :readings, :source => :user, 
    :conditions => "#{User.table_name}.type = 'Participant'", 
    :uniq => true # TODO test
  
  add_index :fields => ["title", "body_copy", "approved", "start_date", "private"], :conditions => "topics.private = 0" # don't show private topics
  
  attr_protected :approved
  
  validates_presence_of :expert
  validates_presence_of :start_date
  validates_length_of :title, :within => 2..255
    
  # is the publish date in the future?
  def for_future?
    self.start_date > Date.today
  end
  
  # can this topic be commented on? (is it approved and past its publish date?)
  def commentable?
    approved? && !for_future?
  end
  
  # can this topic be rated? (calls #commentable?)
  def rateable?
    commentable?
  end
  
  # Cope with invalid dates and MultiparameterAssignmentErrors # TODO write unit test for this
  #
  # from http://www.railtie.net/articles/2006/02/22/handling-invalid-dates-with-activerecord-date-helpers
  def attributes=(attributes)
    begin
      super(attributes)
    # Catch the exception from AR::Base
    rescue ActiveRecord::MultiparameterAssignmentErrors => ex
      # Iterarate over the exceptions and remove the invalid field components from the input
      ex.errors.each { |err| attributes.delete_if { |key, value| key =~ /^#{err.attribute}/ } }
      # try again with the bad input fields removed
      super(attributes)   
    end
  end
  
  def uniq_reading_participants
    reading_participants.collect.uniq
  end
  
  # Makes this topic private, cascades down to comments as well.
  def make_private!
    self.update_attribute(:private, true)
    self.comments.update_all('private = 1')
  end
  
  # Makes this topic public, cascades down to comments as well.
  def make_public!
    self.update_attribute(:private, false)
    self.comments.update_all('private = 0')
  end
  
  class << self
    
    include SphincterWillPaginate
    
    def most_commented
      Comment.count(:group => :topic, :order => 'count_all DESC', :limit => 1).first.first rescue nil
    end
  
    def most_comment_words
      Comment.sum(:word_count, :group => :topic, :order => 'sum_word_count DESC', :limit => 1).first.first rescue nil
    end
  
    def most_rated
      TopicRating.count(:group => :subject, :order => 'count_all DESC', :limit => 1).first.first rescue nil
    end
  
    def best_rated
      TopicRating.average(:value,
        :conditions => 'ratings.value != 77', 
        :having => 'count(*) > 1', 
        :group => :subject, 
        :order => 'avg_value DESC', 
        :limit => 1).first.first rescue nil
    end
    
    include RuportQueryOverArConnection

    def report
      report_raw("SELECT id, title, start_date, expert_id as `user_id` FROM topics ORDER BY start_date")
    end
    
    def list_for(what, options={})
      case what
      when :readings # number of *comment* readings
        query_helper = QueryHelper.new(options, :base => ['readable_type = ?', Comment.to_s], :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 => 'comments.topic_id',
          :joins => 'INNER JOIN comments ON comments.id = readings.readable_id', # needed for group clause
          :conditions => query_helper.conditions,
          :limit => 5, 
          :order => "count_all #{query_helper.sort_order}"
        )
        counts.collect{|id, count| [Topic.find(id), count]}

      when :rated # how many times rated
        query_helper = QueryHelper.new(options, :age => 'ratings.created_on')
        counts = TopicRating.count(:group => :subject_id, 
          :conditions => query_helper.conditions,
          :limit => 5,
          :order => "count_all #{query_helper.sort_order}")
        counts.collect{|id, count| [Topic.find(id), count]}
      
      when :ratings # how well rated (avg)
        query_helper = QueryHelper.new(options, :base => ['ratings.value != 77'], :age => 'ratings.created_on')
        
        TopicRating.average(:value, :group => :subject, 
          :conditions => query_helper.conditions,
          :limit => 5,
          :order => "avg_value #{query_helper.sort_order}")
      
      when :commented_on # most comments
        qh = QueryHelper.new(options, :age => 'comments.created_at')
        Comment.count(:group => :topic, 
           :conditions => qh.conditions,
           :limit => 5, :order => "count_all #{qh.sort_order}"
           )
      
      else
        raise ArgumentError, "unrecognized list '#{what}' for #{self}"
      end # case
    end
    
  end
  
end
