# A local interprocess communication clientsocket.
#
# Available on UNIX-like operating systems, and Windows 10 Build 17063 or above.
# Not all features are supported on Windows.
#
# NOTE: To use `UNIXSocket`, you must explicitly import it with `require "socket"`
#
# Example usage:
# ```
# require "socket"
#
# sock = UNIXSocket.new("/tmp/myapp.sock")
# sock.puts "message"
# response = sock.gets
# sock.close
# ```
class UNIXSocket < Socket
  getter path : String?

  # Connects a named UNIX socket, bound to a filesystem pathname.
  def initialize(path : Path | String, type : Type = Type::STREAM)
    @path = path = path.to_s
    super(Family::UNIX, type, Protocol::IP)

    connect(UNIXAddress.new(path)) do |error|
      close
      raise error
    end
  end

  protected def initialize(family : Family, type : Type)
    super family, type, Protocol::IP
  end

  # Internal constructor for `UNIXSocket#pair` and `UNIXServer#accept?`.
  # The *blocking* arg is purely informational.
  protected def initialize(*, handle : Handle, type : Type = Type::STREAM, path : Path | String? = nil, blocking : Bool = nil)
    @path = path.to_s
    super handle: handle, family: Family::UNIX, type: type, protocol: Protocol::IP, blocking: blocking
  end

  # Creates an UNIXSocket from an existing system file descriptor or socket
  # handle.
  #
  # This adopts the system socket into the IO system that will reconfigure it as
  # per the event loop runtime requirements.
  #
  # NOTE: On Windows, the handle must have been created with
  # `WSA_FLAG_OVERLAPPED`.
  def initialize(*, fd : Handle, type : Type = Type::STREAM, path : Path | String? = nil)
    @path = path.to_s
    super fd, Family::UNIX, type, Protocol::IP
  end

  # Opens an UNIX socket to a filesystem pathname, yields it to the block, then
  # eventually closes the socket when the block returns.
  #
  # Returns the value of the block.
  def self.open(path : Path | String, type : Type = Type::STREAM, &)
    sock = new(path, type)
    begin
      yield sock
    ensure
      sock.close
    end
  end

  # Returns a pair of unnamed UNIX sockets.
  #
  # [Not supported on Windows](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable).
  #
  # ```
  # require "socket"
  #
  # left, right = UNIXSocket.pair
  #
  # spawn do
  #   # echo server
  #   message = right.gets
  #   right.puts message
  # end
  #
  # left.puts "message"
  # left.gets # => "message"
  # ```
  def self.pair(type : Type = Type::STREAM) : {UNIXSocket, UNIXSocket}
    fds, blocking = Crystal::EventLoop.current.socketpair(type, Protocol::IP)
    fds.map do |fd|
      sock = UNIXSocket.new(handle: fd, type: type, blocking: blocking)
      sock.sync = true
      sock
    end
  end

  # Creates a pair of unnamed UNIX sockets (see `pair`) and yields them to the
  # block. Eventually closes both sockets when the block returns.
  #
  # Returns the value of the block.
  #
  # [Not supported on Windows](https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/#unsupportedunavailable).
  def self.pair(type : Type = Type::STREAM, &)
    left, right = pair(type)
    begin
      yield left, right
    ensure
      left.close
      right.close
    end
  end

  def local_address : Socket::UNIXAddress
    UNIXAddress.new(path.to_s)
  end

  def remote_address : Socket::UNIXAddress
    UNIXAddress.new(path.to_s)
  end

  def receive(max_message_size = 512) : {String, UNIXAddress}
    message, address = super(max_message_size)
    {message, address.as(UNIXAddress)}
  end
end
