
/*
 * Copyright (C) Dmitry Volyntsev
 * Copyright (C) NGINX, Inc.
 */


#include <njs_main.h>


#define NJS_CHB_MIN_SIZE       256


void
njs_chb_init(njs_chb_t *chain, void *pool, njs_chb_alloc_t alloc,
    njs_chb_free_t free)
{
    chain->error = 0;
    chain->pool = pool;
    chain->alloc = alloc;
    chain->free = free;
    chain->nodes = NULL;
    chain->last = NULL;
}


void
njs_chb_append0(njs_chb_t *chain, const char *msg, size_t len)
{
    u_char  *p;

    if (len != 0 && !chain->error) {
        p = njs_chb_reserve(chain, len);
        if (njs_slow_path(p == NULL)) {
            return;
        }

        memcpy(p, msg, len);

        njs_chb_written(chain, len);
    }
}


u_char *
njs_chb_reserve(njs_chb_t *chain, size_t size)
{
    njs_chb_node_t  *n;

    n = chain->last;

    if (njs_fast_path(n != NULL && njs_chb_node_room(n) >= size)) {
        return n->pos;
    }

    if (size < NJS_CHB_MIN_SIZE) {
        size = NJS_CHB_MIN_SIZE;
    }

    n = chain->alloc(chain->pool, sizeof(njs_chb_node_t) + size);
    if (njs_slow_path(n == NULL)) {
        chain->error = 1;
        return NULL;
    }

    n->next = NULL;
    n->start = (u_char *) n + sizeof(njs_chb_node_t);
    n->pos = n->start;
    n->end = n->pos + size;

    if (chain->last != NULL) {
        chain->last->next = n;

    } else {
        chain->nodes = n;
    }

    chain->last = n;

    return n->start;
}


void
njs_chb_vsprintf(njs_chb_t *chain, size_t size, const char *fmt, va_list args)
{
    u_char  *start, *end;

    start = njs_chb_reserve(chain, size);
    if (njs_slow_path(start == NULL)) {
        return;
    }

    end = njs_vsprintf(start, start + size, fmt, args);

    njs_chb_written(chain, end - start);
}


void
njs_chb_sprintf(njs_chb_t *chain, size_t size, const char* fmt, ...)
{
    va_list  args;

    va_start(args, fmt);

    njs_chb_vsprintf(chain, size, fmt, args);

    va_end(args);
}


/*
 * Drains size bytes from the beginning of the chain.
 */
void
njs_chb_drain(njs_chb_t *chain, size_t drain)
{
    njs_chb_node_t  *n;

    n = chain->nodes;

    while (n != NULL) {
        if (njs_chb_node_size(n) > drain) {
            n->start += drain;
            return;
        }

        drain -= njs_chb_node_size(n);
        chain->nodes = n->next;

        njs_mp_free(chain->pool, n);
        n = chain->nodes;
    }

    chain->last = NULL;
}


/*
 * Drops size bytes from the end of the chain.
 */
void
njs_chb_drop(njs_chb_t *chain, size_t drop)
{
    uint64_t        size;
    njs_chb_node_t  *n, *next;

    if (njs_slow_path(chain->error)) {
        return;
    }

    n = chain->last;

    if (njs_fast_path(n != NULL && (njs_chb_node_size(n) > drop))) {
        n->pos -= drop;
        return;
    }

    n = chain->nodes;
    size = (uint64_t) njs_chb_size(chain);

    if (drop >= size) {
        njs_chb_destroy(chain);
        njs_chb_init(chain, chain->pool, chain->alloc, chain->free);
        return;
    }

    while (n != NULL) {
        size -= njs_chb_node_size(n);

        if (size <= drop) {
            chain->last = n;
            chain->last->pos -= drop - size;

            n = chain->last->next;
            chain->last->next = NULL;

            break;
        }

        n = n->next;
    }

    while (n != NULL) {
        next = n->next;
        njs_mp_free(chain->pool, n);
        n = next;
    }
}


njs_int_t
njs_chb_join(njs_chb_t *chain, njs_str_t *str)
{
    u_char          *start;
    uint64_t        size;
    njs_chb_node_t  *n;

    if (njs_slow_path(chain->error)) {
        return NJS_DECLINED;
    }

    n = chain->nodes;

    if (n == NULL) {
        str->length = 0;
        str->start = NULL;
        return NJS_OK;
    }

    size = (uint64_t) njs_chb_size(chain);
    if (njs_slow_path(size >= UINT32_MAX)) {
        return NJS_ERROR;
    }

    start = chain->alloc(chain->pool, size);
    if (njs_slow_path(start == NULL)) {
        return NJS_ERROR;
    }

    str->length = size;
    str->start = start;

    njs_chb_join_to(chain, start);

    return NJS_OK;
}


void
njs_chb_join_to(njs_chb_t *chain, u_char *dst)
{
    njs_chb_node_t  *n;

    n = chain->nodes;

    while (n != NULL) {
        dst = njs_cpymem(dst, n->start, njs_chb_node_size(n));
        n = n->next;
    }
}


void
njs_chb_destroy(njs_chb_t *chain)
{
    njs_chb_node_t  *n, *next;

    n = chain->nodes;

    while (n != NULL) {
        next = n->next;

        if (chain->free != NULL) {
            chain->free(chain->pool, n);
        }

        n = next;
    }
}
