require "spec_helper"

describe Redistat::Buffer do

  before(:each) do
    @class  = Redistat::Buffer
    @buffer = Redistat::Buffer.instance
    @key = mock('Key', :to_s => "Scope/label:2011")
    @stats = {:count => 1, :views => 3}
    @depth_limit = :hour
    @opts = {:enable_grouping => true}
  end

  # let's cleanup after ourselves for the other specs
  after(:each) do
    @class.instance_variable_set("@instance", nil)
    @buffer.size = 0
  end

  it "should provide instance of itself" do
    @buffer.should be_a(@class)
  end

  it "should only buffer if buffer size setting is greater than 1" do
    @buffer.size.should == 0
    @buffer.send(:should_buffer?).should be_false
    @buffer.size = 1
    @buffer.size.should == 1
    @buffer.send(:should_buffer?).should be_false
    @buffer.size = 2
    @buffer.size.should == 2
    @buffer.send(:should_buffer?).should be_true
  end

  it "should only flush buffer if buffer size is greater than or equal to buffer size setting" do
    @buffer.size.should == 0
    @buffer.send(:queue).size.should == 0
    @buffer.send(:should_flush?).should be_false
    @buffer.send(:queue)[:hello] = 'world'
    @buffer.send(:incr_count)
    @buffer.send(:should_flush?).should be_true
    @buffer.size = 5
    @buffer.send(:should_flush?).should be_false
    3.times { |i|
      @buffer.send(:queue)[i] = i.to_s
      @buffer.send(:incr_count)
    }
    @buffer.send(:should_flush?).should be_false
    @buffer.send(:queue)[4] = '4'
    @buffer.send(:incr_count)
    @buffer.send(:should_flush?).should be_true
  end

  it "should force flush queue irregardless of result of #should_flush? when #reset_queue is called with true" do
    @buffer.send(:queue)[:hello] = 'world'
    @buffer.send(:incr_count)
    @buffer.send(:should_flush?).should be_true
    @buffer.size = 2
    @buffer.send(:should_flush?).should be_false
    @buffer.send(:reset_queue).should == {}
    @buffer.instance_variable_get("@count").should == 1
    @buffer.send(:reset_queue, true).should == {:hello => 'world'}
    @buffer.instance_variable_get("@count").should == 0
  end

  it "should #flush_data into Summary.update properly" do
    # the root level key value doesn't actually matter, but it's something like this...
    data = {'ScopeName/label/goes/here:2011::true:true' => {
      :key => @key,
      :stats => @stats,
      :depth_limit => @depth_limit,
      :opts => @opts
    }}
    item = data.first[1]
    Redistat::Summary.should_receive(:update).with(@key, @stats, @depth_limit, @opts)
    @buffer.send(:flush_data, data)
  end

  it "should build #buffer_key correctly" do
    opts = {:enable_grouping => true, :label_indexing => false, :connection_ref => nil}
    @buffer.send(:buffer_key, @key, opts).should ==
      "#{@key.to_s}:connection_ref::enable_grouping:true:label_indexing:false"
    opts = {:enable_grouping => false, :label_indexing => true, :connection_ref => :omg}
    @buffer.send(:buffer_key, @key, opts).should ==
      "#{@key.to_s}:connection_ref:omg:enable_grouping:false:label_indexing:true"
  end

  describe "Buffering" do
    it "should store items on buffer queue" do
      @buffer.store(@key, @stats, @depth_limit, @opts).should be_false
      @buffer.size = 5
      @buffer.store(@key, @stats, @depth_limit, @opts).should be_true
      @buffer.send(:queue).should have(1).item
      @buffer.send(:queue)[@buffer.send(:queue).keys.first][:stats][:count].should == 1
      @buffer.send(:queue)[@buffer.send(:queue).keys.first][:stats][:views].should == 3
      @buffer.store(@key, @stats, @depth_limit, @opts).should be_true
      @buffer.send(:queue).should have(1).items
      @buffer.send(:queue)[@buffer.send(:queue).keys.first][:stats][:count].should == 2
      @buffer.send(:queue)[@buffer.send(:queue).keys.first][:stats][:views].should == 6
    end

    it "should flush buffer queue when size is reached" do
      key = mock('Key', :to_s => "Scope/labelx:2011")
      @buffer.size = 10
      Redistat::Summary.should_receive(:update).exactly(2).times.and_return do |k, stats, depth_limit, opts|
        depth_limit.should == @depth_limit
        opts.should == @opts
        if k == @key
          stats[:count].should == 6
          stats[:views].should == 18
        elsif k == key
          stats[:count].should == 4
          stats[:views].should == 12
        end
      end
      6.times { @buffer.store(@key, @stats, @depth_limit, @opts).should be_true }
      4.times { @buffer.store(key, @stats, @depth_limit, @opts).should be_true }
    end
  end

  describe "Thread-Safety" do
    it "should read/write to buffer queue in a thread-safe manner" do

      # Setting thread_safe to false only makes the spec fail with
      # JRuby. 1.8.x and 1.9.x both pass fine for some reason
      # regardless of what the thread_safe option is set to.
      Redistat.thread_safe = true

      key = mock('Key', :to_s => "Scope/labelx:2011")
      @buffer.size = 100

      Redistat::Summary.should_receive(:update).exactly(2).times.and_return do |k, stats, depth_limit, opts|
        depth_limit.should == @depth_limit
        opts.should == @opts
        if k == @key
          stats[:count].should == 60
          stats[:views].should == 180
        elsif k == key
          stats[:count].should == 40
          stats[:views].should == 120
        end
      end

      threads = []
      10.times do
        threads << Thread.new {
          6.times { @buffer.store(@key, @stats, @depth_limit, @opts).should be_true }
          4.times { @buffer.store(key, @stats, @depth_limit, @opts).should be_true }
        }
      end

      threads.each { |t| t.join }
    end

    it "should have specs that fail on 1.8.x/1.9.x when thread_safe is disabled"

  end

end
