/* $NetBSD: tparm.c,v 1.19 2021/08/27 18:40:28 rillig Exp $ */

/*
 * Copyright (c) 2009, 2011, 2013 The NetBSD Foundation, Inc.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Roy Marples.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: tparm.c,v 1.19 2021/08/27 18:40:28 rillig Exp $");
#include <sys/param.h>

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <term_private.h>
#include <term.h>

#define LONG_STR_MAX ((CHAR_BIT * sizeof(long)) / 3)
#define BUFINC 128	/* Size to increment the terminal buffer by */

#define VA_LONG_LONG	1
#define VA_CHAR_INT	2
//#define VA_CHAR_LONG	3	/* No need for this yet */

static TERMINAL *dumbterm; /* For non thread safe functions */

typedef struct {
	long nums[20];
	char *strings[20];
	size_t offset;
} TPSTACK;

typedef struct {
	long num;
	char *string;
} TPVAR;

static int
push(long num, char *string, TPSTACK *stack)
{
	if (stack->offset >= sizeof(stack->nums)) {
		errno = E2BIG;
		return -1;
	}
	stack->nums[stack->offset] = num;
	stack->strings[stack->offset] = string;
	stack->offset++;
	return 0;
}

static int
pop(long *num, char **string, TPSTACK *stack)
{
	if (stack->offset == 0) {
		if (num)
			*num = 0;
		if (string)
			*string = NULL;
		errno = E2BIG;
		return -1;
	}
	stack->offset--;
	if (num)
		*num = stack->nums[stack->offset];
	if (string)
		*string = stack->strings[stack->offset];
	return 0;
}

static char *
checkbuf(TERMINAL *term, size_t len)
{
	char *buf;

	if (term->_bufpos + len >= term->_buflen) {
		len = term->_buflen + MAX(len, BUFINC);
		buf = realloc(term->_buf, len);
		if (buf == NULL)
			return NULL;
		term->_buf = buf;
		term->_buflen = len;
	}
	return term->_buf;
}

static size_t
ochar(TERMINAL *term, int c)
{
	if (c == 0)
		c = 0200;
	/* Check we have space and a terminator */
	if (checkbuf(term, 2) == NULL)
		return 0;
	term->_buf[term->_bufpos++] = (char)c;
	return 1;
}

static size_t
onum(TERMINAL *term, const char *fmt, int num, size_t len)
{
	int l;
	size_t r;

	if (len < LONG_STR_MAX)
		len = LONG_STR_MAX;
	if (checkbuf(term, len + 2) == NULL)
		return 0;
	l = snprintf(term->_buf + term->_bufpos, len + 2, fmt, num);
	if (l == -1)
		return 0;
	r = (size_t)l;
	term->_bufpos += r;
	return r;
}

/*
  Make a pass through the string so we can work out
  which parameters are ints and which are char *.
  Basically we only use char * if %p[1-9] is followed by %l or %s.
*/
int
_ti_parm_analyse(const char *str, int *piss, int piss_len)
{
	int nparm, lpop;
	char c;

	nparm = 0;
	lpop = -1;
	while ((c = *str++) != '\0') {
		if (c != '%')
			continue;
		c = *str++;
		switch (c) {
			case 'l':
			case 's':
				if (lpop > 0) {
					if (lpop <= piss_len)
						piss[lpop - 1] = 1;
					else if (piss)
						errno = E2BIG;
				}
				break;
			case 'p':
				c = *str++;
				if (c < '1' || c > '9') {
					errno = EINVAL;
					continue;
				} else {
					lpop = c - '0';
					if (lpop > nparm)
						nparm = lpop;
				}
				break;
			default:
				lpop = -1;
		}
	}

	return nparm;
}

static char *
_ti_tiparm(TERMINAL *term, const char *str, int va_type, va_list parms)
{
	char c, fmt[64], *fp, *ostr;
	long val, val2;
	long dnums[26]; /* dynamic variables a-z, not preserved */
	size_t l, max, width, precision, olen;
	TPSTACK stack;
	TPVAR params[TPARM_MAX];
	unsigned int done, dot, minus;
	int piss[TPARM_MAX]; /* Parameter IS String - piss ;) */

	if (str == NULL)
		return NULL;

	/*
	  If not passed a terminal, malloc a dummy one.
	  This means we can preserve buffers and variables per terminal and
	  still work with non thread safe functions (which sadly are still the
	  norm and standard).
	*/
	if (term == NULL) {
		if (dumbterm == NULL) {
			dumbterm = malloc(sizeof(*dumbterm));
			if (dumbterm == NULL)
				return NULL;
			dumbterm->_buflen = 0;
		}
		term = dumbterm;
	}

	term->_bufpos = 0;
	/* Ensure we have an initial buffer */
	if (term->_buflen == 0) {
		term->_buf = malloc(BUFINC);
		if (term->_buf == NULL)
			return NULL;
		term->_buflen = BUFINC;
	}

	memset(&piss, 0, sizeof(piss));
	max = (size_t)_ti_parm_analyse(str, piss, TPARM_MAX);

	/* Put our parameters into variables */
	memset(&params, 0, sizeof(params));
	for (l = 0; l < max; l++) {
		if (piss[l]) {
			if (va_type == VA_LONG_LONG) {
				/* This only works if char * fits into a long
				 * on this platform. */
				if (sizeof(char *) <= sizeof(long))
					params[l].string =
					    (char *)va_arg(parms, long);
				else {
					errno = ENOTSUP;
					return NULL;
				}
			} else
				params[l].string = va_arg(parms, char *);
		} else {
			if (va_type == VA_CHAR_INT)
				params[l].num = (long)va_arg(parms, int);
			else
				params[l].num = va_arg(parms, long);
		}
	}

	memset(&stack, 0, sizeof(stack));
	while ((c = *str++) != '\0') {
		if (c != '%' || (c = *str++) == '%') {
			if (c == '\0')
				break;
			if (ochar(term, c) == 0)
				return NULL;
			continue;
		}

		/* Handle formatting. */
		fp = fmt;
		*fp++ = '%';
		done = dot = minus = 0;
		width = precision = 0;
		val = 0;
		while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
			switch (c) {
			case 'c':
			case 's':
				*fp++ = c;
				done = 1;
				break;
			case 'd':
			case 'o':
			case 'x':
			case 'X':
				*fp++ = 'l';
				*fp++ = c;
				done = 1;
				break;
			case '#':
			case ' ':
				*fp++ = c;
				break;
			case '.':
				*fp++ = c;
				if (dot == 0) {
					dot = 1;
					width = (size_t)val;
				} else
					done = 2;
				val = 0;
				break;
			case ':':
				minus = 1;
				break;
			case '-':
				if (minus)
					*fp++ = c;
				else
					done = 1;
				break;
			default:
				if (isdigit((unsigned char)c)) {
					val = (val * 10) + (c - '0');
					if (val > 10000)
						done = 2;
					else
						*fp++ = c;
				} else
					done = 1;
			}
			if (done == 0)
				c = *str++;
		}
		if (done == 2) {
			/* Found an error in the format */
			fp = fmt + 1;
			*fp = *str;
			olen = 0;
		} else {
			if (dot == 0)
				width = (size_t)val;
			else
				precision = (size_t)val;
			olen = MAX(width, precision);
		}
		*fp++ = '\0';

		/* Handle commands */
		switch (c) {
		case 'c':
			pop(&val, NULL, &stack);
			if (ochar(term, (unsigned char)val) == 0)
				return NULL;
			break;
		case 's':
			pop(NULL, &ostr, &stack);
			if (ostr != NULL) {
				int r;

				l = strlen(ostr);
				if (l < (size_t)olen)
					l = olen;
				if (checkbuf(term, (size_t)(l + 1)) == NULL)
					return NULL;
				r = snprintf(term->_buf + term->_bufpos, l + 1,
				    fmt, ostr);
				if (r != -1)
					term->_bufpos += (size_t)r;
			}
			break;
		case 'l':
			pop(NULL, &ostr, &stack);
			if (ostr == NULL)
				l = 0;
			else
				l = strlen(ostr);
#ifdef NCURSES_COMPAT_57
			if (onum(term, "%ld", (long)l, 0) == 0)
				return NULL;
#else
			push((long)l, NULL, &stack);
#endif
			break;
		case 'd':
		case 'o':
		case 'x':
		case 'X':
			pop(&val, NULL, &stack);
			if (onum(term, fmt, (int)val, olen) == 0)
				return NULL;
			break;
		case 'p':
			if (*str < '1' || *str > '9')
				break;
			l = (size_t)(*str++ - '1');
			if (push(params[l].num, params[l].string, &stack))
				return NULL;
			break;
		case 'P':
			pop(&val, NULL, &stack);
			if (*str >= 'a' && *str <= 'z')
				dnums[*str - 'a'] = val;
			else if (*str >= 'A' && *str <= 'Z')
				term->_snums[*str - 'A'] = val;
			break;
		case 'g':
			if (*str >= 'a' && *str <= 'z') {
				if (push(dnums[*str - 'a'], NULL, &stack))
					return NULL;
			} else if (*str >= 'A' && *str <= 'Z') {
				if (push(term->_snums[*str - 'A'],
					NULL, &stack))
					return NULL;
			}
			break;
		case 'i':
			if (piss[0] == 0)
				params[0].num++;
			if (piss[1] == 0)
				params[1].num++;
			break;
		case '\'':
			if (push((long)(unsigned char)*str++, NULL, &stack))
				return NULL;
			while (*str != '\0' && *str != '\'')
				str++;
			if (*str == '\'')
				str++;
			break;
		case '{':
			val = 0;
			for (; isdigit((unsigned char)*str);  str++)
				val = (val * 10) + (*str - '0');
			if (push(val, NULL, &stack))
				return NULL;
			while (*str != '\0' && *str != '}')
				str++;
			if (*str == '}')
				str++;
			break;
		case '+':
		case '-':
		case '*':
		case '/':
		case 'm':
		case 'A':
		case 'O':
		case '&':
		case '|':
		case '^':
		case '=':
		case '<':
		case '>':
			pop(&val, NULL, &stack);
			pop(&val2, NULL, &stack);
			switch (c) {
			case '+':
				val = val + val2;
				break;
			case '-':
				val = val2 - val;
				break;
			case '*':
				val = val * val2;
				break;
			case '/':
				val = val ? val2 / val : 0;
				break;
			case 'm':
				val = val ? val2 % val : 0;
				break;
			case 'A':
				val = val && val2;
				break;
			case 'O':
				val = val || val2;
				break;
			case '&':
				val = val & val2;
				break;
			case '|':
				val = val | val2;
				break;
			case '^':
				val = val ^ val2;
				break;
			case '=':
				val = val == val2;
				break;
			case '<':
				val = val2 < val;
				break;
			case '>':
				val = val2 > val;
				break;
			}
			if (push(val, NULL, &stack))
				return NULL;
			break;
		case '!':
		case '~':
			pop(&val, NULL, &stack);
			switch (c) {
			case '!':
				val = !val;
				break;
			case '~':
				val = ~val;
				break;
			}
			if (push(val, NULL, &stack))
				return NULL;
			break;
		case '?': /* if */
			break;
		case 't': /* then */
			pop(&val, NULL, &stack);
			if (val == 0) {
				l = 0;
				for (; *str != '\0'; str++) {
					if (*str != '%')
						continue;
					str++;
					if (*str == '?')
						l++;
					else if (*str == ';') {
						if (l > 0)
							l--;
						else {
							str++;
							break;
						}
					} else if (*str == 'e' && l == 0) {
						str++;
						break;
					}
				}
			}
			break;
		case 'e': /* else */
			l = 0;
			for (; *str != '\0'; str++) {
				if (*str != '%')
					continue;
				str++;
				if (*str == '?')
					l++;
				else if (*str == ';') {
					if (l > 0)
						l--;
					else {
						str++;
						break;
					}
				}
			}
			break;
		case ';': /* fi */
			break;
		}
	}
	term->_buf[term->_bufpos] = '\0';
	return term->_buf;
}

char *
ti_tiparm(TERMINAL *term, const char *str, ...)
{
	va_list va;
	char *ret;

	_DIAGASSERT(term != NULL);
	_DIAGASSERT(str != NULL);

	va_start(va, str);
	ret = _ti_tiparm(term, str, VA_CHAR_INT, va);
	va_end(va);
	return ret;
}

char *
tiparm(const char *str, ...)
{
	va_list va;
	char *ret;

	_DIAGASSERT(str != NULL);

	va_start(va, str);
	ret = _ti_tiparm(NULL, str, VA_CHAR_INT, va);
	va_end(va);
	return ret;
}

#ifdef VA_CHAR_LONG
char *
ti_tlparm(TERMINAL *term, const char *str, ...)
{
	va_list va;
	char *ret;

	_DIAGASSERT(term != NULL);
	_DIAGASSERT(str != NULL);

	va_start(va, str);
	ret = _ti_tiparm(term, str, VA_CHAR_LONG, va);
	va_end(va);
	return ret;
}

char *
tlparm(const char *str, ...)
{
	va_list va;
	char *ret;

	_DIAGASSERT(str != NULL);

	va_start(va, str);
	ret = _ti_tiparm(NULL, str, VA_CHAR_LONG, va);
	va_end(va);
	return ret;
}
#endif

static char *
_tparm(const char *str, ...)
{
	va_list va;
	char *ret;

	_DIAGASSERT(str != NULL);

	va_start(va, str);
	ret = _ti_tiparm(NULL, str, VA_LONG_LONG, va);
	va_end(va);
	return ret;
}

char *
tparm(const char *str,
    long p1, long p2, long p3, long p4, long p5,
    long p6, long p7, long p8, long p9)
{

	return _tparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
}
