module MetricsReport
  class Abstract
    def initialize(params={})
      @time_scope = params[:time_scope].to_i
      @topic      = Topic.find(params[:id]) unless params[:id].nil? || params[:id].to_i < 1
    end  

    private

    def with_time_and_topic_scope 
      TopicRating::with_scope(rating_conditions) do
        CommentRating::with_scope(rating_conditions) do
          MaterialInformativeRating::with_scope(rating_conditions) do
            MaterialConvincingRating::with_scope(rating_conditions) do
              Comment::with_scope(comment_conditions) do
                Reading::with_scope(reading_conditions) do
                  yield
                end
              end
            end
          end
        end
      end
    end

    def comment_conditions
      if @time_scope > 0
        {:find => { :conditions => ["#{Comment.table_name}.created_at > ?", @time_scope.hours.ago] }}
      else
        {}
      end
    end

    def rating_conditions
      if @time_scope > 0
        {:find => { :conditions => ["#{AbstractRating.table_name}.created_on > ?", @time_scope.hours.ago] }}
      else
        {}
      end
    end

    def reading_conditions
      if @time_scope > 0
        {:find => { :conditions => ["#{Reading.table_name}.created_at > ?", @time_scope.hours.ago] }}
      else
        {}
      end
    end
    
  end


  class Aggregate < MetricsReport::Abstract
    def initialize(params={})
      super

      # Readings are not scopped. This means that recent reads of old comments are missed in favour 
      # of focusing on recently created comments.
      #
      # It is the difference between finding the comment from the last N hours that is the most read
      # vs. finding the comment (from anytime) that is the most read in the last N hours.
      #
      #  If you wish to scope Reading, see Comment.find_most_recently_read
      with_time_and_topic_scope do 
        @topics = Topic.find(:all, :order => 'start_date DESC')

        if @topic
          # Show a set of metrics scopped to a particular topic -- we don't care about inter-topic 
          # comparissons here

          # from 'show'
          # calculate all values in this action and scope (not the views)
          @mean_rating = {
            'topic_rating'                         => @topic.ratings.average_value,
            'comment_rating'                       => @topic.comment_ratings.average_value,
            'support_material_informative_rating'  => @topic.support_material_informative_ratings.average_value,
            'support_material_convincing_rating'   => @topic.support_material_convincing_ratings.average_value
          }
          @count = {
            # House data
            "topic"                    => Topic.count,
            "support_material"         => @topic.support_materials.count,
            "user"                     => @topic.reading_users.count('users.id', :distinct => true), # TODO figure out if needing :distint is a bug
            "participant"              => @topic.reading_participants.count('users.id', :distinct => true),

            # User data
            "comment"                             => @topic.comments.count,
            "comment_words_written"               => @topic.comments.total_words,
            "topic_rating"                        => @topic.ratings.count,
            "comment_rating"                      => @topic.comment_ratings.count,
            "support_material_informative_rating" => @topic.support_material_informative_ratings.count,
            "support_material_convincing_rating"  => @topic.support_material_convincing_ratings.count,

            "support_material_reading" => @topic.support_material_readings.count,
            "comment_reading"          => @topic.comment_readings.count,
            "topic_reading"            => @topic.readings.count
          }

          # comments
          @comment_most_rated          = @topic.comment_ratings.find_most_rated_subject
          @comment_highest_rated       = @topic.comment_ratings.find_best_average_rated_subject
          @comment_most_direct_replies = @topic.comments.find_most_replied
          @comment_most_read           = @topic.comments.find_most_read

          # support material -- + convincing_ratings + informative_ratings
          @topic_support_materials_count           = @topic.support_materials.count
          @support_material_most_recently_read     = @topic.support_materials.find_most_recently_read
          @support_material_most_rated_informative = @topic.support_material_informative_ratings.find_most_rated_subject
          @support_material_most_rated_convincing  = @topic.support_material_convincing_ratings.find_most_rated_subject
          @support_material_best_rated_informative = @topic.support_material_informative_ratings.find_best_average_rated_subject
          @support_material_best_rated_convincing  = @topic.support_material_convincing_ratings.find_best_average_rated_subject

        else
        
          @mean_rating = {
            'topic_rating'                         => TopicRating.average_value,
            'comment_rating'                       => CommentRating.average_value,
            'support_material_informative_rating'  => MaterialInformativeRating.average_value,
            'support_material_convincing_rating'   => MaterialInformativeRating.average_value
          }
      
          # topics
          @topic_most_commented = Topic.most_commented
          @topic_most_words     = Topic.most_comment_words 
          @topic_most_rated     = Topic.most_rated
          @topic_highest_rated  = Topic.best_rated          

          # comments
          @comment_most_rated          = Comment.find_most_rated
          @comment_highest_rated       = Comment.find_best_mean_rated
          @comment_most_direct_replies = Comment.find_most_replied
          @comment_most_read           = Comment.find_most_read

          # calculate all values in this scope
          @topic_highest_rated_mean                   = @topic_highest_rated.ratings.average_value_excluding_77s  if @topic_highest_rated
          @topic_highest_rated_ratings_count          = @topic_highest_rated.ratings.count                        if @topic_highest_rated
          @topic_most_commented_comments_length       = @topic_most_commented.comments.length                     if @topic_most_commented
          @topic_most_commented_comments_total_words  = @topic_most_commented.comments.total_words                if @topic_most_commented                                                                           
          @topic_most_words_comments_total_words      = @topic_most_words.comments.total_words                    if @topic_most_words
          @topic_most_words_comments_count            = @topic_most_words.comments.count                          if @topic_most_words

          # support material -- convincing_ratings + informative_ratings
          @support_material_most_recently_read     = SupportMaterial.find_most_recently_read
          @support_material_most_rated_informative = MaterialInformativeRating.find_most_rated_subject
          @support_material_most_rated_convincing  = MaterialConvincingRating.find_most_rated_subject
          @support_material_best_rated_informative = MaterialInformativeRating.find_best_average_rated_subject
          @support_material_best_rated_convincing  = MaterialConvincingRating.find_best_average_rated_subject
    
          @count = {
            # House data
            "topic"                    => Topic.count,
            "support_material"         => SupportMaterial.count,
            "participant"              => Participant.count, # TODO how to scope this?
            "user"                     => User.count, # TODO how to scope this?
    
            # User data
            "comment"                             => Comment.count,
            "topic_rating"                        => AbstractRating.count,
            "comment_rating"                      => CommentRating.count,
            "support_material_informative_rating" => MaterialInformativeRating.count,
            "support_material_convincing_rating"  => MaterialConvincingRating.count,

            "support_material_reading" => Reading.count(:conditions => 'readable_type = "SupportMaterial"'),
            "comment_reading"          => Reading.count(:conditions => 'readable_type = "Comment"'),
            "topic_reading"            => Reading.count(:conditions => 'readable_type = "Topic"')
          }
    
          @topics_metrics = MetricsGrid.new do |m|
            m.field(:comment_count, Comment.count(:group => :topic))
            m.field(:comment_words, Comment.sum(:word_count, :group => :topic))
            m.field(:rating_count,  TopicRating.count(:group => :subject))
            m.field(:rating_mean,   TopicRating.average(:value, :conditions => 'ratings.value != 77', :having => 'count(*) > 1', :group => :subject))
          end
        end
      end

      set_comment_means_counts_and_replies_to_children
      set_support_material_means_and_counts
    end
  
    def set_comment_means_counts_and_replies_to_children 
      @comment_most_rated_ratings_count           = @comment_most_rated.ratings.count                           if @comment_most_rated
      @comment_most_rated_ratings_mean            = @comment_most_rated.ratings.average_value_excluding_77s     if @comment_most_rated                                                                                                                                     
      @comment_highest_rated_ratings_mean         = @comment_highest_rated.ratings.average_value_excluding_77s  if @comment_highest_rated
      @comment_highest_rated_ratings_count        = @comment_highest_rated.ratings.count                        if @comment_highest_rated                                                    
      @comment_most_direct_replies_children_count = @comment_most_direct_replies.children_count                 if @comment_most_direct_replies                                                                                                                 
      @comment_most_read_readings_count           = @comment_most_read.readings.count                           if @comment_most_read
    end
  
    def set_support_material_means_and_counts 
      @support_material_most_recently_read_reading_count    = @support_material_most_recently_read.readings.count if @support_material_most_recently_read
    
      @support_material_most_rated_informative_rating_count = @support_material_most_rated_informative.informative_ratings.count          if @support_material_most_rated_informative
      @support_material_most_rated_informative_rating_mean  = @support_material_most_rated_informative.informative_ratings.average_value  if @support_material_most_rated_informative
      @support_material_most_rated_convincing_rating_count  = @support_material_most_rated_convincing.convincing_ratings.count            if @support_material_most_rated_convincing
      @support_material_most_rated_convincing_rating_mean   = @support_material_most_rated_convincing.convincing_ratings.average_value    if @support_material_most_rated_convincing
    
      @support_material_best_rated_informative_rating_count = @support_material_best_rated_informative.informative_ratings.count         if @support_material_best_rated_informative
      @support_material_best_rated_informative_rating_mean  = @support_material_best_rated_informative.informative_ratings.average_value if @support_material_best_rated_informative
      @support_material_best_rated_convincing_rating_count  = @support_material_best_rated_convincing.convincing_ratings.count          if @support_material_best_rated_convincing
      @support_material_best_rated_convincing_rating_mean   = @support_material_best_rated_convincing.convincing_ratings.average_value  if @support_material_best_rated_convincing
    end
    
    # allow access to all instance variables
    def method_missing(symbol, *args)
      if self.instance_variables.include?("@" + symbol.to_s)
        self.instance_variable_get("@" + symbol.to_s)
      else
        super
      end
    end
  end

  class Export < Abstract
    include RuportQueryOverArConnection
    def initialize(params={})
      super
      @report = get_data(params[:model])
    end

    def get_data(model) 
      @topic_id = @topic.id if @topic
      return case model
      when 'topic'
        raise ArgumentError, "cannot scope topic" if @topic
        Topic.report
      when 'support_material'
        SupportMaterial.report(@topic_id)
      when 'participant'
        if @topic 
          User.reading_participants_report(@topic_id)
        else
          # I can't decide where the SQL belongs: in the model or the report generator?
          report_raw("SELECT users.email, users.created_at, users.id as `user_id`, users.type FROM users WHERE users.`type` = 'Participant'")            
        end
      when 'user'
        if @topic
          User.reading_users_report(@topic_id)
        else
          report_raw("SELECT users.email, users.created_at, users.id as `user_id`, users.type FROM users")
        end
      when 'comment'
        Comment.report(@topic_id)

      # ratings
      when 'topic_rating'
        TopicRating.report(@topic_id)
      when 'comment_rating'
        CommentRating.report(@topic_id)
      when 'support_material_informative_rating'
        MaterialInformativeRating.report(@topic_id)
      when 'support_material_convincing_rating'
        MaterialConvincingRating.report(@topic_id)
      
      # readings
      when 'topic_reading'
        Reading.report_topic_reading(@topic_id)
      when 'comment_reading'
        Reading.report_comment_reading(@topic_id)
      when 'support_material_reading'
        Reading.report_support_material_reading(@topic_id)
      else
        raise ArgumentError, "unknown model '#{model}'"
      end
    end # get_data
    
    def topic; @topic; end

    def as(*args)
      @report.as(*args)
    end
  end
  
  class Top5 < Abstract
    def initialize(params={})
      super # takes care of @topic and @time_scope
      @reverse = params[:reverse]
      @data = get_data(params[:model], params[:what])
    end
    
    def get_data(model, what)
      m = model.classify.constantize
      options = {}
      options[:topic]   = @topic.id   if @topic
      options[:max_age] = @time_scope if @time_scope > 0
      options[:reverse] = @reverse
      m.list_for(what.to_sym, options)
    end
    
    def each
      @data.each do |record, value|
        yield(record, value)
      end
    end
  end
end