# 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
      _(object_ref_count(value)).must_equal 2
      _(gv.get_value).must_equal value
      _(object_ref_count(value)).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 flag values presented as hashes" do
      value = { flag2: true }
      gv = GObject::Value.for_gtype Regress::TestFlags.gtype
      gv.set_value value
      _(gv.get_value).must_equal value
    end

    it "handles flag values presented as symbols" do
      gv = GObject::Value.for_gtype Regress::TestFlags.gtype
      gv.set_value :flag2
      _(gv.get_value).must_equal(flag2: true)
    end

    it "handles flag values presented as numbers" do
      value = { flag2: true }
      gv = GObject::Value.for_gtype Regress::TestFlags.gtype
      gv.set_value Regress::TestFlags.to_native(value, nil)
      _(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 = { flag1: true, flag3: true }
      gv = GObject::Value.for_gtype Regress::TestFlags.gtype
      gv.set_flags Regress::TestFlags.to_native(value, nil)
      _(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
      skip "cannot be reliably tested on JRuby and Rubinius" if jruby? || rubinius?

      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
