require_relative '../helper'
require 'fluent/config/element'
require 'fluent/config/configure_proxy'
require 'fluent/configurable'
require 'pp'

class TestConfigElement < ::Test::Unit::TestCase
  def element(name = 'ROOT', arg = '', attrs = {}, elements = [], unused = nil)
    Fluent::Config::Element.new(name, arg, attrs, elements, unused)
  end

  sub_test_case '#elements=' do
    test 'elements can be set by others' do
      e = element()
      assert_equal [], e.elements

      e1 = element('e1')
      e2 = element('e2')
      e.elements = [e1, e2]
      assert_equal [e1, e2], e.elements
    end
  end

  sub_test_case '#elements' do
    setup do
      @c1 = element('source')
      @c2 = element('source', 'yay')
      @c3 = element('match', 'test.**')
      @c4 = element('label', '@mytest', {}, [ element('filter', '**'), element('match', '**') ])
      children = [@c1, @c2, @c3, @c4]
      @e = Fluent::Config::Element.new('ROOT', '', {}, children)
    end

    test 'returns all elements without arguments' do
      assert_equal [@c1, @c2, @c3, @c4], @e.elements
    end

    test 'returns elements with specified names' do
      assert_equal [@c1, @c2, @c3], @e.elements('source', 'match')
      assert_equal [@c3, @c4], @e.elements('match', 'label')
    end

    test 'returns elements with specified name, and arg if specified' do
      assert_equal [@c1, @c2], @e.elements(name: 'source')
      assert_equal [@c2], @e.elements(name: 'source', arg: 'yay')
    end

    test 'keyword argument name/arg and names are exclusive' do
      assert_raise ArgumentError do
        @e.elements('source', name: 'match')
      end
      assert_raise ArgumentError do
        @e.elements('source', 'match', name: 'label', arg: '@mytest')
      end
    end

    test 'specifying only arg without name is invalid' do
      assert_raise ArgumentError do
        @e.elements(arg: '@mytest')
      end
    end
  end

  sub_test_case '#initialize' do
    test 'creates object with blank attrs and elements' do
      e = element('ROOT', '', {}, [])
      assert_equal([], e.elements)
    end

    test 'creates object which contains attrs and elements' do
      e = element('ROOT', '', {"k1" => "v1", "k2" => "v2"}, [
                    element('test', 'mydata', {'k3' => 'v3'}, [])
                  ])
      assert_equal('ROOT', e.name)
      assert_equal('',  e.arg)
      assert_equal('v1', e["k1"])
      assert_equal('v2', e["k2"])
      assert_equal(1, e.elements.size)
      e.each_element('test') do |el|
        assert_equal('test', el.name)
        assert_equal('mydata', el.arg)
        assert_equal('v3', el["k3"])
      end
    end

    test 'creates object which contains attrs, elements and unused' do
      e = element('ROOT', '', {"k1" => "v1", "k2" => "v2", "k4" => "v4"}, [
                    element('test', 'mydata', {'k3' => 'v3'}, [])
                  ], "k3")
      assert_equal("k3", e.unused)
      assert_equal('ROOT', e.name)
      assert_equal('',  e.arg)
      assert_equal('v1', e["k1"])
      assert_equal('v2', e["k2"])
      assert_equal('v4', e["k4"])
      assert_equal(1, e.elements.size)
      e.each_element('test') do |el|
        assert_equal('test', el.name)
        assert_equal('mydata', el.arg)
        assert_equal('v3', el["k3"])
      end
      assert_equal("k3", e.unused)
    end
  end

  sub_test_case "@unused" do
    sub_test_case '#[] has side effect for @unused' do
      test 'without unused argument' do
        e = element('ROOT', '', {"k1" => "v1", "k2" => "v2", "k4" => "v4"}, [
                      element('test', 'mydata', {'k3' => 'v3'}, [])
                    ])
        assert_equal(["k1", "k2", "k4"], e.unused)
        assert_equal('v1', e["k1"])
        assert_equal(["k2", "k4"], e.unused)
        assert_equal('v2', e["k2"])
        assert_equal(["k4"], e.unused)
        assert_equal('v4', e["k4"])
        assert_equal([], e.unused)
      end

      test 'with unused argument' do
        e = element('ROOT', '', {"k1" => "v1", "k2" => "v2", "k4" => "v4"}, [
                      element('test', 'mydata', {'k3' => 'v3'}, [])
                    ], ["k4"])
        assert_equal(["k4"], e.unused)
        assert_equal('v1', e["k1"])
        assert_equal(["k4"], e.unused)
        assert_equal('v2', e["k2"])
        assert_equal(["k4"], e.unused)
        assert_equal('v4', e["k4"])
        # only consume for "k4"
        assert_equal([], e.unused)
      end
    end
  end

  sub_test_case '#add_element' do
    test 'elements can be set by #add_element' do
      e = element()
      assert_equal [], e.elements

      e.add_element('e1', '')
      e.add_element('e2', '')
      assert_equal [element('e1', ''), element('e2', '')], e.elements
    end
  end

  sub_test_case '#==' do
    sub_test_case 'compare with two element objects' do
      test 'equal' do
        e1 = element('ROOT', '', {}, [])
        e2 = element('ROOT', '', {}, [])
        assert_true(e1 == e2)
      end

      data("differ args" => [Fluent::Config::Element.new('ROOT', '', {}, []),
                             Fluent::Config::Element.new('ROOT', 'mydata', {}, [])],
           "differ keys" => [Fluent::Config::Element.new('ROOT', 'mydata', {}, []),
                             Fluent::Config::Element.new('ROOT', 'mydata', {"k1" => "v1"}, [])],
           "differ elements" =>
           [Fluent::Config::Element.new('ROOT', 'mydata', {"k1" => "v1"}, []),
            Fluent::Config::Element.new('ROOT', 'mydata', {"k1" => "v1"}, [
              Fluent::Config::Element.new('test', 'mydata', {'k3' => 'v3'}, [])
            ])])
      test 'not equal' do |data|
        e1, e2 = data
        assert_false(e1 == e2)
      end
    end
  end

  sub_test_case '#+' do
    test 'can merge 2 elements: object side is primary' do
      e1 = element('ROOT', 'mydata', {"k1" => "v1"}, [])
      e2 = element('ROOT', 'mydata2', {"k1" => "ignored", "k2" => "v2"}, [
                     element('test', 'ext', {'k3' => 'v3'}, [])
                   ])
      e = e1 + e2

      assert_equal('ROOT', e.name)
      assert_equal('mydata', e.arg)
      assert_equal('v1', e['k1'])
      assert_equal('v2', e['k2'])
      assert_equal(1, e.elements.size)
      e.each_element('test') do |el|
        assert_equal('test', el.name)
        assert_equal('ext', el.arg)
        assert_equal('v3', el["k3"])
      end
    end
  end

  sub_test_case '#check_not_fetched' do
    sub_test_case 'without unused' do
      test 'can get attribute keys and original Config::Element' do
        e = element('ROOT', 'mydata', {"k1" => "v1"}, [])
        e.check_not_fetched { |key, elem|
          assert_equal("k1", key)
          assert_equal(e, elem)
        }
      end
    end

    sub_test_case 'with unused' do
      test 'can get unused marked attribute keys and original Config::Element' do
        e = element('ROOT', 'mydata', {"k1" => "v1", "k2" => "unused", "k3" => "k3"})
        e.unused = "k2"
        e.check_not_fetched { |key, elem|
          assert_equal("k2", key)
          assert_equal(e, elem)
        }
      end
    end
  end

  sub_test_case '#has_key?' do
    test 'can get boolean with key name' do
      e = element('ROOT', 'mydata', {"k1" => "v1"}, [])
      assert_true(e.has_key?("k1"))
      assert_false(e.has_key?("noexistent"))
    end
  end


  sub_test_case '#to_s' do
    data("without v1_config" => [false, <<-CONF
<ROOT>
  k1 v1
  k2 "stringVal"
  <test ext>
    k2 v2
  </test>
</ROOT>
CONF
                                ],
         "with v1_config" => [true, <<-CONF
<ROOT>
  k1 v1
  k2 "stringVal"
  <test ext>
    k2 v2
  </test>
</ROOT>
CONF
                             ],
        )
    test 'dump config element with #to_s' do |data|
      v1_config, expected = data
      e = element('ROOT', '', {'k1' => 'v1', "k2" =>"\"stringVal\""}, [
                    element('test', 'ext', {'k2' => 'v2'}, [])
                  ])
      e.v1_config = v1_config
      dump = expected
      assert_not_equal(e.inspect, e.to_s)
      assert_equal(dump, e.to_s)
    end
  end

  sub_test_case '#inspect' do
    test 'dump config element with #inspect' do
      e = element('ROOT', '', {'k1' => 'v1'}, [
                   element('test', 'ext', {'k2' => 'v2'}, [])
                  ])
      dump = <<-CONF
name:ROOT, arg:, {\"k1\"=>\"v1\"}, [name:test, arg:ext, {\"k2\"=>\"v2\"}, []]
CONF
      assert_not_equal(e.to_s, e.inspect)
      assert_equal(dump.chomp, e.inspect)
    end
  end

  sub_test_case 'for sections which has secret parameter' do
    setup do
      @type_lookup = ->(type){ Fluent::Configurable.lookup_type(type) }
      p1 = Fluent::Config::ConfigureProxy.new(:match, type_lookup: @type_lookup)
      p1.config_param :str1, :string
      p1.config_param :str2, :string, secret: true
      p1.config_param :enum1, :enum, list: [:a, :b, :c]
      p1.config_param :enum2, :enum, list: [:a, :b, :c], secret: true
      p1.config_param :bool1, :bool
      p1.config_param :bool2, :bool, secret: true
      p1.config_param :int1, :integer
      p1.config_param :int2, :integer, secret: true
      p1.config_param :float1, :float
      p1.config_param :float2, :float, secret: true
      p2 = Fluent::Config::ConfigureProxy.new(:match, type_lookup: @type_lookup)
      p2.config_param :size1, :size
      p2.config_param :size2, :size, secret: true
      p2.config_param :time1, :time
      p2.config_param :time2, :time, secret: true
      p2.config_param :array1, :array
      p2.config_param :array2, :array, secret: true
      p2.config_param :hash1, :hash
      p2.config_param :hash2, :hash, secret: true
      p1.config_section :mysection do
        config_param :str1, :string
        config_param :str2, :string, secret: true
        config_param :enum1, :enum, list: [:a, :b, :c]
        config_param :enum2, :enum, list: [:a, :b, :c], secret: true
        config_param :bool1, :bool
        config_param :bool2, :bool, secret: true
        config_param :int1, :integer
        config_param :int2, :integer, secret: true
        config_param :float1, :float
        config_param :float2, :float, secret: true
        config_param :size1, :size
        config_param :size2, :size, secret: true
        config_param :time1, :time
        config_param :time2, :time, secret: true
        config_param :array1, :array
        config_param :array2, :array, secret: true
        config_param :hash1, :hash
        config_param :hash2, :hash, secret: true
      end
      params = {
        'str1' => 'aaa', 'str2' => 'bbb', 'enum1' => 'a', 'enum2' => 'b', 'bool1' => 'true', 'bool2' => 'yes',
        'int1' => '1', 'int2' => '2', 'float1' => '1.0', 'float2' => '0.5', 'size1' => '1k', 'size2' => '1m',
        'time1' => '5m', 'time2' => '3h', 'array1' => 'a,b,c', 'array2' => 'd,e,f',
        'hash1' => 'a:1,b:2', 'hash2' => 'a:2,b:4',
        'unknown1' => 'yay', 'unknown2' => 'boo',
      }
      e2 = Fluent::Config::Element.new('mysection', '', params.dup, [])
      e2.corresponding_proxies << p1.sections.values.first
      @e = Fluent::Config::Element.new('match', '**', params, [e2])
      @e.corresponding_proxies << p1
      @e.corresponding_proxies << p2
    end

    sub_test_case '#to_masked_element' do
      test 'returns a new element object which has masked values for secret parameters and elements' do
        e = @e.to_masked_element
        assert_equal 'aaa',    e['str1']
        assert_equal 'xxxxxx', e['str2']
        assert_equal 'a',      e['enum1']
        assert_equal 'xxxxxx', e['enum2']
        assert_equal 'true',   e['bool1']
        assert_equal 'xxxxxx', e['bool2']
        assert_equal '1',      e['int1']
        assert_equal 'xxxxxx', e['int2']
        assert_equal '1.0',    e['float1']
        assert_equal 'xxxxxx', e['float2']
        assert_equal '1k',     e['size1']
        assert_equal 'xxxxxx', e['size2']
        assert_equal '5m',     e['time1']
        assert_equal 'xxxxxx', e['time2']
        assert_equal 'a,b,c',  e['array1']
        assert_equal 'xxxxxx', e['array2']
        assert_equal 'a:1,b:2', e['hash1']
        assert_equal 'xxxxxx',  e['hash2']
        assert_equal 'yay', e['unknown1']
        assert_equal 'boo', e['unknown2']
        e2 = e.elements.first
        assert_equal 'aaa',    e2['str1']
        assert_equal 'xxxxxx', e2['str2']
        assert_equal 'a',      e2['enum1']
        assert_equal 'xxxxxx', e2['enum2']
        assert_equal 'true',   e2['bool1']
        assert_equal 'xxxxxx', e2['bool2']
        assert_equal '1',      e2['int1']
        assert_equal 'xxxxxx', e2['int2']
        assert_equal '1.0',    e2['float1']
        assert_equal 'xxxxxx', e2['float2']
        assert_equal '1k',     e2['size1']
        assert_equal 'xxxxxx', e2['size2']
        assert_equal '5m',     e2['time1']
        assert_equal 'xxxxxx', e2['time2']
        assert_equal 'a,b,c',  e2['array1']
        assert_equal 'xxxxxx', e2['array2']
        assert_equal 'a:1,b:2', e2['hash1']
        assert_equal 'xxxxxx',  e2['hash2']
        assert_equal 'yay', e2['unknown1']
        assert_equal 'boo', e2['unknown2']
      end
    end

    sub_test_case '#secret_param?' do
      test 'returns boolean which shows values of given key will be masked' do
        assert !@e.secret_param?('str1')
        assert @e.secret_param?('str2')
        assert !@e.elements.first.secret_param?('str1')
        assert @e.elements.first.secret_param?('str2')
      end
    end

    sub_test_case '#param_type' do
      test 'returns parameter type which are registered in corresponding proxy' do
        assert_equal :string, @e.param_type('str1')
        assert_equal :string, @e.param_type('str2')
        assert_equal :enum, @e.param_type('enum1')
        assert_equal :enum, @e.param_type('enum2')
        assert_nil @e.param_type('unknown1')
        assert_nil @e.param_type('unknown2')
      end
    end

    # sub_test_case '#dump_value'
    sub_test_case '#dump_value' do
      test 'dumps parameter_name and values with leading indentation' do
        assert_equal "str1 aaa\n", @e.dump_value("str1", @e["str1"], "")
        assert_equal "str2 xxxxxx\n", @e.dump_value("str2", @e["str2"], "")
      end
    end
  end

  sub_test_case '#set_target_worker' do
    test 'set target_worker_id recursively' do
      e = element('label', '@mytest', {}, [ element('filter', '**'), element('match', '**', {}, [ element('store'), element('store') ]) ])
      e.set_target_worker_id(1)
      assert_equal [1], e.target_worker_ids
      assert_equal [1], e.elements[0].target_worker_ids
      assert_equal [1], e.elements[1].target_worker_ids
      assert_equal [1], e.elements[1].elements[0].target_worker_ids
      assert_equal [1], e.elements[1].elements[1].target_worker_ids
    end
  end

  sub_test_case '#for_every_workers?' do
    test 'has target_worker_id' do
      e = element()
      e.set_target_worker_id(1)
      assert_false e.for_every_workers?
    end

    test "doesn't have target_worker_id" do
      e = element()
      assert e.for_every_workers?
    end
  end

  sub_test_case '#for_this_workers?' do
    test 'target_worker_id == current worker_id' do
      e = element()
      e.set_target_worker_id(0)
      assert e.for_this_worker?
    end

    test 'target_worker_ids includes current worker_id' do
      e = element()
      e.set_target_worker_ids([0])
      assert e.for_this_worker?
    end

    test 'target_worker_id != current worker_id' do
      e = element()
      e.set_target_worker_id(1)
      assert_false e.for_this_worker?
    end

    test 'target_worker_ids does not includes current worker_id' do
      e = element()
      e.set_target_worker_ids([1, 2])
      assert_false e.for_this_worker?
    end

    test "doesn't have target_worker_id" do
      e = element()
      assert_false e.for_this_worker?
    end
  end

  sub_test_case '#for_another_worker?' do
    test 'target_worker_id == current worker_id' do
      e = element()
      e.set_target_worker_id(0)
      assert_false e.for_another_worker?
    end

    test 'target_worker_ids contains current worker_id' do
      e = element()
      e.set_target_worker_ids([0, 1])
      assert_false e.for_another_worker?
    end

    test 'target_worker_id != current worker_id' do
      e = element()
      e.set_target_worker_id(1)
      assert e.for_another_worker?
    end

    test 'target_worker_ids does not contains current worker_id' do
      e = element()
      e.set_target_worker_ids([1, 2])
      assert e.for_another_worker?
    end

    test "doesn't have target_worker_id" do
      e = element()
      assert_false e.for_another_worker?
    end
  end

  sub_test_case '#pretty_print' do
    test 'prints inspect to pp object' do
      q = PP.new
      e = element()
      e.pretty_print(q)
      assert_equal e.inspect, q.output
    end
  end
end
