require 'spec_helper'

describe "Reducers" do
  describe :bin do
    include_context "reducers"
    it_behaves_like 'a processor', :named => :bin

    let(:bins) {
      [
       ['0.0',    '2.000',  '9.000'],
       ['2.000',  '4.000',  '9.000'],
       ['4.000',  '6.000',  '8.000'],
       ['6.000',  '8.000', '11.000'],
       ['8.000', '10.000', '13.000']
      ]
    }

    it "raises an error when called with a non-positive-definite number of bins" do
      lambda { processor(num_bins: -1) }.should raise_error(Wukong::Error)
    end

    it "raises an error when called with a a minimum that's less than or equal to the maximum" do
      lambda { processor(min: 10, max: 0) }.should raise_error(Wukong::Error)
    end

    it "will bin 50 numbers into 7 bins (uses the square root)" do
      processor.given(*nums).should emit(7).records
    end

    it "will bin 50 numbers into 5 bins if asked" do
      processor(num_bins: 10).given(*nums).should emit(10).records
    end

    it "counts correctly in each bin" do
      processor(num_bins: 5).given(*nums).should emit(*bins)
    end

    it "can express counts logarithmically" do
      row = processor(num_bins: 5, log_counts: true).given(*nums).output.first
      row.size.should == 3
      row[2].to_f.should be_within(0.1).of(2.197)
    end

    it "can add a normalized frequency" do
      row = processor(num_bins: 5, normalize: true).given(*nums).output.first
      row.size.should == 4
      row[3].to_f.should be_within(0.1).of(0.18)
    end

    it "can add a normalized frequency and express counts logarithmically" do
      row = processor(num_bins: 5, normalize: true, log_counts: true).given(*nums).output.first
      row.size.should == 4
      row[2].to_f.should be_within(0.1).of(2.197)
      row[3].to_f.should be_within(0.1).of(-1.715)
    end

    it "can bin on the fly given min, max, and num_bins options" do
      output = processor(min: -30, max: 30, num_bins: 3) do |proc|
        # we can bin on the fly
        proc.values.should_not_receive(:<<)
        proc.should_not_receive(:bin!) 
      end.given(*nums).output

      output.size.should == 3
      output.first[0].to_f.should be_within(0.1).of(-30)
      output.last[1].to_f.should be_within(0.1).of(30)
    end

    it "can bin on the fly given fixed bin edges" do
      output = processor(edges: [0,1,5,10]) do |proc|
        # we can bin on the fly
        proc.values.should_not_receive(:<<)
        proc.should_not_receive(:bin!)
      end.given(*nums).output
      output.size.should == 3
      output[0][0].to_f.should be_within(0.1).of(0.0)
      output[0][1].to_f.should be_within(0.1).of(1.0)
      output[1][0].to_f.should be_within(0.1).of(1.0)
      output[1][1].to_f.should be_within(0.1).of(5.0)
      output[2][0].to_f.should be_within(0.1).of(5.0)
      output[2][1].to_f.should be_within(0.1).of(10.0)
    end

    it "can extract the value to bin by from an object" do
      output = processor(by: 'data.n', min: 0).given(*json).output
      output.size.should == 2
      output.first[0].to_f.should be_within(0.1).of(0.0)
      output.last[1].to_f.should be_within(0.1).of(100.0)
    end

  end
end
