######################################################################
#
# Tests for Node core functionality.
#
######################################################################

require 'test_helper'

Chain = C::NodeChain

#
# Some Node classes.
#
class X < C::Node
  child :a
  initializer :a
end
class Y < C::Node
  child :a
  child :b
  initializer :a, :b
end
class Z < C::Node
  child :a
  field :b
  child :c
  field :d
  child :e
  initializer :a, :b, :c, :d, :e
end
class W < C::Node
  field :a
  field :b, 0
  initializer :a, :b
end
class V < C::Node
  child :a, lambda{C::NodeArray.new}
  initializer :a
end

class NodeInitializeTest < Minitest::Test

  # ------------------------------------------------------------------
  #                             initialize
  # ------------------------------------------------------------------

  def test_initialize_w
    w = W.new
    assert_nil(w.a)
    assert_same(0, w.b)

    w = W.new(1, true)
    assert_same(1, w.a)
    assert_same(true, w.b)

    w = W.new(1, :b => true)
    assert_same(1, w.a)
    assert_same(true, w.b)

    w = W.new(:a => 1, :b => true)
    assert_same(1, w.a)
    assert_same(true, w.b)
  end

  def test_initialize_v
    v = V.new
    assert_same_list([], v.a)
    assert_same(C::NodeArray, v.a.class)

    v = V.new(C::NodeChain[])
    assert_same_list([], v.a)
    assert_same(C::NodeChain, v.a.class)
  end

  def test_initialize_attached
    x1, x2 = 2.of{X.new}
    list = C::NodeArray[x1, x2]
    z = Z.new(x1, x2)
    assert_same_list([x1, x2], list)
    assert_copy(x1, z.a)
    assert_same(x2, z.b)
  end

  # ------------------------------------------------------------------
  #                            Node.new_at
  # ------------------------------------------------------------------

  def test_new_at
    pos = C::Node::Pos.new('somefile', 5, 10)
    xa = X.new
    x = X.new_at(pos, xa)
    assert_same(pos, x.pos)
    assert_same(xa, x.a)
  end
end

class NodeEqualTest < Minitest::Test
  def str
    "(struct s){.a = 1, [2] = {3, 4}, .b [5] = 6, 7}"
  end
  def node
    ma = C::Member.new('a')
    mb = C::Member.new('b')
    one   = C::IntLiteral.new(1)
    two   = C::IntLiteral.new(2)
    three = C::IntLiteral.new(3)
    four  = C::IntLiteral.new(4)
    five  = C::IntLiteral.new(5)
    six   = C::IntLiteral.new(6)
    seven = C::IntLiteral.new(7)

    mi0 = C::MemberInit.new(Chain[ma], one)
    mi10 = C::MemberInit.new(nil, three)
    mi11 = C::MemberInit.new(nil, four)
    mi1 = C::MemberInit.new(Chain[two],
                            C::CompoundLiteral.new(nil, Chain[mi10, mi11]))
    mi2 = C::MemberInit.new(Chain[mb, five], six)
    mi3 = C::MemberInit.new(nil, seven)

    c = C::CompoundLiteral.new(C::Struct.new('s'),
                               Chain[mi0, mi1, mi2, mi3])
    return c
  end

  # ------------------------------------------------------------------
  #                              ==, eql?
  # ------------------------------------------------------------------

  def test_eq
    # copy should be equal
    assert_equal(node, node)
    assert(node.eql?(node))

    # change any one field and it should be not_equal
    n = node
    n.type = nil
    refute_equal(node, n)
    assert(!node.eql?(n))

    n = node
    n.member_inits[0].member[0] = C::Member.new('c')
    refute_equal(node, n)
    assert(!node.eql?(n))
    copy = node.dup

    n = node
    n.member_inits[2].member[1] = C::IntLiteral.new(8)
    refute_equal(node, n)
    assert(!node.eql?(n))

    # add a member's init and it should be not_equal
    n = node
    n.member_inits[3].init = C::IntLiteral.new(9)
    refute_equal(node, n)
    assert(!node.eql?(n))

    # change a member's init and it should be not_equal
    n = node
    n.member_inits[0].init = C::IntLiteral.new(10)
    refute_equal(node, n)
    assert(!node.eql?(n))

    # add a member specifier and it should be not_equal
    n = node
    n.member_inits[3].member = Chain[C::Member.new('d')]
    refute_equal(node, n)
    assert(!node.eql?(n))

    # pop a member and it should be not_equal
    n = node
    n.member_inits.pop
    refute_equal(node, n)
    assert(!node.eql?(n))

    # assign a field a copy of what's there and it should still be
    # equal
    n = node
    n.member_inits[0].member[0] = C::Member.new('a')
    assert_equal(node, n)
    assert(node.eql?(n))

    n = node
    n.member_inits[0].init = C::IntLiteral.new(1)
    assert_equal(node, n)
    assert(node.eql?(n))
  end

  # ------------------------------------------------------------------
  #                                hash
  # ------------------------------------------------------------------

  def test_hash
    # copy should be equal
    assert_equal(node.hash, node.hash)

    # should be equal after assigning to a field a copy of what's
    # there
    n = node
    n.member_inits[0].member[0] = C::Member.new('a')
    assert_equal(node.hash, n.hash)

    n = node
    n.member_inits[0].init = C::IntLiteral.new(1)
    assert_equal(node.hash, n.hash)
  end
end

class NodeCopyTest < Minitest::Test
  def setup
    # (struct s){.a = 1, [2] = {3, 4}, .b [5] = 6, 7}

    @c_t_n = 's'
    @c_t   = C::Struct.new(@c_t_n)

    @c_mis0_m0_n = 'a'
    @c_mis0_m0   = C::Member.new(@c_mis0_m0_n)
    @c_mis0_m    = Chain[@c_mis0_m0]
    @c_mis0_i    = C::IntLiteral.new(1)
    @c_mis0      = C::MemberInit.new(@c_mis0_m, @c_mis0_i)

    @c_mis1_m0       = C::IntLiteral.new(2)
    @c_mis1_m        = Chain[@c_mis1_m0]
    @c_mis1_i_mis0_i = C::IntLiteral.new(3)
    @c_mis1_i_mis0   = C::MemberInit.new(nil, @c_mis1_i_mis0_i)
    @c_mis1_i_mis1_i = C::IntLiteral.new(4)
    @c_mis1_i_mis1   = C::MemberInit.new(nil, @c_mis1_i_mis1_i)
    @c_mis1_i_mis    = Chain[@c_mis1_i_mis0, @c_mis1_i_mis1]
    @c_mis1_i        = C::CompoundLiteral.new(nil, @c_mis1_i_mis)
    @c_mis1          = C::MemberInit.new(@c_mis1_m, @c_mis1_i)

    @c_mis2_m0_n
    @c_mis2_m0 = C::Member.new(@c_mis2_m0_n)
    @c_mis2_m1 = C::IntLiteral.new(5)
    @c_mis2_m  = Chain[@c_mis2_m0, @c_mis2_m1]
    @c_mis2_i  = C::IntLiteral.new(6)
    @c_mis2    = C::MemberInit.new(@c_mis2_m, @c_mis2_i)

    @c_mis3_i = C::IntLiteral.new(7)
    @c_mis3   = C::MemberInit.new(nil, @c_mis3_i)
    @c_mis    = Chain[@c_mis0, @c_mis1, @c_mis2, @c_mis3]

    @c = C::CompoundLiteral.new(@c_t, @c_mis)

    class << @c
      def new_method
        100
      end
    end
    class << @c_mis1_i_mis0
      def new_method
        100
      end
    end

    @d = c.dup
    @e = c.clone
  end
  attr_accessor :c, :d, :e

  # ------------------------------------------------------------------
  #                             dup, clone
  # ------------------------------------------------------------------

  def check_node value
    cres = yield(c)
    dres = yield(d)
    eres = yield(e)
    assert_same(value, cres)
    if value.is_a? C::Node
      assert_copy(value, dres)
      assert_copy(value, eres)
    else
      assert_same(value, dres)
      assert_same(value, eres)
    end
    assert_raises(NoMethodError){dres.new_method}
    case value.object_id
    when @c.object_id, @c_mis1_i_mis0.object_id
      assert_same(100, eres.new_method)
    else
      assert_raises(NoMethodError){eres.new_method}
    end
  end

  def test_copy
    # each element should be equal, but not the same, except for
    # immediate values
    #
    # (struct s){.a = 1, [2] = {3, 4}, .b [5] = 6, 7}
    assert_tree(c)
    assert_tree(d)
    assert_tree(e)
    check_node(@c              ){|x| x}
    check_node(@c_t            ){|x| x.type}
    check_node(@c_t_n          ){|x| x.type.name}
    check_node(nil             ){|x| x.type.members}
    check_node(@c_mis          ){|x| x.member_inits}
    check_node(@c_mis0         ){|x| x.member_inits[0]}
    check_node(@c_mis0_m       ){|x| x.member_inits[0].member}
    check_node(@c_mis0_m0      ){|x| x.member_inits[0].member[0]}
    check_node(@c_mis0_m0_n    ){|x| x.member_inits[0].member[0].name}
    check_node(@c_mis0_i       ){|x| x.member_inits[0].init}
    check_node(1               ){|x| x.member_inits[0].init.val}
    check_node(@c_mis1         ){|x| x.member_inits[1]}
    check_node(@c_mis1_m       ){|x| x.member_inits[1].member}
    check_node(@c_mis1_m0      ){|x| x.member_inits[1].member[0]}
    check_node(2               ){|x| x.member_inits[1].member[0].val}
    check_node(@c_mis1_i       ){|x| x.member_inits[1].init}
    check_node(nil             ){|x| x.member_inits[1].init.type}
    check_node(@c_mis1_i_mis   ){|x| x.member_inits[1].init.member_inits}
    check_node(@c_mis1_i_mis0  ){|x| x.member_inits[1].init.member_inits[0]}
    check_node(nil             ){|x| x.member_inits[1].init.member_inits[0].member}
    check_node(@c_mis1_i_mis0_i){|x| x.member_inits[1].init.member_inits[0].init}
    check_node(3               ){|x| x.member_inits[1].init.member_inits[0].init.val}
    check_node(@c_mis1_i_mis1  ){|x| x.member_inits[1].init.member_inits[1]}
    check_node(nil             ){|x| x.member_inits[1].init.member_inits[1].member}
    check_node(@c_mis1_i_mis1_i){|x| x.member_inits[1].init.member_inits[1].init}
    check_node(4               ){|x| x.member_inits[1].init.member_inits[1].init.val}
    check_node(@c_mis2         ){|x| x.member_inits[2]}
    check_node(@c_mis2_m       ){|x| x.member_inits[2].member}
    check_node(@c_mis2_m0      ){|x| x.member_inits[2].member[0]}
    check_node(@c_mis2_m0_n    ){|x| x.member_inits[2].member[0].name}
    check_node(@c_mis2_m1      ){|x| x.member_inits[2].member[1]}
    check_node(5               ){|x| x.member_inits[2].member[1].val}
    check_node(@c_mis2_i       ){|x| x.member_inits[2].init}
    check_node(6               ){|x| x.member_inits[2].init.val}
    check_node(@c_mis3         ){|x| x.member_inits[3]}
    check_node(nil             ){|x| x.member_inits[3].member}
    check_node(@c_mis3_i       ){|x| x.member_inits[3].init}
    check_node(7               ){|x| x.member_inits[3].init.val}
  end
end

class NodeWalkTest < Minitest::Test
  #
  # Collect and return the args yielded to `node.send(method)' as an
  # Array, each element of which is an array of args yielded.
  #
  # Also, assert that the return value of the method is `exp'.
  #
  def yields(method, node, exp)
    ret = []
    out = node.send(method) do |*args|
      ret << args
      yield *args if block_given?
    end
    assert_same(exp, out)
    return ret
  end

  #
  # Assert exp and out are equal, where elements are compared with
  # Array#same_list?.  That is, exp[i].same_list?(out[i]) for all i.
  #
  def assert_equal_yields(exp, out)
    if exp.zip(out).all?{|a,b| a.same_list?(b)}
      assert(true)
    else
      flunk("walk not equal: #{walk_str(out)} (expected #{walk_str(exp)})")
    end
  end
  def walk_str(walk)
    walk.is_a? ::Array or
      raise "walk_str: expected ::Array"
    if walk.empty?
      return '[]'
    else
      s = StringIO.new
      s.puts '['
      walk.each do |args|
        args.map! do |arg|
          if arg.is_a? C::Node
            argstr = arg.class.name << ' (' << arg.object_id.to_s <<
              "): " << arg.to_s
          else
            argstr = arg.inspect
          end
          if argstr.length > 50
            argstr[48..-1] = '...'
          end
          argstr
        end
        s.puts "    [#{args.join(', ')}]"
      end
      s.puts ']'
      return s.string
    end
  end

  # ------------------------------------------------------------------
  #                  depth_first, reverse_depth_first
  # ------------------------------------------------------------------

  def check_depth_firsts(node, exp)
    # depth_first
    out = yields(:depth_first, node, node)
    assert_equal_yields exp, out

    # reverse_depth_first
    exp = exp.reverse.map! do |ev, node|
      if ev == :ascending
        [:descending, node]
      else
        [:ascending, node]
      end
    end
    out = yields(:reverse_depth_first, node, node)
    assert_equal_yields exp, out
  end

  def test_depth_first
    # empty node
    d = C::Int.new
    d.longness = 1
    check_depth_firsts(d,
                       [[:descending, d], [:ascending, d]])

    # one-storey -- populate both the list child and nonlist child
    d = C::Declaration.new(C::Int.new)
    d.declarators << C::Declarator.new
    d.declarators[0].name = 'one'
    d.declarators << C::Declarator.new
    d.declarators[1].name = 'two'
    check_depth_firsts(d,
                       [
                         [:descending, d],
                           [:descending, d.type], [:ascending, d.type],
                           [:descending, d.declarators],
                             [:descending, d.declarators[0]], [:ascending, d.declarators[0]],
                             [:descending, d.declarators[1]], [:ascending, d.declarators[1]],
                           [:ascending, d.declarators],
                         [:ascending, d]
                       ])

    # multi-layer
    d.declarators[0].indirect_type = C::Function.new
    d.declarators[0].indirect_type.params = Chain[]
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Int.new, 'i')
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Float.new, 'f')
    check_depth_firsts(d,
                       [
                         [:descending, d],
                           [:descending, d.type], [:ascending, d.type],
                           [:descending, d.declarators],
                             [:descending, d.declarators[0]],
                               [:descending, d.declarators[0].indirect_type],
                                 [:descending, d.declarators[0].indirect_type.params],
                                   [:descending, d.declarators[0].indirect_type.params[0]],
                                     [:descending, d.declarators[0].indirect_type.params[0].type], [:ascending, d.declarators[0].indirect_type.params[0].type],
                                   [:ascending, d.declarators[0].indirect_type.params[0]],
                                   [:descending, d.declarators[0].indirect_type.params[1]],
                                     [:descending, d.declarators[0].indirect_type.params[1].type], [:ascending, d.declarators[0].indirect_type.params[1].type],
                                   [:ascending, d.declarators[0].indirect_type.params[1]],
                                 [:ascending, d.declarators[0].indirect_type.params],
                               [:ascending, d.declarators[0].indirect_type],
                             [:ascending, d.declarators[0]],
                             [:descending, d.declarators[1]], [:ascending, d.declarators[1]],
                           [:ascending, d.declarators],
                         [:ascending, d]
                       ])
  end

  def check_depth_first_prunes(pruned_nodes, node, exp)
    # depth_first
    out = yields(:depth_first, node, node) do |ev, node|
      if ev.equal? :descending
        if pruned_nodes.any?{|n| n.equal? node}
          throw :prune
        end
      end
    end
    assert_equal_yields exp, out
    #
    ret = catch :prune do
      node.depth_first do |ev, node|
        throw :prune, :x if ev.equal? :ascending
      end
      :oops
    end
    assert_same(:x, ret)

    # reverse_depth_first
    exp = exp.reverse.map! do |ev, node|
      if ev.equal? :ascending
        [:descending, node]
      else
        [:ascending, node]
      end
    end
    out = yields(:reverse_depth_first, node, node) do |ev, node|
      if ev.equal? :descending
        if pruned_nodes.any?{|n| n.equal? node}
          throw :prune
        end
      end
    end
    assert_equal_yields exp, out
    #
    ret = catch :prune do
      node.reverse_depth_first do |ev, node|
        throw :prune, :x if ev.equal? :ascending
      end
      :oops
    end
    assert_same(:x, ret)
  end

  def test_depth_first_prune
    # empty node
    d = C::Int.new
    d.longness = 1
    check_depth_first_prunes([d], d,
                       [[:descending, d], [:ascending, d]])

    # one-storey -- populate both the list child and nonlist child
    d = C::Declaration.new(C::Int.new)
    d.declarators << C::Declarator.new
    d.declarators[0].name = 'one'
    d.declarators << C::Declarator.new
    d.declarators[1].name = 'two'
    check_depth_first_prunes([d.declarators], d,
                       [
                         [:descending, d],
                           [:descending, d.type], [:ascending, d.type],
                           [:descending, d.declarators], [:ascending, d.declarators],
                         [:ascending, d]
                       ])

    # multi-layer
    d.type = C::Struct.new('S')
    d.type.members = Chain[]
    d.type.members << C::Declaration.new(C::Int.new)
    d.type.members[0].declarators << C::Declarator.new(nil, 'x')
    d.declarators[0].indirect_type = C::Function.new
    d.declarators[0].indirect_type.params = Chain[]
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Int.new, 'i')
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Float.new, 'f')
    check_depth_first_prunes([d.type.members, d.declarators[0]], d,
                       [
                         [:descending, d],
                           [:descending, d.type],
                             [:descending, d.type.members], [:ascending, d.type.members],
                           [:ascending, d.type],
                           [:descending, d.declarators],
                             [:descending, d.declarators[0]], [:ascending, d.declarators[0]],
                             [:descending, d.declarators[1]], [:ascending, d.declarators[1]],
                           [:ascending, d.declarators],
                         [:ascending, d]
                       ])
  end

  # ------------------------------------------------------------------
  #                         each, reverse_each
  # ------------------------------------------------------------------

  def iter_str(iter)
    iter.is_a? ::Array or
      raise "iter_str: expected ::Array"
    if iter.empty?
      return '[]'
    else
      s = StringIO.new
      s.puts '['
      iter.each do |node|
        nodestr = node.class.name << '(' << node.object_id.to_s << "): " << node.to_s
        if nodestr.length > 70
          nodestr[68..-1] = '...'
        end
        s.puts "    #{nodestr}"
      end
      s.puts ']'
      return s.string
    end
  end
  def check_each(node, exp)
    exp.map!{|n| [n]}

    out = yields(:each, node, node)
    assert_equal_yields exp, out

    out = yields(:reverse_each, node, node)
    exp.reverse!
    assert_equal_yields exp, out
  end
  def test_each
    # empty
    parent = X.new
    check_each(parent, [])

    # one child
    x1 = X.new
    parent = X.new(x1)
    check_each(parent, [x1])

    # two children
    x1, x2 = 2.of{X.new}
    parent = Y.new(x1, x2)
    check_each(parent, [x1, x2])

    # three with some nil and some fields
    x1, x2, x3, x4, x5 = 5.of{X.new}
    parent = Z.new(x1, x2, nil, x4, x5)
    check_each(parent, [x1, x5])
  end

  # ------------------------------------------------------------------
  #      preorder, reverse_preorder, postorder, reverse_postorder
  # ------------------------------------------------------------------

  def check_preorder(node, exp)
    exp.map!{|n| [n]}

    out = yields(:preorder, node, node)
    assert_equal_yields exp, out

    out = yields(:reverse_postorder, node, node)
    exp.reverse!
    assert_equal_yields exp, out
  end
  def check_postorder(node, exp)
    exp.map!{|n| [n]}

    out = yields(:postorder, node, node)
    assert_equal_yields exp, out

    out = yields(:reverse_preorder, node, node)
    exp.reverse!
    assert_equal_yields exp, out
  end
  def test_preorder
    # empty node
    d = C::Int.new
    d.longness = 1
    check_preorder(d, [d])

    # one-storey -- populate both the list child and nonlist child
    d = C::Declaration.new(C::Int.new)
    d.declarators << C::Declarator.new
    d.declarators[0].name = 'one'
    d.declarators << C::Declarator.new
    d.declarators[1].name = 'two'
    check_preorder(d,
                   [
                     d,
                     d.type,
                     d.declarators,
                     d.declarators[0],
                     d.declarators[1]
                   ])

    # multi-layer
    d.declarators[0].indirect_type = C::Function.new
    d.declarators[0].indirect_type.params = Chain[]
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Int.new, 'i')
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Float.new, 'f')
    check_preorder(d,
                   [
                     d,
                     d.type,
                     d.declarators,
                     d.declarators[0],
                     d.declarators[0].indirect_type,
                     d.declarators[0].indirect_type.params,
                     d.declarators[0].indirect_type.params[0],
                     d.declarators[0].indirect_type.params[0].type,
                     d.declarators[0].indirect_type.params[1],
                     d.declarators[0].indirect_type.params[1].type,
                     d.declarators[1]
                   ])
  end
  def test_postorder
    # empty node
    d = C::Int.new
    d.longness = 1
    check_preorder(d, [d])

    # one-storey -- populate both the list child and nonlist child
    d = C::Declaration.new(C::Int.new)
    d.declarators << C::Declarator.new
    d.declarators[0].name = 'one'
    d.declarators << C::Declarator.new
    d.declarators[1].name = 'two'
    check_postorder(d,
                    [
                     d.type,
                     d.declarators[0],
                     d.declarators[1],
                     d.declarators,
                     d
                    ])

    # multi-layer
    d.declarators[0].indirect_type = C::Function.new
    d.declarators[0].indirect_type.params = Chain[]
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Int.new, 'i')
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Float.new, 'f')
    check_postorder(d,
                    [
                     d.type,
                     d.declarators[0].indirect_type.params[0].type,
                     d.declarators[0].indirect_type.params[0],
                     d.declarators[0].indirect_type.params[1].type,
                     d.declarators[0].indirect_type.params[1],
                     d.declarators[0].indirect_type.params,
                     d.declarators[0].indirect_type,
                     d.declarators[0],
                     d.declarators[1],
                     d.declarators,
                     d
                    ])
  end
  def check_preorder_prune(method, pruned_nodes, root, exp)
    exp.map!{|n| [n]}

    out = yields(method, root, root) do |node|
      if pruned_nodes.any?{|n| n.equal? node}
        throw :prune
      end
    end
    assert_equal_yields exp, out
  end

  def test_preorder_prune
    # empty node
    d = C::Int.new
    d.longness = 1
    check_preorder_prune(:preorder, [d], d, [d])
    check_preorder_prune(:reverse_preorder, [d], d, [d])

    # one-storey -- populate both the list child and nonlist child
    d = C::Declaration.new(C::Int.new)
    d.declarators << C::Declarator.new
    d.declarators[0].name = 'one'
    d.declarators << C::Declarator.new
    d.declarators[1].name = 'two'
    check_preorder_prune(:preorder, [d.declarators], d,
                         [
                           d,
                           d.type,
                           d.declarators,
                         ])
    check_preorder_prune(:reverse_preorder, [d.declarators], d,
                                 [
                                   d,
                                   d.declarators,
                                   d.type,
                                 ])

    # multi-layer
    d.type = C::Struct.new('S')
    d.type.members = Chain[]
    d.type.members << C::Declaration.new(C::Int.new)
    d.type.members[0].declarators << C::Declarator.new(nil, 'x')
    d.declarators[0].indirect_type = C::Function.new
    d.declarators[0].indirect_type.params = Chain[]
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Int.new, 'i')
    d.declarators[0].indirect_type.params << C::Parameter.new(C::Float.new, 'f')
    check_preorder_prune(:preorder, [d.type.members, d.declarators[0]], d,
                         [
                           d,
                           d.type,
                           d.type.members,
                           d.declarators,
                           d.declarators[0],
                           d.declarators[1]
                         ])
    check_preorder_prune(:reverse_preorder, [d.type.members, d.declarators[0]], d,
                                 [
                                   d,
                                   d.declarators,
                                   d.declarators[1],
                                   d.declarators[0],
                                   d.type,
                                   d.type.members
                                 ])
  end

  # ------------------------------------------------------------------
  #                  next, prev, list_next, list_prev
  # ------------------------------------------------------------------

  def test_next_prev
    # list parent
    i1 = C::Int.new
    i2 = C::Int.new
    list = Chain[i1, i2]
    assert_same(i2, i1.next)
    assert_nil(i2.next)
    assert_same(i1, i2.prev)
    assert_nil(i1.prev)

    # node parent
    i1 = C::IntLiteral.new(1)
    i2 = C::IntLiteral.new(2)
    a = C::Add.new(i1, i2)
    assert_same(i2, i1.next)
    assert_nil(i2.next)
    assert_same(i1, i2.prev)
    assert_nil(i1.prev)

    # no parent
    i = C::Int.new
    assert_raises(C::Node::NoParent){i.next}
    assert_raises(C::Node::NoParent){i.prev}
  end

  def test_list_next_prev
    # list parent
    i1 = C::Int.new
    i2 = C::Int.new
    list = Chain[i1, i2]
    assert_same(i2, i1.list_next)
    assert_nil(i2.list_next)
    assert_same(i1, i2.list_prev)
    assert_nil(i1.list_prev)

    # node parent
    i1 = C::IntLiteral.new(1)
    i2 = C::IntLiteral.new(2)
    a = C::Add.new(i1, i2)
    assert_raises(C::Node::BadParent){i1.list_next}
    assert_raises(C::Node::BadParent){i2.list_next}
    assert_raises(C::Node::BadParent){i1.list_prev}
    assert_raises(C::Node::BadParent){i2.list_prev}

    # no parent
    i = C::Int.new
    assert_raises(C::Node::NoParent){i.list_next}
    assert_raises(C::Node::NoParent){i.list_prev}
  end
end

class NodeTreeTest < Minitest::Test
  def setup
    # @c = "(int){[1] = 10,
    #             .x = 20,
    #             [2] .y = 30
    #            }
    @c = C::CompoundLiteral.new
    c.type = C::Int.new
    c.member_inits << C::MemberInit.new
    c.member_inits[0].member = C::NodeChain.new
    c.member_inits[0].member << C::IntLiteral.new(1)
    c.member_inits[0].init = C::IntLiteral.new(10)
    c.member_inits << C::MemberInit.new
    c.member_inits[1].member = C::NodeChain.new
    c.member_inits[1].member << C::Member.new('x')
    c.member_inits[1].init = C::IntLiteral.new(20)
    c.member_inits << C::MemberInit.new
    c.member_inits[2].member = C::NodeChain.new
    c.member_inits[2].member << C::IntLiteral.new(2)
    c.member_inits[2].member << C::Member.new('y')
    c.member_inits[2].init = C::IntLiteral.new(30)
  end
  attr_reader :c

  def test_empty
    ast = C::TranslationUnit.new
    assert_tree(ast)
  end
  def test_basic
    assert_tree(c)
  end

  def test_assign_field
    c.type.unsigned = true
    assert_tree(c)
    assert_same(true, c.type.unsigned?)
    c.type.unsigned = false
    assert_tree(c)
    assert_same(false, c.type.unsigned?)
  end
  def test_assign_child
    c.type = nil
    assert_tree(c)
    assert_nil(c.type)
    f = C::Float.new
    c.type = f
    assert_tree(c)
    assert_same(f, c.type)
  end
  def test_assign_list
    old_list = c.member_inits[2].member
    new_list = C::NodeChain[C::IntLiteral.new(4), C::Member.new('a')]
    c.member_inits[2].member = new_list
    assert_tree(c)
    assert_tree(old_list)
    assert_same(new_list, c.member_inits[2].member)
  end
  def test_assign_attached
    f = C::Float.new
    c2 = C::CompoundLiteral.new
    c2.type = f

    c.type = f
    assert_same(f, c2.type)
    assert_copy(f, c.type)
    assert_tree(c)
    assert_tree(c2)
  end

  def test_detach_node
    d = c.type.detach
    assert_tree(c)
    assert_tree(d)
  end

  def test_detach_list_element
    member_one = c.member_inits[1]
    member_two = c.member_inits[2]
    d = c.member_inits[0].detach
    assert_tree(c)
    assert_tree(d)
    assert_same_list([member_one, member_two], c.member_inits)
  end

  def test_detach_list
    d = c.member_inits.detach
    assert_tree(c)
    assert_tree(d)
  end

  def test_node_replace_with
    i = C::Int.new
    t = c.type
    assert_same(c.type, c.type.replace_with(i))
    assert_tree(c)
    assert_tree(t)
    assert_same(i, c.type)

    assert_same(c.type, c.type.replace_with(nil))
    assert_tree(c)
    assert_nil(c.type)
  end
  def test_node_replace_with_none
    t = c.type
    assert_same(c.type, c.type.replace_with)
    assert_tree(c)
    assert_tree(t)
    assert_nil(c.type)
  end
  def test_node_replace_with_many
    mi = c.member_inits[0]
    mis = [c.member_inits[0], c.member_inits[1], c.member_inits[2]]

    mi1 = C::MemberInit.new
    mi1.init = C::IntLiteral.new(1)
    mi2 = C::MemberInit.new
    mi2.init = C::IntLiteral.new(2)

    assert_same(mi, mi.replace_with(mi1, mi2))
    assert_tree(c)
    assert_tree(mi)
    assert_same_list([mi1, mi2, mis[1], mis[2]], c.member_inits)

    assert_raises(C::Node::NoParent){mi.replace_with(nil)}
    i1 = C::Int.new
    i2 = C::Int.new
    assert_raises(ArgumentError){c.type.replace_with(i1, i2)}
  end

  def test_node_swap_with
    # swap with itself -- attached
    x = X.new
    parent = X.new(x)
    assert_same(x, x.swap_with(x))
    assert_same(parent, x.parent)
    assert_same(x, parent.a)

    # swap with itself -- detached
    x = X.new
    assert_same(x, x.swap_with(x))
    assert_nil(x.parent)

    # both attached
    x = X.new
    y = X.new
    xp = X.new(x)
    yp = X.new(y)
    assert_same(x, x.swap_with(y))
    assert_same(xp, y.parent)
    assert_same(x, yp.a)
    assert_same(yp, x.parent)
    assert_same(y, xp.a)

    # only receiver attached
    x = X.new
    y = X.new
    xp = X.new(x)
    assert_same(x, x.swap_with(y))
    assert_nil(x.parent)
    assert_same(xp, y.parent)
    assert_same(y, xp.a)

    # only arg attached
    x = X.new
    y = X.new
    yp = X.new(y)
    assert_same(x, x.swap_with(y))
    assert_same(yp, x.parent)
    assert_same(x, yp.a)
    assert_nil(y.parent)

    # neither attached
    x = X.new
    y = X.new
    assert_same(x, x.swap_with(y))
    assert_nil(x.parent)
    assert_nil(y.parent)
  end

  # ------------------------------------------------------------------
  #                      insert_next, insert_prev
  # ------------------------------------------------------------------

  def test_node_insert_next_detached
    x1, x2 = 2.of{X.new}
    assert_raises(C::Node::NoParent){x1.insert_next}
    assert_nil(x1.parent)
    assert_raises(C::Node::NoParent){x1.insert_next(x2)}
    assert_nil(x1.parent)
    assert_nil(x2.parent)
  end
  def test_node_insert_next_nonlist_parent
    parent = X.new
    x1, x2 = 2.of{X.new}
    parent.a = x1
    assert_raises(C::Node::BadParent){x1.insert_next}
    assert_same(parent, x1.parent)
    assert_raises(C::Node::BadParent){x1.insert_next(x2)}
    assert_same(parent, x1.parent)
    assert_nil(x2.parent)
  end
  def test_node_insert_next_none
    x1 = X.new
    parent = Chain[x1]
    assert_same(x1, x1.insert_next)
    assert_same_list([x1], parent)
  end
  def test_node_insert_next_many
    x1, x2, x3, x4 = 4.of{X.new}
    parent = Chain[x1]
    assert_same(x1, x1.insert_next(x2, x3, x4))
    assert_same_list([x1, x2, x3, x4], parent)
  end

  def test_node_insert_prev_detached
    a1, a2 = 2.of{X.new}
    assert_raises(C::Node::NoParent){a1.insert_prev}
    assert_nil(a1.parent)
    assert_raises(C::Node::NoParent){a1.insert_prev(a2)}
    assert_nil(a1.parent)
    assert_nil(a2.parent)
  end
  def test_node_insert_prev_nonlist_parent
    parent = X.new
    x1, x2 = 2.of{X.new}
    parent.a = x1
    assert_raises(C::Node::BadParent){x1.insert_prev}
    assert_same(parent, x1.parent)
    assert_raises(C::Node::BadParent){x1.insert_prev(x2)}
    assert_same(parent, x1.parent)
    assert_nil(x2.parent)
  end
  def test_node_insert_prev_none
    x1 = X.new
    parent = Chain[x1]
    assert_same(x1, x1.insert_prev)
    assert_same_list([x1], parent)
  end
  def test_node_insert_prev_many
    x1, x2, x3, x4 = 4.of{X.new}
    parent = Chain[x1]
    assert_same(x1, x1.insert_prev(x2, x3, x4))
    assert_same_list([x2, x3, x4, x1], parent)
  end

  # ------------------------------------------------------------------
  #                      node_after, node_before
  # ------------------------------------------------------------------

  def test_node_after_before
    # node not a child
    x1, x2 = 2.of{X.new}
    parent = X.new(x1)
    assert_raises(ArgumentError){parent.node_after(x2)}
    assert_raises(ArgumentError){parent.node_before(x2)}

    x1, x2 = 2.of{X.new}
    parent = Z.new(nil, x1, nil, x2, nil)
    assert_raises(ArgumentError){parent.node_after(x1)}
    assert_raises(ArgumentError){parent.node_after(x2)}
    assert_raises(ArgumentError){parent.node_before(x1)}
    assert_raises(ArgumentError){parent.node_before(x2)}

    # one child
    x = X.new
    parent = X.new(x)
    assert_nil(parent.node_after(x))
    assert_nil(parent.node_before(x))

    # two children
    x1 = X.new
    x2 = X.new
    parent = Y.new(x1, x2)
    assert_same(x2, parent.node_after(x1))
    assert_nil(parent.node_after(x2))
    assert_same(x1, parent.node_before(x2))
    assert_nil(parent.node_before(x1))

    # skip over stuff in the middle
    x1, x2, x3, x4, x5 = 5.of{X.new}
    parent = Z.new(x1, x2, nil, x4, x5)
    assert_same(x5, parent.node_after(x1))
    assert_nil(parent.node_after(x5))
    assert_same(x1, parent.node_before(x5))
    assert_nil(parent.node_before(x1))

    # skip over stuff at the end
    x1, x2, x3, x4, x5 = 5.of{X.new}
    parent = Z.new(nil, x2, x3, x4, nil)
    assert_nil(parent.node_after(x3))
    assert_nil(parent.node_before(x3))
  end

  def test_remove_node
    # node not a child
    x1, x2, x3 = 3.of{X.new}
    parent = Z.new(x1, x2)
    assert_raises(ArgumentError){parent.remove_node(x2)}
    assert_raises(ArgumentError){parent.remove_node(x3)}

    # one child
    x = X.new
    parent = X.new(x)
    assert_same(parent, parent.remove_node(x))
    assert_tree(parent)
    assert_tree(x)

    # two children
    x1, x2 = 2.of{X.new}
    parent = Y.new(x1, x2)
    assert_same(parent, parent.remove_node(x2))
    assert_tree(parent)
    assert_tree(x2)
    assert_same(x1, parent.a)
    assert_nil(parent.b)
  end

  def test_replace_node
    # node not a child
    x1, x2, x3, x4 = 3.of{X.new}
    parent = Z.new(x1, x2)
    assert_raises(ArgumentError){parent.replace_node(x2, x4)}
    assert_raises(ArgumentError){parent.replace_node(x3, x4)}

    # no newnode
    x = X.new
    parent = X.new(x)
    assert_same(parent, parent.replace_node(x))
    assert_tree(parent)
    assert_tree(x)
    assert_nil(parent.a)

    # >1 newnode
    x1, x2, x3 = 3.of{X.new}
    parent = X.new(x1)
    assert_raises(ArgumentError){parent.replace_node(x1, x2, x3)}

    # one child
    x1, x2 = 2.of{X.new}
    parent = X.new(x1)
    assert_same(parent, parent.replace_node(x1, x2))
    assert_tree(parent)
    assert_tree(x1)
    assert_same(x2, parent.a)
    #
    assert_same(parent, parent.replace_node(x2, nil))
    assert_tree(parent)
    assert_tree(x2)
    assert_nil(parent.a)

    # two children
    x1, x2, x3 = 3.of{X.new}
    parent = Y.new(x1, x2)
    assert_same(parent, parent.replace_node(x2, x3))
    assert_tree(parent)
    assert_tree(x2)
    assert_same(x3, parent.b)
    #
    assert_same(parent, parent.replace_node(x3, nil))
    assert_tree(parent)
    assert_tree(x3)
    assert_nil(parent.b)
  end
end
