require 'spec_helper'

require 'puppet/graph'

describe Puppet::Graph::RbTreeMap do
  describe "#push" do
    it "should allow a new element to be added" do
      subject[5] = 'foo'
      expect(subject.size).to eq(1)

      expect(subject[5]).to eq('foo')
    end

    it "should replace the old value if the key is in tree already" do
      subject[0] = 10
      subject[0] = 20

      expect(subject[0]).to eq(20)
      expect(subject.size).to eq(1)
    end

    it "should be able to add a large number of elements" do
      (1..1000).each {|i| subject[i] = i.to_s}

      expect(subject.size).to eq(1000)
    end

    it "should create a root node if the tree was empty" do
      expect(subject.instance_variable_get(:@root)).to be_nil

      subject[5] = 'foo'

      expect(subject.instance_variable_get(:@root)).to be_a(Puppet::Graph::RbTreeMap::Node)
    end
  end

  describe "#size" do
    it "should be 0 for en empty tree" do
      expect(subject.size).to eq(0)
    end

    it "should correctly report the size for a non-empty tree" do
      (1..10).each {|i| subject[i] = i.to_s}

      expect(subject.size).to eq(10)
    end
  end

  describe "#has_key?" do
    it "should be true if the tree contains the key" do
      subject[1] = 2

      expect(subject).to be_has_key(1)
    end

    it "should be true if the tree contains the key and its value is nil" do
      subject[0] = nil

      expect(subject).to be_has_key(0)
    end

    it "should be false if the tree does not contain the key" do
      subject[1] = 2

      expect(subject).not_to be_has_key(2)
    end

    it "should be false if the tree is empty" do
      expect(subject).not_to be_has_key(5)
    end
  end

  describe "#get" do
    it "should return the value at the key" do
      subject[1] = 2
      subject[3] = 4

      expect(subject.get(1)).to eq(2)
      expect(subject.get(3)).to eq(4)
    end

    it "should return nil if the tree is empty" do
      expect(subject[1]).to be_nil
    end

    it "should return nil if the key is not in the tree" do
      subject[1] = 2

      expect(subject[3]).to be_nil
    end

    it "should return nil if the value at the key is nil" do
      subject[1] = nil

      expect(subject[1]).to be_nil
    end
  end

  describe "#min_key" do
    it "should return the smallest key in the tree" do
      [4,8,12,3,6,2,-4,7].each do |i|
        subject[i] = i.to_s
      end

      expect(subject.min_key).to eq(-4)
    end

    it "should return nil if the tree is empty" do
      expect(subject.min_key).to be_nil
    end
  end

  describe "#max_key" do
    it "should return the largest key in the tree" do
      [4,8,12,3,6,2,-4,7].each do |i|
        subject[i] = i.to_s
      end

      expect(subject.max_key).to eq(12)
    end

    it "should return nil if the tree is empty" do
      expect(subject.max_key).to be_nil
    end
  end

  describe "#delete" do
    before :each do
      subject[1] = '1'
      subject[0] = '0'
      subject[2] = '2'
    end

    it "should return the value at the key deleted" do
      expect(subject.delete(0)).to eq('0')
      expect(subject.delete(1)).to eq('1')
      expect(subject.delete(2)).to eq('2')
      expect(subject.size).to eq(0)
    end

    it "should be able to delete the last node" do
      tree = described_class.new
      tree[1] = '1'

      expect(tree.delete(1)).to eq('1')
      expect(tree).to be_empty
    end

    it "should be able to delete the root node" do
      expect(subject.delete(1)).to eq('1')

      expect(subject.size).to eq(2)

      expect(subject.to_hash).to eq({
        :node => {
          :key => 2,
          :value => '2',
          :color => :black,
        },
        :left => {
          :node => {
            :key => 0,
            :value => '0',
            :color => :red,
          }
        }
      })
    end

    it "should be able to delete the left child" do
      expect(subject.delete(0)).to eq('0')

      expect(subject.size).to eq(2)

      expect(subject.to_hash).to eq({
        :node => {
          :key => 2,
          :value => '2',
          :color => :black,
        },
        :left => {
          :node => {
            :key => 1,
            :value => '1',
            :color => :red,
          }
        }
      })
    end

    it "should be able to delete the right child" do
      expect(subject.delete(2)).to eq('2')

      expect(subject.size).to eq(2)

      expect(subject.to_hash).to eq({
        :node => {
          :key => 1,
          :value => '1',
          :color => :black,
        },
        :left => {
          :node => {
            :key => 0,
            :value => '0',
            :color => :red,
          }
        }
      })
    end

    it "should be able to delete the left child if it is a subtree" do
      (3..6).each {|i| subject[i] = i.to_s}

      expect(subject.delete(1)).to eq('1')

      expect(subject.to_hash).to eq({
        :node => {
          :key => 5,
          :value => '5',
          :color => :black,
        },
        :left => {
          :node => {
            :key => 3,
            :value => '3',
            :color => :red,
          },
          :left => {
            :node => {
              :key => 2,
              :value => '2',
              :color => :black,
            },
            :left => {
              :node => {
                :key => 0,
                :value => '0',
                :color => :red,
              },
            },
          },
          :right => {
            :node => {
              :key => 4,
              :value => '4',
              :color => :black,
            },
          },
        },
        :right => {
          :node => {
            :key => 6,
            :value => '6',
            :color => :black,
          },
        },
      })
    end

    it "should be able to delete the right child if it is a subtree" do
      (3..6).each {|i| subject[i] = i.to_s}

      expect(subject.delete(5)).to eq('5')

      expect(subject.to_hash).to eq({
        :node => {
          :key => 3,
          :value => '3',
          :color => :black,
        },
        :left => {
          :node => {
            :key => 1,
            :value => '1',
            :color => :red,
          },
          :left => {
            :node => {
              :key => 0,
              :value => '0',
              :color => :black,
            },
          },
          :right => {
            :node => {
              :key => 2,
              :value => '2',
              :color => :black,
            },
          },
        },
        :right => {
          :node => {
            :key => 6,
            :value => '6',
            :color => :black,
          },
          :left => {
            :node => {
              :key => 4,
              :value => '4',
              :color => :red,
            },
          },
        },
      })
    end

    it "should return nil if the tree is empty" do
      tree = described_class.new

      expect(tree.delete(14)).to be_nil

      expect(tree.size).to eq(0)
    end

    it "should return nil if the key is not in the tree" do
      (0..4).each {|i| subject[i] = i.to_s}

      expect(subject.delete(2.5)).to be_nil
      expect(subject.size).to eq(5)
    end

    it "should return nil if the key is larger than the maximum key" do
      expect(subject.delete(100)).to be_nil
      expect(subject.size).to eq(3)
    end

    it "should return nil if the key is smaller than the minimum key" do
      expect(subject.delete(-1)).to be_nil
      expect(subject.size).to eq(3)
    end
  end

  describe "#empty?" do
    it "should return true if the tree is empty" do
      expect(subject).to be_empty
    end

    it "should return false if the tree is not empty" do
      subject[5] = 10

      expect(subject).not_to be_empty
    end
  end

  describe "#delete_min" do
    it "should delete the smallest element of the tree" do
      (1..15).each {|i| subject[i] = i.to_s}

      expect(subject.delete_min).to eq('1')
      expect(subject.size).to eq(14)
    end

    it "should return nil if the tree is empty" do
      expect(subject.delete_min).to be_nil
    end
  end

  describe "#delete_max" do
    it "should delete the largest element of the tree" do
      (1..15).each {|i| subject[i] = i.to_s}

      expect(subject.delete_max).to eq('15')
      expect(subject.size).to eq(14)
    end

    it "should return nil if the tree is empty" do
      expect(subject.delete_max).to be_nil
    end
  end

  describe "#each" do
    it "should yield each pair in the tree in order if a block is provided" do
      # Insert in reverse to demonstrate they aren't being yielded in insertion order
      (1..5).to_a.reverse_each {|i| subject[i] = i.to_s}

      nodes = []
      subject.each do |key,value|
        nodes << [key,value]
      end

      expect(nodes).to eq((1..5).map {|i| [i, i.to_s]})
    end

    it "should do nothing if the tree is empty" do
      subject.each do |key,value|
        raise "each on an empty tree incorrectly yielded #{key}, #{value}"
      end
    end
  end

  describe "#isred" do
    it "should return true if the node is red" do
      node = Puppet::Graph::RbTreeMap::Node.new(1,2)
      node.color = :red

      expect(subject.send(:isred, node)).to eq(true)
    end

    it "should return false if the node is black" do
      node = Puppet::Graph::RbTreeMap::Node.new(1,2)
      node.color = :black

      expect(subject.send(:isred, node)).to eq(false)
    end

    it "should return false if the node is nil" do
      expect(subject.send(:isred, nil)).to eq(false)
    end
  end
end

describe Puppet::Graph::RbTreeMap::Node do
  let(:tree) { Puppet::Graph::RbTreeMap.new }
  let(:subject) { tree.instance_variable_get(:@root) }

  before :each do
    (1..3).each {|i| tree[i] = i.to_s}
  end

  describe "#red?" do
    it "should return true if the node is red" do
      subject.color = :red

      expect(subject).to be_red
    end

    it "should return false if the node is black" do
      subject.color = :black

      expect(subject).not_to be_red
    end
  end

  describe "#colorflip" do
    it "should switch the color of the node and its children" do
      expect(subject.color).to eq(:black)
      expect(subject.left.color).to eq(:black)
      expect(subject.right.color).to eq(:black)

      subject.colorflip

      expect(subject.color).to eq(:red)
      expect(subject.left.color).to eq(:red)
      expect(subject.right.color).to eq(:red)
    end
  end

  describe "#rotate_left" do
    it "should rotate the tree once to the left" do
      (4..7).each {|i| tree[i] = i.to_s}

      root = tree.instance_variable_get(:@root)

      root.rotate_left

      expect(tree.to_hash).to eq({
        :node => {
          :key => 6,
          :value => '6',
          :color => :black,
        },
        :left => {
          :node => {
            :key => 4,
            :value => '4',
            :color => :red,
          },
          :left => {
            :node => {
              :key => 2,
              :value => '2',
              :color => :black,
            },
            :left => {
              :node => {
                :key => 1,
                :value => '1',
                :color => :black,
              },
            },
            :right => {
              :node => {
                :key => 3,
                :value => '3',
                :color => :black,
              },
            },
          },
          :right => {
            :node => {
              :key => 5,
              :value => '5',
              :color => :black,
            },
          },
        },
        :right => {
          :node => {
            :key => 7,
            :value => '7',
            :color => :black,
          },
        },
      })
    end
  end

  describe "#rotate_right" do
    it "should rotate the tree once to the right" do
      (4..7).each {|i| tree[i] = i.to_s}

      root = tree.instance_variable_get(:@root)

      root.rotate_right

      expect(tree.to_hash).to eq({
        :node => {
          :key => 2,
          :value => '2',
          :color => :black,
        },
        :left => {
          :node => {
            :key => 1,
            :value => '1',
            :color => :black,
          },
        },
        :right => {
          :node => {
            :key => 4,
            :value => '4',
            :color => :red,
          },
          :left => {
            :node => {
              :key => 3,
              :value => '3',
              :color => :black,
            },
          },
          :right => {
            :node => {
              :key => 6,
              :value => '6',
              :color => :black,
            },
            :left => {
              :node => {
                :key => 5,
                :value => '5',
                :color => :black,
              },
            },
            :right => {
              :node => {
                :key => 7,
                :value => '7',
                :color => :black,
              },
            },
          },
        },
      })
    end
  end
end
