describe Daru::DataFrame do
  context "#join" do
    before do
      @left = Daru::DataFrame.new({
        :id   => [1,2,3,4],
        :name => ['Pirate', 'Monkey', 'Ninja', 'Spaghetti']
      })
      @right = Daru::DataFrame.new({
        :id => [1,2,3,4],
        :name => ['Rutabaga', 'Pirate', 'Darth Vader', 'Ninja']
      })
      @right_many = Daru::DataFrame.new({
        :id => [1,1,1,1],
        :name => ['Rutabaga', 'Pirate', 'Darth Vader', 'Ninja']
      })
      @empty = Daru::DataFrame.new({
        :id => [],
        :name => []
      })
    end

    it "performs an inner join of two dataframes" do
      answer = Daru::DataFrame.new({
        :id_1   => [3,1],
        :name => ['Ninja', 'Pirate'],
        :id_2   => [4,2]
      }, order: [:id_1, :name, :id_2])
      expect(@left.join(@right, how: :inner, on: [:name])).to eq(answer)
    end

    it "performs an inner join of two dataframes that has one to many mapping" do
      answer = Daru::DataFrame.new({
        :name_1 => ['Pirate', 'Pirate', 'Pirate', 'Pirate'],
        :id => [1,1,1,1],
        :name_2 => ['Rutabaga', 'Pirate', 'Darth Vader', 'Ninja']
      }, order: [:name_1, :id, :name_2])
      expect(@left.join(@right_many, how: :inner, on: [:id])).to eq(answer)
    end

    it "performs an inner join of two dataframes that has many to many mapping" do
      @left[:id].recode! { |v| v == 2 ? 1 : v }
      answer = Daru::DataFrame.new({
        :name_1 => ['Pirate', 'Pirate', 'Pirate', 'Pirate', 'Monkey', 'Monkey', 'Monkey', 'Monkey'],
        :id => [1,1,1,1,1,1,1,1],
        :name_2 => ['Rutabaga', 'Pirate', 'Darth Vader', 'Ninja', 'Rutabaga', 'Pirate', 'Darth Vader', 'Ninja']
      }, order: [:name_1, :id, :name_2])
      expect(@left.join(@right_many, how: :inner, on: [:id])).to eq(answer)
    end

    it "performs a full outer join" do
      answer = Daru::DataFrame.new({
        :id_1 => [nil,2,3,1,nil,4],
        :name => ["Darth Vader", "Monkey", "Ninja", "Pirate", "Rutabaga", "Spaghetti"],
        :id_2 => [3,nil,4,2,1,nil]
      }, order: [:id_1, :name, :id_2])
      expect(@left.join(@right, how: :outer, on: [:name])).to eq(answer)
    end

    it "performs a full outer join when the right join keys have nils" do
      @right[:name].recode! { |v| v == 'Rutabaga' ? nil : v }
      answer = Daru::DataFrame.new({
        :id_1 => [nil, nil,2,3,1,4],
        :name => [nil, "Darth Vader", "Monkey", "Ninja", "Pirate", "Spaghetti"],
        :id_2 => [1,3,nil,4,2,nil]
      }, order: [:id_1, :name, :id_2])
      expect(@left.join(@right, how: :outer, on: [:name])).to eq(answer)
    end

    it "performs a full outer join when the left join keys have nils" do
      @left[:name].recode! { |v| v == 'Monkey' ? nil : v }
      answer = Daru::DataFrame.new({
        :id_1 => [2,nil,3,1,nil,4],
        :name => [nil, "Darth Vader", "Ninja", "Pirate", "Rutabaga", "Spaghetti"],
        :id_2 => [nil,3,4,2,1,nil]
      }, order: [:id_1, :name, :id_2])
      expect(@left.join(@right, how: :outer, on: [:name])).to eq(answer)
    end

    it "performs a full outer join when both left and right join keys have nils" do
      @left[:name].recode! { |v| v == 'Monkey' ? nil : v }
      @right[:name].recode! { |v| v == 'Rutabaga' ? nil : v }

      answer = Daru::DataFrame.new({
        :id_1 => [nil,2,nil,3,1,4],
        :name => [nil, nil, "Darth Vader", "Ninja", "Pirate", "Spaghetti"],
        :id_2 => [1,nil,3,4,2,nil]
      }, order: [:id_1, :name, :id_2])
      expect(@left.join(@right, how: :outer, on: [:name])).to eq(answer)
    end

    it "performs a left outer join", focus: true do
      answer = Daru::DataFrame.new({
        :id_1 => [2,3,1,4],
        :name => ["Monkey", "Ninja", "Pirate", "Spaghetti"],
        :id_2 => [nil,4,2,nil]
      }, order: [:id_1, :name, :id_2])
      expect(@left.join(@right, how: :left, on: [:name])).to eq(answer)
    end

    it "performs a left join with an empty dataframe" do

      answer = Daru::DataFrame.new({
        :id_1 => [2,3,1,4],
        :name => ["Monkey", "Ninja", "Pirate", "Spaghetti"],
        :id_2 => [nil,nil,nil,nil]
      }, order: [:id_1, :name, :id_2])

      expect(@left.join(@empty, how: :left, on: [:name])).to eq(answer)
    end

    it "performs a right outer join" do
      answer = Daru::DataFrame.new({
        :id_1 => [nil,3,1,nil],
        :name => ["Darth Vader", "Ninja", "Pirate", "Rutabaga"],
        :id_2 => [3,4,2,1]
      }, order: [:id_1, :name, :id_2])
      expect(@left.join(@right, how: :right, on: [:name])).to eq(answer)
    end

    it "raises if :on field are absent in one of dataframes" do
      @right.vectors = [:id, :other_name]
      expect { @left.join(@right, how: :right, on: [:name]) }.to \
        raise_error(ArgumentError, /Both dataframes expected .* :name/)

      expect { @left.join(@right, how: :right, on: [:other_name]) }.to \
        raise_error(ArgumentError, /Both dataframes expected .* :other_name/)
    end

    it "is able to join by several :on fields" do
      @left.gender = ['m', 'f', 'm', nil]
      @right.gender = ['m', 'm', nil, 'f']

      answer = Daru::DataFrame.new({
        id_1: [1],
        name: ['Pirate'],
        gender: ['m'],
        id_2: [2]
      }, order: [:id_1, :name, :gender, :id_2])
      expect(@left.join(@right, how: :inner, on: [:name, :gender])).to eq(answer)
    end
  end
end