require 'leaderboard'

class CompetitionRankingLeaderboard < Leaderboard
  # Retrieve the rank for a member in the named leaderboard.
  #
  # @param leaderboard_name [String] Name of the leaderboard.
  # @param member [String] Member name.
  #
  # @return the rank for a member in the leaderboard.
  def rank_for_in(leaderboard_name, member)
    member_score = score_for_in(leaderboard_name, member)
    if @reverse
      return @redis_connection.zcount(leaderboard_name, '-inf', "(#{member_score}") + 1 rescue nil
    else
      return @redis_connection.zcount(leaderboard_name, "(#{member_score}", '+inf') + 1 rescue nil
    end
  end

  # Retrieve the score and rank for a member in the named leaderboard.
  #
  # @param leaderboard_name [String]Name of the leaderboard.
  # @param member [String] Member name.
  #
  # @return the score and rank for a member in the named leaderboard as a Hash.
  def score_and_rank_for_in(leaderboard_name, member)
    responses = @redis_connection.multi do |transaction|
      transaction.zscore(leaderboard_name, member)
      if @reverse
        transaction.zrank(leaderboard_name, member)
      else
        transaction.zrevrank(leaderboard_name, member)
      end
    end

    responses[0] = responses[0].to_f if responses[0]
    responses[1] =
      if @reverse
        @redis_connection.zcount(leaderboard_name, '-inf', "(#{responses[0]}") + 1 rescue nil
      else
        @redis_connection.zcount(leaderboard_name, "(#{responses[0]}", '+inf') + 1 rescue nil
      end

    {@member_key => member, @score_key => responses[0], @rank_key => responses[1]}
  end

  # Retrieve a page of leaders from the named leaderboard for a given list of members.
  #
  # @param leaderboard_name [String] Name of the leaderboard.
  # @param members [Array] Member names.
  # @param options [Hash] Options to be used when retrieving the page from the named leaderboard.
  #
  # @return a page of leaders from the named leaderboard for a given list of members.
  def ranked_in_list_in(leaderboard_name, members, options = {})
    leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
    leaderboard_options.merge!(options)

    ranks_for_members = []

    responses = @redis_connection.multi do |transaction|
      members.each do |member|
        if @reverse
          transaction.zrank(leaderboard_name, member)
        else
          transaction.zrevrank(leaderboard_name, member)
        end
        transaction.zscore(leaderboard_name, member)
      end
    end unless leaderboard_options[:members_only]

    included_members = []
    scores = []

    members.each_with_index do |member, index|
      data = {}
      data[@member_key] = member
      unless leaderboard_options[:members_only]
        data[@score_key] = responses[index * 2 + 1].to_f if responses[index * 2 + 1]

        if data[@score_key] == nil
          next unless leaderboard_options[:include_missing]
        end
      end

      included_members << member
      scores << data[@score_key]

      ranks_for_members << data
    end

    if leaderboard_options[:with_member_data]
      members_data_for_in(leaderboard_name, included_members).each_with_index do |member_data, index|
        ranks_for_members[index][@member_data_key] = member_data
      end
    end

    rankings_for_members_having_scores_in(leaderboard_name, included_members, scores).each_with_index do |rank, index|
      ranks_for_members[index][@rank_key] = rank
    end

    case leaderboard_options[:sort_by]
    when :rank
      ranks_for_members = ranks_for_members.sort_by { |member| member[@rank_key] }
    when :score
      ranks_for_members = ranks_for_members.sort_by { |member| member[@score_key] }
    end

    ranks_for_members
  end

  protected

  # Retrieve a list of the rankings of leaders given their member names and scores
  #
  # @param leaderboard_name [String] Name of the leaderboard.
  # @param members [Array] Member names.
  # @param scores [Array] a list of scores for the members, aligned with the member names
  #
  # @return a list of the rankings for the passed in members and scores
  def rankings_for_members_having_scores_in(leaderboard_name, members, scores)
    @redis_connection.multi do |transaction|
      members.each_with_index do |member, index|
        if @reverse
          transaction.zcount(leaderboard_name, '-inf', "(#{scores[index]}")
        else
          transaction.zcount(leaderboard_name, "(#{scores[index]}", '+inf')
        end
      end
    end.map{|rank| rank ? rank + 1 : rank}
  end

end
