# frozen_string_literal: true

require 'gir_ffi_test_helper'

describe GirFFI::SizedArray do
  describe '::wrap' do
    it 'takes a type, size and pointer and returns a GirFFI::SizedArray wrapping them' do
      expect(ptr = Object.new).to receive(:null?).and_return false
      sarr = GirFFI::SizedArray.wrap :gint32, 3, ptr
      assert_instance_of GirFFI::SizedArray, sarr
      assert_equal ptr, sarr.to_ptr
      assert_equal 3, sarr.size
      assert_equal :gint32, sarr.element_type
    end

    it 'returns nil if the wrapped pointer is null' do
      expect(ptr = Object.new).to receive(:null?).and_return true
      sarr = GirFFI::SizedArray.wrap :gint32, 3, ptr
      sarr.must_be_nil
    end
  end

  describe '#each' do
    it 'yields each element' do
      ary = %w(one two three)
      ptrs = ary.map { |a| FFI::MemoryPointer.from_string(a) }
      ptrs << nil
      block = FFI::MemoryPointer.new(:pointer, ptrs.length)
      block.write_array_of_pointer ptrs

      sarr = GirFFI::SizedArray.new :utf8, 3, block
      arr = []
      sarr.each do |str|
        arr << str
      end
      assert_equal %w(one two three), arr
    end
  end

  describe '::from' do
    describe 'from a Ruby array' do
      it 'creates a GirFFI::SizedArray with the same elements' do
        arr = GirFFI::SizedArray.from :gint32, 3, [3, 2, 1]
        arr.must_be_instance_of GirFFI::SizedArray
        assert_equal [3, 2, 1], arr.to_a
      end

      it 'raises an error if the array has the wrong number of elements' do
        proc { GirFFI::SizedArray.from :gint32, 4, [3, 2, 1] }.must_raise ArgumentError
      end

      it "uses the array's size if passed -1 as the size" do
        arr = GirFFI::SizedArray.from :gint32, -1, [3, 2, 1]
        arr.size.must_equal 3
      end
    end

    describe 'from a GirFFI::SizedArray' do
      it 'return its argument' do
        arr = GirFFI::SizedArray.from :gint32, 3, [3, 2, 1]
        arr2 = GirFFI::SizedArray.from :gint32, 3, arr
        assert_equal arr, arr2
      end

      it 'raises an error if the argument has the wrong number of elements' do
        arr = GirFFI::SizedArray.from :gint32, 3, [3, 2, 1]
        proc { GirFFI::SizedArray.from :gint32, 4, arr }.must_raise ArgumentError
      end
    end

    it 'returns nil when passed nil' do
      arr = GirFFI::SizedArray.from :gint32, 0, nil
      arr.must_be_nil
    end

    it 'wraps its argument if given a pointer' do
      arr = GirFFI::SizedArray.from :gint32, 3, [3, 2, 1]
      arr2 = GirFFI::SizedArray.from :gint32, 3, arr.to_ptr
      assert_instance_of GirFFI::SizedArray, arr2
      refute arr2.equal? arr
      arr2.to_ptr.must_equal arr.to_ptr
    end
  end

  describe '.copy_from' do
    describe 'from a Ruby array' do
      it 'creates an unowned GirFFI::SizedArray with the same elements' do
        arr = GirFFI::SizedArray.copy_from :gint32, 3, [3, 2, 1]
        arr.must_be_instance_of GirFFI::SizedArray
        assert_equal [3, 2, 1], arr.to_a
        arr.to_ptr.wont_be :autorelease?
      end

      it 'creates unowned copies of struct pointer elements' do
        struct = GIMarshallingTests::BoxedStruct.new
        struct.long_ = 2342
        struct.struct.must_be :owned?

        arr = GirFFI::SizedArray.copy_from([:pointer, GIMarshallingTests::BoxedStruct],
                                           1,
                                           [struct])
        arr.must_be_instance_of GirFFI::SizedArray
        arr.to_ptr.wont_be :autorelease?

        struct_copy = arr.first
        struct_copy.long_.must_equal struct.long_
        struct_copy.to_ptr.wont_be :==, struct.to_ptr
        struct_copy.struct.wont_be :owned?
      end

      it 'increases the ref count of object pointer elements' do
        obj = GIMarshallingTests::Object.new 42
        arr = GirFFI::SizedArray.copy_from([:pointer, GIMarshallingTests::Object],
                                           -1,
                                           [obj, nil])
        arr.must_be_instance_of GirFFI::SizedArray
        arr.to_ptr.wont_be :autorelease?

        arr.to_a.must_equal [obj, nil]
        obj.ref_count.must_equal 2
      end
    end

    describe 'from a GirFFI::SizedArray' do
      it 'return an unowned copy of its argument' do
        arr = GirFFI::SizedArray.from :gint32, 3, [3, 2, 1]
        arr2 = GirFFI::SizedArray.copy_from :gint32, 3, arr
        arr.to_ptr.wont_be :==, arr2.to_ptr
        arr2.to_a.must_equal [3, 2, 1]
        arr2.to_ptr.wont_be :autorelease?
      end
    end

    it 'returns nil when passed nil' do
      arr = GirFFI::SizedArray.copy_from :gint32, 0, nil
      arr.must_be_nil
    end

    it 'creates an unowned copy of its argument if given a pointer' do
      arr = GirFFI::SizedArray.from :gint32, 3, [3, 2, 1]
      arr2 = GirFFI::SizedArray.copy_from :gint32, 3, arr.to_ptr
      assert_instance_of GirFFI::SizedArray, arr2
      arr2.to_ptr.wont_be :==, arr.to_ptr
      arr2.to_ptr.wont_be :autorelease?
      arr2.to_a.must_equal [3, 2, 1]
    end
  end

  describe '#==' do
    it 'returns true when comparing to an array with the same elements' do
      sized = GirFFI::SizedArray.from :int32, 3, [1, 2, 3]

      sized.must_be :==, [1, 2, 3]
    end

    it 'returns false when comparing to an array with different elements' do
      sized = GirFFI::SizedArray.from :int32, 3, [1, 2, 3]

      sized.wont_be :==, [1, 2]
    end

    it 'returns true when comparing to a sized array with the same elements' do
      sized = GirFFI::SizedArray.from :int32, 3, [1, 2, 3]
      other = GirFFI::SizedArray.from :int32, 3, [1, 2, 3]

      sized.must_be :==, other
    end

    it 'returns false when comparing to a sized array with different elements' do
      sized = GirFFI::SizedArray.from :int32, 3, [1, 2, 3]
      other = GirFFI::SizedArray.from :int32, 2, [1, 2]

      sized.wont_be :==, other
    end
  end

  describe '#size_in_bytes' do
    it 'returns the correct value' do
      sized = GirFFI::SizedArray.from :int32, 3, [1, 2, 3]

      sized.size_in_bytes.must_equal 12
    end
  end

  describe '.get_value_from_pointer' do
    it 'returns just a pointer' do
      sized = GirFFI::SizedArray.from :int32, 3, [1, 2, 3]
      ptr = sized.to_ptr
      result = GirFFI::SizedArray.get_value_from_pointer(ptr, 0)
      result.must_be :==, ptr
    end

    it 'offsets correctly' do
      sized = GirFFI::SizedArray.from :int32, 3, [1, 2, 3]
      ptr = sized.to_ptr
      next_ptr = GirFFI::SizedArray.get_value_from_pointer(ptr, 4)
      tail = GirFFI::SizedArray.from(:int32, 2, next_ptr)
      tail.must_be :==, [2, 3]
    end
  end

  describe '.copy_value_to_pointer' do
    it 'copies data correctly' do
      sized = GirFFI::SizedArray.from :int32, 3, [1, 2, 3]
      target = FFI::MemoryPointer.new sized.size_in_bytes
      GirFFI::SizedArray.copy_value_to_pointer(sized, target)
      result = GirFFI::SizedArray.from :int32, 3, target
      result.must_be :==, [1, 2, 3]
    end
  end

  it 'includes Enumerable' do
    GirFFI::SizedArray.must_include Enumerable
  end
end
