/*  =========================================================================
    zframe - working with single message frames

    Copyright (c) the Contributors as noted in the AUTHORS file.
    This file is part of CZMQ, the high-level C binding for 0MQ:
    http://czmq.zeromq.org.

    This Source Code Form is subject to the terms of the Mozilla Public
    License, v. 2.0. If a copy of the MPL was not distributed with this
    file, You can obtain one at http://mozilla.org/MPL/2.0/.
    =========================================================================
*/

/*
@header
    The zframe class provides methods to send and receive single message
    frames across 0MQ sockets. A 'frame' corresponds to one zmq_msg_t. When
    you read a frame from a socket, the zframe_more() method indicates if the
    frame is part of an unfinished multipart message. The zframe_send method
    normally destroys the frame, but with the ZFRAME_REUSE flag, you can send
    the same frame many times. Frames are binary, and this class has no
    special support for text data.
@discuss
@end
*/

#include "czmq_classes.h"

//  zframe_t instances always have this tag as the first 4 octets of
//  their data, which lets us do runtime object typing & validation.
#define ZFRAME_TAG              0xcafe0002

//  Structure of our class

struct _zframe_t {
    uint32_t tag;               //  Object tag for runtime detection
    zmq_msg_t zmsg;             //  zmq_msg_t blob for frame
    int more;                   //  More flag, from last read
    uint32_t routing_id;        //  Routing ID back to sender, if any
#ifdef ZMQ_GROUP_MAX_LENGTH
    char group[ZMQ_GROUP_MAX_LENGTH + 1];
#endif
    zframe_destructor_fn *destructor;   //  Destructor for memory
    void * hint;                        //  Hint for destroying the memory
};

//  --------------------------------------------------------------------------
//  Constructor; if size is >0, allocates frame with that size, and if data
//  is not null, copies data into frame.

zframe_t *
zframe_new (const void *data, size_t size)
{
    zframe_t *self = (zframe_t *) zmalloc (sizeof (zframe_t));
    assert (self);
    self->tag = ZFRAME_TAG;
    if (size) {
        //  Catch heap exhaustion in this specific case
        if (zmq_msg_init_size (&self->zmsg, size)) {
            zframe_destroy (&self);
            return NULL;
        }
        if (data)
            memcpy (zmq_msg_data (&self->zmsg), data, size);
    }
    else
        zmq_msg_init (&self->zmsg);

    return self;
}


//  --------------------------------------------------------------------------
//  Create an empty (zero-sized) frame. The caller is responsible for
//  destroying the return value when finished with it.

zframe_t *
zframe_new_empty (void)
{
    zframe_t *self = (zframe_t *) zmalloc (sizeof (zframe_t));
    assert (self);
    self->tag = ZFRAME_TAG;
    zmq_msg_init (&self->zmsg);
    return self;
}


//  --------------------------------------------------------------------------
//  Destructor

void
zframe_destroy (zframe_t **self_p)
{
    assert (self_p);
    if (*self_p) {
        zframe_t *self = *self_p;
        assert (zframe_is (self));
        zmq_msg_close (&self->zmsg);
        self->tag = 0xDeadBeef;
        freen (self);

        *self_p = NULL;
    }
}


//  --------------------------------------------------------------------------
//  Create a frame with a specified string content.
//  The caller is responsible for destroying the return value when finished with it.

zframe_t *
zframe_from (const char *string)
{
    return zframe_new (string, strlen (string));
}

//  --------------------------------------------------------------------------
//  Create a new frame from memory. Taking ownership of the memory and calling the destructor
//  on destroy.

struct zmq_msg_free_arg {
    zframe_destructor_fn *destructor;
    void *hint;
};

static void
zmq_msg_free (void *data, void *hint) {
    struct zmq_msg_free_arg *arg = (struct zmq_msg_free_arg *)hint;
    arg->destructor (&arg->hint);

    freen (arg);
}

zframe_t *
zframe_frommem (void *data, size_t size, zframe_destructor_fn destructor, void *hint) {
    assert (data);

    zframe_t *self = (zframe_t *) zmalloc (sizeof (zframe_t));
    assert (self);
    self->tag = ZFRAME_TAG;
    self->destructor = destructor;
    self->hint = hint;

    struct zmq_msg_free_arg *arg = (struct zmq_msg_free_arg*) zmalloc (sizeof (struct zmq_msg_free_arg));
    arg->destructor = destructor;
    arg->hint = hint;

    //  Catch heap exhaustion in this specific case
    if (zmq_msg_init_data (&self->zmsg, data, size, zmq_msg_free, arg)) {
        freen (arg);
        zframe_destroy (&self);
        return NULL;
    }

    return self;
}


//  --------------------------------------------------------------------------
//  Receive frame from socket, returns zframe_t object or NULL if the recv
//  was interrupted. Does a blocking recv, if you want to not block then use
//  zpoller or zloop.

zframe_t *
zframe_recv (void *source)
{
    assert (source);
    void *handle = zsock_resolve (source);
    zframe_t *self = zframe_new (NULL, 0);
    assert (self);

    if (zmq_recvmsg (handle, &self->zmsg, 0) < 0) {
        zframe_destroy (&self);
        return NULL;            //  Interrupted or terminated
    }
    self->more = zsock_rcvmore (source);
#if defined (ZMQ_SERVER)
    //  Grab routing ID if we're reading from a SERVER socket (ZMQ 4.2 and later)
    if (zsock_type (source) == ZMQ_SERVER)
        self->routing_id = zmq_msg_routing_id (&self->zmsg);
#endif
#if defined (ZMQ_DISH)
    //  Grab group if we're reading from a DISH Socket (ZMQ 4.2 and later)
    if (zsock_type (source) == ZMQ_DISH)
        strcpy (self->group, zmq_msg_group (&self->zmsg));
#endif
    return self;
}


//  --------------------------------------------------------------------------
//  Send frame to socket, destroy after sending unless ZFRAME_REUSE is
//  set or the attempt to send the message errors out.

int
zframe_send (zframe_t **self_p, void *dest, int flags)
{
    assert (dest);
    assert (self_p);

    void *handle = zsock_resolve (dest);
    if (*self_p) {
        zframe_t *self = *self_p;
        assert (zframe_is (self));

        int send_flags = (flags & ZFRAME_MORE)? ZMQ_SNDMORE: 0;
        send_flags |= (flags & ZFRAME_DONTWAIT)? ZMQ_DONTWAIT: 0;
        if (flags & ZFRAME_REUSE) {
            zmq_msg_t copy;
            zmq_msg_init (&copy);
            if (zmq_msg_copy (&copy, &self->zmsg))
                return -1;
#if defined (ZMQ_SERVER)
            if (zsock_type (dest) == ZMQ_SERVER)
                zmq_msg_set_routing_id (&copy, self->routing_id);
#endif
#if defined (ZMQ_RADIO)
            if (zsock_type (dest) == ZMQ_RADIO)
                zmq_msg_set_group (&copy, self->group);
#endif
            if (zmq_sendmsg (handle, &copy, send_flags) == -1) {
                zmq_msg_close (&copy);
                return -1;
            }
        }
        else {
#if defined (ZMQ_SERVER)
            if (zsock_type (dest) == ZMQ_SERVER)
                zmq_msg_set_routing_id (&self->zmsg, self->routing_id);
#endif
#if defined (ZMQ_RADIO)
            if (zsock_type (dest) == ZMQ_RADIO)
                zmq_msg_set_group (&self->zmsg, self->group);
#endif
            if (zmq_sendmsg (handle, &self->zmsg, send_flags) >= 0)
                zframe_destroy (self_p);
            else
                return -1;
        }
    }
    return 0;
}


//  --------------------------------------------------------------------------
//  Return size of frame.

size_t
zframe_size (zframe_t *self)
{
    assert (self);
    assert (zframe_is (self));

    return zmq_msg_size (&self->zmsg);
}


//  --------------------------------------------------------------------------
//  Return pointer to frame data.

byte *
zframe_data (zframe_t *self)
{
    assert (self);
    assert (zframe_is (self));

    return (byte *) zmq_msg_data (&self->zmsg);
}


//  --------------------------------------------------------------------------
//  Return meta data property for frame.
//  The caller shall not modify or free the returned value, which shall be
//  owned by the message.

const char *
zframe_meta (zframe_t *self, const char *property)
{
#if (ZMQ_VERSION >= ZMQ_MAKE_VERSION (4, 1, 0))
    assert (self);
    assert (zframe_is (self));

    return zmq_msg_gets (&self->zmsg, property);
#else
    return NULL;
#endif
}


//  --------------------------------------------------------------------------
//  Create a new frame that duplicates an existing frame. If frame is null,
//  or memory was exhausted, returns null.

zframe_t *
zframe_dup (zframe_t *self)
{
    if (self) {
        assert (zframe_is (self));
        return zframe_new (zframe_data (self), zframe_size (self));
    }
    else
        return NULL;
}


//  --------------------------------------------------------------------------
//  Return frame data encoded as printable hex string, useful for 0MQ UUIDs.
//  Caller must free string when finished with it.

char *
zframe_strhex (zframe_t *self)
{
    assert (self);
    assert (zframe_is (self));

    static const char
        hex_char [] = "0123456789ABCDEF";

    size_t size = zframe_size (self);
    byte *data = zframe_data (self);
    char *hex_str = (char *) malloc (size * 2 + 1);
    if (!hex_str)
        return NULL;

    uint byte_nbr;
    for (byte_nbr = 0; byte_nbr < size; byte_nbr++) {
        hex_str [byte_nbr * 2 + 0] = hex_char [data [byte_nbr] >> 4];
        hex_str [byte_nbr * 2 + 1] = hex_char [data [byte_nbr] & 15];
    }
    hex_str [size * 2] = 0;
    return hex_str;
}


//  --------------------------------------------------------------------------
//  Return frame data copied into freshly allocated string
//  Caller must free string when finished with it.

char *
zframe_strdup (zframe_t *self)
{
    assert (self);
    assert (zframe_is (self));

    size_t size = zframe_size (self);
    char *string = (char *) malloc (size + 1);
    assert (string);
    memcpy (string, zframe_data (self), size);
    string [size] = 0;
    return string;
}


//  --------------------------------------------------------------------------
//  Return true if frame body is equal to string, excluding terminator

bool
zframe_streq (zframe_t *self, const char *string)
{
    assert (self);
    assert (zframe_is (self));

    if (zframe_size (self) == strlen (string)
    && memcmp (zframe_data (self), string, strlen (string)) == 0)
        return true;
    else
        return false;
}


//  --------------------------------------------------------------------------
//  Return frame MORE indicator (1 or 0), set when reading frame from socket
//  or by the zframe_set_more() method.

int
zframe_more (zframe_t *self)
{
    assert (self);
    assert (zframe_is (self));

    return self->more;
}


//  --------------------------------------------------------------------------
//  Set frame MORE indicator (1 or 0). Note this is NOT used when sending
//  frame to socket, you have to specify flag explicitly.

void
zframe_set_more (zframe_t *self, int more)
{
    assert (self);
    assert (zframe_is (self));
    assert (more == 0 || more == 1);

    self->more = more;
}


//  --------------------------------------------------------------------------
//  Return frame routing ID, if the frame came from a ZMQ_SERVER socket.
//  Else returns zero.

uint32_t
zframe_routing_id (zframe_t *self)
{
    assert (self);
    assert (zframe_is (self));
    return self->routing_id;
}


//  --------------------------------------------------------------------------
//  Set routing ID on frame. This is used if/when the frame is sent to a
//  ZMQ_SERVER socket.

void
zframe_set_routing_id (zframe_t *self, uint32_t routing_id)
{
    assert (self);
    assert (zframe_is (self));
    self->routing_id = routing_id;
}


//  --------------------------------------------------------------------------
//  Return frame group of radio-dish pattern.
const char *
zframe_group (zframe_t *self)
{
    assert (self);
#ifdef ZMQ_DISH
    return self->group;
#else
    return NULL;
#endif
}


//  --------------------------------------------------------------------------
//  Set group on frame. This is used if/when the frame is sent to a
//  ZMQ_RADIO socket.
//  Return -1 on error, 0 on success.
int
zframe_set_group (zframe_t *self, const char *group)
{
#ifdef ZMQ_RADIO
    if (strlen(group) > ZMQ_GROUP_MAX_LENGTH) {
        errno = EINVAL;
        return -1;
    }

    strcpy (self->group, group);
    return 0;
#else
    errno = ENOTSUP;
    return -1;
#endif
}


//  --------------------------------------------------------------------------
//  Return true if two frames have identical size and data

bool
zframe_eq (zframe_t *self, zframe_t *other)
{
    if (!self || !other)
        return false;           //  Tolerate null references here
    else {
        assert (zframe_is (self));
        assert (zframe_is (other));

        if (zframe_size (self) == zframe_size (other)
        &&  memcmp (zframe_data (self),
                    zframe_data (other),
                    zframe_size (self)) == 0)
            return true;
        else
            return false;
    }
}


//  --------------------------------------------------------------------------
//  Set new contents for frame

void
zframe_reset (zframe_t *self, const void *data, size_t size)
{
    assert (self);
    assert (zframe_is (self));
    assert (data);

    zmq_msg_close (&self->zmsg);
    zmq_msg_init_size (&self->zmsg, size);
    memcpy (zmq_msg_data (&self->zmsg), data, size);
}


//  --------------------------------------------------------------------------
//  Send message to zsys log sink (may be stdout, or system facility as
//  configured by zsys_set_logstream). Prefix shows before frame, if not null.

//  Send the specified number of chars.
//  If len is 0, then log 35 hex or 70 chars with ellipsis.
//  If 0 < len < frame size,
//  then log in blocks of 35 or 70 up to the specified length.
//
void
zframe_print_n (zframe_t *self, const char *prefix, size_t length)
{
    assert (self);
    assert (zframe_is (self));

    byte *data = zframe_data (self);
    size_t size = zframe_size (self);

    //  Probe data to check if it looks like unprintable binary
    int is_bin = 0;
    uint char_nbr;
    for (char_nbr = 0; char_nbr < size; char_nbr++)
        if (data [char_nbr] < 32 || data [char_nbr] > 127)
            is_bin = 1;

    char buffer [256] = "";
    size_t max_size = is_bin? 35: 70;
    const char *ellipsis = "";

    // backwards compatibility
    if (!length) {
        if (size > max_size) {
            size = max_size;
            ellipsis = "...";
        }

	length = size;
    }
    if (length > size)
        length = size;

    for (char_nbr = 0; char_nbr < length; char_nbr++) {
        if (!(char_nbr % max_size)) {
            if (char_nbr) zsys_debug (buffer);
            snprintf (buffer, 30, "%s[%03d] ", prefix? prefix: "", (int) size);
        }
        if (is_bin)
            sprintf (buffer + strlen (buffer), "%02X", (unsigned char) data [char_nbr]);
        else
            sprintf (buffer + strlen (buffer), "%c", data [char_nbr]);
    }
    strcat (buffer, ellipsis);
    zsys_debug (buffer);
}

void
zframe_print (zframe_t *self, const char *prefix)
{
    zframe_print_n (self, prefix, 0);
}


//  --------------------------------------------------------------------------
//  Probe the supplied object, and report if it looks like a zframe_t.

bool
zframe_is (void *self)
{
    assert (self);
    return ((zframe_t *) self)->tag == ZFRAME_TAG;
}


//  --------------------------------------------------------------------------
//  DEPRECATED as poor style -- callers should use zloop or zpoller
//  Receive a new frame off the socket. Returns newly allocated frame, or
//  NULL if there was no input waiting, or if the read was interrupted.

zframe_t *
zframe_recv_nowait (void *source)
{
    assert (source);
    void *handle = zsock_resolve (source);

    zframe_t *self = zframe_new (NULL, 0);
    assert (self);
    if (zmq_recvmsg (handle, &self->zmsg, ZMQ_DONTWAIT) < 0) {
        zframe_destroy (&self);
        return NULL;            //  Interrupted or terminated
    }
    self->more = zsock_rcvmore (source);
#if defined (ZMQ_SERVER)
    //  Grab routing ID if we're reading from a SERVER socket (ZMQ 4.2 and later)
    if (zsock_type (source) == ZMQ_SERVER)
        self->routing_id = zmq_msg_routing_id (&self->zmsg);
#endif
#if defined (ZMQ_DISH)
    //  Grab group if we're reading from a DISH Socket (ZMQ 4.2 and later)
    if (zsock_type (source) == ZMQ_DISH)
        strcpy (self->group, zmq_msg_group (&self->zmsg));
#endif
    return self;
}


//  --------------------------------------------------------------------------
//  DEPRECATED as inconsistent; breaks principle that logging should all go
//  to a single destination.
//  Print contents of frame to FILE stream, prefix is ignored if null.

void
zframe_fprint (zframe_t *self, const char *prefix, FILE *file)
{
    assert (self);
    assert (zframe_is (self));

    if (prefix)
        fprintf (file, "%s", prefix);
    byte *data = zframe_data (self);
    size_t size = zframe_size (self);

    int is_bin = 0;
    uint char_nbr;
    for (char_nbr = 0; char_nbr < size; char_nbr++)
        if (data [char_nbr] < 9 || data [char_nbr] > 127)
            is_bin = 1;

    fprintf (file, "[%03d] ", (int) size);
    size_t max_size = is_bin? 35: 70;
    const char *ellipsis = "";
    if (size > max_size) {
        size = max_size;
        ellipsis = "...";
    }
    for (char_nbr = 0; char_nbr < size; char_nbr++) {
        if (is_bin)
            fprintf (file, "%02X", (unsigned char) data [char_nbr]);
        else
            fprintf (file, "%c", data [char_nbr]);
    }
    fprintf (file, "%s\n", ellipsis);
}


//  --------------------------------------------------------------------------
//  Selftest

static void
mem_destructor (void **hint) {
    strcpy ((char*)*hint, "world");
}


void
zframe_test (bool verbose)
{
    printf (" * zframe: ");
    int rc;
    zframe_t* frame;

    //  @selftest
    //  Create two PAIR sockets and connect over TCP
    zsock_t *output = zsock_new (ZMQ_PAIR);
    assert (output);
    int port = zsock_bind (output, "tcp://127.0.0.1:*");
    assert (port != -1);
    zsock_t *input = zsock_new (ZMQ_PAIR);
    assert (input);
    rc = zsock_connect (input, "tcp://127.0.0.1:%d", port);
    assert (rc != -1);

    //  Send five different frames, test ZFRAME_MORE
    int frame_nbr;
    for (frame_nbr = 0; frame_nbr < 5; frame_nbr++) {
        frame = zframe_new ("Hello", 5);
        assert (frame);
        rc = zframe_send (&frame, output, ZFRAME_MORE);
        assert (rc == 0);
    }
    //  Send same frame five times, test ZFRAME_REUSE
    frame = zframe_new ("Hello", 5);
    assert (frame);
    for (frame_nbr = 0; frame_nbr < 5; frame_nbr++) {
        rc = zframe_send (&frame, output, ZFRAME_MORE + ZFRAME_REUSE);
        assert (rc == 0);
    }
    assert (frame);
    zframe_t *copy = zframe_dup (frame);
    assert (zframe_eq (frame, copy));
    zframe_destroy (&frame);
    assert (!zframe_eq (frame, copy));
    assert (zframe_size (copy) == 5);
    zframe_destroy (&copy);
    assert (!zframe_eq (frame, copy));

    //  Test zframe_new_empty
    frame = zframe_new_empty ();
    assert (frame);
    assert (zframe_size (frame) == 0);
    zframe_destroy (&frame);

    //  Send END frame
    frame = zframe_new ("NOT", 3);
    assert (frame);
    zframe_reset (frame, "END", 3);
    char *string = zframe_strhex (frame);
    assert (streq (string, "454E44"));
    freen (string);
    string = zframe_strdup (frame);
    assert (streq (string, "END"));
    freen (string);
    rc = zframe_send (&frame, output, 0);
    assert (rc == 0);

    //  Read and count until we receive END
    frame_nbr = 0;
    for (frame_nbr = 0;; frame_nbr++) {
        zframe_t *frame = zframe_recv (input);
        if (zframe_streq (frame, "END")) {
            zframe_destroy (&frame);
            break;
        }
        assert (zframe_more (frame));
        zframe_set_more (frame, 0);
        assert (zframe_more (frame) == 0);
        zframe_destroy (&frame);
    }
    assert (frame_nbr == 10);

#if (ZMQ_VERSION >= ZMQ_MAKE_VERSION (4, 1, 0))
    // Test zframe_meta
    frame = zframe_new ("Hello", 5);
    assert (frame);
    rc = zframe_send (&frame, output, 0);
    assert (rc == 0);
    frame = zframe_recv (input);
    const char *meta = zframe_meta (frame, "Socket-Type");
    assert (meta != NULL);
    assert (streq (meta, "PAIR"));
    assert (zframe_meta (frame, "nonexistent") == NULL);
    zframe_destroy (&frame);
#endif

    zsock_destroy (&input);
    zsock_destroy (&output);

#if defined (ZMQ_SERVER)
    //  Create server and client sockets and connect over inproc
    zsock_t *server = zsock_new_server ("inproc://zframe-test-routing");
    assert (server);
    zsock_t *client = zsock_new_client ("inproc://zframe-test-routing");
    assert (client);

    //  Send request from client to server
    zframe_t *request = zframe_new ("Hello", 5);
    assert (request);
    rc = zframe_send (&request, client, 0);
    assert (rc == 0);
    assert (!request);

    //  Read request and send reply
    request = zframe_recv (server);
    assert (request);
    assert (zframe_streq (request, "Hello"));
    assert (zframe_routing_id (request));

    zframe_t *reply = zframe_new ("World", 5);
    assert (reply);
    zframe_set_routing_id (reply, zframe_routing_id (request));
    rc = zframe_send (&reply, server, 0);
    assert (rc == 0);
    zframe_destroy (&request);

    //  Read reply
    reply = zframe_recv (client);
    assert (zframe_streq (reply, "World"));
    assert (zframe_routing_id (reply) == 0);
    zframe_destroy (&reply);

    //  Client and server disallow multipart
    frame = zframe_new ("Hello", 5);
    rc = zframe_send (&frame, client, ZFRAME_MORE);
    assert (rc == -1);
    rc = zframe_send (&frame, server, ZFRAME_MORE);
    assert (rc == -1);
    zframe_destroy (&frame);

    zsock_destroy (&client);
    zsock_destroy (&server);
#endif

#ifdef ZMQ_RADIO
    //  Create radio and dish sockets and connect over inproc
    zsock_t *radio = zsock_new_radio ("inproc://zframe-test-radio");
    assert (radio);
    zsock_t *dish = zsock_new_dish ("inproc://zframe-test-radio");
    assert (dish);

    //  Join the group
    rc = zsock_join (dish, "World");
    assert (rc == 0);

    //  Publish message from radio
    zframe_t *message = zframe_new ("Hello", 5);
    assert (message);
    rc = zframe_set_group (message, "World");
    assert (rc == 0);
    rc = zframe_send (&message, radio, 0);
    assert (rc == 0);
    assert (!message);

    //  Receive the message from dish
    message = zframe_recv (dish);
    assert (message);
    assert (zframe_streq (message, "Hello"));
    assert (strcmp("World", zframe_group (message)) == 0);
    zframe_destroy (&message);

    zsock_destroy (&dish);
    zsock_destroy (&radio);
#else
    frame = zframe_new ("Hello", 5);
    rc = zframe_set_group (frame, "World");
    assert(rc == -1);
    assert(errno == ENOTSUP);
    zframe_destroy (&frame);
#endif

    char str[] = "hello";
    frame = zframe_frommem (str, 5, mem_destructor, str);
    assert (frame);
    zframe_destroy (&frame);

    //  The destructor doesn't free the memory, only changing the strid,
    //  so we can check if the destructor was invoked
    assert (streq (str, "world"));


    // zframe_print tests

    zsys_set_logstream(verbose ? stdout : NULL);

    // == No data ==
    frame = zframe_new ("", 0);

    // no prefix, backwards compatible
    //  - emits nothing but the timestamp
    zframe_print (frame, "");
    zframe_print_n (frame, "", 0);

    // prefix, backwards compatible
    //  - emits nothing but the timestamp
    zframe_print (frame, "Prefix");
    zframe_print_n (frame, "Prefix", 0);

    // len > data
    //  - emits nothing but the timestamp
    zframe_print_n (frame, "", 15);
    zframe_print_n (frame, "Prefix", 15);

    // max len
    //  - emits nothing but the timestamp
    zframe_print_n (frame, "", -1);
    zframe_print_n (frame, "Prefix", -1);

    zframe_destroy (&frame);


    // == Short data ==
    frame = zframe_new ("Hello there!", 12);

    // no prefix, backwards compatible: ellipsis
    //  - "[012] Hello there!"
    zframe_print (frame, "");
    zframe_print_n (frame, "", 0);

    // prefix, backwards compatible: ellipsis
    //  - "Prefix[012] Hello there!"
    zframe_print (frame, "Prefix");
    zframe_print_n (frame, "Prefix", 0);

    // len < data
    //  - "[012] Hello"
    //  - "Prefix[012] Hello"
    zframe_print_n (frame, "", 5);
    zframe_print_n (frame, "Prefix", 5);

    // len > data
    //  - "[012] Hello there!"
    //  - "Prefix[012] Hello there!"
    zframe_print_n (frame, "", 15);
    zframe_print_n (frame, "Prefix", 15);

    // max len
    //  - "[012] Hello there!"
    //  - "Prefix[012] Hello there!"
    zframe_print_n (frame, "", -1);
    zframe_print_n (frame, "Prefix", -1);

    zframe_destroy (&frame);


    // == Long data ==
    frame = zframe_new ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus ligula et aliquam tristique. Phasellus consequat, enim et blandit varius, sapien diam faucibus lorem, non ultricies lacus turpis sed lectus. Vivamus id elit urna. In sit amet lacinia mauris. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Integer ut cursus diam. Vestibulum semper vel leo eu finibus. Ut urna magna, commodo vel auctor sed, eleifend quis lacus. Aenean quis ipsum et velit auctor ultrices.", 519);

    // no prefix, backwards compatible: ellipsis
    //  - "[070] Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus..."
    zframe_print (frame, "");
    zframe_print_n (frame, "", 0);

    // prefix, backwards compatible: ellipsis
    //  - "Prefix[070] Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus..."
    zframe_print (frame, "Prefix");
    zframe_print_n (frame, "Prefix", 0);

    // len < data
    //  - "[519] Lorem"
    //  - "Prefix[519] Lorem"
    zframe_print_n (frame, "", 5);
    zframe_print_n (frame, "Prefix", 5);

    // small len
    //  - "[519] Lorem ipsum dolor sit amet"
    //  - "Prefix[519] Lorem ipsum dolor sit amet"
    zframe_print_n (frame, "", 26);
    zframe_print_n (frame, "Prefix", 26);

    // mid len
    // - "[519] Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus"
    //   "[519]  ligula et aliquam tristique. Phasellus consequat, enim et blandit var"
    //   "[519] ius, sapie"
    // - "Prefix[519] Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin finibus"
    //   "Prefix[519]  ligula et aliquam tristique. Phasellus consequat, enim et blandit var"
    //   "Prefix[519] ius, sapie"
    zframe_print_n (frame, "", 150);
    zframe_print_n (frame, "Prefix", 150);

    // len > data
    //  - emits the whole paragraph
    zframe_print_n (frame, "", 1500);
    zframe_print_n (frame, "Prefix", 1500);

    // max len
    //  - emits the whole paragraph
    zframe_print_n (frame, "", -1);
    zframe_print_n (frame, "Prefix", -1);

    zframe_destroy (&frame);

#if defined (__WINDOWS__)
    zsys_shutdown();
#endif

    //  @end
    printf ("OK\n");
}
