/* This file is part of Mailfromd.
   Copyright (C) 2009-2025 Sergey Poznyakoff

   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, 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 "mailfromd.h"
#include "prog.h"

/* Opool used for expanding built-in constants in pragmas */
mu_opool_t expand_pool;

/* Select next "word" from STRING. Point PSTART to the beginning
   of the word, and PEND to its end. A "word" is either a sequence
   of non-whitespace characters, or a sequence of characters enclosed
   in "" or ''. */
static const char *
nextword(const char *string, const char **pstart, const char **pend)
{
	const char *end;

	while (*string && mu_isspace(*string))
		string++;
	if (*string == '"' || *string == '\'') {
		int quote = *string;
		for (end = string + 1; *end && *end != quote; end++)
			if (quote == '"' && *end == '\\' && *++end == 0)
				break;
		end++;
	} else
		for (end = string + 1; *end && !mu_isspace(*end); end++)
			;
	*pstart = string;
	*pend = end;
	return string;
}

/* Expand (possible) built-in constant from the first LEN
   bytes of TEXT. */
void
expand_builtin(const char *text, size_t len)
{
	char buf[NUMERIC_BUFSIZE_BOUND];
	const char *sval;
	long nval;

	switch (builtin_const_value(text, len, &sval, &nval)) {
	case dtype_string:
		mu_opool_appendz(expand_pool, sval);
		break;
				
	case dtype_number:
		snprintf(buf, sizeof(buf), "%ld", nval);
		mu_opool_appendz(expand_pool, buf);
		break;
		
	default:
		mu_opool_append(expand_pool, text, len);
	}
}


/* Find double underscore in first LEN bytes of TEXT.
   If found, return 1 and store to POS the offset of the byte
   past the `__'.
*/
static int
find_double_underscore(const char *text, size_t len, size_t *pos)
{
	const char *q;
	size_t ret = 0;
	while (len && (q = memchr(text, '_', len))) {
		size_t s = q - text + 1;
		
		if (q[1] == '_') {
			*pos = ret + s + 1;
			return 1;
		}
		
		text = q + 1;
		len -= s;
		ret += s;
	}
	return 0;
}

static int
is_delim(int c)
{
	return !mu_isalnum(c) || mu_isblank(c);
}

/* Expand LEN bytes of quoted string TEXT. Constants are
   expanded only if prefixed by %. */
void
expand_string(const char *text, size_t len)
{
	const char *p;

	while (*text) {
		size_t s;
		
		p = memchr(text, '%', len);
		if (!p) {
			mu_opool_append(expand_pool, text, len);
			break;
		}

		s = p - text;
		mu_opool_append(expand_pool, text, s);
		text = p + 1;
		len -= s + 1;

		if (len > 4 && memcmp(text, "__", 2) == 0
		    && find_double_underscore(text + 2, len - 2, &s)
		    && is_delim(text[s+2])) {
			s += 2; /* account for initial __ */
		        expand_builtin(text, s);
			text += s;
			len -= s;
		} else if (len > 6 && memcmp(text, "{__", 3) == 0
			   && find_double_underscore(text + 3, len - 3,
						     &s)
			   && text[s + 3] == '}') {
			expand_builtin(text + 1, s + 2);
			s += 4; /* "{__" + "}" */
			text += s;
			len -= s;
		} else {
			mu_opool_append_char(expand_pool, '%');
		}
	}
}

void
expand_text(const char *text)
{
	const char *end;

	while (nextword(text, &text, &end)) {
		size_t len = end - text;

		if (*text == '"' && end > text && end[-1] == '"')
			expand_string(text, len);
		else if (len > 2 && memcmp(text, "__", 2) == 0)
			expand_builtin(text, len);
		else
			mu_opool_append(expand_pool, text, len);

		if (*end == 0)
			break;
		text = end + 1;
		mu_opool_append_char(expand_pool, ' ');
	}
	mu_opool_append_char(expand_pool, 0);
}

int
check_pragma_args(const struct pragma *pdef, int argc)
{
	if (pdef->minargs > 0 && argc < pdef->minargs) {
		parse_error(_("too few arguments in pragma"));
		return 1;
	}
	if (pdef->maxargs > 0 && argc > pdef->maxargs) {
		parse_error(_("too many arguments in pragma"));
		return 1;
	}
	return 0;
}

void
parse_pragma(const char *text)
{
	int rc;
	struct mu_wordsplit ws;
	const struct pragma *pdef;
	char *s;
	
	while (*text != '#')
		text++;
	++text;
	while (*text && mu_isspace(*text)) text++;
	text += 6; /* "pragma" */

	mu_opool_create(&expand_pool, MU_OPOOL_ENOMEMABRT);
	expand_text(text);
	s = mu_opool_finish(expand_pool, NULL);
	rc = mu_wordsplit(s, &ws, MU_WRDSF_DEFFLAGS);
	mu_opool_destroy(&expand_pool);
	if (rc) {
		parse_error(_("error parsing pragma: %s"),
			    mu_wordsplit_strerror (&ws));
		return;
	}

	if (ws.ws_wordc == 0) {
		parse_error(_("empty pragma"));
		mu_wordsplit_free(&ws);
		return;
	}

	pdef = lookup_pragma(ws.ws_wordv[0]);
	if (!pdef) 
		parse_error(_("unknown pragma"));
	else if (check_pragma_args(pdef, ws.ws_wordc) == 0)
		pdef->handler(ws.ws_wordc, ws.ws_wordv, text);
	mu_wordsplit_free(&ws);
}
