/* vifm
 * Copyright (C) 2012 xaizek.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "text_buffer.h"

#include <stdarg.h> /* va_list va_copy() va_start() va_end() */
#include <stddef.h> /* NULL size_t */
#include <stdio.h> /* vsnprintf() vsprintf() */
#include <stdlib.h> /* calloc() free() realloc() */
#include <string.h> /* strcpy() */

/* Text buffer type. */
struct vle_textbuf
{
	size_t capacity;     /* Total amount of available space. */
	size_t len;          /* Total amount of used space. */
	char *data;          /* Allocated piece of memory. */
	int need_line_break; /* Need to put line break before newly appended data. */
};

static void appendfv(vle_textbuf *tb, int line, const char format[],
		va_list ap);
static void ensure_allocated(vle_textbuf *tb, size_t extra);

/* Definition of predefined buffer for collecting errors of the engine. */
static vle_textbuf vle_err_data;
vle_textbuf *const vle_err = &vle_err_data;

vle_textbuf *
vle_tb_create()
{
	return calloc(1, sizeof(vle_textbuf));
}

void
vle_tb_free(vle_textbuf *tb)
{
	if(tb != NULL)
	{
		vle_tb_clear(tb);
		free(tb);
	}
}

char *
vle_tb_release(vle_textbuf *tb)
{
	char *const data = (tb->data != NULL) ? tb->data : strdup("");
	free(tb);
	return data;
}

void
vle_tb_clear(vle_textbuf *tb)
{
	if(tb->data != NULL)
	{
		free(tb->data);
		tb->data = 0;
		tb->capacity = 0;
		tb->len = 0;
		tb->need_line_break = 0;
	}
}

void
vle_tb_append(vle_textbuf *tb, const char str[])
{
	vle_tb_appendf(tb, "%s", str);
}

void
vle_tb_appendf(vle_textbuf *tb, const char format[], ...)
{
	va_list ap;
	va_start(ap, format);
	appendfv(tb, 0, format, ap);
	va_end(ap);
}

void
vle_tb_append_line(vle_textbuf *tb, const char str[])
{
	vle_tb_append_linef(tb, "%s", str);
}

void
vle_tb_append_linef(vle_textbuf *tb, const char format[], ...)
{
	va_list ap;
	va_start(ap, format);
	appendfv(tb, 1, format, ap);
	va_end(ap);
}

/* Appends formatted string to the buffer.  New data is either a string or a
 * line (string that terminates with new line character).*/
static void
appendfv(vle_textbuf *tb, int line, const char format[], va_list ap)
{
	va_list aq;
	size_t needed_size;

	va_copy(aq, ap);
	needed_size = (tb->len != 0) + vsnprintf(tb->data, 0, format, ap);

	ensure_allocated(tb, needed_size);
	if(tb->need_line_break && tb->len != 0 && tb->capacity > tb->len)
	{
		strcpy(&tb->data[tb->len++], "\n");
		tb->need_line_break = 0;
	}

	tb->len += vsnprintf(tb->data + tb->len, tb->capacity - tb->len, format, aq);
	va_end(aq);

	tb->need_line_break = line;
}

/* Ensures that buffer has at least specified amount of bytes after its end. */
static void
ensure_allocated(vle_textbuf *tb, size_t extra)
{
	const size_t needed_size = tb->len + extra + 1;
	if(tb->capacity < needed_size)
	{
		char *const ptr = realloc(tb->data, needed_size);
		if(ptr != NULL)
		{
			tb->data = ptr;
			tb->capacity = needed_size;
		}
	}
}

const char *
vle_tb_get_data(vle_textbuf *tb)
{
	return (tb->data == NULL) ? "" : tb->data;
}

size_t
vle_tb_get_len(vle_textbuf *tb)
{
	return tb->len;
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
