/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

#include "dumper.h"

void dumpf(struct dumper *dumper, const char *format, ...)
{
	va_list ap;
	va_start(ap, format);
	dumper->vprintf(dumper, format, ap);
	va_end(ap);
}

/* dumper which outputs to syslog */

struct syslog_data {
	int priority;
	struct dumper *mem_dumper;
};

static void syslog_vprintf(struct dumper *dumper, const char *fmt, va_list ap)
{
	char *buf;
	int size, i;
	struct syslog_data *data = (struct syslog_data *)dumper->data;
	struct dumper *mem_dumper = data->mem_dumper;

	/* We cannot use syslog() directly each time we are called,
	 * because syslog() will always append a newline to the
	 * output, so the user will not be able to construct a line
	 * incrementally using multiple calls. What we do here is to
	 * collect the output in a buffer until a newline is given by
	 * the user. */

	mem_dumper->vprintf(mem_dumper, fmt, ap);
again:
	mem_dumper_get(mem_dumper, &buf, &size);
	for (i = 0; i < size; i++) {
		if (buf[i] == '\n') {
			syslog(data->priority, "%.*s", i + 1, buf);
			mem_dumper_consume(mem_dumper, i + 1);
			goto again;
		}
	}
}

struct dumper *syslog_dumper_create(int priority)
{
	struct dumper *dumper = calloc(1, sizeof(struct dumper));
	struct syslog_data *data = calloc(1, sizeof(struct syslog_data));
	data->priority = priority;
	data->mem_dumper = mem_dumper_create();
	dumper->data = data;
	dumper->vprintf = &syslog_vprintf;
	return dumper;
}

void syslog_dumper_free(struct dumper *dumper)
{
	mem_dumper_free(((struct syslog_data *)dumper->data)->mem_dumper);
	free(dumper->data);
	free(dumper);
}

/* dumper which outputs to a memory buffer */

struct mem_data {
	char *buf;
	int size;
	int capacity;
};

static void mem_vprintf(struct dumper *dumper, const char *format, va_list ap)
{
	int n;
	char *tmp;
	struct mem_data *data = (struct mem_data *)dumper->data;

	while (1) {
		/* try to use the remaining space */
		int remaining = data->capacity - data->size;
		n = vsnprintf(data->buf + data->size, remaining, format, ap);

		/* enough space? */
		if (n > -1 && n < remaining) {
			data->size += n;
			return;
		}

		/* allocate more space and try again */
		tmp = realloc(data->buf, data->capacity * 2);
		if (tmp == NULL)
			return;
		data->buf = tmp;
		data->capacity *= 2;
	}
}

struct dumper *mem_dumper_create()
{
	struct dumper *dumper = calloc(1, sizeof(struct dumper));
	struct mem_data *data = calloc(1, sizeof(struct mem_data));
	if (!dumper || !data)
		return NULL;
	data->size = 0;
	data->capacity = 80;
	data->buf = malloc(data->capacity);
	if (!data->buf) {
		free(data);
		return NULL;
	}
	data->buf[0] = '\0';
	dumper->data = data;
	dumper->vprintf = &mem_vprintf;
	return dumper;
}

void mem_dumper_free(struct dumper *dumper)
{
	struct mem_data *data = (struct mem_data *)dumper->data;
	free(data->buf);
	free(data);
	free(dumper);
}

void mem_dumper_clear(struct dumper *dumper)
{
	struct mem_data *data = (struct mem_data *)dumper->data;
	data->buf[0] = '\0';
	data->size = 0;
}

void mem_dumper_consume(struct dumper *dumper, int n)
{
	struct mem_data *data = (struct mem_data *)dumper->data;
	if (n > data->size)
		n = data->size;
	memmove(data->buf, data->buf + n, data->size - n + 1);
	data->size -= n;
}

void mem_dumper_get(struct dumper *dumper, char **buf, int *size)
{
	struct mem_data *data = (struct mem_data *)dumper->data;
	*buf = data->buf;
	*size = data->size;
}