
## Send/receive messages.

############################################################################

function _send(socket::Socket, zmsg, more::Bool=false)
    while true
        if -1 == lib.zmq_msg_send(zmsg, socket, (ZMQ_SNDMORE*more) | ZMQ_DONTWAIT)
            lib.zmq_errno() == EAGAIN || throw(StateError(jl_zmq_error_str()))
            while (socket.events & POLLOUT) == 0
                wait(socket)
            end
        else
            notify_is_expensive = !isempty(getfield(socket,:pollfd).watcher.notify.waitq)
            if notify_is_expensive
                socket.events != 0 && notify(socket)
            end
            break
        end
    end
end

# By default, we send using _Message objects, which are optimized for
# small messages and copy the data.    If the caller wants zero-copy communications,
# then should explicitly create a Message() object, a more heavyweight object
# that allows zero-copy access.

"""
    send(socket::Socket, data; more=false)

Send `data` over `socket`.  A `more=true` keyword argument can be passed
to indicate that `data` is a portion of a larger multipart message.
`data` can be any `isbits` type, a `Vector` of `isbits` elements, a
`String`, or a [`Message`](@ref) object to perform zero-copy sends
of large arrays.
"""
function Sockets.send(socket::Socket, data; more::Bool=false)
    zmsg = create_MessageRef(data)
    try
        _send(socket, zmsg, more)
    finally
        close(zmsg)
    end
end

"""
    send(socket::Socket, zmsg::Message; more::Bool=false)

Zero-copy version of [`Sockets.send(socket, data)`](@ref) using a user-allocated
[`Message`](@ref).
"""
Sockets.send(socket::Socket, zmsg::Message; more::Bool=false) = _send(socket, zmsg, more)

import Sockets: send
@deprecate send(socket::Socket, data, more::Bool) send(socket, data; more=more)

function Sockets.send(f::Function, socket::Socket; more::Bool=false)
    io = IOBuffer()
    f(io)
    send(socket, take!(io); more=more)
end

"""
    send_multipart(socket::Socket, parts)

Send a multipart message composed of the elements in `parts`. `parts` may be any
object that supports `getindex()`, `eachindex()`, and `lastindex()`.
"""
function send_multipart(socket::Socket, parts)
    for i in eachindex(parts)
        is_last = i == lastindex(parts)
        send(socket, parts[i]; more=!is_last)
    end
end

############################################################################

function _recv!(socket::Socket, zmsg)
    while true
        if -1 == lib.zmq_msg_recv(zmsg, socket, ZMQ_DONTWAIT)
            lib.zmq_errno() == EAGAIN || throw(StateError(jl_zmq_error_str()))
            while socket.events & POLLIN== 0
                # If there is no receive timeout, just wait on the socket
                if socket.rcvtimeo == -1
                    wait(socket)
                else
                    # Otherwise, implement a receive timeout for the wait. We
                    # can't rely on ZMQ's native blocking behaviour because that
                    # doesn't play nicely with Julia's tasks so instead we use
                    # poll_fd() with a timeout. Note that we bypass the Socket's
                    # own FDWatcher and pass the native ZMQ socket to poll_fd().
                    timeout_secs = socket.rcvtimeo / 1000
                    ret = poll_fd(fd(socket), timeout_secs; readable=true, writable=false)
                    if ret.timedout
                        throw(TimeoutError(repr(socket), timeout_secs))
                    end
                end
            end
        else
            notify_is_expensive = !isempty(getfield(socket,:pollfd).watcher.notify.waitq)
            if notify_is_expensive
                socket.events != 0 && notify(socket)
            end
            break
        end
    end
    return zmsg
end

"""
    recv(socket::Socket)

Return a `Message` object representing a message received from a ZMQ `Socket`
(without making a copy of the message data).

If [`rcvtimeo`](https://libzmq.readthedocs.io/en/latest/zmq_setsockopt.html) is
set then a [`TimeoutError`](@ref) will be thrown upon timeout. Note that because
of the integration with Julia's event loop there is some overhead to using
timeouts, on the order of ~10μs. All other `recv*()` methods also respect
`rcvtimeo`.
"""
Sockets.recv(socket::Socket) = _recv!(socket, Message())

"""
    recv(socket::Socket, ::Type{T})

Receive a message of type `T` (typically a `String`, `Vector{UInt8}`, or
[`isbits`](https://docs.julialang.org/en/v1/base/base/#Base.isbits) type)
from a ZMQ [`Socket`](@ref).  (Makes a copy of the message data; you can alternatively
use [`recv(socket)`](@ref) to work with zero-copy bytearray-like representation for
large messages.)

"""
function Sockets.recv(socket::Socket, ::Type{T}) where {T}
    zmsg = msg_init()
    try
        _recv!(socket, zmsg)
        return unsafe_copy(T, zmsg)
    finally
        close(zmsg)
    end
end

# Specialization so that recv(::Socket, ::Message) works
Sockets.recv(socket::Socket, ::Type{Message}) = recv(socket)

"""
    recv_multipart(socket::Socket, ::Type{T}) -> Vector{T}

Receive a multipart message of a specific type `T`. This behaves in the same way
as [`recv(::Socket, ::Type)`](@ref).
"""
function recv_multipart(socket::Socket, ::Type{T}) where {T}
    parts = T[recv(socket, T)]
    while socket.rcvmore
        push!(parts, recv(socket, T))
    end

    return parts
end

"""
    recv_multipart(socket::Socket) -> Vector{Message}

Receive a multipart message as a sequence of zero-copy [`Message`](@ref)'s. See
[`recv(::Socket)`](@ref).
"""
recv_multipart(socket::Socket) = recv_multipart(socket, Message)
