# Schema as of Fri Jun 29 21:58:21 EDT 2007 (schema version 45)
#
#  id                  :integer(11)   not null
#  value               :integer(11)   default(0), not null
#  subject_id          :integer(11)   default(0), not null
#  participant_id      :integer(11)   
#  created_on          :datetime      not null
#  type                :string(255)   
#

# Are ratings polymorphic?
#   Comment has_many :ratings, :as => :ratings
#   SupportMaterial has_many :info_ratings, :as => :ratings
#   SupportMaterial has_many :agree_ratings, :as => :ratings44
class AbstractRating < ActiveRecord::Base
  set_table_name 'ratings'

  belongs_to :user, :foreign_key => 'participant_id' # TODO change to user_id
  
  
  validates_uniqueness_of :participant_id, :scope => 'subject_id'
  validate :subject_is_rateable
  validates_presence_of :value # can't be null
  validate :value_is_in_mapping 
  validate :passes_all_specifications
    
  def subject_is_rateable
    errors.add(:subject, "cannot be rated") unless subject.rateable?
  end
  
  def value_is_in_mapping
    errors.add(:value, "is invalid") unless mapping.assoc(value)
  end
  
  def passes_all_specifications
    errors.add_to_base(RatingSpecification::failures) unless RatingSpecification::passes_all?(subject, user, self.class)
  end
  
  def in_words
    mapping.assoc(value) && mapping.assoc(value).last || "*unknown*"
  end
  
  def range_in_words
    "%s or %s" % [mapping.first.last, mapping.last.last]
  end
    
  def select_intro
    ' Rate as... '
  end
  
  class << self
    
    # Catch calls to find_most_rated_subject and find_best_average_rated_subject
    #
    # where the method name looks like: "find_#{method_name_ending_in}_subject"
    # where this class exposes a method named "#{method_name_ending_in}_subject_id"
    def method_missing(method, *args, &block) 
      if method.to_s =~ /^find_(.*_subject)$/ && respond_to?($1 + '_id')
        subject_class.find(send($1 + '_id')) rescue nil
      else
        super
      end
    end
    
    # Returns the ID of the subject with the most number of ratings
    #
    # This method currently only returns a single value, so in cases
    # where there is a tie (numerous subjects are rated an equal number)
    # of times), this method ignores all but one of those subjects
    #
    # TODO: cope with ties
    def most_rated_subject_id
      count( :group => 'subject_id', :order => :count_all).last.first
    end
    
    # Returns the ID of the subject that has the highest mean rating.
    #
    # TODO: cope with ties
    def best_average_rated_subject_id
      best_average_rated_subject_ids(1).pop
    end
    
    # Returns an array of ID, value pairs.
    #
    #    => [[1, 5], [4, 4.0], [2, 3.4], [5, 3.0], [3, 2.6667]]
    def best_average_ratings
      average('value', 
        :group => 'subject_id', 
        :order => "avg_value DESC", # descending because higher ratings are more positive
        :conditions => 'ratings.value != 77 && ratings.value != 0') # don't count in the average, but DO count as a rating(later)
    end
    
    # Returns the top (three, by default) rated subject that have at least two ratings each.
    def best_average_rated_subject_ids(number_of_records = 3)
      ids_sorted_by_ratings = best_average_ratings.collect{|r|r.first}
      ids = []
      while (subject_id = ids_sorted_by_ratings.shift) && (ids.length < number_of_records)
        next unless count("#{table_name}.id", :group => :subject_id, :conditions => ["subject_id = ?", subject_id]).first.last > 1 
        ids.push subject_id
      end
      ids
    end
    
    # return the class of the subject for this type of rating
    def subject_class
      reflect_on_association(:subject).klass
    end
    
    # Returns the average value, excludes 77s
    def average_value
      average(:value, :conditions => 'value != 77')
    end

    def create_for_user_with_type(user, rating_type, subject_id, value)
      raise ArgumentError unless is_a_rating?(rating_type)
      
      rating_class = rating_type.to_s.camelize.constantize
      subject      = rating_class.subject_class.find(subject_id)

      rating_class.create(:subject => subject, :user => user, :value => value) 
    end
    
    def is_a_rating?(rating_type)
      rating_type.to_s.camelize.constantize.new.is_a? AbstractRating rescue false
    end

    def move_to_new_user(from_user, to_user)
      update_all("participant_id = #{to_user.id}", ['participant_id = ?', from_user.id])
    end

     include RuportQueryOverArConnection
    
    def report
      report_raw("SELECT #{selected_fields} FROM ratings WHERE ORDER BY created_on")
    end
    
    def type_restriction
      " #{AbstractRating.table_name}.type = '#{self}' " unless self == AbstractRating
    end
    
    def selected_fields
      "ratings.subject_id, ratings.participant_id as `user_id`, ratings.value, ratings.created_on"
    end
  end
  
end
