require "c/string"

# A typed pointer to some memory.
#
# This is the only unsafe type in Crystal. If you are using a pointer, you are writing
# unsafe code because a pointer doesn't know where it's pointing to nor how much memory
# starting from it is valid. However, pointers make it possible to interface with C and
# to implement efficient data structures. For example, both `Array` and `Hash` are
# implemented using pointers.
#
# You can obtain pointers in four ways: `#new`, `#malloc`, `pointerof`, or by calling a C
# function that returns a pointer.
#
# `pointerof(x)`, where *x* is a variable or an instance variable, returns a pointer to
# that variable:
#
# ```
# x = 1
# ptr = pointerof(x)
# ptr.value = 2
# x # => 2
# ```
#
# Use `#value` to dereference the pointer.
#
# Note that a pointer is *falsey* if it's null (if its address is zero).
#
# When calling a C function that expects a pointer you can also pass `nil` instead of using
# `Pointer.null` to construct a null pointer.
#
# For a safe alternative, see `Slice`, which is a pointer with a size and with bounds checking.
struct Pointer(T)
  # Unsafe wrapper around a `Pointer` that allows to write values to
  # it while advancing the location and keeping track of how many elements
  # were written.
  #
  # See also: `Pointer#appender`.
  struct Appender(T)
    def initialize(@pointer : Pointer(T))
      @start = @pointer
    end

    def <<(value : T)
      @pointer.value = value
      @pointer += 1
    end

    def size : Int64
      @pointer - @start
    end

    def pointer
      @pointer
    end

    # Creates a slice pointing at the values appended by this instance.
    #
    # ```
    # slice = Slice(Int32).new(5)
    # appender = slice.to_unsafe.appender
    # appender << 1
    # appender << 2
    # appender << 3
    # appender.to_slice # => Slice[1, 2, 3]
    # ```
    def to_slice : Slice(T)
      @start.to_slice(size)
    end
  end

  include Comparable(self)

  # Returns `true` if this pointer's address is zero.
  #
  # ```
  # a = 1
  # pointerof(a).null? # => false
  #
  # b = Pointer(Int32).new(0)
  # b.null? # => true
  # ```
  def null? : Bool
    address == 0
  end

  # Returns a new pointer whose address is this pointer's address incremented by `other * sizeof(T)`.
  #
  # ```
  # ptr = Pointer(Int32).new(1234)
  # ptr.address # => 1234
  #
  # # An Int32 occupies four bytes
  # ptr2 = ptr + 1
  # ptr2.address # => 1238
  # ```
  def +(other : Int)
    self + other.to_i64
  end

  # Returns a new pointer whose address is this pointer's address decremented by `other * sizeof(T)`.
  #
  # ```
  # ptr = Pointer(Int32).new(1234)
  # ptr.address # => 1234
  #
  # # An Int32 occupies four bytes
  # ptr2 = ptr - 1
  # ptr2.address # => 1230
  # ```
  def -(other : Int)
    # TODO: If throwing on overflow for integer conversion is implemented,
    # then (here and in `Pointer#-`) for a `UInt64` argument the call to
    # `to_i64` should become `as_unsafe`.
    self + (-other.to_i64!)
  end

  # Returns `-1`, `0` or `1` depending on whether this pointer's address is less, equal or greater than *other*'s address,
  # respectively.
  def <=>(other : self)
    address <=> other.address
  end

  # Gets the value pointed at this pointer's address plus `offset * sizeof(T)`.
  #
  # ```
  # ptr = Pointer.malloc(4) { |i| i + 10 }
  # ptr[0] # => 10
  # ptr[1] # => 11
  # ptr[2] # => 12
  # ptr[3] # => 13
  # ```
  def [](offset)
    (self + offset).value
  end

  # Sets the value pointed at this pointer's address plus `offset * sizeof(T)`.
  #
  # ```
  # ptr = Pointer(Int32).malloc(4)
  # ptr.to_slice(4) # => Slice[0, 0, 0, 0]
  # ptr[1] = 42
  #
  # ptr2 = ptr + 1
  # ptr2.value       # => 42
  # ptr1.to_slice(4) # => Slice[0, 42, 0, 0]
  # ```
  def []=(offset, value : T)
    (self + offset).value = value
  end

  # Copies *count* elements from *source* into `self`.
  # If *source* and `self` overlap, behaviour is undefined.
  # Use `#move_from` if they overlap (slower but always works).
  #
  # ```
  # ptr1 = Pointer.malloc(4) { |i| i + 1 }
  # ptr2 = Pointer.malloc(4) { |i| i + 11 }
  #
  # ptr1.to_slice(4) # => Slice[1, 2, 3, 4]
  # ptr2.to_slice(4) # => Slice[11, 12, 13, 14]
  #
  # # ptr2 -> [11, 12, 13, 14]
  # #          ^---^           <- copy this
  # # ptr1 -> [1,  2,  3,  4]
  # #          ^---^           <- here
  # ptr1.copy_from(ptr2, 2)
  # ptr1.to_slice(4) # => Slice[1, 12, 3, 4]
  # ```
  def copy_from(source : Pointer(T), count : Int)
    source.copy_to(self, count)
  end

  # :nodoc:
  def copy_from(source : Pointer(NoReturn), count : Int)
    raise ArgumentError.new("Negative count") if count < 0

    # We need this overload for cases when we have a pointer to unreachable
    # data, like when doing Tuple.new.to_a
    self
  end

  # Copies *count* elements from `self` into *target*.
  # If `self` and *target* overlap, behaviour is undefined.
  # Use `#move_to` if they overlap (slower but always works).
  #
  # ```
  # ptr1 = Pointer.malloc(4) { |i| i + 1 }
  # ptr2 = Pointer.malloc(4) { |i| i + 11 }
  #
  # ptr1.to_slice(4) # => Slice[1, 2, 3, 4]
  # ptr2.to_slice(4) # => Slice[11, 12, 13, 14]
  #
  # # ptr1 -> [1,  2,  3,  4]
  # #          ^---^           <- copy this
  # # ptr2 -> [11, 12, 13, 14]
  # #          ^---^           <- here
  # ptr1.copy_to(ptr2, 2)
  # ptr2.to_slice(4) # => Slice[1, 2, 13, 14]
  # ```
  def copy_to(target : Pointer, count : Int)
    target.copy_from_impl(self, count)
  end

  # Copies *count* elements from *source* into `self`.
  # *source* and `self` may overlap; the copy is always done in a non-destructive manner.
  #
  # ```
  # ptr1 = Pointer.malloc(4) { |i| i + 1 }
  # ptr1.to_slice(4) # => Slice[1, 2, 3, 4]
  # #                              ^-- ptr2
  # ptr2 = ptr1 + 1
  #
  # # [1, 2, 3, 4]
  # #  ^-----^       <- copy this
  # #     ^------^   <- here
  # ptr2.move_from(ptr1, 3)
  #
  # ptr1.to_slice(4) # => Slice[1, 1, 2, 3]
  # ```
  def move_from(source : Pointer(T), count : Int)
    source.move_to(self, count)
  end

  # :nodoc:
  def move_from(source : Pointer(NoReturn), count : Int)
    raise ArgumentError.new("Negative count") if count < 0

    # We need this overload for cases when we have a pointer to unreachable
    # data, like when doing Tuple.new.to_a
    self
  end

  # Copies *count* elements from `self` into *target*.
  # *target* and `self` may overlap; the copy is always done in a non-destructive manner.
  #
  # ```
  # ptr1 = Pointer.malloc(4) { |i| i + 1 }
  # ptr1.to_slice(4) # => Slice[1, 2, 3, 4]
  # #                              ^-- ptr2
  # ptr2 = ptr1 + 1
  #
  # # [1, 2, 3, 4]
  # #  ^-----^       <- copy this
  # #     ^------^   <- here
  # ptr1.move_to(ptr2, 3)
  #
  # ptr1.to_slice(4) # => Slice[1, 1, 2, 3]
  # ```
  def move_to(target : Pointer, count : Int)
    target.move_from_impl(self, count)
  end

  # We use separate method in which we make sure that `source`
  # is never a union of pointers. This is guaranteed because both
  # copy_from/move_from/copy_to/move_to reverse self and caller,
  # and so if either self or the arguments are unions a dispatch
  # will happen and unions will disappear.
  protected def copy_from_impl(source : Pointer(T), count : Int)
    raise ArgumentError.new("Negative count") if count < 0

    if self.class == source.class
      Intrinsics.memcpy(self.as(Void*), source.as(Void*), bytesize(count), false)
    else
      while count > 0
        count &-= 1
        self[count] = source[count]
      end
    end
    self
  end

  protected def move_from_impl(source : Pointer(T), count : Int)
    raise ArgumentError.new("Negative count") if count < 0

    if self.class == source.class
      Intrinsics.memmove(self.as(Void*), source.as(Void*), bytesize(count), false)
    else
      if source.address < address
        copy_from source, count
      else
        count.times do |i|
          self[i] = source[i]
        end
      end
    end
    self
  end

  # Compares *count* elements from this pointer and *other*, lexicographically.
  #
  # Returns 0 if both pointers point to the same sequence of *count* bytes.
  # Otherwise, if the first two differing bytes (treated as UInt8) from `self`
  # and *other* are `x` and `y` respectively, returns a negative value if
  # `x < y`, or a positive value if `x > y`.
  #
  # ```
  # ptr1 = Pointer.malloc(4) { |i| i + 1 }
  # ptr2 = Pointer.malloc(4) { |i| i + 11 }
  #
  # ptr1.to_slice(4) # => Slice[1, 2, 3, 4]
  # ptr2.to_slice(4) # => Slice[11, 12, 13, 14]
  #
  # ptr1.memcmp(ptr2, 4) < 0  # => true
  # ptr2.memcmp(ptr1, 4) > 0  # => true
  # ptr1.memcmp(ptr1, 4) == 0 # => true
  # ```
  def memcmp(other : Pointer(T), count : Int) : Int32
    LibC.memcmp(self.as(Void*), (other.as(Void*)), (count * sizeof(T)))
  end

  # Swaps the contents pointed at the offsets *i* and *j*.
  #
  # ```
  # ptr = Pointer.malloc(4) { |i| i + 1 }
  # ptr[2] # => 3
  # ptr[3] # => 4
  # ptr.swap(2, 3)
  # ptr[2] # => 4
  # ptr[3] # => 3
  # ```
  def swap(i, j)
    self[i], self[j] = self[j], self[i]
  end

  # Returns the address of this pointer.
  #
  # ```
  # ptr = Pointer(Int32).new(1234)
  # ptr.hash # => 1234
  # ```
  def_hash address

  # Appends a string representation of this pointer to the given `IO`,
  # including its type and address in hexadecimal.
  #
  # ```
  # ptr1 = Pointer(Int32).new(1234)
  # ptr1.to_s # => "Pointer(Int32)@0x4d2"
  #
  # ptr2 = Pointer(Int32).new(0)
  # ptr2.to_s # => "Pointer(Int32).null"
  # ```
  def to_s(io : IO) : Nil
    io << {{ @type.name.stringify }}
    if address == 0
      io << ".null"
    else
      io << "@0x"
      address.to_s(io, 16)
    end
  end

  # Tries to change the size of the allocation pointed to by this pointer to *size*,
  # and returns that pointer.
  #
  # Since the space after the end of the block may be in use, realloc may find it
  # necessary to copy the block to a new address where more free space is available.
  # The value of realloc is the new address of the block.
  # If the block needs to be moved, realloc copies the old contents.
  #
  # Remember to always assign the value of realloc.
  #
  # ```
  # ptr = Pointer.malloc(4) { |i| i + 1 }
  # ptr.to_slice(4) # => Slice[1, 2, 3, 4]
  #
  # ptr = ptr.realloc(8)
  # ptr.to_slice(8) # => Slice[1, 2, 3, 4, 0, 0, 0, 0]
  # ```
  #
  # WARNING: Memory allocated using `GC.malloc` or `GC.malloc_atomic` must be
  # reallocated using `GC.realloc` instead.
  def realloc(size : Int)
    if size < 0
      raise ArgumentError.new("Negative size")
    end

    realloc(size.to_u64)
  end

  # Shuffles *count* consecutive values pointed by this pointer.
  #
  # ```
  # ptr = Pointer.malloc(4) { |i| i + 1 }
  # ptr.to_slice(4) # => Slice[1, 2, 3, 4]
  # ptr.shuffle!(4)
  # ptr.to_slice(4) # => Slice[3, 4, 1, 2]
  # ```
  def shuffle!(count : Int, random : Random? = nil)
    rng = random || Random.thread_default
    (count - 1).downto(1) do |i|
      j = rng.rand(i + 1)
      swap(i, j)
    end
    self
  end

  # Sets *count* consecutive values pointed by this pointer to the
  # values returned by the block.
  #
  # ```
  # ptr = Pointer.malloc(4) { |i| i + 1 }
  # ptr.to_slice(4) # => Slice[1, 2, 3, 4]
  # ptr.map!(4) { |value| value * 2 }
  # ptr.to_slice(4) # => Slice[2, 4, 6, 8]
  # ```
  def map!(count : Int, & : T -> T) : self
    fill(count) do |i|
      yield self[i]
    end
  end

  # Like `map!`, but yields 2 arguments, the element and its index
  #
  # Accepts an optional *offset* parameter, which tells it to start counting
  # from there.
  def map_with_index!(count : Int, offset = 0, &block) : self
    fill(count) do |i|
      yield self[i], offset + i
    end
  end

  # Replaces *count* elements in `self` with *value*. Returns `self`.
  #
  # ```
  # ptr = Pointer(Int32).malloc(5) { |i| i }
  # ptr.to_slice(5) # => Slice[0, 1, 2, 3, 4]
  # ptr.fill(3, 0)
  # ptr.to_slice(5) # => Slice[0, 0, 0, 0, 4]
  # ```
  def fill(count : Int, value : T) : self
    {% if T == UInt8 %}
      Intrinsics.memset(self.as(Void*), value, count, false)
      self
    {% else %}
      {% if Number::Primitive.union_types.includes?(T) %}
        if value == 0
          clear(count)
          return self
        end
      {% end %}

      fill(count) { value }
    {% end %}
  end

  # Yields *count* indices starting from `self` to the given block and then
  # assigns the block's output value in that position. Returns `self`.
  #
  # ```
  # ptr = Pointer(Int32).malloc(5) { |i| i }
  # ptr.to_slice(5) # => Slice[0, 1, 2, 3, 4]
  #
  # (ptr + 1).fill(3) { |i| i * i }
  # ptr.to_slice(5) # => Slice[0, 0, 1, 4, 4]
  #
  # (ptr + 1).fill(3, offset: 3) { |i| i * i }
  # ptr.to_slice(5) # => Slice[0, 9, 16, 25, 4]
  # ```
  def fill(count : Int, *, offset : Int = 0, &) : self
    count.times do |i|
      self[i] = yield i + offset
    end
    self
  end

  # Returns a pointer with the address of this pointer
  # aligned downwards to the next given byte *boundary*.
  #
  # INFO: This method requires the given parameter to be a power of 2
  #
  # INFO: This method aligns on byte boundaries, not sizeof(T) boundaries
  #
  # ```
  # ptr = Pointer(Void).new(0x30_u64)
  # ptr.align_down(16) # => Pointer(Void)@0x30
  # ptr.align_down(32) # => Pointer(Void)@0x20
  # ```
  @[AlwaysInline]
  def align_down(boundary : UInt64) : Pointer(T)
    Pointer(T).new(self.address & (&-boundary))
  end

  # Returns a pointer with the address of this pointer
  # aligned upwards to the next given byte *boundary*.
  #
  # INFO: This method requires the given parameter to be a power of 2
  #
  # INFO: This method aligns on byte boundaries, not sizeof(T) boundaries
  #
  # ```
  # ptr = Pointer(Void).new(0x30_u64)
  # ptr.align_up(16) # => Pointer(Void)@0x30
  # ptr.align_up(32) # => Pointer(Void)@0x40
  # ```
  @[AlwaysInline]
  def align_up(boundary : UInt64) : Pointer(T)
    Pointer(T).new((self.address &+ (boundary &- 1)) & (&-boundary))
  end

  # :nodoc:
  #
  # This definition is required for the invalid `Pointer.new` method to not get
  # documented. This will never be called, the compiler will fail to compile.
  def initialize
    raise "can't create instance of a pointer type"
  end

  # Returns a pointer whose memory address is zero. This doesn't allocate memory.
  #
  # When calling a C function you can also pass `nil` instead of constructing a
  # null pointer with this method.
  #
  # ```
  # ptr = Pointer(Int32).null
  # ptr.address # => 0
  # ```
  def self.null
    new 0_u64
  end

  # Returns a pointer that points to the given memory address. This doesn't allocate memory.
  #
  # ```
  # ptr = Pointer(Int32).new(5678)
  # ptr.address # => 5678
  # ```
  @[Deprecated("Call `.new(UInt64)` directly instead")]
  def self.new(address : Int)
    new address.to_u64!
  end

  # Allocates `size * sizeof(T)` bytes from the system's heap initialized
  # to zero and returns a pointer to the first byte from that memory.
  # The memory is allocated by the `GC`, so when there are
  # no pointers to this memory, it will be automatically freed.
  #
  # ```
  # # Allocate memory for an Int32: 4 bytes
  # ptr = Pointer(Int32).malloc
  # ptr.value # => 0
  #
  # # Allocate memory for 10 Int32: 40 bytes
  # ptr = Pointer(Int32).malloc(10)
  # ptr[0] # => 0
  # # ...
  # ptr[9] # => 0
  # ```
  def self.malloc(size : Int = 1)
    if size < 0
      raise ArgumentError.new("Negative Pointer#malloc size")
    end

    malloc(size.to_u64)
  end

  # Allocates `size * sizeof(T)` bytes from the system's heap initialized
  # to *value* and returns a pointer to the first byte from that memory.
  # The memory is allocated by the `GC`, so when there are
  # no pointers to this memory, it will be automatically freed.
  #
  # ```
  # # An Int32 occupies 4 bytes, so here we are requesting 8 bytes
  # # initialized to the number 42
  # ptr = Pointer.malloc(2, 42)
  # ptr[0] # => 42
  # ptr[1] # => 42
  # ```
  def self.malloc(size : Int, value : T) : Pointer(T)
    ptr = Pointer(T).malloc(size)

    {% if Number::Primitive.union_types.includes?(T) %}
      return ptr if value.zero?
    {% elsif T < Pointer %}
      return ptr if value.null?
    {% end %}

    ptr.fill(size, value)
  end

  # Allocates `size * sizeof(T)` bytes from the system's heap initialized
  # to the value returned by the block (which is invoked once with each index in the range `0...size`)
  # and returns a pointer to the first byte from that memory.
  # The memory is allocated by the `GC`, so when there are
  # no pointers to this memory, it will be automatically freed.
  #
  # ```
  # # An Int32 occupies 4 bytes, so here we are requesting 16 bytes.
  # # i is an index in the range 0 .. 3
  # ptr = Pointer.malloc(4) { |i| i + 10 }
  # ptr[0] # => 10
  # ptr[1] # => 11
  # ptr[2] # => 12
  # ptr[3] # => 13
  # ```
  def self.malloc(size : Int, & : Int32 -> T) : Pointer(T)
    ptr = Pointer(T).malloc(size)
    ptr.fill(size) { |i| yield i }
  end

  # Returns a `Pointer::Appender` for this pointer.
  def appender : Pointer::Appender
    Pointer::Appender.new(self)
  end

  # Returns a `Slice` that points to this pointer and is bounded by the given *size*.
  #
  # ```
  # ptr = Pointer.malloc(6) { |i| i + 10 } # [10, 11, 12, 13, 14, 15]
  # slice = ptr.to_slice(4)                # => Slice[10, 11, 12, 13]
  # slice.class                            # => Slice(Int32)
  # ```
  def to_slice(size) : Slice(T)
    Slice.new(self, size)
  end

  # Clears (sets to "zero" bytes) a number of values pointed by this pointer.
  #
  # ```
  # ptr = Pointer.malloc(6) { |i| i + 10 } # [10, 11, 12, 13, 14, 15]
  # ptr.clear(3)
  # ptr.to_slice(6) # => Slice[0, 0, 0, 13, 14, 15]
  # ```
  def clear(count = 1)
    Intrinsics.memset(self.as(Void*), 0_u8, bytesize(count), false)
  end

  def clone
    self
  end

  private def bytesize(count)
    {% if flag?(:bits64) %}
      count.to_u64 * sizeof(T)
    {% else %}
      if count > UInt32::MAX
        raise ArgumentError.new("Given count is bigger than UInt32::MAX")
      end

      count.to_u32 * sizeof(T)
    {% end %}
  end
end
