#
# Module Generated by Template::Tiny on [% date %]
#

package ZMQ::FFI::ZMQ3::Socket;

use FFI::Platypus;
use FFI::Platypus::Buffer;
use FFI::Platypus::Memory qw(malloc free memcpy);
use ZMQ::FFI::Constants qw(:all);
use Carp;
use Try::Tiny;

use Moo;
use namespace::clean;

no if $] >= 5.018, warnings => "experimental";
use feature 'switch';

with qw(
    ZMQ::FFI::SocketRole
    ZMQ::FFI::ErrorHandler
    ZMQ::FFI::Versioner
);

my $FFI_LOADED;

sub BUILD {
    my ($self) = @_;

    unless ($FFI_LOADED) {
        _load_common_ffi($self->soname);
        _load_zmq3_ffi($self->soname);
        $FFI_LOADED = 1;
    }

    try {
        # XXX
        # not clear why this is necessary, but the setter doesn't actually
        # take affect if you directly nest the zmq_socket call in the _socket
        # call... some Class::XSAccessor weirdness/bug? Need to investigate.
        my $s = zmq_socket($self->ctx->_ctx, $self->type);
        $self->_socket($s);
        $self->check_null('zmq_socket', $self->_socket);
    }
    catch {
        $self->_socket(-1);
        die $_;
    };

    # ensure clean edge state
    while ( $self->has_pollin ) {
        $self->recv();
    }
}

### ZMQ3 API ###

sub _load_zmq3_ffi {
    my ($soname) = @_;

    my $ffi = FFI::Platypus->new( lib => $soname );

    $ffi->attach(
        # int zmq_send(void *socket, void *buf, size_t len, int flags)
        'zmq_send' => ['pointer', 'string', 'size_t', 'int'] => 'int'
    );

    $ffi->attach(
        # int zmq_msg_recv(zmq_msg_t *msg, void *socket, int flags)
        'zmq_msg_recv' => ['pointer', 'pointer', 'int'] => 'int'
    );

    $ffi->attach(
        # int zmq_unbind(void *socket, const char *endpoint)
        'zmq_unbind' => ['pointer', 'string'] => 'int'
    );

    $ffi->attach(
        # int zmq_disconnect(void *socket, const char *endpoint)
        'zmq_disconnect' => ['pointer', 'string'] => 'int'
    );
}

#
# send/recv are hot spots, so sacrificing some readability for performance
#

sub send {
    # 0: self
    # 1: data
    # 2: flags

    use bytes;
    my $length = length($_[1]);
    no bytes;

    if ( -1 == zmq_send($_[0]->_socket, $_[1], $length, ($_[2] // 0)) ) {
        $_[0]->fatal('zmq_send');
    }
}

sub recv {
    # 0: self
    # 1: flags

    my $msg_ptr = malloc zmq_msg_t_size;

    if ( -1 == zmq_msg_init($msg_ptr) ) {
        $_[0]->fatal('zmq_msg_init');
    }

    my $msg_size = zmq_msg_recv($msg_ptr, $_[0]->_socket, $_[1] // 0);

    my $rv = '';
    if ( $msg_size == -1 ) {
        $_[0]->fatal('zmq_msg_recv');
    }
    elsif ($msg_size) {
        $rv = buffer_to_scalar(zmq_msg_data($msg_ptr), $msg_size);
    }

    zmq_msg_close($msg_ptr);
    return $rv;
}

sub disconnect {
    my ($self, $endpoint) = @_;

    unless ($endpoint) {
        croak 'usage: $socket->disconnect($endpoint)';
    }

    $self->check_error(
        'zmq_disconnect',
        zmq_disconnect($self->_socket, $endpoint)
    );
}

sub unbind {
    my ($self, $endpoint) = @_;

    unless ($endpoint) {
        croak 'usage: $socket->unbind($endpoint)';
    }

    $self->check_error(
        'zmq_unbind',
        zmq_unbind($self->_socket, $endpoint)
    );
}

[% zmq_common_api %]
1;