/* $Cambridge: hermes/src/prayer/lib/memblock.c,v 1.3 2008/09/16 09:59:57 dpc22 Exp $ */
/************************************************
 *    Prayer - a Webmail Interface              *
 ************************************************/

/* Copyright (c) University of Cambridge 2000 - 2008 */
/* See the file NOTICE for conditions of use and distribution. */

#include "lib.h"

/* Classs that support linear memory block management. Typically used for
 * variable length error messages */

/* memblock_create() ****************************************************
 *
 * Create a new memory block
 *          pool: Target pool for memblock structure (not contains)
 *  initial_size: Initial size of memblock
 ***********************************************************************/

struct memblock *memblock_create(struct pool *pool,
                                 unsigned long initial_size)
{
    struct memblock *m = pool_alloc(pool, sizeof(struct memblock));

    m->pool = pool;
    m->block = NIL;
    m->bsize = 0;
    m->size = 0;

    memblock_resize(m, initial_size);
    memset(m->block, 0, initial_size);

    m->size = 0;                /* Actually zero length */
    return (m);
}

/* memblock_free() *******************************************************
 *
 * Free memory block
 ************************************************************************/

void memblock_free(struct memblock *m)
{
    if (m->block)
        free(m->block);

    m->block = NIL;
    m->bsize = 0;
    m->size = 0;

    if (m->pool == NIL)
        free(m);
}

/* ====================================================================== */

/* Static support routine */

static unsigned long memblock_roundup_size(unsigned long size)
{
    static unsigned long boundary[]
        = { 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
        131072, 262144, 524288, 1048576, 0
    };
    unsigned long *p = boundary;

    if (size > 1048576)
        return (size);

    /* Find first power of two larger than given size */
    while (*p) {
        if (size <= *p)
            return (*p);
        p++;
    }

    /* Shouldn't get here */
    return (size);
}

/* memblock_resize() *****************************************************
 *
 * Make sure that memblock is (at least) given size
 *   Only resize block if:
 *     1) requested size larger than current size      OR
 *     2) requested size less than 1/4 current size
 *
 *    m: Memblock
 * size: Desired size
 ************************************************************************/

void memblock_resize(struct memblock *m, unsigned long size)
{
    unsigned long new_bsize;

    new_bsize = memblock_roundup_size(m->size = size);

    if (m->bsize == new_bsize)
        return;

    if (m->block)
        free(m->block);

    m->bsize = new_bsize;
    if ((m->block = malloc(m->bsize)) == NIL)
        log_fatal("Out of memory");

    m->size = size;
}

/* ====================================================================== */

/* memblock_data() *******************************************************
 * Returns: Ptr to data associated with memblock
 ************************************************************************/

void *memblock_data(struct memblock *m)
{
    return (m->block);
}

/* memblock_size() *******************************************************
 * Returns: Current size of memblock
 ************************************************************************/

unsigned long memblock_size(struct memblock *m)
{
    return (m->size);
}

/* memblock_string() *****************************************************
 * Returns: Memblock data as string.
 ************************************************************************/

char *memblock_string(struct memblock *m)
{
    if (m->block && m->size)
        return ((char *) m->block);

    return ("");
}

/* ====================================================================== */

/* memblock_strdup() *****************************************************
 *
 * Duplicate a string into memblock
 *    m: Memblock
 *    s: String to duplicate
 *
 * Returns: Ptr to new memblock contents.
 ************************************************************************/

char *memblock_strdup(struct memblock *m, char *s)
{
    if (!s)
        return (NIL);

    memblock_resize(m, strlen(s) + 1);
    strcpy(m->block, s);
    return (m->block);
}

/* memblock_strcpy() *****************************************************
 *
 * Copy string into memblock
 *    m: Memblock
 *    s: String to duplicate
 ************************************************************************/

void memblock_strcpy(struct memblock *m, char *s)
{
    if (!s)
        s = "";

    memblock_resize(m, strlen(s) + 1);
    strcpy(m->block, s);
}

/* ====================================================================== */

/* memblock_puts() *******************************************************
 *
 * Print string into memblock
 *   m: Memblock
 *   s: String to print
 ************************************************************************/

void memblock_puts(struct memblock *m, char *s)
{
    memblock_resize(m, strlen(s) + 1);
    strcpy((char *) m->block, s);
}

/* ====================================================================== */

/* Static support routines for memblock_printf() */

static unsigned long memblock_ulong_size(unsigned long value)
{
    unsigned long digits = 1;

    for (value /= 10; value > 0; value /= 10)
        digits++;

    return (digits);
}

static char *memblock_ulong_print(char *s, unsigned long value)
{
    unsigned long tmp, weight;

    /* All numbers contain at least one digit.
     * Find weight of most significant digit. */
    for (weight = 1, tmp = value / 10; tmp > 0; tmp /= 10)
        weight *= 10;

    for (tmp = value; weight > 0; weight /= 10) {
        if (value >= weight) {  /* Strictly speaking redundant... */
            *s++ = '0' + (value / weight);      /* Digit other than zero */
            value -= weight * (value / weight); /* Calculate remainder */
        } else
            *s++ = '0';
    }
    return (s);
}

/* ====================================================================== */

/* memblock_vprintf_size() ***********************************************
 *
 * Calculate desired size for memblock which will be target of a
 * memblock_vprintf.
 *    m: Memblock
 *  fmt: Format string
 *   ap: va_list
 *
 * Returns: Desired size for memblock
 ************************************************************************/

unsigned long
memblock_vprintf_size(struct memblock *m, char *fmt, va_list ap)
{
    char *s;
    char c;
    unsigned long size = 0;

    while ((c = *fmt++)) {
        if (c != '%') {
            size++;
        } else
            switch (*fmt++) {
            case 's':          /* string */
                if ((s = va_arg(ap, char *)))
                     size += strlen(s);
                else
                    size += strlen("(nil)");
                break;
            case 'l':
                if (*fmt == 'u') {
                    size += memblock_ulong_size(va_arg(ap, unsigned long));
                    fmt++;
                } else
                    size += memblock_ulong_size(va_arg(ap, long));
                break;
            case 'd':
                if (*fmt == 'u') {
                    size += memblock_ulong_size(va_arg(ap, unsigned int));
                    fmt++;
                } else
                    size += memblock_ulong_size(va_arg(ap, int));
                break;
            case 'c':
                size++;
                break;
            case '%':
                size++;
                break;
            default:
                log_fatal("Bad format string to memblock_printf");
            }
    }

    return (size);
}

/* ====================================================================== */

/* memblock_vprintf() ****************************************************
 *
 * Print string into (correctly resized) memblock
 *    m: Memblock
 *  fmt: Format string
 *   ap: va_list
 ************************************************************************/

void memblock_vprintf(struct memblock *m, char *fmt, va_list ap)
{
    char *s, *t;
    char c;

    t = (char *) m->block;

    while ((c = *fmt++)) {
        if (c != '%') {
            *t++ = c;
        } else
            switch (*fmt++) {
            case 's':          /* string */
                if ((s = va_arg(ap, char *))) {
                    while ((c = *s++))
                        *t++ = c;
                } else {
                    strcpy(t, "(nil)");
                    t += strlen("(nil)");
                }
                break;
            case 'l':
                if (*fmt == 'u') {
                    t = memblock_ulong_print(t, va_arg(ap, unsigned long));
                    fmt++;
                } else
                    t = memblock_ulong_print(t, va_arg(ap, long));
                break;
            case 'd':
                if (*fmt == 'u') {
                    t = memblock_ulong_print(t, va_arg(ap, unsigned int));
                    fmt++;
                } else
                    t = memblock_ulong_print(t, va_arg(ap, int));
                break;
            case 'c':
                *t++ = (char) va_arg(ap, int);
                break;
            case '%':
                *t++ = '%';
                break;
            default:
                log_fatal("Bad format string to memblock_printf");
            }
    }
    *t = '\0';
    va_end(ap);
}

/* ====================================================================== */

/* memblock_printf() *****************************************************
 *
 * Print string into memblock
 *    m: Memblock
 *  fmt: Format string followed by arguments
 ************************************************************************/

void memblock_printf(struct memblock *m, char *fmt, ...)
{
    va_list ap;
    unsigned long size;

    va_start(ap, fmt);
    size = memblock_vprintf_size(m, fmt, ap);
    va_end(ap);

    memblock_resize(m, size + 1);

    va_start(ap, fmt);
    memblock_vprintf(m, fmt, ap);
    va_end(ap);
}
