#define _GNU_SOURCE
#include <sys/types.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <assert.h>
#include "config.h"

#include "asn1_buffer.h"

#if !defined(HAVE_DECL_VASPRINTF) || (HAVE_DECL_VASPRINTF == 0)
int vasprintf(char **ret, const char *fmt, va_list args);
#endif

/*
 * Create and destroy the buffer.
 */
abuf *
abuf_new() {
    abuf *ab = calloc(1, sizeof(abuf));
    assert(ab);
    ab->length = 0;
    ab->size = 32;
    ab->buffer = calloc(1, ab->size);
    assert(ab->buffer);
    return ab;
}

void abuf_free(abuf *ab) {
    if(ab) {
        union {
            const char *c_buf;
            char *nc_buf;
        } const_cast;
        const_cast.c_buf = ab->buffer;
        free(const_cast.nc_buf);
        free(ab);
    }
}

/*
 * Erase contents of the buffer (without destroying it).
 */
void
abuf_clear(abuf *ab) {
    union {
        const char *c_buf;
        char *nc_buf;
    } const_cast;
    if(!ab->buffer) {
        ab->size = 32;
        ab->buffer = calloc(1, ab->size);
        assert(ab->buffer);
    }
    const_cast.c_buf = ab->buffer;
    ab->length = 0;
    const_cast.nc_buf[0] = '\0';
}

static void
abuf_resize_by(abuf *ab, size_t size) {
    union {
        const char *c_buf;
        char *nc_buf;
    } const_cast;
    const_cast.c_buf = ab->buffer;

    assert(!ab->buffer || ab->buffer[ab->length] == '\0');

    size_t new_size = ab->length + size;
    char *p = realloc(const_cast.nc_buf, new_size);
    assert(p);
    if(!ab->buffer) {
        assert(ab->length == 0);
        *p = '\0';
    }
    ab->buffer = p;
    assert(ab->buffer[ab->length] == '\0');
    ab->size = new_size;
}

void abuf_add_bytes(abuf *ab, const char *str, size_t size) {
    abuf_resize_by(ab, size + 1);
    union {
        const char *c_buf;
        char *nc_buf;
    } const_cast;
    const_cast.c_buf = ab->buffer;
    memcpy(&const_cast.nc_buf[ab->length], str, size);
    ab->length += size;
    const_cast.nc_buf[ab->length] = '\0';
}

void abuf_str(abuf *ab, const char *str) {
    abuf_add_bytes(ab, str, strlen(str));
}

void abuf_buf(abuf *ab, const abuf *buf) {
    abuf_add_bytes(ab, buf->buffer, buf->length);
}

int abuf_printf(abuf *ab, const char *fmt, ...) {
    va_list ap;

    for(;;) {
        union {
            const char *c_buf;
            char *nc_buf;
        } const_cast;
        const_cast.c_buf = ab->buffer;
        va_start(ap, fmt);
        int ret = vsnprintf(&const_cast.nc_buf[ab->length],
                            ab->size - ab->length, fmt, ap);
        va_end(ap);
        assert(ret >= 0);
        if((size_t)ret < ab->size - ab->length) {
            ab->length += ret;
            assert(ab->buffer[ab->length] == '\0');
            return ret;
        }
        const_cast.nc_buf[ab->length] = '\0'; /* Restore order */
        abuf_resize_by(ab, ret + 1);
    }
}

int abuf_vprintf(abuf *ab, const char *fmt, va_list ap) {
    int ret;
    char *str = 0;

    ret = vasprintf(&str, fmt, ap);
    assert(ret >= 0);
    assert(str != NULL);

    abuf_add_bytes(ab, str, ret);

    free(str);

    return ret;
}

#if !defined(HAVE_DECL_VASPRINTF) || (HAVE_DECL_VASPRINTF == 0)
/* Solaris doesn't have vasprintf(3). */
int
vasprintf(char **ret, const char *fmt, va_list args) {
    int actual_length = -1;
    va_list copy;
    va_copy(copy, args);

    int suggested = vsnprintf(NULL, 0, fmt, args);
    if(suggested >= 0) {
        *ret = malloc(suggested + 1);
        if(*ret) {
            actual_length = vsnprintf(*ret, suggested + 1, fmt, copy);
            if(actual_length >= 0) {
                assert(actual_length == suggested);
                assert((*ret)[actual_length] == '\0');
            } else {
                free(*ret);
                *ret = 0;
            }
        }
    } else {
        *ret = NULL;
        assert(suggested >= 0); /* Can't function like this */
    }
    va_end(args);

    return actual_length;
}
#endif
