/*
 * call-seq:
 *   socket.getsockopt(option)
 *
 * Retrieves the value of the specified 0MQ socket option.
 *
 * The following options can be retrievesd with the getsockopt() function:
 *
 * == ZMQ::RCVMORE: More message parts to follow
 * The ZMQ::RCVMORE option shall return a boolean value indicating if the
 * multi-part message currently being read from the specified socket has more
 * message parts to follow. If there are no message parts to follow or if the
 * message currently being read is not a multi-part message a value of false
 * shall be returned. Otherwise, a value of true shall be returned.
 *
 * Refer to send() and recv() for a detailed description of sending/receiving
 * multi-part messages.
 *
 * [Option value type] Boolean
 * [Option value unit] N/A
 * [Default value] N/A
 * [Applicable socket types] all
 *
 * == ZMQ::HWM: Retrieve high water mark
 * The ZMQ::HWM option shall retrieve the high water mark for the specified
 * _socket_. The high water mark is a hard limit on the maximum number of
 * outstanding messages 0MQ shall queue in memory for any single peer that the
 * specified _socket_ is communicating with.
 *
 * If this limit has been reached the socket shall enter an exceptional state
 * and depending on the socket type, 0MQ shall take appropriate action such as
 * blocking or dropping sent messages. Refer to the individual socket
 * descriptions in ZMQ::Socket for details on the exact action taken for each
 * socket type.
 *
 * The default ZMQ::HWM value of zero means "no limit".
 *
 * [Option value type] Integer
 * [Option value unit] messages
 * [Default value] 0
 * [Applicable socket types] all
 *
 * == ZMQ::SWAP: Retrieve disk offload size
 * The ZMQ::SWAP option shall retrieve the disk offload (swap) size for the
 * specified _socket_. A socket which has ZMQ::SWAP set to a non-zero value may
 * exceed it’s high water mark; in this case outstanding messages shall be
 * offloaded to storage on disk rather than held in memory.
 *
 * The value of ZMQ::SWAP defines the maximum size of the swap space in bytes.
 *
 * [Option value type] Integer
 * [Option value unit] bytes
 * [Default value] 0
 * [Applicable socket types] all
 *
 * == ZMQ::AFFINITY: Retrieve I/O thread affinity
 * The ZMQ::AFFINITY option shall retrieve the I/O thread affinity for newly
 * created connections on the specified _socket_.
 *
 * Affinity determines which threads from the 0MQ I/O thread pool associated
 * with the socket’s _context_ shall handle newly created connections. A value of
 * zero specifies no affinity, meaning that work shall be distributed fairly
 * among all 0MQ I/O threads in the thread pool. For non-zero values, the lowest
 * bit corresponds to thread 1, second lowest bit to thread 2 and so on. For
 * example, a value of 3 specifies that subsequent connections on _socket_ shall
 * be handled exclusively by I/O threads 1 and 2.
 *
 * See also ZMQ::Context#new for details on allocating the number of
 * I/O threads for a specific _context_.
 *
 * [Option value type] Integer
 * [Option value unit] N/A (bitmap)
 * [Default value] 0
 * [Applicable socket types] all
 *
 * == ZMQ::IDENTITY: Retrieve socket identity
 * The ZMQ::IDENTITY option shall retrieve the identity of the specified _socket_.
 * Socket identity determines if existing 0MQ infastructure (<em>message queues</em>,
 * <em>forwarding devices</em>) shall be identified with a specific application and
 * persist across multiple runs of the application.
 *
 * If the socket has no identity, each run of an application is completely
 * separate from other runs. However, with identity set the socket shall re-use
 * any existing 0MQ infrastructure configured by the previous run(s). Thus the
 * application may receive messages that were sent in the meantime, <em>message
 * queue</em> limits shall be shared with previous run(s) and so on.
 *
 * Identity can be at least one byte and at most 255 bytes long. Identities
 * starting with binary zero are reserved for use by 0MQ infrastructure.
 *
 * [Option value type] String
 * [Option value unit] N/A
 * [Default value] nil
 * [Applicable socket types] all
 *
 * == ZMQ::RATE: Retrieve multicast data rate
 *
 * The ZMQ::Rate option shall retrieve the maximum send or receive data
 * rate for multicast transports using the specified _socket_.
 * 
 * [Option value type] Integer
 * [Option value unit] kilobits per second
 * [Default value] 100
 * [Applicable socket types] all, when using multicast transports
 *
 * == ZMQ::RECOVERY_IVL: Get multicast recovery interval
 *
 * The ZMQ::RECOVERY_IVL option shall retrieve the recovery interval for
 * multicast transports using the specified _socket_. The recovery interval
 * determines the maximum time in seconds that a receiver can be absent from a
 * multicast group before unrecoverable data loss will occur.
 *
 * [Option value type] Integer
 * [Option value unit] seconds
 * [Default value] 10
 * [Applicable socket types] all, when using multicast transports
 *
 * == ZMQ::RECOVERY_IVL_MSEC: Get multicast recovery interval in milliseconds
 * The ZMQ::RECOVERY_IVL_MSEC option shall retrieve the recovery interval, in
 * milliseconds (ms) for multicast transports using the specified socket. The
 * recovery interval determines the maximum time in milliseconds that a receiver
 * can be absent from a multicast group before unrecoverable data loss will occur.
 *
 * For backward compatibility, the default value of ZMQ::RECOVERY_IVL_MSEC is -1
 * indicating that the recovery interval should be obtained from the
 * ZMQ::RECOVERY_IVL option. However, if the ZMQ::RECOVERY_IVL_MSEC value is not
 * zero, then it will take precedence, and be used.
 *
 * [Option value type] Integer
 * [Option value unit] milliseconds
 * [Default value] -1
 * [Applicable socket types] all, when using multicast transports
 *
 * == ZMQ::MCAST_LOOP: Control multicast loopback
 * The ZMQ::MCAST_LOOP option controls whether data sent via multicast transports
 * can also be received by the sending host via loopback. A value of zero
 * indicates that the loopback functionality is disabled, while the default
 * value of 1 indicates that the loopback functionality is enabled. Leaving
 * multicast loopback enabled when it is not required can have a negative impact
 * on performance. Where possible, disable ZMQ::MCAST_LOOP in production
 * environments.
 *
 * [Option value type] Boolean
 * [Option value unit] N/A
 * [Default value] true
 * [Applicable socket types] all, when using multicast transports
 *
 * == ZMQ::SNDBUF: Retrieve kernel transmit buffer size
 * The ZMQ::SNDBUF option shall retrieve the underlying kernel transmit buffer
 * size for the specified _socket_. A value of zero means that the OS default is
 * in effect. For details refer to your operating system documentation for the
 * SO_SNDBUF socket option.
 * 
 * [Option value type] Integer
 * [Option value unit] bytes
 * [Default value] 0
 * [Applicable socket types] all
 *
 * == ZMQ::RCVBUF: Retrieve kernel receive buffer size
 * The ZMQ::RCVBUF option shall retrieve the underlying kernel receive buffer
 * size for the specified _socket_. A value of zero means that the OS default is
 * in effect. For details refer to your operating system documentation for the
 * SO_RCVBUF socket option.
 * 
 * [Option value type] Integer
 * [Option value unit] bytes
 * [Default value] 0
 * [Applicable socket types] all
 *
 * == ZMQ::LINGER: Retrieve linger period for socket shutdown
 * The ZMQ::LINGER option shall retrieve the linger period for the specified
 * socket. The linger period determines how long pending messages which have
 * yet to be sent to a peer shall linger in memory after a socket is closed
 * with ZMQ::Socket#close(), and further affects the termination of the
 * socket’s context with ZMQ#close(). The following outlines the different
 * behaviours:
 *
 * * The default value of −1 specifies an infinite linger period.
 *   Pending messages shall not be discarded after a call to ZMQ::Socket#close();
 *   attempting to terminate the socket’s context with ZMQ::Context#close() shall block
 *   until all pending messages have been sent to a peer.
 *
 * * The value of 0 specifies no linger period. Pending messages shall be
 *   discarded immediately when the socket is closed with ZMQ::Socket#close.
 *
 * * Positive values specify an upper bound for the linger period in
 *   milliseconds. Pending messages shall not be discarded after a call to
 *   ZMQ::Socket#close(); attempting to terminate the socket’s context with
 *   ZMQ::Context#close() shall block until either all pending messages have been sent
 *   to a peer, or the linger period expires, after which any pending messages
 *   shall be discarded.
 *
 * [Option value type] Integer
 * [Option value unit] milliseconds
 * [Default value] -1 (infinite)
 * [Applicable socket types] all
 *
 * == ZMQ::RECONNECT_IVL: Retrieve reconnection interval
 * The ZMQ::RECONNECT_IVL option shall retrieve the reconnection interval for
 * the specified socket. The reconnection interval is the maximum period 0MQ
 * shall wait between attempts to reconnect disconnected peers when using
 * connection−oriented transports.
 *
 * [Option value type] Integer
 * [Option value unit] milliseconds
 * [Default value] 100
 * [Applicable socket types] all, only for connection-oriented transports
 *
 * == ZMQ::RECONNECT_IVL_MAX: Retrieve maximum reconnection interval
 * The ZMQ::RECONNECT_IVL_MAX option shall set the maximum reconnection interval
 * for the specified socket. This is the maximum period ØMQ shall wait between
 * attempts to reconnect. On each reconnect attempt, the previous interval
 * shall be doubled untill ZMQ::RECONNECT_IVL_MAX is reached. This allows for
 * exponential backoff strategy. Default value means no exponential backoff
 * is performed and reconnect interval calculations are only based on
 * ZMQ::RECONNECT_IVL.
 *
 * Values less than ZMQ::RECONNECT_IVL will be ignored.
 *
 * [Option value type] Integer
 * [Option value unit] milliseconds
 * [Default value] 0 (only use RECONNECT_IVL)
 * [Applicable socket types] all, only for connection-oriented transports
 *
 * == ZMQ::BACKLOG: Retrieve maximum length of the queue of outstanding connections
 * The ZMQ::BACKLOG option shall retrieve the maximum length of the queue of
 * outstanding peer connections for the specified socket; this only applies to
 * connection−oriented transports. For details refer to your operating system
 * documentation for the listen function.
 *
 * [Option value type] Integer
 * [Option value unit] connections
 * [Default value] 100
 * [Applicable socket types] all, only for connection-oriented transports
 *
 * == ZMQ::FD: Retrieve file descriptor associated with the socket
 * The ZMQ::FD option shall retrieve the file descriptor associated with the
 * specified socket. The returned file descriptor can be used to integrate the
 * socket into an existing event loop; the 0MQ library shall signal any pending
 * events on the socket in an edge−triggered fashion by making the file
 * descriptor become ready for reading.
 *
 * === Note
 * The ability to read from the returned file descriptor does not necessarily
 * indicate that messages are available to be read from, or can be written to,
 * the underlying socket; applications must retrieve the actual event state
 * with a subsequent retrieval of the ZMQ::EVENTS option.
 *
 * === Caution
 * The returned file descriptor is intended for use with a poll or similar
 * system call only. Applications must never attempt to read or write data
 * to it directly.
 *
 * [Option value type] int on POSIX systems, SOCKT on Windows
 * [Option value unit] N/A
 * [Default value] N/A
 * [Applicable socket types] all
 *
 * == ZMQ::EVENTS: Retrieve socket event state
 * The ZMQ::EVENTS option shall retrieve the event state for the specified
 * socket. The returned value is a bit mask constructed by OR’ing a combination
 * of the following event flags:
 *
 * === ZMQ::POLLIN
 * Indicates that at least one message may be received from the specified
 * socket without blocking.
 *
 * == ZMQ::POLLOUT
 * Indicates that at least one message may be sent to the specified socket
 * without blocking.
 *
 * The combination of a file descriptor returned by the ZMQ::FD option being
 * ready for reading but no actual events returned by a subsequent retrieval of
 * the ZMQ::EVENTS option is valid; applications should simply ignore this case
 * and restart their polling operation/event loop.
 *
 * [Option value type] uint32_t
 * [Option value unit] N/A (flags)
 * [Default value] N/A
 * [Applicable socket types] all
 *
 */
static VALUE socket_getsockopt (VALUE self_, VALUE option_)
{
    int rc = 0;
    VALUE retval;
    void * s;
    
    Data_Get_Struct (self_, void, s);
    Check_Socket (s);
  
    switch (NUM2INT (option_)) {
#if ZMQ_VERSION >= 20100
        case ZMQ_FD:
        {
#ifdef _WIN32
                        SOCKET optval;
#else
                        int optval;
#endif
            size_t optvalsize = sizeof(optval);

            rc = zmq_getsockopt (s, NUM2INT (option_), (void *)&optval,
                                 &optvalsize);

            if (rc != 0) {
              rb_raise (exception_type, "%s", zmq_strerror (zmq_errno ()));
              return Qnil;
            }

            if (NUM2INT (option_) == ZMQ_RCVMORE)
                retval = optval ? Qtrue : Qfalse;
            else
                retval = INT2NUM (optval);
        }
        break;
        case ZMQ_EVENTS:
        {
            uint32_t optval;
            size_t optvalsize = sizeof(optval);

            rc = zmq_getsockopt (s, NUM2INT (option_), (void *)&optval,
                                 &optvalsize);

            if (rc != 0) {
              rb_raise (exception_type, "%s", zmq_strerror (zmq_errno ()));
              return Qnil;
            }

            if (NUM2INT (option_) == ZMQ_RCVMORE)
                retval = optval ? Qtrue : Qfalse;
            else
                retval = INT2NUM (optval);
        }
        break;
        case ZMQ_TYPE:
        case ZMQ_LINGER:
        case ZMQ_RECONNECT_IVL:
        case ZMQ_BACKLOG:
#if ZMQ_VERSION >= 20101
        case ZMQ_RECONNECT_IVL_MAX:
        case ZMQ_RECOVERY_IVL_MSEC:
#endif
        {
            int optval;
            size_t optvalsize = sizeof(optval);

            rc = zmq_getsockopt (s, NUM2INT (option_), (void *)&optval,
                                 &optvalsize);

            if (rc != 0) {
              rb_raise (exception_type, "%s", zmq_strerror (zmq_errno ()));
              return Qnil;
            }

            if (NUM2INT (option_) == ZMQ_RCVMORE)
                retval = optval ? Qtrue : Qfalse;
            else
                retval = INT2NUM (optval);
        }
        break;
#endif
    case ZMQ_RCVMORE:
    case ZMQ_HWM:
    case ZMQ_SWAP:
    case ZMQ_AFFINITY:
    case ZMQ_RATE:
    case ZMQ_RECOVERY_IVL:
    case ZMQ_MCAST_LOOP:
    case ZMQ_SNDBUF:
    case ZMQ_RCVBUF:
        {
            int64_t optval;
            size_t optvalsize = sizeof(optval);

            rc = zmq_getsockopt (s, NUM2INT (option_), (void *)&optval,
                                 &optvalsize);

            if (rc != 0) {
              rb_raise (exception_type, "%s", zmq_strerror (zmq_errno ()));
              return Qnil;
            }

            if (NUM2INT (option_) == ZMQ_RCVMORE)
                retval = optval ? Qtrue : Qfalse;
            else
                retval = INT2NUM (optval);
        }
        break;
    case ZMQ_IDENTITY:
        {
            char identity[255];
            size_t optvalsize = sizeof (identity);

            rc = zmq_getsockopt (s, NUM2INT (option_), (void *)identity,
                                 &optvalsize);

            if (rc != 0) {
              rb_raise (exception_type, "%s", zmq_strerror (zmq_errno ()));
              return Qnil;
            }

            if (optvalsize > sizeof (identity))
                optvalsize = sizeof (identity);

            retval = rb_str_new (identity, optvalsize);
        }
        break;
    default:
        rb_raise (exception_type, "%s", zmq_strerror (EINVAL));
        return Qnil;
    }
  
    return retval;
}