# encoding: UTF-8

module Vines

  # The token bucket algorithm is useful for rate limiting.
  # Before an operation can be completed, a token is taken from
  # the bucket.  If no tokens are available, the operation fails.
  # The bucket is refilled with tokens at the maximum allowed rate
  # of operations.
  class TokenBucket

    # Create a full bucket with `capacity` number of tokens to be filled
    # at the given rate of tokens/second.
    #
    # capacity - The Fixnum maximum number of tokens the bucket can hold.
    # rate     - The Fixnum number of tokens per second at which the bucket is
    #            refilled.
    def initialize(capacity, rate)
      raise ArgumentError.new('capacity must be > 0') unless capacity > 0
      raise ArgumentError.new('rate must be > 0') unless rate > 0
      @capacity = capacity
      @tokens = capacity
      @rate = rate
      @timestamp = Time.new
    end

    # Remove tokens from the bucket if it's full enough. There's no way, or
    # need, to add tokens to the bucket. It refills over time.
    #
    # tokens - The Fixnum number of tokens to attempt to take from the bucket.
    #
    # Returns true if the bucket contains enough tokens to take, false if the
    # bucket isn't full enough to satisy the request.
    def take(tokens)
      raise ArgumentError.new('tokens must be > 0') unless tokens > 0
      tokens <= fill ? @tokens -= tokens : false
    end

    private

    # Add tokens to the bucket at the `rate` provided in the constructor. This
    # fills the bucket slowly over time.
    #
    # Returns the Fixnum number of tokens left in the bucket.
    def fill
      if @tokens < @capacity
        now = Time.new
        @tokens += (@rate * (now - @timestamp)).round
        @tokens = @capacity if @tokens > @capacity
        @timestamp = now
      end
      @tokens
    end
  end
end
