require 'spec_helper'

def create_label_tree
  @d1 = Label.find_or_create_by_path %w(a1 b1 c1 d1)
  @c1 = @d1.parent
  @b1 = @c1.parent
  @a1 = @b1.parent
  @d2 = Label.find_or_create_by_path %w(a1 b1 c2 d2)
  @c2 = @d2.parent
  @d3 = Label.find_or_create_by_path %w(a2 b2 c3 d3)
  @c3 = @d3.parent
  @b2 = @c3.parent
  @a2 = @b2.parent
  Label.update_all("sort_order = id")
end

def create_preorder_tree(suffix = "", &block)
  %w(
    a/l/n/r
    a/l/n/q
    a/l/n/p
    a/l/n/o
    a/l/m
    a/b/h/i/j/k
    a/b/c/d/g
    a/b/c/d/f
    a/b/c/d/e
  ).shuffle.each { |ea| Label.find_or_create_by_path(ea.split('/').collect { |ea| "#{ea}#{suffix}" }) }

  Label.roots.each_with_index do |root, root_idx|
    root.order_value = root_idx
    yield(root) if block_given?
    root.save!
    root.self_and_descendants.each do |ea|
      ea.children.to_a.sort_by(&:name).each_with_index do |ea, idx|
        ea.order_value = idx
        yield(ea) if block_given?
        ea.save!
      end
    end
  end
end

describe Label do

  context "destruction" do
    it "properly destroys descendents" do
      c = Label.find_or_create_by_path %w(a b c)
      b = c.parent
      a = c.root
      a.destroy
      Label.exists?(a).should be_false
      Label.exists?(b).should be_false
      Label.exists?(c).should be_false
    end
  end

  context "roots" do
    it "sorts alphabetically" do
      expected = (0..10).to_a
      expected.shuffle.each do |ea|
        Label.create! do |l|
          l.name = "root #{ea}"
          l.sort_order = ea
        end
      end
      Label.roots.collect { |ea| ea.sort_order }.should == expected
    end
  end

  context "Base Label class" do
    it "should find or create by path" do
      # class method:
      c = Label.find_or_create_by_path(%w{grandparent parent child})
      c.ancestry_path.should == %w{grandparent parent child}
      c.name.should == "child"
      c.parent.name.should == "parent"
    end
  end

  context "Parent/child inverse relationships" do
    it "should associate both sides of the parent and child relationships" do
      parent = Label.new(:name => 'parent')
      child = parent.children.build(:name => 'child')
      parent.should be_root
      parent.should_not be_leaf
      child.should_not be_root
      child.should be_leaf
    end
  end

  context "DateLabel" do
    it "should find or create by path" do
      date = DateLabel.find_or_create_by_path(%w{2011 November 23})
      date.ancestry_path.should == %w{2011 November 23}
      date.self_and_ancestors.each { |ea| ea.class.should == DateLabel }
      date.name.should == "23"
      date.parent.name.should == "November"
    end
  end

  context "DirectoryLabel" do
    it "should find or create by path" do
      dir = DirectoryLabel.find_or_create_by_path(%w{grandparent parent child})
      dir.ancestry_path.should == %w{grandparent parent child}
      dir.name.should == "child"
      dir.parent.name.should == "parent"
      dir.parent.parent.name.should == "grandparent"
      dir.root.name.should == "grandparent"
      dir.id.should_not == Label.find_or_create_by_path(%w{grandparent parent child})
      dir.self_and_ancestors.each { |ea| ea.class.should == DirectoryLabel }
    end
  end

  context "Mixed class tree" do
    context "preorder tree" do
      before do
        classes = [Label, DateLabel, DirectoryLabel, EventLabel]
        create_preorder_tree do |ea|
          ea.type = classes[ea.sort_order % 4].to_s
        end
      end
      it "finds roots with specific classes" do
        Label.roots.should == Label.where(:name => 'a').to_a
        DirectoryLabel.roots.should be_empty
        EventLabel.roots.should be_empty
      end

      it "all is limited to subclasses" do
        DateLabel.all.map(&:name).should =~ %w(f h l n p)
        DirectoryLabel.all.map(&:name).should =~ %w(g q)
        EventLabel.all.map(&:name).should == %w(r)
      end

      it "returns descendents regardless of subclass" do
        Label.root.descendants.map{|ea|ea.class.to_s}.uniq.should =~
          %w(Label DateLabel DirectoryLabel EventLabel)
      end
    end

    it "supports children << and add_child" do
      a = EventLabel.create!(:name => "a")
      b = DateLabel.new(:name => "b")
      a.children << b
      c = Label.new(:name => "c")
      b.add_child(c)

      a.self_and_descendants.collect do |ea|
        ea.class
      end.should == [EventLabel, DateLabel, Label]

      a.self_and_descendants.collect do |ea|
        ea.name
      end.should == %w(a b c)
    end
  end

  context "find_all_by_generation" do
    before :each do
      create_label_tree
    end

    it "finds roots from the class method" do
      Label.find_all_by_generation(0).to_a.should == [@a1, @a2]
    end

    it "finds roots from themselves" do
      @a1.find_all_by_generation(0).to_a.should == [@a1]
    end

    it "finds itself for non-roots" do
      @b1.find_all_by_generation(0).to_a.should == [@b1]
    end

    it "finds children for roots" do
      Label.find_all_by_generation(1).to_a.should == [@b1, @b2]
    end

    it "finds children" do
      @a1.find_all_by_generation(1).to_a.should == [@b1]
      @b1.find_all_by_generation(1).to_a.should == [@c1, @c2]
    end

    it "finds grandchildren for roots" do
      Label.find_all_by_generation(2).to_a.should == [@c1, @c2, @c3]
    end

    it "finds grandchildren" do
      @a1.find_all_by_generation(2).to_a.should == [@c1, @c2]
      @b1.find_all_by_generation(2).to_a.should == [@d1, @d2]
    end

    it "finds great-grandchildren for roots" do
      Label.find_all_by_generation(3).to_a.should == [@d1, @d2, @d3]
    end
  end

  context "loading through self_and_ scopes" do
    before :each do
      create_label_tree
    end

    it "self_and_descendants should result in one select" do
      DB_QUERIES.clear
      a1_array = @a1.self_and_descendants
      a1_array.collect { |ea| ea.name }.should == %w(a1 b1 c1 c2 d1 d2)
      DB_QUERIES.size.should == 1
    end

    it "self_and_ancestors should result in one select" do
      DB_QUERIES.clear
      d1_array = @d1.self_and_ancestors
      d1_array.collect { |ea| ea.name }.should == %w(d1 c1 b1 a1)
      DB_QUERIES.size.should == 1
    end
  end

  context "deterministically orders with polymorphic siblings" do
    before :each do
      @parent = Label.create!(:name => "parent")
      @a = EventLabel.new(:name => "a")
      @b = DirectoryLabel.new(:name => "b")
      @c = DateLabel.new(:name => "c")
      @d = Label.new(:name => "d")
      @parent.children << @a
      @a.append_sibling(@b)
      @b.append_sibling(@c)
      @c.append_sibling(@d)
    end

    def children_name_and_order
      @parent.reload.children.map { |ea| [ea.name, ea.sort_order] }
    end

    it "sort_orders properly" do
      children_name_and_order.should == [['a', 0], ['b', 1], ['c', 2], ['d', 3]]
    end

    it "when inserted before" do
      @b.append_sibling(@a)
      children_name_and_order.should == [['b', 0], ['a', 1], ['c', 2], ['d', 3]]
    end

    it "when inserted after" do
      @a.append_sibling(@c)
      children_name_and_order.should == [['a', 0], ['c', 1], ['b', 2], ['d', 3]]
    end
  end

  it "behaves like the readme" do
    root = Label.create(:name => "root")
    a = Label.create(:name => "a", :parent => root)
    b = Label.create(:name => "b")
    c = Label.create(:name => "c")

    a.append_sibling(b)
    a.self_and_siblings.collect(&:name).should == %w(a b)
    root.reload.children.collect(&:name).should == %w(a b)
    root.children.collect(&:sort_order).should == [0, 1]

    a.prepend_sibling(b)
    a.self_and_siblings.collect(&:name).should == %w(b a)
    root.reload.children.collect(&:name).should == %w(b a)
    root.children.collect(&:sort_order).should == [0, 1]

    a.append_sibling(c)
    a.self_and_siblings.collect(&:name).should == %w(b a c)
    root.reload.children.collect(&:name).should == %w(b a c)
    root.children.collect(&:sort_order).should == [0, 1, 2]

    # We need to reload b because it was updated by a.append_sibling(c)
    b.reload.append_sibling(c)
    root.reload.children.collect(&:name).should == %w(b c a)
    root.children.collect(&:sort_order).should == [0, 1, 2]

    # We need to reload a because it was updated by b.append_sibling(c)
    d = a.reload.append_sibling(Label.new(:name => "d"))
    d.self_and_siblings.collect(&:name).should == %w(b c a d)
    d.self_and_siblings.collect(&:sort_order).should == [0, 1, 2, 3]
  end

  context "#add_sibling" do
    it "should move a node before another node which has an uninitialized sort_order" do
      f = Label.find_or_create_by_path %w(a b c d e fa)
      f0 = f.prepend_sibling(Label.new(:name => "fb")) # < not alpha sort, so name shouldn't matter
      f0.ancestry_path.should == %w(a b c d e fb)
      f.siblings_before.to_a.should == [f0]
      f0.siblings_before.should be_empty
      f0.siblings_after.should == [f]
      f.siblings_after.should be_empty
      f0.self_and_siblings.should == [f0, f]
      f.self_and_siblings.should == [f0, f]
    end

    it "should move a node to another tree" do
      f1 = Label.find_or_create_by_path %w(a1 b1 c1 d1 e1 f1)
      f2 = Label.find_or_create_by_path %w(a2 b2 c2 d2 e2 f2)
      f1.add_sibling(f2)
      f2.ancestry_path.should == %w(a1 b1 c1 d1 e1 f2)
      f1.parent.children.should == [f1, f2]
    end

    it "should reorder old-parent siblings when a node moves to another tree" do
      f1 = Label.find_or_create_by_path %w(a1 b1 c1 d1 e1 f1)
      f2 = Label.find_or_create_by_path %w(a2 b2 c2 d2 e2 f2)
      f3 = f2.prepend_sibling(Label.new(:name => "f3"))
      f4 = f2.append_sibling(Label.new(:name => "f4"))
      f1.add_sibling(f2)
      f1.self_and_siblings.collect(&:sort_order).should == [0, 1]
      f3.self_and_siblings.collect(&:sort_order).should == [0, 1]
      f1.self_and_siblings.collect(&:name).should == %w(f1 f2)
      f3.self_and_siblings.collect(&:name).should == %w(f3 f4)
    end
  end

  context "destructive reordering" do
    before :each do
      # to make sure sort_order isn't affected by additional nodes:
      create_preorder_tree
      @root = Label.create(:name => "root")
      @a = @root.children.create!(:name => "a")
      @b = @a.append_sibling(Label.new(:name => "b"))
      @c = @b.append_sibling(Label.new(:name => "c"))
    end
    context "doesn't create sort order gaps from" do
      it "from head" do
        @a.destroy
        @root.reload.children.should == [@b, @c]
        @root.children.map { |ea| ea.sort_order }.should == [0, 1]
      end
      it "from mid" do
        @b.destroy
        @root.reload.children.should == [@a, @c]
        @root.children.map { |ea| ea.sort_order }.should == [0, 1]
      end
      it "from tail" do
        @c.destroy
        @root.reload.children.should == [@a, @b]
        @root.children.map { |ea| ea.sort_order }.should == [0, 1]
      end
    end
    it "shouldn't fail if all children are destroyed" do
      roots = Label.roots.to_a
      roots.each { |ea| ea.children.destroy_all }
      Label.all.to_a.should =~ roots
    end
  end

  context "preorder" do
    it "returns descendants in proper order" do
      create_preorder_tree
      a = Label.root
      a.name.should == "a"
      expected = ('a'..'r').to_a
      a.self_and_descendants_preordered.collect { |ea| ea.name }.should == expected
      Label.roots_and_descendants_preordered.collect { |ea| ea.name }.should == expected
      # Let's create the second root by hand so we can explicitly set the sort order
      Label.create! do |l|
        l.name = "a1"
        l.sort_order = a.sort_order + 1
      end
      create_preorder_tree("1")
      # Should be no change:
      a.reload.self_and_descendants_preordered.collect { |ea| ea.name }.should == expected
      expected += ('a'..'r').collect { |ea| "#{ea}1" }
      Label.roots_and_descendants_preordered.collect { |ea| ea.name }.should == expected
    end
  end unless ENV["DB"] == "sqlite"
end