# frozen_string_literal: true

require 'gir_ffi_test_helper'

GirFFI.setup :Regress
GirFFI.setup :Gio

describe GObject::Value do
  describe '::Struct' do
    describe 'layout' do
      let(:layout) { GObject::Value::Struct.layout }

      it 'consists of :g_type and :data' do
        layout.members.must_equal [:g_type, :data]
      end

      it 'has an array as its second element' do
        types = layout.fields.map(&:type)
        types[1].class.must_equal FFI::Type::Array
      end
    end
  end

  describe '.for_gtype' do
    it 'handles char' do
      gv = GObject::Value.for_gtype GObject::TYPE_CHAR
      gv.current_gtype.must_equal GObject::TYPE_CHAR
    end

    it 'handles invalid type' do
      gv = GObject::Value.for_gtype GObject::TYPE_INVALID
      gv.current_gtype.must_equal GObject::TYPE_INVALID
    end

    it 'handles void type' do
      gv = GObject::Value.for_gtype GObject::TYPE_NONE
      gv.current_gtype.must_equal GObject::TYPE_INVALID
    end
  end

  describe '::wrap_ruby_value' do
    it 'wraps a boolean false' do
      gv = GObject::Value.wrap_ruby_value false
      assert_instance_of GObject::Value, gv
      gv.get_boolean.must_equal false
    end

    it 'wraps a boolean true' do
      gv = GObject::Value.wrap_ruby_value true
      assert_instance_of GObject::Value, gv
      gv.get_boolean.must_equal true
    end

    it 'wraps an Integer' do
      gv = GObject::Value.wrap_ruby_value 42
      gv.get_int.must_equal 42
    end

    it 'wraps a String' do
      gv = GObject::Value.wrap_ruby_value 'Some Random String'
      gv.get_string.must_equal 'Some Random String'
    end

    it 'wraps nil' do
      gv = GObject::Value.wrap_ruby_value nil
      assert_instance_of GObject::Value, gv
      gv.get_value.must_be_nil
    end

    it 'wraps object values' do
      value = GObject::Object.new({})
      gv = GObject::Value.wrap_ruby_value value
      value.ref_count.must_equal 2
      gv.get_value.must_equal value
      value.ref_count.must_equal 3
    end
  end

  describe '#set_value' do
    it 'handles signed char values' do
      value = -83
      gv = GObject::Value.for_gtype GObject::TYPE_CHAR
      gv.set_value value
      gv.get_schar.must_equal value
    end

    it 'handles unsigned char values' do
      value = 174
      gv = GObject::Value.for_gtype GObject::TYPE_UCHAR
      gv.set_value value
      gv.get_uchar.must_equal value
    end

    it 'handles enum values presented as symbols' do
      value = :value2
      gv = GObject::Value.for_gtype Regress::TestEnum.gtype
      gv.set_value value
      gv.get_value.must_equal value
    end

    it 'handles enum values presented as numbers' do
      value = :value2
      gv = GObject::Value.for_gtype Regress::TestEnum.gtype
      gv.set_value Regress::TestEnum[value]
      gv.get_value.must_equal value
    end

    it 'handles GType values' do
      value = GObject::TYPE_STRING
      gv = GObject::Value.for_gtype GObject::TYPE_GTYPE
      gv.set_value value
      gv.get_gtype.must_equal value
    end

    it 'handles int64 values' do
      value = 0x1234_5678_9012_3456
      gv = GObject::Value.for_gtype GObject::TYPE_INT64
      gv.set_value value
      gv.get_int64.must_equal value
    end

    it 'handles long values' do
      value = FFI.type_size(:long) == 8 ? 0x1234_5678_9012_3456 : 0x1234_5678
      gv = GObject::Value.for_gtype GObject::TYPE_LONG
      gv.set_value value
      gv.get_long.must_equal value
    end

    it 'handles uchar values' do
      value = 83
      gv = GObject::Value.for_gtype GObject::TYPE_UCHAR
      gv.set_value value
      gv.get_uchar.must_equal value
    end

    it 'handles uint values' do
      value = 0x1234_5678
      gv = GObject::Value.for_gtype GObject::TYPE_UINT
      gv.set_value value
      gv.get_uint.must_equal value
    end

    it 'handles uint64 values' do
      value = 0x1234_5678_9012_3456
      gv = GObject::Value.for_gtype GObject::TYPE_UINT64
      gv.set_value value
      gv.get_uint64.must_equal value
    end

    it 'handles ulong values' do
      value = FFI.type_size(:long) == 8 ? 0x1234_5678_9012_3456 : 0x1234_5678
      gv = GObject::Value.for_gtype GObject::TYPE_ULONG
      gv.set_value value
      gv.get_ulong.must_equal value
    end

    it 'handles variant values' do
      value = GLib::Variant.new_string('Foo')
      gv = GObject::Value.for_gtype GObject::TYPE_VARIANT
      gv.set_value value
      gv.get_variant.must_equal value
    end

    it 'handles object values' do
      value = GObject::Object.new({})
      gv = GObject::Value.for_gtype GObject::Object.gtype
      gv.set_value value
      gv.get_object.must_equal value
    end

    it 'handles interface values' do
      value = Gio.file_new_for_path('/')
      gv = GObject::Value.for_gtype Gio::File.gtype
      gv.set_value value
      gv.get_object.must_equal value
    end
  end

  describe '#get_value' do
    it 'unwraps a boolean false' do
      gv = GObject::Value.wrap_ruby_value false
      result = gv.get_value
      result.must_equal false
    end

    it 'unwraps a boolean true' do
      gv = GObject::Value.wrap_ruby_value true
      result = gv.get_value
      result.must_equal true
    end

    it 'unwraps a signed char' do
      value = -42
      gv = GObject::Value.for_gtype GObject::TYPE_CHAR
      gv.set_schar value
      gv.get_value.must_equal value
    end

    it 'unwraps an unsigned char' do
      value = 173
      gv = GObject::Value.for_gtype GObject::TYPE_UCHAR
      gv.set_uchar value
      gv.get_value.must_equal value
    end

    it 'unwraps an enum value' do
      value = :value2
      gv = GObject::Value.for_gtype Regress::TestEnum.gtype
      gv.set_enum Regress::TestEnum[value]
      gv.get_value.must_equal value
    end

    it 'unwraps a flags value' do
      value = Regress::TestFlags[:flag1] | Regress::TestFlags[:flag3]
      gv = GObject::Value.for_gtype Regress::TestFlags.gtype
      gv.set_flags value
      gv.get_value.must_equal value
    end

    it 'unwraps a GType' do
      value = GObject::TYPE_STRING
      gv = GObject::Value.for_gtype GObject::TYPE_GTYPE
      gv.set_gtype value
      gv.get_value.must_equal value
    end

    it 'unwraps an int64' do
      value = 0x1234_5678_9012_3456
      gv = GObject::Value.for_gtype GObject::TYPE_INT64
      gv.set_int64 value
      gv.get_value.must_equal value
    end

    it 'unwraps a long' do
      value = FFI.type_size(:long) == 8 ? 0x1234_5678_9012_3456 : 0x1234_5678
      gv = GObject::Value.for_gtype GObject::TYPE_LONG
      gv.set_long value
      gv.get_value.must_equal value
    end

    it 'unwraps an uchar' do
      value = 3
      gv = GObject::Value.for_gtype GObject::TYPE_UCHAR
      gv.set_uchar value
      gv.get_value.must_equal value
    end

    it 'unwraps an uint' do
      value = 0x1234_5678
      gv = GObject::Value.for_gtype GObject::TYPE_UINT
      gv.set_uint value
      gv.get_value.must_equal value
    end

    it 'unwraps an uint64' do
      value = 0x1234_5678_9012_3456
      gv = GObject::Value.for_gtype GObject::TYPE_UINT64
      gv.set_uint64 value
      gv.get_value.must_equal value
    end

    it 'unwraps an ulong' do
      value = FFI.type_size(:long) == 8 ? 0x1234_5678_9012_3456 : 0x1234_5678
      gv = GObject::Value.for_gtype GObject::TYPE_ULONG
      gv.set_ulong value
      gv.get_value.must_equal value
    end

    it 'unwraps a variant' do
      value = GLib::Variant.new_string('Foo')
      gv = GObject::Value.for_gtype GObject::TYPE_VARIANT
      gv.set_variant value
      gv.get_value.must_equal value
    end

    it 'works with a ByteArray' do
      ba = GLib::ByteArray.new.append('some bytes')
      v = GObject::Value.for_gtype GObject::TYPE_BYTE_ARRAY
      v.set_boxed ba

      result = v.get_value

      result.to_string.must_equal 'some bytes'
      result.must_be_kind_of GLib::ByteArray
    end

    it 'works with an Array' do
      arr = GLib::Array.from(:uint, [1, 2, 3])
      v = GObject::Value.for_gtype GObject::TYPE_ARRAY
      v.set_boxed arr

      result = v.get_value

      result.must_be_kind_of GLib::Array
      result.reset_typespec(:uint).to_a.must_equal [1, 2, 3]
    end

    it 'unwraps a Strv' do
      strv = GLib::Strv.from %w(foo bar)
      val = GObject::Value.for_gtype GObject::TYPE_STRV
      val.set_boxed strv

      result = val.get_value

      result.must_be_kind_of GLib::Strv
      result.to_a.must_equal %w(foo bar)
    end
  end

  describe '::from' do
    it 'creates a gint GValue from a Ruby Integer' do
      gv = GObject::Value.from 21
      gv.current_gtype_name.must_equal 'gint'
      gv.get_value.must_equal 21
    end

    it 'returns its argument if given a GValue' do
      gv = GObject::Value.from 21
      gv2 = GObject::Value.from gv
      gv2.current_gtype_name.must_equal 'gint'
      gv2.get_value.must_equal 21
    end

    it 'creates a null GValue from a Ruby nil' do
      gv = GObject::Value.from nil
      gv.current_gtype.must_equal GObject::TYPE_INVALID
      gv.get_value.must_be_nil
    end
  end

  describe '#set_value' do
    before do
      GirFFI.setup :GIMarshallingTests
    end

    it 'raises an error when setting an incompatible object type' do
      o = GIMarshallingTests::Object.new 1
      v = GObject::Value.new.init(GIMarshallingTests::OverridesObject.gtype)
      proc { v.set_value o }.must_raise ArgumentError
    end

    it 'works with a ByteArray' do
      ba = GLib::ByteArray.new.append('some bytes')
      v = GObject::Value.new.init(GObject.type_from_name('GByteArray'))
      v.set_value ba
      v.get_value.to_string.must_equal 'some bytes'
    end
  end

  describe 'upon garbage collection' do
    it 'restores the underlying GValue to its pristine state' do
      if jruby? || rubinius?
        skip 'cannot be reliably tested on JRuby and Rubinius'
      end

      value = GObject::Value.from 42

      # Drop reference to original GObject::Value
      value = GObject::Value.wrap value.to_ptr
      value.current_gtype_name.must_equal 'gint'

      GC.start
      # Creating a new object is sometimes needed to trigger enough garbage collection.
      GObject::Value.new
      sleep 1
      GC.start
      GC.start

      value.current_gtype_name.wont_equal 'gint'
    end
  end
end
