require 'leaderboard'

class CompetitionRankingLeaderboard < StoredLeaderboard
  # 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]

    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 @reverse
          data[@rank_key] = @redis_connection.zcount(leaderboard_name, '-inf', "(#{data[@score_key]}") + 1 rescue nil
        else
          data[@rank_key] = @redis_connection.zcount(leaderboard_name, "(#{data[@score_key]}", '+inf') + 1 rescue nil
        end
      end

      if leaderboard_options[:with_member_data]
        data[@member_data_key] = member_data_for_in(leaderboard_name, member)
      end

      ranks_for_members << data
    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
end