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

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

describe "MemoryPointer" do
  it "makes a pointer from a string" do
    m = FFI::MemoryPointer.from_string("FFI is Awesome")
    expect(m.total).to eq(15)
    expect(m.type_size).to eq(1)
  end

  it "does not make a pointer from non-strings" do
    expect { FFI::MemoryPointer.from_string(nil) }.to raise_error(TypeError)
  end

  it "makes a pointer from a string with multibyte characters" do
    m = FFI::MemoryPointer.from_string("ぱんだ")
    expect(m.total).to eq(10)
    expect(m.type_size).to eq(1)
  end

  it "reads back a string" do
    m = FFI::MemoryPointer.from_string("FFI is Awesome")
    expect(m.read_string).to eq("FFI is Awesome")
  end

  it "reads back an empty string" do
    expect(FFI::Pointer::NULL.read_string(0)).to eq('')
    expect(FFI::Pointer::NULL.read_string(0).encoding).to eq(Encoding::BINARY)
  end

  it "makes a pointer for a certain number of bytes" do
    m = FFI::MemoryPointer.new(8)
    m.write_array_of_int([1,2])
    expect(m.read_array_of_int(2)).to eq([1,2])
  end

  it "allows access to an element of the pointer (as an array)" do
    m = FFI::MemoryPointer.new(:int, 2)
    m.write_array_of_int([1,2])
    expect(m[0].read_int).to eq(1)
    expect(m[1].read_int).to eq(2)
  end

  it "allows writing as an int" do
    m = FFI::MemoryPointer.new(:int)
    m.write_int(1)
    expect(m.read_int).to eq(1)
    expect(m.read :int).to eq(1)
    expect(m.read FFI::Type::INT).to eq(1)
  end

  it "allows writing as a sized int" do
    m = FFI::MemoryPointer.new(:uint32)
    m.write_uint32(1)
    expect(m.read_uint32).to eq(1)
    expect(m.read :uint32).to eq(1)
    expect(m.read FFI::Type::UINT32).to eq(1)

    m = FFI::MemoryPointer.new(:uint32)
    m.write :uint32, 1
    expect(m.read :uint32).to eq(1)

    m = FFI::MemoryPointer.new(:int64)
    m.write_int64(1)
    expect(m.read_int64).to eq(1)
    expect(m.read :int64).to eq(1)
    expect(m.read FFI::Type::INT64).to eq(1)

    m = FFI::MemoryPointer.new(:int64)
    m.write :int64, 1
    expect(m.read :int64).to eq(1)
  end

  it "allows writing as a long" do
    m = FFI::MemoryPointer.new(:long)
    m.write_long(10)
    expect(m.read_long).to eq(10)
    expect(m.read :long).to eq(10)
    expect(m.read FFI::Type::LONG).to eq(10)

    m.write :long, 10
    expect(m.read :long).to eq(10)
  end

  it "allows writing as a size_t" do
    m = FFI::MemoryPointer.new(:size_t)
    m.write(:size_t, 10)
    expect(m.read :size_t).to eq(10)
  end

  it "allows writing as a bool" do
    m = FFI::MemoryPointer.new(:bool)
    m.write(:bool, true)
    expect(m.read :bool).to eq(true)
    expect(m.read FFI::Type::BOOL).to eq(true)

    m.write(:bool, false)
    expect(m.read :bool).to eq(false)
    expect(m.read FFI::Type::BOOL).to eq(false)
  end

  it "allows definition of a custom typedef" do
    FFI.typedef :uint32, :fubar_t
    expect(FFI.find_type(:fubar_t)).to eq(FFI::Type::Builtin::UINT32)
  end

  it "allows overwriting of a default typedef" do
    FFI.typedef :uint32, :char
    expect(FFI.find_type(:char)).to eq(FFI::Type::Builtin::UINT32)
  ensure
    FFI.typedef FFI::Type::Builtin::CHAR, :char
  end

  it "allows writing a custom typedef" do
    FFI.typedef :uint, :fubar_t
    FFI.typedef :size_t, :fubar2_t

    m = FFI::MemoryPointer.new(:fubar_t)
    m.write(:fubar_t, 10)
    expect(m.read :fubar_t).to eq(10)

    m = FFI::MemoryPointer.new(:fubar2_t)
    m.write(:fubar2_t, 10)
    expect(m.read :fubar2_t).to eq(10)
  end

  it "raises an error if you try to read an undefined type" do
    m = FFI::MemoryPointer.new(:long)
    expect { m.read(:undefined_type) }.to raise_error(ArgumentError)
  end

  it "raises an error if you try putting a long into a pointer of size 1" do
    m = FFI::MemoryPointer.new(1)
    expect { m.write_long(10) }.to raise_error(IndexError)
  end

  it "raises an error if you try putting an int into a pointer of size 1" do
    m = FFI::MemoryPointer.new(1)
    expect { m.write_int(10) }.to raise_error(IndexError)
  end
#  it "does not raise IndexError for opaque pointers" do
#    m = FFI::MemoryPointer.new(8)
#    p2 = FFI::MemoryPointer.new(1024)
#    m.write_long(p2.address)
#    p = m.read_pointer
#    lambda { p.write_int(10) }.should_not raise_error
#  end

  it "makes a pointer for a certain type" do
    m = FFI::MemoryPointer.new(:int)
    m.write_int(10)
    expect(m.read_int).to eq(10)
  end

  it "makes a memory pointer for a number of a certain type" do
    m = FFI::MemoryPointer.new(:int, 2)
    m.write_array_of_int([1,2])
    expect(m.read_array_of_int(2)).to eq([1,2])
  end

  it "makes a pointer for an object responding to #size" do
    m = FFI::MemoryPointer.new(Struct.new(:size).new(8))
    m.write_array_of_int([1,2])
    expect(m.read_array_of_int(2)).to eq([1,2])
  end

  it "makes a pointer for a number of an object responding to #size" do
    m = FFI::MemoryPointer.new(Struct.new(:size).new(4), 2)
    m.write_array_of_int([1,2])
    expect(m.read_array_of_int(2)).to eq([1,2])
  end

  it "MemoryPointer#address returns correct value" do
    m = FFI::MemoryPointer.new(:long_long)
    magic = 0x12345678
    m.write_long(magic)
    expect(m.read_pointer.address).to eq(magic)
  end

  it "MemoryPointer#null? returns true for zero value" do
    m = FFI::MemoryPointer.new(:long_long)
    m.write_long(0)
    expect(m.read_pointer.null?).to be true
  end

  it "MemoryPointer#null? returns false for non-zero value" do
    m = FFI::MemoryPointer.new(:long_long)
    m.write_long(0x12345678)
    expect(m.read_pointer.null?).to be false
  end

  it "initialize with block should execute block" do
    block_executed = false
    FFI::MemoryPointer.new(:pointer) do |ptr|
      block_executed = true
    end
    expect(block_executed).to be true
  end

  it "has a memsize function", skip: RUBY_ENGINE != "ruby" do
    base_size = ObjectSpace.memsize_of(Object.new)

    pointer = FFI::MemoryPointer.from_string("FFI is Awesome")
    size = ObjectSpace.memsize_of(pointer)
    expect(size).to be > base_size
  end
end
