/* Copyright (C) 2004       Manuel Novoa III    <mjn3@codepoet.org>
 *
 * GNU Library General Public License (LGPL) version 2 or later.
 *
 * Dedicated to Toni.  See uClibc/DEDICATION.mjn3 for details.
 */

#include "_stdio.h"
#include <stdarg.h>

#ifdef __STDIO_BUFFERS
/* NB: we can still have __USE_OLD_VFPRINTF__ defined in this case! */

int vsnprintf(char *__restrict buf, size_t size,
			  const char * __restrict format, va_list arg)
{
	FILE f;
	int rv;

	f.__filedes = __STDIO_STREAM_FAKE_VSNPRINTF_FILEDES;
	f.__modeflags = (__FLAG_NARROW|__FLAG_WRITEONLY|__FLAG_WRITING);

#ifdef __UCLIBC_HAS_WCHAR__
	f.__ungot_width[0] = 0;
#endif /* __UCLIBC_HAS_WCHAR__ */
#ifdef __STDIO_MBSTATE
	__INIT_MBSTATE(&(f.__state));
#endif /* __STDIO_MBSTATE */

#if (defined(__STDIO_BUFFERS) || defined(__USE_OLD_VFPRINTF__)) && defined(__UCLIBC_HAS_THREADS__)
	f.__user_locking = 1;		/* Set user locking. */
	STDIO_INIT_MUTEX(f.__lock);
#endif
	f.__nextopen = NULL;

	if (size > SIZE_MAX - (size_t) buf) {
		size = SIZE_MAX - (size_t) buf;
	}

/* TODO: this comment seems to be wrong */
	/* Set these last since __bufputc initialization depends on
	 * __user_locking and only gets set if user locking is on. */
	f.__bufstart = (unsigned char *) buf;
	f.__bufend = (unsigned char *) buf + size;
	__STDIO_STREAM_INIT_BUFREAD_BUFPOS(&f);
	__STDIO_STREAM_DISABLE_GETC(&f);
	__STDIO_STREAM_ENABLE_PUTC(&f);

#ifdef __USE_OLD_VFPRINTF__
	rv = vfprintf(&f, format, arg);
#else
	rv = _vfprintf_internal(&f, format, arg);
#endif
	if (size) {
		if (f.__bufpos == f.__bufend) {
			--f.__bufpos;
		}
		*f.__bufpos = 0;
	}
	return rv;
}
libc_hidden_def(vsnprintf)

#elif defined(__USE_OLD_VFPRINTF__)

typedef struct {
	FILE f;
	unsigned char *bufend;		/* pointer to 1 past end of buffer */
	unsigned char *bufpos;
} __FILE_vsnprintf;

int vsnprintf(char *__restrict buf, size_t size,
			  const char * __restrict format, va_list arg)
{
	__FILE_vsnprintf f;
	int rv;

	f.bufpos = buf;

	if (size > SIZE_MAX - (size_t) buf) {
		size = SIZE_MAX - (size_t) buf;
	}
	f.bufend = buf + size;

	f.f.__filedes = __STDIO_STREAM_FAKE_VSNPRINTF_FILEDES_NB;
	f.f.__modeflags = (__FLAG_NARROW|__FLAG_WRITEONLY|__FLAG_WRITING);

#ifdef __UCLIBC_HAS_WCHAR__
	f.f.__ungot_width[0] = 0;
#endif /* __UCLIBC_HAS_WCHAR__ */
#ifdef __STDIO_MBSTATE
	__INIT_MBSTATE(&(f.f.__state));
#endif /* __STDIO_MBSTATE */

#ifdef __UCLIBC_HAS_THREADS__
	f.f.__user_locking = 1;		/* Set user locking. */
	STDIO_INIT_MUTEX(f.f.__lock);
#endif
	f.f.__nextopen = NULL;

	rv = vfprintf((FILE *) &f, format, arg);
	if (size) {
		if (f.bufpos == f.bufend) {
			--f.bufpos;
		}
		*f.bufpos = 0;
	}
	return rv;
}
libc_hidden_def(vsnprintf)

#elif defined(__UCLIBC_HAS_GLIBC_CUSTOM_STREAMS__)

typedef struct {
	size_t pos;
	size_t len;
	char *buf;
	FILE *fp;
} __snpf_cookie;

#define COOKIE ((__snpf_cookie *) cookie)

static ssize_t snpf_write(register void *cookie, const char *buf,
						  size_t bufsize)
{
	size_t count;
	register char *p;

	/* Note: bufsize < SSIZE_MAX because of _stdio_WRITE. */

	if (COOKIE->len > COOKIE->pos) {
		count = COOKIE->len - COOKIE->pos - 1; /* Leave space for nul. */
		if (count > bufsize) {
			count = bufsize;
		}

		p = COOKIE->buf + COOKIE->pos;
		while (count) {
			*p++ = *buf++;
			--count;
		}
		*p = 0;
	}

	COOKIE->pos += bufsize;

	return bufsize;
}

#undef COOKIE

int vsnprintf(char *__restrict buf, size_t size,
			  const char * __restrict format, va_list arg)
{
	_IO_cookie_file_t cf;
	__snpf_cookie cookie;
	int rv;

	cookie.buf = buf;
	cookie.len = size;
	cookie.pos = 0;
	cookie.fp = &cf.__fp;

	cf.__cookie = &cookie;
	cf.__gcs.write = snpf_write;
	cf.__gcs.read = NULL;
	cf.__gcs.seek = NULL;
	cf.__gcs.close = NULL;

	cf.__fp.__filedes = __STDIO_STREAM_GLIBC_CUSTOM_FILEDES;
	cf.__fp.__modeflags = (__FLAG_NARROW|__FLAG_WRITEONLY|__FLAG_WRITING);

#ifdef __UCLIBC_HAS_WCHAR__
	cf.__fp.__ungot_width[0] = 0;
#endif /* __UCLIBC_HAS_WCHAR__ */
#ifdef __STDIO_MBSTATE
	__INIT_MBSTATE(&(cf.__fp.__state));
#endif /* __STDIO_MBSTATE */

	cf.__fp.__nextopen = NULL;

	rv = _vfprintf_internal(&cf.__fp, format, arg);

	return rv;
}
libc_hidden_def(vsnprintf)

#else
#warning Skipping vsnprintf since no buffering, no custom streams, and not old vfprintf!
#ifdef __STDIO_HAS_VSNPRINTF
#error WHOA! __STDIO_HAS_VSNPRINTF is defined!
#endif
#endif
