/*
 * Generic routines to deal with send buffers: a linked list of
 * smallish blocks, with the operations
 *
 *  - add an arbitrary amount of data to the end of the list
 *  - remove the first N bytes from the list
 *  - return a (pointer,length) pair giving some initial data in
 *    the list, suitable for passing to a send or write system
 *    call
 *  - retrieve a larger amount of initial data from the list
 *  - return the current size of the buffer chain in bytes
 */

#include "defs.h"
#include "misc.h"

#define BUFFER_MIN_GRANULE  512

struct bufchain_granule {
    struct bufchain_granule *next;
    char *bufpos, *bufend, *bufmax;
};

static void uninitialised_queue_idempotent_callback(IdempotentCallback *ic)
{
    unreachable("bufchain callback used while uninitialised");
}

void bufchain_init(bufchain *ch)
{
    ch->head = ch->tail = NULL;
    ch->buffersize = 0;
    ch->ic = NULL;
    ch->queue_idempotent_callback = uninitialised_queue_idempotent_callback;
}

void bufchain_clear(bufchain *ch)
{
    struct bufchain_granule *b;
    while (ch->head) {
        b = ch->head;
        ch->head = ch->head->next;
        smemclr(b, sizeof(*b));
        sfree(b);
    }
    ch->tail = NULL;
    ch->buffersize = 0;
}

size_t bufchain_size(bufchain *ch)
{
    return ch->buffersize;
}

void bufchain_set_callback_inner(
    bufchain *ch, IdempotentCallback *ic,
    void (*queue_idempotent_callback)(IdempotentCallback *ic))
{
    ch->queue_idempotent_callback = queue_idempotent_callback;
    ch->ic = ic;
}

void bufchain_add(bufchain *ch, const void *data, size_t len)
{
    const char *buf = (const char *)data;

    if (len == 0) return;

    ch->buffersize += len;

    while (len > 0) {
        if (ch->tail && ch->tail->bufend < ch->tail->bufmax) {
            size_t copylen = min(len, ch->tail->bufmax - ch->tail->bufend);
            memcpy(ch->tail->bufend, buf, copylen);
            buf += copylen;
            len -= copylen;
            ch->tail->bufend += copylen;
        }
        if (len > 0) {
            size_t grainlen =
                max(sizeof(struct bufchain_granule) + len, BUFFER_MIN_GRANULE);
            struct bufchain_granule *newbuf;
            newbuf = smalloc(grainlen);
            newbuf->bufpos = newbuf->bufend =
                (char *)newbuf + sizeof(struct bufchain_granule);
            newbuf->bufmax = (char *)newbuf + grainlen;
            newbuf->next = NULL;
            if (ch->tail)
                ch->tail->next = newbuf;
            else
                ch->head = newbuf;
            ch->tail = newbuf;
        }
    }

    if (ch->ic)
        ch->queue_idempotent_callback(ch->ic);
}

void bufchain_consume(bufchain *ch, size_t len)
{
    struct bufchain_granule *tmp;

    assert(ch->buffersize >= len);
    while (len > 0) {
        int remlen = len;
        assert(ch->head != NULL);
        if (remlen >= ch->head->bufend - ch->head->bufpos) {
            remlen = ch->head->bufend - ch->head->bufpos;
            tmp = ch->head;
            ch->head = tmp->next;
            if (!ch->head)
                ch->tail = NULL;
            smemclr(tmp, sizeof(*tmp));
            sfree(tmp);
        } else
            ch->head->bufpos += remlen;
        ch->buffersize -= remlen;
        len -= remlen;
    }
}

ptrlen bufchain_prefix(bufchain *ch)
{
    return make_ptrlen(ch->head->bufpos, ch->head->bufend - ch->head->bufpos);
}

void bufchain_fetch(bufchain *ch, void *data, size_t len)
{
    struct bufchain_granule *tmp;
    char *data_c = (char *)data;

    tmp = ch->head;

    assert(ch->buffersize >= len);
    while (len > 0) {
        int remlen = len;

        assert(tmp != NULL);
        if (remlen >= tmp->bufend - tmp->bufpos)
            remlen = tmp->bufend - tmp->bufpos;
        memcpy(data_c, tmp->bufpos, remlen);

        tmp = tmp->next;
        len -= remlen;
        data_c += remlen;
    }
}

void bufchain_fetch_consume(bufchain *ch, void *data, size_t len)
{
    bufchain_fetch(ch, data, len);
    bufchain_consume(ch, len);
}

bool bufchain_try_fetch(bufchain *ch, void *data, size_t len)
{
    if (ch->buffersize >= len) {
        bufchain_fetch(ch, data, len);
        return true;
    } else {
        return false;
    }
}

bool bufchain_try_consume(bufchain *ch, size_t len)
{
    if (ch->buffersize >= len) {
        bufchain_consume(ch, len);
        return true;
    } else {
        return false;
    }
}

bool bufchain_try_fetch_consume(bufchain *ch, void *data, size_t len)
{
    if (ch->buffersize >= len) {
        bufchain_fetch_consume(ch, data, len);
        return true;
    } else {
        return false;
    }
}

size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len)
{
    if (len > ch->buffersize)
        len = ch->buffersize;
    if (len)
        bufchain_fetch_consume(ch, data, len);
    return len;
}