/*
 * Copyright (c) 1997, 1998, 1999, 2000, 2002, 2005, 2006
 *	Tama Communications Corporation
 *
 * This file is part of GNU GLOBAL.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
#ifdef STDC_HEADERS
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif

#include "checkalloc.h"
#include "die.h"
#include "strbuf.h"

#ifndef isblank
#define isblank(c)	((c) == ' ' || (c) == '\t')
#endif
/*

String buffer: usage and memory status

					[xxx]: string buffer
					'v': current pointer

Function call                           Memory status
----------------------------------------------------------
                                        (not exist)
                                         v
sb = strbuf_open(0);                    []
                                          v
strbuf_putc(sb, 'a');                   [a]
                                          v
char *s = strbuf_value(sb);             [a\0]           s == "a"
                                            v
strbuf_puts(sb, "bc");                  [abc]
                                            v
char *s = strbuf_value(sb);             [abc\0]         s == "abc"
                                            v
int len = strbuf_getlen(sb);            [abc\0]         len == 3
                                         v
strbuf_reset(sb);                       [abc\0]
                                         v
int len = strbuf_getlen(sb);            [abc\0]         len == 0
                                           v
strbuf_puts(sb, "XY");                  [XYc\0]
                                           v
char *s = strbuf_value(sb);             [XY\0]          s == "XY"

fp = fopen("/etc/passwd", "r");                                             v
char *s = strbuf_fgets(sb, fp, 0)       [root:*:0:0:Charlie &:/root:/bin/csh\0]
fclose(fp)				s == "root:*:0:0:Charlie &:/root:/bin/csh"

strbuf_close(sb);                       (not exist)

*/

static void print_and_abort (void);
void (*strbuf_alloc_failed_handler) (void) = print_and_abort;

static void
print_and_abort(void)
{
	die("short of memory.");
}

/*
 * __strbuf_expandbuf: expand buffer so that afford to the length data at least.
 *
 *	i)	sb	STRBUF structure
 *	i)	length	required room
 */
void
__strbuf_expandbuf(STRBUF *sb, int length)
{
	int count = sb->curp - sb->sbuf;
	int newsize = sb->sbufsize + (length > EXPANDSIZE ? length : EXPANDSIZE);
	char *newbuf;

	if (sb->alloc_failed)
		return;
	newbuf = (char *)check_realloc(sb->sbuf, newsize + 1);
	sb->sbufsize = newsize;
	sb->sbuf = newbuf;

	sb->curp = sb->sbuf + count;
	sb->endp = sb->sbuf + sb->sbufsize;
}
/*
 * strbuf_open: open string buffer.
 *
 *	i)	init	initial buffer size
 *			if 0 is specified then use default value.
 *	r)	sb	STRBUF structure
 */
STRBUF *
strbuf_open(int init)
{
	STRBUF *sb = (STRBUF *)check_calloc(sizeof(STRBUF), 1);

	sb->sbufsize = (init > 0) ? init : INITIALSIZE;
	sb->sbuf = (char *)check_malloc(sb->sbufsize + 1);
	sb->curp = sb->sbuf;
	sb->endp = sb->sbuf + sb->sbufsize;

	return sb;
}
/*
 * strbuf_reset: reset string buffer.
 *
 *	i)	sb	string buffer
 */
void
strbuf_reset(STRBUF *sb)
{
	sb->curp = sb->sbuf;
	sb->alloc_failed = 0;
}
/*
 * strbuf_clear: clear static string buffer.
 *
 *	i)	sb	statically defined string buffer
 *
 * This function is used for the initializing of static string buffer.
 * For the detail, see 'STATIC_STRBUF(sb)' macro in strbuf.h.
 */
void
strbuf_clear(STRBUF *sb)
{
	if (sb == NULL)
		die("NULL string buffer. (strbuf_clear)");
	if (strbuf_empty(sb)) {
		sb->sbufsize = INITIALSIZE;
		sb->sbuf = (char *)check_malloc(sb->sbufsize + 1);
		sb->curp = sb->sbuf;
		sb->endp = sb->sbuf + sb->sbufsize;
	} else {
		strbuf_reset(sb);
	}
}
/*
 * strbuf_nputs: Put string with length
 *
 *	i)	sb	string buffer
 *	i)	s	string
 *	i)	len	length of string
 */
void
strbuf_nputs(STRBUF *sb, const char *s, int len)
{
	if (!sb->alloc_failed && len > 0) {
		if (sb->curp + len > sb->endp)
			__strbuf_expandbuf(sb, len);
		while (len-- > 0)
			*sb->curp++ = *s++;
	}
}
/*
 * strbuf_nputc: Put characters with length
 *
 *	i)	sb	string buffer
 *	i)	c	character
 *	i)	len	length of string
 */
void
strbuf_nputc(STRBUF *sb, int c, int len)
{
	if (!sb->alloc_failed && len > 0) {
		if (sb->curp + len > sb->endp)
			__strbuf_expandbuf(sb, len);
		while (len-- > 0)
			*sb->curp++ = c;
	}
}
/*
 * strbuf_puts: Put string
 *
 *	i)	sb	string buffer
 *	i)	s	string
 */
void
strbuf_puts(STRBUF *sb, const char *s)
{
	if (!sb->alloc_failed) {
		while (*s) {
			if (sb->curp >= sb->endp)
				__strbuf_expandbuf(sb, 0);
			*sb->curp++ = *s++;
		}
	}
}
/*
 * strbuf_puts_nl: Put string with a new line
 *
 *	i)	sb	string buffer
 *	i)	s	string
 */
void
strbuf_puts_nl(STRBUF *sb, const char *s)
{
	if (!sb->alloc_failed) {
		while (*s) {
			if (sb->curp >= sb->endp)
				__strbuf_expandbuf(sb, 0);
			*sb->curp++ = *s++;
		}
		if (sb->curp >= sb->endp)
			__strbuf_expandbuf(sb, 0);
		*sb->curp++ = '\n';
	}
}
/*
 * strbuf_putn: put digit string at the last of buffer.
 *
 *	i)	sb	STRBUF structure
 *	i)	n	number
 */
void
strbuf_putn(STRBUF *sb, int n)
{
	if (n == 0) {
		strbuf_putc(sb, '0');
	} else {
		char num[128];
		int i = 0;

		while (n) {
			if (i >= sizeof(num))
				die("Too big integer value.");
			num[i++] = n % 10 + '0';
			n = n / 10;
		}
		while (--i >= 0)
			strbuf_putc(sb, num[i]);
	}
}
/*
 * strbuf_unputc: remove specified char from the last of buffer
 *
 *	i)	sb	STRBUF structure
 *	i)	c	character
 *	r)		0: do nothing, 1: removed
 */
int
strbuf_unputc(STRBUF *sb, int c)
{
	if (sb->curp > sb->sbuf && *(sb->curp - 1) == c) {
		sb->curp--;
		return 1;
	}
	return 0;
}
/*
 * strbuf_value: return the content of string buffer.
 *
 *	i)	sb	STRBUF structure
 *	r)		string
 */
char *
strbuf_value(STRBUF *sb)
{
	*sb->curp = 0;
	return sb->sbuf;
}
/*
 * strbuf_trim: trim following blanks.
 *
 *	i)	sb	STRBUF structure
 */
void
strbuf_trim(STRBUF *sb)
{
	char *p = sb->curp;

	while (p > sb->sbuf && isblank(*(p - 1)))
		*--p = 0;
	sb->curp = p;
}
/*
 * strbuf_fgets: read whole record into string buffer
 *
 *	o)	sb	string buffer
 *	i)	ip	input stream
 *	i)	flags	flags
 *			STRBUF_NOCRLF	remove last '\n' if exist.
 *			STRBUF_APPEND	append next record to existing data
 *	r)		record buffer (NULL at end of file)
 *
 * Returned buffer has whole record.
 * The buffer end with '\0'.If STRBUF_NOCRLF is set then buffer doesn't
 * include '\r' and '\n'.
 */
char *
strbuf_fgets(STRBUF *sb, FILE *ip, int flags)
{
	if (!(flags & STRBUF_APPEND))
		strbuf_reset(sb);

	if (sb->curp >= sb->endp)
		__strbuf_expandbuf(sb, EXPANDSIZE);	/* expand buffer */
	if (sb->alloc_failed)
		return sb->sbuf;

	for (;;) {
		if (!fgets(sb->curp, sb->endp - sb->curp, ip)) {
			if (sb->curp == sb->sbuf)
				return NULL;
			break;
		}
		sb->curp += strlen(sb->curp);
		if (sb->curp > sb->sbuf && *(sb->curp - 1) == '\n')
			break;
		else if (feof(ip)) {
			return sb->sbuf;
		}
		__strbuf_expandbuf(sb, EXPANDSIZE);	/* expand buffer */
		if (sb->alloc_failed)
			return sb->sbuf;
	}
	if (flags & STRBUF_NOCRLF) {
		if (*(sb->curp - 1) == '\n')
			*(--sb->curp) = 0;
		if (sb->curp > sb->sbuf && *(sb->curp - 1) == '\r')
			*(--sb->curp) = 0;
	}
	return sb->sbuf;
}
/*
 * strbuf_sprintf: do sprintf into string buffer.
 *
 *	i)	sb	STRBUF structure
 *	i)	s	similar to sprintf()
 *			Currently the following format is supported.
 *			%s, %d, %<number>d, %<number>s, %-<number>d, %-<number>s
 */
void
strbuf_sprintf(STRBUF *sb, const char *s, ...)
{
	va_list ap;

	va_start(ap, s);
	if (sb->alloc_failed)
		return;
	for (; *s; s++) {
		/*
		 * Put the before part of '%'.
		 */
		{
			const char *p;
			for (p = s; *p && *p != '%'; p++)
				;
			if (p > s) {
				strbuf_nputs(sb, s, p - s);
				s = p;
			}
		}
		if (*s == '\0')
			break;
		if (*s == '%') {
			int c = (unsigned char)*++s;
			/*
			 * '%%' means '%'.
			 */
			if (c == '%') {
				strbuf_putc(sb, c);
			}
			/*
			 * If the optional number is specified then
			 * we forward the job to snprintf(3).
			 * o %<number>d
			 * o %<number>s
			 * o %-<number>d
			 * o %-<number>s
			 */
			else if (isdigit(c) || (c == '-' && isdigit((unsigned char)*(s + 1)))) {
				char format[32], buf[1024];
				int i = 0;

				format[i++] = '%';
				if (c == '-')
					format[i++] = *s++;
				while (isdigit((unsigned char)*s))
					format[i++] = *s++;
				format[i++] = c = *s;
				format[i] = '\0';
				if (c == 'd' || c == 'x')
					snprintf(buf, sizeof(buf), format, va_arg(ap, int));
				else if (c == 's')
					snprintf(buf, sizeof(buf), format, va_arg(ap, char *));
				else
					die("Unsupported control character '%c'.", c);
				strbuf_puts(sb, buf);
			} else if (c == 's') {
				strbuf_puts(sb, va_arg(ap, char *));
			} else if (c == 'd') {
				strbuf_putn(sb, va_arg(ap, int));
			} else {
				die("Unsupported control character '%c'.", c);
			}
		}
	}
	va_end(ap);
}
/*
 * strbuf_close: close string buffer.
 *
 *	i)	sb	STRBUF structure
 */
void
strbuf_close(STRBUF *sb)
{
	if (sb->name)
		(void)free(sb->name);
	(void)free(sb->sbuf);
	(void)free(sb);
}
/*
 * Temporary string buffer for general purpose.
 *
 * Usage:
 *
 *	STRBUF *sbt = strbuf_open_tempbuf();
 *	....
 *	strbuf_puts(sbtemp, "xxx");
 *	...
 *	strbuf_release_tempbuf(sbt);
 *
 */
int used = 0;

STRBUF *
strbuf_open_tempbuf(void)
{
	STATIC_STRBUF(sb);
	if (used)
		die("Internal error: temporary string buffer is already used.");
	used = 1;
	strbuf_clear(sb);
	return sb;
}
void
strbuf_release_tempbuf(STRBUF *sb)
{
	used = 0;
}
