require "spec"

private lib LibPointerSpec
  type A = Void
  type B = Void
end

private def reset(p1, p2)
  p1.value = 10
  p2.value = 20
end

describe "Pointer" do
  it "does malloc with value" do
    p1 = Pointer.malloc(4, 1)
    4.times do |i|
      p1[i].should eq(1)
    end
  end

  it "does malloc with value from block" do
    p1 = Pointer.malloc(4) { |i| i }
    4.times do |i|
      p1[i].should eq(i)
    end
  end

  it "does index with count" do
    p1 = Pointer.malloc(4) { |i| i ** 2 }
    p1.to_slice(4).index(4).should eq(2)
    p1.to_slice(4).index(5).should be_nil
  end

  describe "copy_from" do
    it "performs" do
      p1 = Pointer.malloc(4) { |i| i }
      p2 = Pointer.malloc(4) { 0 }
      p2.copy_from(p1, 4)
      4.times do |i|
        p2[i].should eq(p1[i])
      end
    end

    it "raises on negative count" do
      p1 = Pointer.malloc(4, 0)
      expect_raises(ArgumentError, "Negative count") do
        p1.copy_from(p1, -1)
      end
    end

    it "copies from union of pointers" do
      p1 = Pointer.malloc(4, 1)
      p2 = Pointer.malloc(4, 1.5)
      p3 = Pointer.malloc(4, 0 || 0.0)
      p3.copy_from(p1 || p2, 4)
      4.times { |i| p3[i].should eq(p1[i]) }
    end
  end

  describe "realloc" do
    it "raises on negative count" do
      p1 = Pointer(Int32).new(123)
      expect_raises(ArgumentError) do
        p1.realloc(-1)
      end
    end
  end

  describe "copy_to" do
    it "performs" do
      p1 = Pointer.malloc(4) { |i| i }
      p2 = Pointer.malloc(4) { 0 }
      p1.copy_to(p2, 4)
      4.times do |i|
        p2[i].should eq(p1[i])
      end
    end

    it "raises on negative count" do
      p1 = Pointer.malloc(4, 0)
      expect_raises(ArgumentError, "Negative count") do
        p1.copy_to(p1, -1)
      end
    end

    it "copies to union of pointers" do
      p1 = Pointer.malloc(4, 1)
      p2 = Pointer.malloc(4, 0 || 1.5)
      p3 = Pointer.malloc(4, 0 || 'a')
      p1.copy_to(p2 || p3, 4)
      4.times { |i| p2[i].should eq(p1[i]) }
    end

    it "doesn't raise OverflowError on unsigned size and different target type" do
      p1 = Pointer.malloc(4, 1)
      p2 = Pointer.malloc(4, 0 || nil)
      p1.copy_to(p2, 4_u32)
      4.times { |i| p2[i].should eq(p1[i]) }
    end
  end

  describe "move_from" do
    it "performs with overlap right to left" do
      p1 = Pointer.malloc(4) { |i| i }
      (p1 + 1).move_from(p1 + 2, 2)
      p1[0].should eq(0)
      p1[1].should eq(2)
      p1[2].should eq(3)
      p1[3].should eq(3)
    end

    it "performs with overlap left to right" do
      p1 = Pointer.malloc(4) { |i| i }
      (p1 + 2).move_from(p1 + 1, 2)
      p1[0].should eq(0)
      p1[1].should eq(1)
      p1[2].should eq(1)
      p1[3].should eq(2)
    end

    it "raises on negative count" do
      p1 = Pointer.malloc(4, 0)
      expect_raises(ArgumentError, "Negative count") do
        p1.move_from(p1, -1)
      end
    end

    it "moves from union of pointers" do
      p1 = Pointer.malloc(4, 1)
      p2 = Pointer.malloc(4, 1.5)
      p3 = Pointer.malloc(4, 0 || 0.0)
      p3.move_from(p1 || p2, 4)
      4.times { |i| p3[i].should eq(p1[i]) }
    end
  end

  describe "move_to" do
    it "performs with overlap right to left" do
      p1 = Pointer.malloc(4) { |i| i }
      (p1 + 2).move_to(p1 + 1, 2)
      p1[0].should eq(0)
      p1[1].should eq(2)
      p1[2].should eq(3)
      p1[3].should eq(3)
    end

    it "performs with overlap left to right" do
      p1 = Pointer.malloc(4) { |i| i }
      (p1 + 1).move_to(p1 + 2, 2)
      p1[0].should eq(0)
      p1[1].should eq(1)
      p1[2].should eq(1)
      p1[3].should eq(2)
    end

    it "raises on negative count" do
      p1 = Pointer.malloc(4, 0)
      expect_raises(ArgumentError, "Negative count") do
        p1.move_to(p1, -1)
      end
    end

    it "moves to union of pointers" do
      p1 = Pointer.malloc(4, 1)
      p2 = Pointer.malloc(4, 0 || 1.5)
      p3 = Pointer.malloc(4, 0 || 'a')
      p1.move_to(p2 || p3, 4)
      4.times { |i| p2[i].should eq(p1[i]) }
    end
  end

  describe "memcmp" do
    it do
      p1 = Pointer.malloc(4) { |i| i }
      p2 = Pointer.malloc(4) { |i| i }
      p3 = Pointer.malloc(4) { |i| i + 1 }

      p1.memcmp(p2, 4).should eq(0)
      p1.memcmp(p3, 4).should be < 0
      p3.memcmp(p1, 4).should be > 0
    end
  end

  it "compares two pointers by address" do
    p1 = Pointer(Int32).malloc(1)
    p2 = Pointer(Int32).malloc(1)
    p1.should eq(p1)
    p1.should_not eq(p2)
    p1.should_not eq(1)
  end

  it "does to_s" do
    Pointer(Int32).null.to_s.should eq("Pointer(Int32).null")
    Pointer(Int32).new(1234_u64).to_s.should eq("Pointer(Int32)@0x4d2")
  end

  it "doesn't confuse lib typedefs (#16686)" do
    Pointer(LibPointerSpec::A).null.inspect.should eq "Pointer(LibPointerSpec::A).null"
    Pointer(LibPointerSpec::B).null.inspect.should eq "Pointer(LibPointerSpec::B).null"
  end

  it "creates from int" do
    Pointer(Int32).new(1234).address.should eq(1234)
  end

  it "performs arithmetic with u64" do
    p = Pointer(Int8).new(1234)
    d = 4_u64
    (p + d).address.should eq(1238)
    (p - d).address.should eq(1230)

    p = Pointer(Int8).new(UInt64::MAX)
    d = UInt64::MAX - 1
    (p - d).address.should eq(1)
  end

  it "performs arithmetic with u32" do
    p = Pointer(Int8).new(1234)
    d = 4_u32
    (p + d).address.should eq(1238)
    (p - d).address.should eq(1230)
  end

  it "shuffles!" do
    a = Pointer(Int32).malloc(3) { |i| i + 1 }
    a.shuffle!(3)

    (a[0] + a[1] + a[2]).should eq(6)

    3.times do |i|
      a.to_slice(3).should contain(i + 1)
    end
  end

  it "maps!" do
    a = Pointer(Int32).malloc(3) { |i| i + 1 }
    a.map!(3) { |i| i + 1 }
    a[0].should eq(2)
    a[1].should eq(3)
    a[2].should eq(4)
  end

  it "maps_with_index!" do
    a = Pointer(Int32).malloc(3) { |i| i + 1 }
    a.map_with_index!(3) { |e, i| e + i }
    a[0].should eq(1)
    a[1].should eq(3)
    a[2].should eq(5)
  end

  it "maps_with_index!, with offset" do
    a = Pointer(Int32).malloc(3) { |i| i + 1 }
    a.map_with_index!(3, offset: 10) { |e, i| e + i }
    a[0].should eq(11)
    a[1].should eq(13)
    a[2].should eq(15)
  end

  describe "#fill" do
    it "int" do
      slice = Slice[0, 1, 2, 3, 4]
      ptr = slice.to_unsafe + 1
      ptr.fill(3, 7)
      slice.should eq Slice[0, 7, 7, 7, 4]
    end

    it "string" do
      slice = Slice["a", "b", "c", "d", "e"]
      ptr = slice.to_unsafe + 1
      ptr.fill(3, " ")
      slice.should eq Slice["a", " ", " ", " ", "e"]
    end

    it "pointer" do
      slice = Slice[Pointer(Void).new(0x1_u64), Pointer(Void).new(0x2_u64), Pointer(Void).new(0x3_u64), Pointer(Void).new(0x4_u64), Pointer(Void).new(0x5_u64)]
      ptr = slice.to_unsafe + 1
      ptr.fill(3, Pointer(Void).new(0x10_u64))
      slice.should eq Slice[Pointer(Void).new(0x1_u64), Pointer(Void).new(0x10_u64), Pointer(Void).new(0x10_u64), Pointer(Void).new(0x10_u64), Pointer(Void).new(0x5_u64)]
    end

    describe "yielding" do
      it "int" do
        slice = Slice[1, 1, 1, 1, 1]
        ptr = slice.to_unsafe + 1
        ptr.fill(3) { |i| i * i }
        slice.should eq Slice[1, 0, 1, 4, 1]
        ptr.fill(3, offset: 3) { |i| i * i }
        slice.should eq Slice[1, 9, 16, 25, 1]
      end
    end
  end

  it "raises if mallocs negative size" do
    expect_raises(ArgumentError) { Pointer.malloc(-1, 0) }
  end

  it "copies/move with different types" do
    p1 = Pointer(Int32).malloc(1)
    p2 = Pointer(Int32 | String).malloc(1)

    reset p1, p2
    p1.copy_from(p1, 1)
    p1.value.should eq(10)

    # p1.copy_from(p2, 10) # invalid

    reset p1, p2
    p2.copy_from(p1, 1)
    p2.value.should eq(10)

    reset p1, p2
    p2.copy_from(p2, 1)
    p2.value.should eq(20)

    reset p1, p2
    p1.move_from(p1, 1)
    p1.value.should eq(10)

    # p1.move_from(p2, 10) # invalid

    reset p1, p2
    p2.move_from(p1, 1)
    p2.value.should eq(10)

    reset p1, p2
    p2.move_from(p2, 1)
    p2.value.should eq(20)

    # ---

    reset p1, p2
    p1.copy_to(p1, 1)
    p1.value.should eq(10)

    reset p1, p2
    p1.copy_to(p2, 1)
    p2.value.should eq(10)

    # p2.copy_to(p1, 10) # invalid

    reset p1, p2
    p2.copy_to(p2, 1)
    p2.value.should eq(20)

    reset p1, p2
    p1.move_to(p1, 1)
    p1.value.should eq(10)

    reset p1, p2
    p1.move_to(p2, 1)
    p2.value.should eq(10)

    # p2.move_to(p1, 10) # invalid

    reset p1, p2
    p2.move_to(p2, 1)
    p2.value.should eq(20)
  end

  describe "clear" do
    it "clears one" do
      ptr = Pointer(Int32).malloc(2)
      ptr[0] = 10
      ptr[1] = 20
      ptr.clear
      ptr[0].should eq(0)
      ptr[1].should eq(20)
    end

    it "clears many" do
      ptr = Pointer(Int32).malloc(4)
      ptr[0] = 10
      ptr[1] = 20
      ptr[2] = 30
      ptr[3] = 40
      ptr.clear(2)
      ptr[0].should eq(0)
      ptr[1].should eq(0)
      ptr[2].should eq(30)
      ptr[3].should eq(40)
    end

    it "clears with union" do
      ptr = Pointer(Int32 | Nil).malloc(4)
      ptr[0] = 10
      ptr[1] = 20
      ptr[2] = 30
      ptr[3] = 0
      ptr.clear(2)
      ptr[0].should be_nil
      ptr[1].should be_nil
      ptr[2].should eq(30)
      ptr[3].should eq(0)
      ptr[3].should_not be_nil
    end
  end

  it "does !" do
    (!Pointer(Int32).null).should be_true
    (!Pointer(Int32).new(123)).should be_false
  end

  it "clones" do
    ptr = Pointer(Int32).new(123)
    ptr.clone.should eq(ptr)
  end

  it "aligns small pointers using #align_down and #align_up" do
    ptr = Pointer(Void).new(0x30_u64)
    ptr.align_down(16).should eq(Pointer(Void).new(0x30_u64))
    ptr.align_down(32).should eq(Pointer(Void).new(0x20_u64))

    ptr = Pointer(Void).new(0x30_u64)
    ptr.align_up(16).should eq(Pointer(Void).new(0x30_u64))
    ptr.align_up(32).should eq(Pointer(Void).new(0x40_u64))

    ptr = Pointer(Void).new(1_u64)
    ptr.align_up(1024).should eq(Pointer(Void).new(1024_u64))
    ptr.align_down(1024).should eq(Pointer(Void).new(0_u64))

    ptr = Pointer(Void).new(0xDEADC0DE_u64)
    ptr.align_up(1u64 << 29).address.should eq(0xE0000000_u64)
    ptr.align_down(1u64 << 29).address.should eq(0xC0000000_u64)

    ptr = Pointer(Void).new(0xBADCAB1E_u64)
    ptr.align_up(1u64 << 32).address.should eq({% if flag?(:bits32) %} 0u64 {% else %} 1u64 << 32 {% end %})
    ptr.align_down(1u64 << 32).address.should eq(0_u64)
    ptr.align_up(1u64 << 40).address.should eq({% if flag?(:bits32) %} 0u64 {% else %} 1u64 << 40 {% end %})
    ptr.align_down(1u64 << 40).address.should eq(0_u64)
  end

  it "only aligns pointers on byte boundaries using #align_down and #align_up" do
    ptr = Pointer(UInt128).new(10_u64)

    ptr.align_up(4).should eq(Pointer(UInt128).new(12_u64))
    ptr.align_down(4).should eq(Pointer(UInt128).new(8_u64))

    ptr.align_up(16).should eq(Pointer(UInt128).new(16_u64))
    ptr.align_down(16).should eq(Pointer(UInt128).new(0_u64))

    ptr = Pointer(UInt32).new(3_u64)
    ptr.align_down(2).should eq(Pointer(UInt32).new(2_u64))

    ptr = Pointer(UInt32).new(1_u64)
    ptr.align_up(2).should eq(Pointer(UInt32).new(2_u64))
  end

  it "correctly wraps around zero using #align_up" do
    # On 32 bit platforms, this results in a Pointer with address UInt32::MAX (truncated)
    ptr = Pointer(Void*).new(&-1_u64)

    ptr.align_up(1).address.should eq({% if flag?(:bits32) %} (&-1u32).to_u64 {% else %} &-1u64 {% end %})

    ptr.align_up(2).address.should eq(0_u64)

    ptr.align_up(1u64 << 16).address.should eq(0_u64)
  end

  {% if flag?(:bits64) %}
    it "correctly aligns pointers using operands exceeding 32-bit range using #align_down and #align_up" do
      ptr = Pointer(Void).new(0xDECAFFEDC0FFEEEE_u64)

      # align to 48 bits (281_474_976_710_656 byte boundary)
      ptr.align_down(1u64 << 48).address.should eq(0xDECA000000000000_u64)
      ptr.align_up(1u64 << 48).address.should eq(0xDECB000000000000_u64)

      # align to 63 bits
      ptr = Pointer(Void).new((1u64 << 63) + 1)
      ptr.align_down(1u64 << 63).address.should eq(1u64 << 63)
      ptr.align_up(1u64 << 63).address.should eq(0_u64)

      ptr = Pointer(Void).new((1u64 << 63) - 1)
      ptr.align_down(1u64 << 63).address.should eq(0_u64)
      ptr.align_up(1u64 << 63).address.should eq(1u64 << 63)
    end
  {% end %}

  {% if flag?(:bits32) %}
    it "raises on copy_from with size bigger than UInt32::MAX" do
      ptr = Pointer(Int32).new(123)

      expect_raises(ArgumentError) do
        ptr.copy_from(ptr, UInt32::MAX.to_u64 + 1)
      end
    end

    it "raises on move_from with size bigger than UInt32::MAX" do
      ptr = Pointer(Int32).new(123)

      expect_raises(ArgumentError) do
        ptr.move_from(ptr, UInt32::MAX.to_u64 + 1)
      end
    end

    it "raises on clear with size bigger than UInt32::MAX" do
      ptr = Pointer(Int32).new(123)

      expect_raises(ArgumentError) do
        ptr.clear(UInt32::MAX.to_u64 + 1)
      end
    end
  {% end %}
end
