require 'helper'
require 'set'

module Arel
  module Visitors
    class TestDepthFirst < Minitest::Test
      Collector = Struct.new(:calls) do
        def call object
          calls << object
        end
      end

      def setup
        @collector = Collector.new []
        @visitor = Visitors::DepthFirst.new @collector
      end

      def test_raises_with_object
        assert_raises(TypeError) do
          @visitor.accept(Object.new)
        end
      end


      # unary ops
      [
        Arel::Nodes::Not,
        Arel::Nodes::Group,
        Arel::Nodes::On,
        Arel::Nodes::Grouping,
        Arel::Nodes::Offset,
        Arel::Nodes::Ordering,
        Arel::Nodes::Having,
        Arel::Nodes::StringJoin,
        Arel::Nodes::UnqualifiedColumn,
        Arel::Nodes::Top,
        Arel::Nodes::Limit,
      ].each do |klass|
        define_method("test_#{klass.name.gsub('::', '_')}") do
          op = klass.new(:a)
          @visitor.accept op
          assert_equal [:a, op], @collector.calls
        end
      end

      # functions
      [
        Arel::Nodes::Exists,
        Arel::Nodes::Avg,
        Arel::Nodes::Min,
        Arel::Nodes::Max,
        Arel::Nodes::Sum,
      ].each do |klass|
        define_method("test_#{klass.name.gsub('::', '_')}") do
          func = klass.new(:a, "b")
          @visitor.accept func
          assert_equal [:a, "b", false, func], @collector.calls
        end
      end

      def test_named_function
        func = Arel::Nodes::NamedFunction.new(:a, :b, "c")
        @visitor.accept func
        assert_equal [:a, :b, false, "c", func], @collector.calls
      end

      def test_lock
        lock = Nodes::Lock.new true
        @visitor.accept lock
        assert_equal [lock], @collector.calls
      end

      def test_count
        count = Nodes::Count.new :a, :b, "c"
        @visitor.accept count
        assert_equal [:a, "c", :b, count], @collector.calls
      end

      def test_inner_join
        join = Nodes::InnerJoin.new :a, :b
        @visitor.accept join
        assert_equal [:a, :b, join], @collector.calls
      end

      def test_full_outer_join
        join = Nodes::FullOuterJoin.new :a, :b
        @visitor.accept join
        assert_equal [:a, :b, join], @collector.calls
      end

      def test_outer_join
        join = Nodes::OuterJoin.new :a, :b
        @visitor.accept join
        assert_equal [:a, :b, join], @collector.calls
      end

      def test_right_outer_join
        join = Nodes::RightOuterJoin.new :a, :b
        @visitor.accept join
        assert_equal [:a, :b, join], @collector.calls
      end

      [
        Arel::Nodes::Assignment,
        Arel::Nodes::Between,
        Arel::Nodes::DoesNotMatch,
        Arel::Nodes::Equality,
        Arel::Nodes::GreaterThan,
        Arel::Nodes::GreaterThanOrEqual,
        Arel::Nodes::In,
        Arel::Nodes::LessThan,
        Arel::Nodes::LessThanOrEqual,
        Arel::Nodes::Matches,
        Arel::Nodes::NotEqual,
        Arel::Nodes::NotIn,
        Arel::Nodes::Or,
        Arel::Nodes::TableAlias,
        Arel::Nodes::Values,
        Arel::Nodes::As,
        Arel::Nodes::DeleteStatement,
        Arel::Nodes::JoinSource,
      ].each do |klass|
        define_method("test_#{klass.name.gsub('::', '_')}") do
          binary = klass.new(:a, :b)
          @visitor.accept binary
          assert_equal [:a, :b, binary], @collector.calls
        end
      end

      def test_Arel_Nodes_InfixOperation
        binary = Arel::Nodes::InfixOperation.new(:o, :a, :b)
        @visitor.accept binary
        assert_equal [:a, :b, binary], @collector.calls
      end

      # N-ary
      [
        Arel::Nodes::And,
      ].each do |klass|
        define_method("test_#{klass.name.gsub('::', '_')}") do
          binary = klass.new([:a, :b, :c])
          @visitor.accept binary
          assert_equal [:a, :b, :c, binary], @collector.calls
        end
      end

      [
        Arel::Attributes::Integer,
        Arel::Attributes::Float,
        Arel::Attributes::String,
        Arel::Attributes::Time,
        Arel::Attributes::Boolean,
        Arel::Attributes::Attribute
      ].each do |klass|
        define_method("test_#{klass.name.gsub('::', '_')}") do
          binary = klass.new(:a, :b)
          @visitor.accept binary
          assert_equal [:a, :b, binary], @collector.calls
        end
      end

      def test_table
        relation = Arel::Table.new(:users)
        @visitor.accept relation
        assert_equal ['users', relation], @collector.calls
      end

      def test_array
        node = Nodes::Or.new(:a, :b)
        list = [node]
        @visitor.accept list
        assert_equal [:a, :b, node, list], @collector.calls
      end

      def test_set
        node = Nodes::Or.new(:a, :b)
        set  = Set.new([node])
        @visitor.accept set
        assert_equal [:a, :b, node, set], @collector.calls
      end

      def test_hash
        node = Nodes::Or.new(:a, :b)
        hash = { node => node }
        @visitor.accept hash
        assert_equal [:a, :b, node, :a, :b, node, hash], @collector.calls
      end

      def test_update_statement
        stmt = Nodes::UpdateStatement.new
        stmt.relation = :a
        stmt.values << :b
        stmt.wheres << :c
        stmt.orders << :d
        stmt.limit = :e

        @visitor.accept stmt
        assert_equal [:a, :b, stmt.values, :c, stmt.wheres, :d, stmt.orders,
          :e, stmt], @collector.calls
      end

      def test_select_core
        core = Nodes::SelectCore.new
        core.projections << :a
        core.froms = :b
        core.wheres << :c
        core.groups << :d
        core.windows << :e
        core.having = :f

        @visitor.accept core
        assert_equal [
          :a, core.projections,
          :b, [],
          core.source,
          :c, core.wheres,
          :d, core.groups,
          :e, core.windows,
          :f,
          core], @collector.calls
      end

      def test_select_statement
        ss = Nodes::SelectStatement.new
        ss.cores.replace [:a]
        ss.orders << :b
        ss.limit = :c
        ss.lock = :d
        ss.offset = :e

        @visitor.accept ss
        assert_equal [
          :a, ss.cores,
          :b, ss.orders,
          :c,
          :d,
          :e,
          ss], @collector.calls
      end

      def test_insert_statement
        stmt = Nodes::InsertStatement.new
        stmt.relation = :a
        stmt.columns << :b
        stmt.values = :c

        @visitor.accept stmt
        assert_equal [:a, :b, stmt.columns, :c, stmt], @collector.calls
      end

      def test_node
        node = Nodes::Node.new
        @visitor.accept node
        assert_equal [node], @collector.calls
      end
    end
  end
end
