#
# This file is part of ruby-ffi.
# For licensing, see LICENSE.SPECS
#

require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper"))

describe "Struct aligns fields correctly" do
  it "char, followed by an int" do
    pending("not supported in 1.8") if RUBY_VERSION =~ /^1\.8\..*/
    class CIStruct < FFI::Struct
      layout :c => :char, :i => :int
    end
    expect(CIStruct.size).to eq(8)
  end

  it "short, followed by an int" do
    pending("not supported in 1.8") if RUBY_VERSION =~ /^1\.8\..*/
    class SIStruct < FFI::Struct
      layout :s => :short, :i => :int
    end
    expect(SIStruct.size).to eq(8)
  end

  it "int, followed by an int" do
    pending("not supported in 1.8") if RUBY_VERSION =~ /^1\.8\..*/
    class IIStruct < FFI::Struct
      layout :i1 => :int, :i => :int
    end
    expect(IIStruct.size).to eq(8)
  end

  it "long long, followed by an int" do
    pending("not supported in 1.8") if RUBY_VERSION =~ /^1\.8\..*/
    class LLIStruct < FFI::Struct
      layout :l => :long_long, :i => :int
    end
    expect(LLIStruct.size).to eq(FFI::TYPE_UINT64.alignment == 4 ? 12 : 16)
  end
end

describe "Struct tests" do
  StructTypes = {
    's8' => :char,
    's16' => :short,
    's32' => :int,
    's64' => :long_long,
    'long' => :long,
    'f32' => :float,
    'f64' => :double
  }
  module LibTest
    extend FFI::Library
    ffi_lib TestLibrary::PATH
    attach_function :ptr_ret_pointer, [ :pointer, :int], :string
    begin
      attach_function :ptr_ret_int32_t, [ :pointer, :int ], :int
    rescue FFI::NotFoundError
      # NetBSD uses #define instead of typedef for these
      attach_function :ptr_ret_int32_t, :ptr_ret___int32_t, [ :pointer, :int ], :int
    end
    attach_function :ptr_from_address, [ :ulong ], :pointer
    attach_function :string_equals, [ :string, :string ], :int
    [ 's8', 's16', 's32', 's64', 'f32', 'f64', 'long' ].each do |t|
      attach_function "struct_align_#{t}", [ :pointer ], StructTypes[t]
    end
  end
  class PointerMember < FFI::Struct
    layout :pointer, :pointer
  end
  class StringMember < FFI::Struct
    layout :string, :string
  end

  it "Struct#[:pointer]" do
    magic = 0x12345678
    mp = FFI::MemoryPointer.new :long
    mp.put_long(0, magic)
    smp = FFI::MemoryPointer.new :pointer
    smp.put_pointer(0, mp)
    s = PointerMember.new smp
    expect(s[:pointer]).to eq(mp)
  end

  it "Struct#[:pointer].nil? for NULL value" do
    magic = 0x12345678
    mp = FFI::MemoryPointer.new :long
    mp.put_long(0, magic)
    smp = FFI::MemoryPointer.new :pointer
    smp.put_pointer(0, nil)
    s = PointerMember.new smp
    expect(s[:pointer].null?).to be true
  end

  it "Struct#[:pointer]=" do
    magic = 0x12345678
    mp = FFI::MemoryPointer.new :long
    mp.put_long(0, magic)
    smp = FFI::MemoryPointer.new :pointer
    s = PointerMember.new smp
    s[:pointer] = mp
    expect(smp.get_pointer(0)).to eq(mp)
  end

  it "Struct#[:pointer]=struct" do
    smp = FFI::MemoryPointer.new :pointer
    s = PointerMember.new smp
    expect { s[:pointer] = s }.not_to raise_error Exception
    expect { s[:pointer].nil? }.not_to raise_error Exception
  end

  it "Struct#[:pointer]=nil" do
    smp = FFI::MemoryPointer.new :pointer
    s = PointerMember.new smp
    s[:pointer] = nil
    expect(smp.get_pointer(0)).to be_null
  end

  it "Struct#[:string]" do
    magic = "test"
    mp = FFI::MemoryPointer.new 1024
    mp.put_string(0, magic)
    smp = FFI::MemoryPointer.new :pointer
    smp.put_pointer(0, mp)
    s = StringMember.new smp
    expect(s[:string]).to eq(magic)
  end

  it "Struct#[:string].nil? for NULL value" do
    smp = FFI::MemoryPointer.new :pointer
    smp.put_pointer(0, nil)
    s = StringMember.new smp
    expect(s[:string]).to be_nil
  end

  it "Struct#layout works with :name, :type pairs" do
    class PairLayout < FFI::Struct
      layout :a, :int, :b, :long_long
    end
    ll_off = (FFI::TYPE_UINT64.alignment == 4 ? 4 : 8)
    expect(PairLayout.size).to eq((ll_off + 8))
    mp = FFI::MemoryPointer.new(PairLayout.size)
    s = PairLayout.new mp
    s[:a] = 0x12345678
    expect(mp.get_int(0)).to eq(0x12345678)
    s[:b] = 0xfee1deadbeef
    expect(mp.get_int64(ll_off)).to eq(0xfee1deadbeef)
  end

  it "Struct#layout works with :name, :type, offset tuples" do
    class PairLayout < FFI::Struct
      layout :a, :int, 0, :b, :long_long, 4
    end
    expect(PairLayout.size).to eq((FFI::TYPE_UINT64.alignment == 4 ? 12 : 16))
    mp = FFI::MemoryPointer.new(PairLayout.size)
    s = PairLayout.new mp
    s[:a] = 0x12345678
    expect(mp.get_int(0)).to eq(0x12345678)
    s[:b] = 0xfee1deadbeef
    expect(mp.get_int64(4)).to eq(0xfee1deadbeef)
  end

  it "Struct#layout works with mixed :name,:type and :name,:type,offset" do
    class MixedLayout < FFI::Struct
      layout :a, :int, :b, :long_long, 4
    end
    expect(MixedLayout.size).to eq((FFI::TYPE_UINT64.alignment == 4 ? 12 : 16))
    mp = FFI::MemoryPointer.new(MixedLayout.size)
    s = MixedLayout.new mp
    s[:a] = 0x12345678
    expect(mp.get_int(0)).to eq(0x12345678)
    s[:b] = 0xfee1deadbeef
    expect(mp.get_int64(4)).to eq(0xfee1deadbeef)
  end

  rb_maj, rb_min = RUBY_VERSION.split('.')
  if rb_maj.to_i >= 1 && rb_min.to_i >= 9 || RUBY_PLATFORM =~ /java/
    it "Struct#layout withs with a hash of :name => type" do
      class HashLayout < FFI::Struct
        layout :a => :int, :b => :long_long
      end
      ll_off = (FFI::TYPE_UINT64.alignment == 4 ? 4 : 8)
      expect(HashLayout.size).to eq(ll_off + 8)
      mp = FFI::MemoryPointer.new(HashLayout.size)
      s = HashLayout.new mp
      s[:a] = 0x12345678
      expect(mp.get_int(0)).to eq(0x12345678)
      s[:b] = 0xfee1deadbeef
      expect(mp.get_int64(ll_off)).to eq(0xfee1deadbeef)
    end
  end

  it "subclass overrides initialize without calling super" do
    class InitializeWithoutSuper < FFI::Struct
      layout :a, :int, :b, :long_long, :d, [:double, 2]

      def initialize(a, b)
        self[:a] = a
        self[:b] = b
        self[:d][0] = 1.2
        self[:d][1] = 3.4
      end

    end
    s = InitializeWithoutSuper.new(0x1eefbeef, 0xdeadcafebabe)
    expect(s[:a]).to eq(0x1eefbeef)
    expect(s[:b]).to eq(0xdeadcafebabe)
  end

  it "Can use Struct subclass as parameter type" do
    expect(module StructParam
      extend FFI::Library
      ffi_lib TestLibrary::PATH
      class TestStruct < FFI::Struct
        layout :c, :char
      end
      attach_function :struct_field_s8, [ TestStruct.in ], :char
    end).to be_an_instance_of FFI::Function
  end

  it "Can use Struct subclass as IN parameter type" do
    expect(module StructParam2
      extend FFI::Library
      ffi_lib TestLibrary::PATH
      class TestStruct < FFI::Struct
        layout :c, :char
      end
      attach_function :struct_field_s8, [ TestStruct.in ], :char
    end).to be_an_instance_of FFI::Function
  end

  it "Can use Struct subclass as OUT parameter type" do
    expect(module StructParam3
      extend FFI::Library
      ffi_lib TestLibrary::PATH
      class TestStruct < FFI::Struct
        layout :c, :char
      end
      attach_function :struct_field_s8, [ TestStruct.out ], :char
    end).to be_an_instance_of FFI::Function
  end

  it "can be passed directly as a :pointer parameter" do
    class TestStruct < FFI::Struct
      layout :i, :int
    end
    s = TestStruct.new
    s[:i] = 0x12
    expect(LibTest.ptr_ret_int32_t(s, 0)).to eq(0x12)
  end

  it ":char member aligned correctly" do
    class AlignChar < FFI::Struct
      layout :c, :char, :v, :char
    end
    s = AlignChar.new
    s[:v] = 0x12
    expect(LibTest.struct_align_s8(s.pointer)).to eq(0x12)
  end

  it ":short member aligned correctly" do
    class AlignShort < FFI::Struct
      layout :c, :char, :v, :short
    end
    s = AlignShort.alloc_in
    s[:v] = 0x1234
    expect(LibTest.struct_align_s16(s.pointer)).to eq(0x1234)
  end

  it ":int member aligned correctly" do
    class AlignInt < FFI::Struct
      layout :c, :char, :v, :int
    end
    s = AlignInt.alloc_in
    s[:v] = 0x12345678
    expect(LibTest.struct_align_s32(s.pointer)).to eq(0x12345678)
  end

  it ":long_long member aligned correctly" do
    class AlignLongLong < FFI::Struct
      layout :c, :char, :v, :long_long
    end
    s = AlignLongLong.alloc_in
    s[:v] = 0x123456789abcdef0
    expect(LibTest.struct_align_s64(s.pointer)).to eq(0x123456789abcdef0)
  end

  it ":long member aligned correctly" do
    class AlignLong < FFI::Struct
      layout :c, :char, :v, :long
    end
    s = AlignLong.alloc_in
    s[:v] = 0x12345678
    expect(LibTest.struct_align_long(s.pointer)).to eq(0x12345678)
  end

  it ":float member aligned correctly" do
    class AlignFloat < FFI::Struct
      layout :c, :char, :v, :float
    end
    s = AlignFloat.alloc_in
    s[:v] = 1.23456
    expect((LibTest.struct_align_f32(s.pointer) - 1.23456).abs).to be < 0.00001
  end

  it ":double member aligned correctly" do
    class AlignDouble < FFI::Struct
      layout :c, :char, :v, :double
    end
    s = AlignDouble.alloc_in
    s[:v] = 1.23456789
    expect((LibTest.struct_align_f64(s.pointer) - 1.23456789).abs).to be < 0.00000001
  end

  it ":ulong, :pointer struct" do
    class ULPStruct < FFI::Struct
      layout :ul, :ulong, :p, :pointer
    end
    s = ULPStruct.alloc_in
    s[:ul] = 0xdeadbeef
    s[:p] = LibTest.ptr_from_address(0x12345678)
    expect(s.pointer.get_ulong(0)).to eq(0xdeadbeef)
  end
  def test_num_field(type, v)
    klass = Class.new(FFI::Struct)
    klass.layout :v, type, :dummy, :long

    s = klass.new
    s[:v] = v
    expect(s.pointer.send("get_#{type.to_s}", 0)).to eq(v)
    s.pointer.send("put_#{type.to_s}", 0, 0)
    expect(s[:v]).to eq(0)
  end
  def self.int_field_test(type, values)
    values.each do |v|
      it "#{type} field r/w (#{v.to_s(16)})" do
        test_num_field(type, v)
      end
    end
  end
  int_field_test(:char, [ 0, 127, -128, -1 ])
  int_field_test(:uchar, [ 0, 0x7f, 0x80, 0xff ])
  int_field_test(:short, [ 0, 0x7fff, -0x8000, -1 ])
  int_field_test(:ushort, [ 0, 0x7fff, 0x8000, 0xffff ])
  int_field_test(:int, [ 0, 0x7fffffff, -0x80000000, -1 ])
  int_field_test(:uint, [ 0, 0x7fffffff, 0x80000000, 0xffffffff ])
  int_field_test(:long_long, [ 0, 0x7fffffffffffffff, -0x8000000000000000, -1 ])
  int_field_test(:ulong_long, [ 0, 0x7fffffffffffffff, 0x8000000000000000, 0xffffffffffffffff ])
  if FFI::Platform::LONG_SIZE == 32
    int_field_test(:long, [ 0, 0x7fffffff, -0x80000000, -1 ])
    int_field_test(:ulong, [ 0, 0x7fffffff, 0x80000000, 0xffffffff ])
  else
    int_field_test(:long, [ 0, 0x7fffffffffffffff, -0x8000000000000000, -1 ])
    int_field_test(:ulong, [ 0, 0x7fffffffffffffff, 0x8000000000000000, 0xffffffffffffffff ])
  end

  it ":float field r/w" do
    klass = Class.new(FFI::Struct)
    klass.layout :v, :float, :dummy, :long

    s = klass.new
    value = 1.23456
    s[:v] = value
    expect((s.pointer.get_float(0) - value).abs).to be < 0.0001
  end

  it ":double field r/w" do
    klass = Class.new(FFI::Struct)
    klass.layout :v, :double, :dummy, :long

    s = klass.new
    value = 1.23456
    s[:v] = value
    expect((s.pointer.get_double(0) - value).abs).to be < 0.0001
  end
  module EnumFields
    extend FFI::Library
    TestEnum = enum :test_enum, [:c1, 10, :c2, 20, :c3, 30, :c4, 40]
    class TestStruct < FFI::Struct
      layout :a, :int, :c, :test_enum,
        :d, [ TestEnum, TestEnum.symbols.length ]
    end
  end

  it ":enum field r/w" do
    s = EnumFields::TestStruct.new
    s[:c] = :c3

    expect(s.pointer.get_uint(FFI::Type::INT32.size)).to eq(30)
    expect(s[:c]).to eq(:c3)
  end

  it "array of :enum field" do
    s = EnumFields::TestStruct.new
    EnumFields::TestEnum.symbols.each_with_index do |val, i|
      s[:d][i] = val
    end

    EnumFields::TestEnum.symbols.each_with_index do |val, i|
      expect(s.pointer.get_uint(FFI::Type::INT32.size * (2 + i))).to eq(EnumFields::TestEnum[val])
    end

    s[:d].each_with_index do |val, i|
      expect(val).to eq(EnumFields::TestEnum.symbols[i])
    end
  end

  module CallbackMember
    extend FFI::Library
    ffi_lib TestLibrary::PATH
    callback :add, [ :int, :int ], :int
    callback :sub, [ :int, :int ], :int
    class TestStruct < FFI::Struct
      layout :add, :add,
        :sub, :sub
    end
    attach_function :struct_call_add_cb, [TestStruct.in, :int, :int], :int
    attach_function :struct_call_sub_cb, [TestStruct.in, :int, :int], :int
  end

  it "Can have CallbackInfo struct field" do
      s = CallbackMember::TestStruct.new
      add_proc = lambda { |a, b| a+b }
      sub_proc = lambda { |a, b| a-b }
      s[:add] = add_proc
      s[:sub] = sub_proc
      expect(CallbackMember.struct_call_add_cb(s, 40, 2)).to eq(42)
      expect(CallbackMember.struct_call_sub_cb(s, 44, 2)).to eq(42)
  end

  it "Can return its members as a list" do
    class TestStruct < FFI::Struct
      layout :a, :int, :b, :int, :c, :int
    end
    expect(TestStruct.members).to include(:a, :b, :c)
  end

  it "Can return its instance members and values as lists" do
    class TestStruct < FFI::Struct
      layout :a, :int, :b, :int, :c, :int
    end
    s = TestStruct.new
    expect(s.members).to include(:a, :b, :c)
    s[:a] = 1
    s[:b] = 2
    s[:c] = 3
    expect(s.values).to include(1, 2, 3)
  end

  it 'should return an ordered field/offset pairs array' do
    class TestStruct < FFI::Struct
      layout :a, :int, :b, :int, :c, :int
    end
    s = TestStruct.new
    expect(s.offsets).to eq([[:a, 0], [:b, 4], [:c, 8]])
    expect(TestStruct.offsets).to eq([[:a, 0], [:b, 4], [:c, 8]])
  end

  it "Struct#offset_of returns offset of field within struct" do
    class TestStruct < FFI::Struct
      layout :a, :int, :b, :int, :c, :int
    end
    expect(TestStruct.offset_of(:a)).to eq(0)
    expect(TestStruct.offset_of(:b)).to eq(4)
    expect(TestStruct.offset_of(:c)).to eq(8)
  end
end

describe FFI::Struct, ".layout" do
  module FFISpecs
    module LibTest
      extend FFI::Library
      ffi_lib TestLibrary::PATH
      begin
        attach_function :ptr_ret_int32_t, [ :pointer, :int ], :int
      rescue FFI::NotFoundError
        # NetBSD uses #define instead of typedef for these
        attach_function :ptr_ret_int32_t, :ptr_ret___int32_t, [ :pointer, :int ], :int
      end
    end
  end

  describe "when derived class is not assigned to any constant" do
    it "resolves a built-in type" do
      klass = Class.new FFI::Struct
      klass.layout :number, :int

      instance = klass.new
      instance[:number] = 0xA1
      expect(FFISpecs::LibTest.ptr_ret_int32_t(instance, 0)).to eq(0xA1)
    end
  end

  describe "when derived class is assigned to a constant" do
    it "resolves a built-in type" do
      class FFISpecs::TestStruct < FFI::Struct
        layout :number, :int
      end

      instance = FFISpecs::TestStruct.new
      instance[:number] = 0xA1
      expect(FFISpecs::LibTest.ptr_ret_int32_t(instance, 0)).to eq(0xA1)
    end

    it "resolves a type from the enclosing module" do
      module FFISpecs::LibTest
        typedef :uint, :custom_int

        class TestStruct < FFI::Struct
          layout :number, :custom_int
        end
      end

      instance = FFISpecs::LibTest::TestStruct.new
      instance[:number] = 0xA1
      expect(FFISpecs::LibTest.ptr_ret_int32_t(instance, 0)).to eq(0xA1)
    end
  end
end

describe FFI::Struct, ' with a nested struct field'  do
  module LibTest
    extend FFI::Library
    ffi_lib TestLibrary::PATH
    class NestedStruct < FFI::Struct
      layout :i, :int
    end
    class ContainerStruct < FFI::Struct
      layout :first, :char, :ns, NestedStruct
    end
    attach_function :struct_align_nested_struct, [ :pointer ], :int
    attach_function :struct_make_container_struct, [ :int ], :pointer
  end
  before do
    @cs = LibTest::ContainerStruct.new
  end

  it 'should align correctly nested struct field' do
    @cs[:ns][:i] = 123
    expect(LibTest.struct_align_nested_struct(@cs.to_ptr)).to eq(123)
  end

  it 'should correctly calculate Container size (in bytes)' do
    expect(LibTest::ContainerStruct.size).to eq(8)
  end

  it 'should return a Struct object when the field is accessed' do
    expect(@cs[:ns].is_a?(FFI::Struct)).to be true
  end

  it 'should read a value from memory' do
    @cs = LibTest::ContainerStruct.new(LibTest.struct_make_container_struct(123))
    expect(@cs[:ns][:i]).to eq(123)
  end

  it 'should write a value to memory' do
    @cs = LibTest::ContainerStruct.new(LibTest.struct_make_container_struct(123))
    @cs[:ns][:i] = 456
    expect(LibTest.struct_align_nested_struct(@cs.to_ptr)).to eq(456)
  end

  it 'should be able to assign struct instance to nested field' do
    cs = LibTest::ContainerStruct.new(LibTest.struct_make_container_struct(123))
    ns = LibTest::NestedStruct.new
    ns[:i] = 567
    cs[:ns] = ns
    expect(cs[:ns][:i]).to eq(567)
    expect(LibTest.struct_align_nested_struct(cs.to_ptr)).to eq(567)
  end
end

describe FFI::Struct, ' with a nested array of structs'  do
  module InlineArrayOfStructs
    extend FFI::Library
    ffi_lib TestLibrary::PATH
    class NestedStruct < FFI::Struct
      layout :i, :int
    end
    class ContainerStruct < FFI::Struct
      layout :first, :char, :ns, [ NestedStruct, 1 ]
    end
    attach_function :struct_align_nested_struct, [ :pointer ], :int
    attach_function :struct_make_container_struct, [ :int ], :pointer
  end

  before do
    @cs = InlineArrayOfStructs::ContainerStruct.new
  end

  it 'should align correctly nested struct field' do
    @cs[:ns][0][:i] = 123
    expect(InlineArrayOfStructs.struct_align_nested_struct(@cs.to_ptr)).to eq(123)
  end

  it 'should correctly calculate Container size (in bytes)' do
    expect(InlineArrayOfStructs::ContainerStruct.size).to eq(8)
  end

  it 'should return a Struct object when the field is accessed' do
    expect(@cs[:ns][0].is_a?(FFI::Struct)).to be true
  end

  it 'should read a value from memory' do
    @cs = InlineArrayOfStructs::ContainerStruct.new(InlineArrayOfStructs.struct_make_container_struct(123))
    expect(@cs[:ns][0][:i]).to eq(123)
  end

  it 'should write a value to memory' do
    @cs = InlineArrayOfStructs::ContainerStruct.new(InlineArrayOfStructs.struct_make_container_struct(123))
    @cs[:ns][0][:i] = 456
    expect(InlineArrayOfStructs.struct_align_nested_struct(@cs.to_ptr)).to eq(456)
  end

  it 'should support Enumerable#each' do
    @cs = InlineArrayOfStructs::ContainerStruct.new(InlineArrayOfStructs.struct_make_container_struct(123))
    ints = []
    @cs[:ns].each { |s| ints << s[:i] }
    expect(ints[0]).to eq(123)
  end
end

describe FFI::Struct, ' by value'  do
  module LibTest
    extend FFI::Library
    ffi_lib TestLibrary::PATH

    class S8S32 < FFI::Struct
      layout :s8, :char, :s32, :int
    end

    class StructString < FFI::Struct
      layout :bytes, :string, :len, :int
    end

    attach_function :struct_return_s8s32, [ ], S8S32.by_value
    attach_function :struct_s8s32_set, [ :char, :int ], S8S32.by_value
    attach_function :struct_s8s32_get_s8, [ S8S32.by_value ], :char
    attach_function :struct_s8s32_get_s32, [ S8S32.by_value ], :int
    attach_function :struct_s8s32_s32_ret_s32, [ S8S32.by_value, :int ], :int
    attach_function :struct_s8s32_s64_ret_s64, [ S8S32.by_value, :long_long ], :long_long
    attach_function :struct_s8s32_ret_s8s32, [ S8S32.by_value ], S8S32.by_value
    attach_function :struct_s32_ptr_s32_s8s32_ret_s32, [ :int, :pointer, :int, S8S32.by_value ], :int
    attach_function :struct_varargs_ret_struct_string, [ :int, :varargs ], StructString.by_value
  end

  it 'return using pre-set values' do
    s = LibTest.struct_return_s8s32
    expect(s[:s8]).to eq(0x7f)
    expect(s[:s32]).to eq(0x12345678)
  end

  it 'return using passed in values' do
    s = LibTest.struct_s8s32_set(123, 456789)
    expect(s[:s8]).to eq(123)
    expect(s[:s32]).to eq(456789)
  end

  it 'parameter' do
    s = LibTest::S8S32.new
    s[:s8] = 0x12
    s[:s32] = 0x34567890
    expect(LibTest.struct_s8s32_get_s8(s)).to eq(0x12)
    expect(LibTest.struct_s8s32_get_s32(s)).to eq(0x34567890)
  end

  it 'parameter with following s32' do
    s = LibTest::S8S32.new
    s[:s8] = 0x12
    s[:s32] = 0x34567890

    expect(LibTest.struct_s8s32_s32_ret_s32(s, 0x1eefdead)).to eq(0x1eefdead)
  end

  # it 'parameter with following s64' do
  #   s = LibTest::S8S64.new
  #   s[:s8] = 0x12
  #   s[:s64] = 0x34567890
  #
  #
  #   LibTest.struct_s8s64_s64_ret_s64(s, 0x1eefdead1eefdead).should == 0x1eefdead1eefdead
  # end

  it 'parameter with preceding s32,ptr,s32' do
    s = LibTest::S8S32.new
    s[:s8] = 0x12
    s[:s32] = 0x34567890
    out = LibTest::S8S32.new
    expect(LibTest.struct_s32_ptr_s32_s8s32_ret_s32(0x1000000, out, 0x1eafbeef, s)).to eq(0x34567890)
    expect(out[:s8]).to eq(s[:s8])
    expect(out[:s32]).to eq(s[:s32])
  end

  it 'parameter with preceding s32,string,s32' do
    s = LibTest::S8S32.new
    s[:s8] = 0x12
    s[:s32] = 0x34567890
    out = 0.chr * 32
    expect(LibTest.struct_s32_ptr_s32_s8s32_ret_s32(0x1000000, out, 0x1eafbeef, s)).to eq(0x34567890)
  end

  it 'parameter, returning struct by value' do
    s = LibTest::S8S32.new
    s[:s8] = 0x12
    s[:s32] = 0x34567890

    ret = LibTest.struct_s8s32_ret_s8s32(s)
    expect(ret[:s8]).to eq(s[:s8])
    expect(ret[:s32]).to eq(s[:s32])
  end

  it 'varargs returning a struct' do
    string = "test"
    s = LibTest.struct_varargs_ret_struct_string(4, :string, string)
    expect(s[:len]).to eq(string.length)
    expect(s[:bytes]).to eq(string)
  end
end

describe FFI::Struct, ' with an array field'  do
  module LibTest
    extend FFI::Library
    ffi_lib TestLibrary::PATH
    class StructWithArray < FFI::Struct
      layout :first, :char, :a, [:int, 5]
    end
    attach_function :struct_make_struct_with_array, [:int, :int, :int, :int, :int], :pointer
    attach_function :struct_field_array, [:pointer], :pointer
  end
  before do
    @s = LibTest::StructWithArray.new
  end

  it 'should correctly calculate StructWithArray size (in bytes)' do
    expect(LibTest::StructWithArray.size).to eq(24)
  end

  it 'should read values from memory' do
    @s = LibTest::StructWithArray.new(LibTest.struct_make_struct_with_array(0, 1, 2, 3, 4))
    expect(@s[:a].to_a).to eq([0, 1, 2, 3, 4])
  end
#  it 'should cache array object for successive calls' do
#    @s[:a].object_id.should == @s[:a].object_id
#  end

  it 'should return the number of elements in the array field' do
    @s = LibTest::StructWithArray.new(LibTest.struct_make_struct_with_array(0, 1, 2, 3, 4))
    expect(@s[:a].size).to eq(5)
  end

  it 'should allow iteration through the array elements' do
    @s = LibTest::StructWithArray.new(LibTest.struct_make_struct_with_array(0, 1, 2, 3, 4))
    @s[:a].each_with_index { |elem, i| expect(elem).to eq(i) }
  end

  it 'should return the pointer to the array' do
    @s = LibTest::StructWithArray.new(LibTest.struct_make_struct_with_array(0, 1, 2, 3, 4))
    expect(@s[:a].to_ptr).to eq(LibTest::struct_field_array(@s.to_ptr))
  end
end

describe 'BuggedStruct' do
  module LibTest
    extend FFI::Library
    ffi_lib TestLibrary::PATH
    class BuggedStruct < FFI::Struct
      layout :visible, :uchar,
              :x, :uint,
              :y, :uint,
              :rx, :short,
              :ry, :short,
              :order, :uchar,
              :size, :uchar
    end
    attach_function :bugged_struct_size, [], :uint
  end

  it 'should return its correct size' do
    expect(LibTest::BuggedStruct.size).to eq(LibTest.bugged_struct_size)
  end

  it "offsets within struct should be correct" do
    expect(LibTest::BuggedStruct.offset_of(:visible)).to eq(0)
    expect(LibTest::BuggedStruct.offset_of(:x)).to eq(4)
    expect(LibTest::BuggedStruct.offset_of(:y)).to eq(8)
    expect(LibTest::BuggedStruct.offset_of(:rx)).to eq(12)
    expect(LibTest::BuggedStruct.offset_of(:ry)).to eq(14)
    expect(LibTest::BuggedStruct.offset_of(:order)).to eq(16)
    expect(LibTest::BuggedStruct.offset_of(:size)).to eq(17)
  end

  it 'should return correct field/offset pairs' do
    expect(LibTest::BuggedStruct.offsets.sort do |a, b|
      a[1] <=> b[1]
    end).to eq([[:visible, 0], [:x, 4], [:y, 8], [:rx, 12], [:ry, 14], [:order, 16], [:size, 17]])
  end
end

describe "Struct allocation" do
  it "MemoryPointer.new(Struct, 2)" do
    class S < FFI::Struct
      layout :i, :uint
    end
    p = FFI::MemoryPointer.new(S, 2)
    expect(p.total).to eq(8)
    expect(p.type_size).to eq(4)
    p.put_uint(4, 0xdeadbeef)
    expect(S.new(p[1])[:i]).to eq(0xdeadbeef)
    expect(p[1].address).to eq((p[0].address + 4))
  end

  it "Buffer.new(Struct, 2)" do
    class S < FFI::Struct
      layout :i, :uint
    end
    p = FFI::Buffer.new(S, 2)
    expect(p.total).to eq(8)
    expect(p.type_size).to eq(4)
    p.put_uint(4, 0xdeadbeef)
    expect(S.new(p[1])[:i]).to eq(0xdeadbeef)
  end

  it "null? should be true when initialized with NULL pointer" do
    class S < FFI::Struct
      layout :i, :uint
    end
    expect(S.new(FFI::Pointer::NULL)).to be_null
  end

  it "null? should be false when initialized with non-NULL pointer" do
    class S < FFI::Struct
      layout :i, :uint
    end
    expect(S.new(FFI::MemoryPointer.new(S))).not_to be_null
  end

  it "supports :bool as a struct member" do
    expect do
      c = Class.new(FFI::Struct) do
        layout :b, :bool
      end
      struct = c.new
      struct[:b] = ! struct[:b]
    end.not_to raise_error Exception
  end

end

describe "variable-length arrays" do
  it "zero length array should be accepted as last field" do
    expect {
      Class.new(FFI::Struct) do
        layout :count, :int, :data, [ :char, 0 ]
      end
    }.not_to raise_error Exception
  end

  it "zero length array before last element should raise error" do
    expect {
      Class.new(FFI::Struct) do
        layout :data, [ :char, 0 ], :count, :int
      end
    }.to raise_error
  end

  it "can access elements of array" do
    struct_class = Class.new(FFI::Struct) do
      layout :count, :int, :data, [ :long, 0 ]
    end
    s = struct_class.new(FFI::MemoryPointer.new(1024))
    s[:data][0] = 0x1eadbeef
    s[:data][1] = 0x12345678
    expect(s[:data][0]).to eq(0x1eadbeef)
    expect(s[:data][1]).to eq(0x12345678)
  end

  it "non-variable length array is bounds checked" do
    struct_class = Class.new(FFI::Struct) do
      layout :count, :int, :data, [ :long, 1 ]
    end
    s = struct_class.new(FFI::MemoryPointer.new(1024))
    s[:data][0] = 0x1eadbeef
    expect { s[:data][1] = 0x12345678 }.to raise_error
    expect(s[:data][0]).to eq(0x1eadbeef)
    expect { expect(s[:data][1]).to == 0x12345678 }.to raise_error
  end
end
