/*
 *	fhist - file history and comparison tools
 *	Copyright (C) 1998, 2000, 2002 Peter Miller;
 *	All rights reserved.
 *
 *	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., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: functions to perform $ substitutions
 */

#include <ac/errno.h>
#include <ac/libintl.h>
#include <ac/limits.h>
#include <ac/string.h>
#include <ac/wctype.h>

#include <arglex.h>
#include <error.h>
#include <error_intl.h>
#include <language.h>
#include <mem.h>
#include <str.h>
#include <sub.h>
#include <sub/basename.h>
#include <sub/date.h>
#include <sub/dirname.h>
#include <sub/downcase.h>
#include <sub/errno.h>
#include <sub/expr.h>
#include <sub/ident.h>
#include <sub/length.h>
#include <sub/left.h>
#include <sub/plural.h>
#include <sub/private.h>
#include <sub/progname.h>
#include <sub/right.h>
#include <sub/upcase.h>
#include <sub/zero_pad.h>
#include <trace.h>
#include <wstr_list.h>


typedef wstring_ty *(*fp)(sub_context_ty *, wstring_list_ty *);

typedef struct table_ty table_ty;
struct table_ty
{
	const char	*name;
	fp		func;
	int		resubstitute;
	wstring_ty	*value;
	int		must_be_used	:1;
	int		append_if_unused :1;
	int		override	:1;
};

typedef struct diversion_ty diversion_ty;
struct diversion_ty
{
	long		pos;
	wstring_ty	*text;
	diversion_ty	*prev;
	int		resubstitute;
};


typedef struct collect_ty collect_ty;
struct collect_ty
{
	size_t	pos;
	size_t	size;
	wchar_t	*buf;
};

/* typedef struct sub_context_ty sub_context_ty; */
struct sub_context_ty
{
	diversion_ty	*diversion;
	table_ty	*sub_var_list;
	size_t		sub_var_size;
	size_t		sub_var_pos;
	char		*suberr;
	int		errno_sequester;
};


static void
collect_constructor(collect_ty *cp)
{
	cp->buf = 0;
	cp->size = 0;
	cp->pos = 0;
}


static void
collect_destructor(collect_ty *cp)
{
	if (cp->buf)
		mem_free(cp->buf);
	cp->buf = 0;
	cp->size = 0;
	cp->pos = 0;
}


/*
 * NAME
 *	collect
 *
 * SYNOPSIS
 *	void collect(collect_ty *, int c);
 *
 * DESCRIPTION
 *	The collect function is used to accumulate a string
 *	one character at a time.  No size limit.
 *
 * ARGUMENTS
 *	c - the character being collected
 */

static void
collect(collect_ty *cp, wchar_t c)
{
	if (cp->pos >= cp->size)
	{
		size_t	nbytes;

		cp->size += (1L << 10);
		nbytes = cp->size * sizeof(wchar_t);
		cp->buf = mem_change_size(cp->buf, nbytes);
	}
	cp->buf[cp->pos++] = c;
}


static void
collect_n(collect_ty *cp, wchar_t *s, size_t n)
{
	while (n > 0)
	{
		collect(cp, *s++);
		--n;
	}
}


/*
 * NAME
 *	collect_end
 *
 * SYNOPSIS
 *	wstring_ty *collect_end(collect_ty *);
 *
 * DESCRIPTION
 *	The collect_end function is used to fetch the string
 *	accumulated with the collect function.
 *	The bufferer for the collect function is cleared.
 *
 * RETURNS
 *	wstring_ty *; pointer to the string in dynamic memory.
 */

static wstring_ty *
collect_end(collect_ty *cp)
{
	wstring_ty	*result;

	result = wstr_n_from_wc(cp->buf, cp->pos);
	cp->pos = 0;
	return result;
}


static void
sub_context_constructor(sub_context_ty *scp)
{
	trace(("sub_context_constructor()\n{\n"));
	scp->diversion = 0;
	scp->sub_var_list = 0;
	scp->sub_var_size = 0;
	scp->sub_var_pos = 0;
	scp->suberr = 0;
	scp->errno_sequester = 0;
	trace(("}\n"));
}


sub_context_ty *
sub_context_new(void)
{
	sub_context_ty	*scp;
	int		hang_on_a_second;

	hang_on_a_second = errno;
	scp = mem_alloc(sizeof(sub_context_ty));
	errno = hang_on_a_second;
	sub_context_constructor(scp);
	return scp;
}


static void
sub_context_destructor(sub_context_ty *scp)
{
	size_t		j;

	for (j = 0; j < scp->sub_var_pos; ++j)
		wstr_free(scp->sub_var_list[j].value);
	if (scp->sub_var_list)
		mem_free(scp->sub_var_list);

	scp->diversion = 0;
	scp->sub_var_list = 0;
	scp->sub_var_size = 0;
	scp->sub_var_pos = 0;
	scp->suberr = 0;
	scp->errno_sequester = 0;
}


void
sub_context_delete(sub_context_ty *scp)
{
	sub_context_destructor(scp);
	mem_free(scp);
}


void
sub_context_error_set(sub_context_ty *scp, char *s)
{
	scp->suberr = s;
}



static table_ty	table[] =
{
	{ "Basename",			sub_basename,			},
	{ "DAte",			sub_date,			},
	{ "Dirname",			sub_dirname,			},
	{ "DownCase",			sub_downcase,			},
	{ "ERrno",			sub_errno,			},
	{ "EXpression",			sub_expression,			},
	/* File_Name							*/
	/* Guess							*/
	{ "IDentifier",			sub_identifier,			},
	{ "LEFt",			sub_left,			},
	{ "LENgth",			sub_length,			},
	/* Name								*/
	/* Number							*/
	{ "PLural",			sub_plural,			},
	{ "PROGname",			sub_progname,			},
	{ "RIght",			sub_right,			},
	{ "UpCase",			sub_upcase,			},
	/* Value							*/
	{ "Zero_Pad",			sub_zero_pad,			},
};


/*
 * NAME
 *	sub_diversion
 *
 * SYNOPSIS
 *	void sub_diversion(wstring_ty *s, int resub);
 *
 * DESCRIPTION
 *	The sub_diversion function is used to divert input
 *	to a string, until that string is exhausted.
 *	When the string is exhausted, input will resume
 *	from the previous string.
 *
 * ARGUMENTS
 *	s - string to take as input
 */

static void
sub_diversion(sub_context_ty *scp, wstring_ty *s, int resubstitute)
{
	diversion_ty	*dp;

	trace(("sub_diversion(s = %8.8lX, resub = %d)\n{\n",
		(long)s, resubstitute));
	dp = (diversion_ty *)mem_alloc(sizeof(diversion_ty));
	dp->text = wstr_copy(s);
	dp->pos = 0;
	dp->resubstitute = resubstitute;
	dp->prev = scp->diversion;
	scp->diversion = dp;
	trace(("}\n"));
}


/*
 * NAME
 *	sub_diversion_close
 *
 * SYNOPSIS
 *	void sub_diversion_close(void);
 *
 * DESCRIPTION
 *	The sub_diversion_close function is used to
 *	release a diversion when it has been exhausted.
 */

static void
sub_diversion_close(sub_context_ty *scp)
{
	diversion_ty	*dp;

	trace(("sub_diversion_close()\n{\n"));
	assert(scp->diversion);
	dp = scp->diversion;
	scp->diversion = dp->prev;
	wstr_free(dp->text);
	mem_free((char *)dp);
	trace(("}\n"));
}


/*
 * NAME
 *	execute
 *
 * SYNOPSIS
 *	void execute(wstring_list_ty *args);
 *
 * DESCRIPTION
 *	The execute function is used to perform the substitution
 *	described by the argument list.
 *
 * ARGUMENTS
 *	args - the name and arguments of the substitution
 */

static void
execute(sub_context_ty *scp, wstring_list_ty *arg)
{
	string_ty	*cmd;
	wstring_ty	*s;
	table_ty	*hit[20];
	int		nhits;
	table_ty	*tp;
	long		j;

	trace(("execute(scp = %08lX, arg = %08lX)\n{\n",
		(long)scp, (long)arg));
	if (arg->nitems == 0)
	{
		sub_context_ty	*scp2;

		scp2 = sub_context_new();
		fatal_intl(scp2, i18n("empty $${} substitution"));
		/* NOTREACHED */
		sub_context_delete(scp2);
	}
	
	/*
	 * scan the variables
	 */
	nhits = 0;
	cmd = wstr_to_str(arg->item[0]);
	trace_string(cmd->str_text);
	for (j = 0; j < scp->sub_var_pos; ++j)
	{
		tp = &scp->sub_var_list[j];
		trace(("tp = %08lX\n", (long)tp));
		trace_string(tp->name);
		if (arglex_compare(tp->name, cmd->str_text))
		{
			if (nhits < SIZEOF(hit))
				hit[nhits++] = tp;
		}
	}

	/*
	 * scan the functions
	 */
	for (tp = table; tp < ENDOF(table); ++tp)
	{
		if (arglex_compare(tp->name, cmd->str_text))
		{
			if (tp->override)
				goto override;
			if (nhits < SIZEOF(hit))
				hit[nhits++] = tp;
		}
	}
	str_free(cmd);

	/*
	 * figure what to do
	 */
	switch (nhits)
	{
	case 0:
		scp->suberr = i18n("unknown substitution name");
		s = 0;
		break;

	case 1:
		tp = hit[0];
		override:
		if (tp->value)
		{
			/*
			 * flag that the variable has been used
			 */
			tp->must_be_used = 0;
			s = wstr_copy(tp->value);
		}
		else
		{
			wstr_free(arg->item[0]);
			arg->item[0] = wstr_from_c(tp->name);
			s = tp->func(scp, arg);
		}
		break;

	default:
		scp->suberr = i18n("ambiguous substitution name");
		s = 0;
		break;
	}

	/*
	 * deal with the result
	 */
	if (s)
	{
		sub_diversion(scp, s, tp->resubstitute);
		wstr_free(s);
	}
	else
	{
		wstring_ty	*s2;
		string_ty	*s3;
		sub_context_ty	*scp2;
		char		*the_error;

		assert(scp->suberr);
		s2 = wstring_list_to_wstring(arg, 0, arg->nitems, (char *)0);
		s3 = wstr_to_str(s2);
		wstr_free(s2);
		the_error = scp->suberr ? scp->suberr : "this is a bug";
		scp2 = sub_context_new();
		sub_var_set_string(scp2, "Name", s3);
		sub_var_set_charstar(scp2, "Message", gettext(the_error));
		fatal_intl(scp2, i18n("substitution $${$name} failed: $message"));
		/* NOTREACHED */
		sub_context_delete(scp2);
		str_free(s3);
	}
	trace(("}\n"));
}


/*
 * NAME
 *	sub_getc_meta
 *
 * SYNOPSIS
 *	void sub_getc_meta(void);
 *
 * DESCRIPTION
 *	The sub_getc_meta function is used to get a character from
 *	the current input string.  When the current string is exhaused,
 *	the previous string is resumed.
 *
 * RETURNS
 *	int - the chacater, or NUL to indicate end of input
 */

static wchar_t
sub_getc_meta(sub_context_ty *scp)
{
	wchar_t		result;
	diversion_ty	*dp;

	trace(("sub_getc_meta()\n{\n"));
	dp = scp->diversion;
	if (!dp)
		result = 0;
	else if (dp->pos >= dp->text->wstr_length)
		result = 0;
	else
		result = dp->text->wstr_text[dp->pos++];
#ifdef DEBUG
	if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
		trace(("return '%c';\n", (char)result));
	else
		trace(("return %4.4lX;\n", (long)result));
#endif
	trace(("}\n"));
	return result;
}


/*
 * NAME
 *	sub_getc_meta_undo
 *
 * SYNOPSIS
 *	void sub_getc_meta_undo(int c);
 *
 * DESCRIPTION
 *	The sub_getc_meta_undo function is used to give back
 *	a character output by sub_getc_meta.
 *
 * ARGUMENTS
 *	c - character being given back
 *
 * CAVEAT
 *	Only push back what was read.
 */

static void
sub_getc_meta_undo(sub_context_ty *scp, wchar_t c)
{
	diversion_ty	*dp;

	trace(("sub_getc_meta_undo(%ld)\n{\n", (long)c));
#ifdef DEBUG
	if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
		trace(("c = '%c'\n", (char)c));
#endif
	dp = scp->diversion;
	assert(dp);
	if (!c)
	{
		assert(dp->pos == dp->text->wstr_length);
	}
	else
	{
		assert(dp->pos >= 1);
		dp->pos--;
		assert(c == dp->text->wstr_text[dp->pos]);
	}
	trace(("}\n"));
}


/*
 * NAME
 *	dollar
 *
 * SYNOPSIS
 *	wchar_t dollar(void);
 *
 * DESCRIPTION
 *	The dollar function is used to perform dollar ($) substitutions.
 *	On entry, the $ is expected to have been consumed.
 *
 *	The substitution is usually achieved as a side-effect,
 *	by using the sub_diversion function.
 *
 * RETURNS
 *	wchar_t	a character to deliver as output,
 *		or NUL if none.
 */

static wchar_t sub_getc(sub_context_ty *); /* forward */

static wchar_t
dollar(sub_context_ty *scp)
{
	wstring_list_ty	arg;
	int		result;
	wchar_t		c;
	wstring_ty	*s;
	wchar_t		quoted;
	collect_ty	tmp;

	trace(("dollar()\n{\n"));
	collect_constructor(&tmp);
	wstring_list_zero(&arg);
	result = 0;
	c = sub_getc_meta(scp);
	switch (c)
	{
	case '0': case '1': case '2': case '3': case '4':
	case '5': case '6': case '7': case '8': case '9': 
		for (;;)
		{
			collect(&tmp, c);
			c = sub_getc_meta(scp);
			switch (c)
			{
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				continue;

			default:
				sub_getc_meta_undo(scp, c);
				break;
			}
			break;
		}
		s = collect_end(&tmp);
		trace(("push arg\n"));
		wstring_list_append(&arg, s);
		wstr_free(s);
		execute(scp, &arg);
		wstring_list_free(&arg);
		break;

	case 'a': case 'b': case 'c': case 'd': case 'e':
	case 'f': case 'g': case 'h': case 'i': case 'j':
	case 'k': case 'l': case 'm': case 'n': case 'o':
	case 'p': case 'q': case 'r': case 's': case 't':
	case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': 
	case 'A': case 'B': case 'C': case 'D': case 'E':
	case 'F': case 'G': case 'H': case 'I': case 'J':
	case 'K': case 'L': case 'M': case 'N': case 'O':
	case 'P': case 'Q': case 'R': case 'S': case 'T':
	case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': 
		for (;;)
		{
			collect(&tmp, c);
			c = sub_getc_meta(scp);
			switch (c)
			{
			case 'a': case 'b': case 'c': case 'd': case 'e':
			case 'f': case 'g': case 'h': case 'i': case 'j':
			case 'k': case 'l': case 'm': case 'n': case 'o':
			case 'p': case 'q': case 'r': case 's': case 't':
			case 'u': case 'v': case 'w': case 'x': case 'y':
			case 'z': 
			case 'A': case 'B': case 'C': case 'D': case 'E':
			case 'F': case 'G': case 'H': case 'I': case 'J':
			case 'K': case 'L': case 'M': case 'N': case 'O':
			case 'P': case 'Q': case 'R': case 'S': case 'T':
			case 'U': case 'V': case 'W': case 'X': case 'Y':
			case 'Z': 
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9': 
			case '_': case '-':
				continue;

			default:
				sub_getc_meta_undo(scp, c);
				break;
			}
			break;
		}
		s = collect_end(&tmp);
		trace(("push arg\n"));
		wstring_list_append(&arg, s);
		wstr_free(s);
		execute(scp, &arg);
		wstring_list_free(&arg);
		break;

	case '{':
		c = sub_getc(scp);
		for (;;)
		{
			/*
			 * look for terminator
			 */
			if (c == '}')
				break;

			/*
			 * watch out for unterminated substitutions
			 */
			if (!c)
			{
				sub_context_ty	*scp2;

				scp2 = sub_context_new();
				fatal_intl(scp2, i18n("unterminated $${} substitution"));
				/* NOTREACHED */
				sub_context_delete(scp2);
				break;
			}

			/*
			 * skip white space separating the arguments
			 */
			if (iswspace(c))
			{
				c = sub_getc(scp);
				continue;
			}

			/*
			 * collect the argument
			 *	any run of non-white-space characters
			 */
			quoted = 0;
			for (;;)
			{
				if (!c)
				{
					if (quoted)
					{
						sub_context_ty	*scp2;

						scp2 = sub_context_new();
						fatal_intl
						(
							scp2,
						i18n("unterminated $${} quotes")
						);
						/* NOTREACHED */
						sub_context_delete(scp2);
					}
					break;
				}
				if (!quoted && (iswspace(c) || c == '}'))
					break;
				if (c == quoted)
				{
					assert(quoted);
					quoted = 0;
				}
				else if (c == '\'')
				{
					assert(!quoted);
					quoted = c;
				}
				else if (c == '\\')
				{
					c = sub_getc(scp);
					if (!c)
					{
						sub_context_ty	*scp2;

						scp2 = sub_context_new();
						fatal_intl
						(
							scp2,
					   i18n("unterminated $${} \\ sequence")
						);
						/* NOTREACHED */
						sub_context_delete(scp2);
					}
					collect(&tmp, c);
				}
				else
					collect(&tmp, c);
				c = sub_getc(scp);
			}
			s = collect_end(&tmp);
			trace(("push arg\n"));
			wstring_list_append(&arg, s);
			wstr_free(s);
		}
		execute(scp, &arg);
		wstring_list_free(&arg);
		break;

	case '$':
		result = '$';
		break;

	default:
		sub_getc_meta_undo(scp, c);
		result = '$';
		break;
	}
	collect_destructor(&tmp);
#ifdef DEBUG
	if (iswprint(result) && result >= CHAR_MIN && result <= CHAR_MAX)
		trace(("return '%c';\n", (char)result));
	else
		trace(("return %4.4lX;\n", (long)result));
#endif
	trace(("}\n"));
	return result;
}


/*
 * NAME
 *	sub_getc
 *
 * SYNOPSIS
 *	wchar_t sub_getc(void);
 *
 * DESCRIPTION
 *	The sub_getc function is used to get a character from
 *	the substitution stream.  This is used both for the final output
 *	and for fetching arguments to dollar ($) substitutions.
 *
 * RETURNS
 *	wchar_t	a character from the stream,
 *		or NUL to indicate end of input.
 */

static wchar_t
sub_getc(sub_context_ty *scp)
{
	wchar_t		c;

	trace(("sub_getc()\n{\n"));
	for (;;)
	{
		c = sub_getc_meta(scp);
		switch (c)
		{
		default:
			break;

		case 0:
			if (!scp->diversion)
				break;
			sub_diversion_close(scp);
			continue;

		case '$':
			if (!scp->diversion->resubstitute)
				break;
			c = dollar(scp);
			if (!c)
				continue;
			break;
		}
		break;
	}
#ifdef DEBUG
	if (iswprint(c) && c >= CHAR_MIN && c <= CHAR_MAX)
		trace(("return '%c';\n", (char)c));
	else
		trace(("return %4.4lX;\n", (long)c));
#endif
	trace(("}\n"));
	return c;
}


static wstring_ty *
subst(sub_context_ty *scp, wstring_ty *s)
{
	collect_ty	buf;
	wchar_t		c;
	wstring_ty	*result;
	table_ty	*tp;
	table_ty	*the_end;
	int		error_count;

	trace(("subst(scp = %08lX, s = %8.8lX)\n{\n",
		(long)scp, (long)s));
	collect_constructor(&buf);
	sub_diversion(scp, s, 1);
	for (;;)
	{
		/*
		 * get the next character
		 */
		c = sub_getc(scp);
		if (!c)
			break;

		/*
		 * save the character
		 */
		collect(&buf, c);
	}

	/*
	 * find any unused variables marked "append if unused"
	 */
	the_end = scp->sub_var_list + scp->sub_var_pos;
	for (tp = scp->sub_var_list; tp < the_end; ++tp)
	{
		if (!tp->append_if_unused)
			continue;
		if (!tp->must_be_used)
			continue;
		assert(tp->value);

		/*
		 * flag that the variable has been used
		 */
		tp->must_be_used = 0;
		if (!tp->value->wstr_length)
			continue;

		/*
		 * append to the buffer, separated by a space
		 */
		collect(&buf, (wchar_t)' ');
		collect_n(&buf, tp->value->wstr_text, tp->value->wstr_length);
	}

	/*
	 * find any unused variables
	 * and complain about them
	 */
	error_count = 0;
	for (tp = scp->sub_var_list; tp < the_end; ++tp)
	{
		sub_context_ty	*scp2;
		string_ty	*tmp;

		if (!tp->must_be_used)
			continue;

		/*
		 * Make sure the variables of this message are optional,
		 * to avoid infinite loops if there is a mistake in the
		 * translation string.
		 */
		scp2 = sub_context_new();
		tmp = wstr_to_str(s);
		sub_var_set_string(scp2, "Message", tmp);
		sub_var_optional(scp2, "Message");
		sub_var_set_format(scp2, "Name", "$%s", tp->name);
		sub_var_optional(scp2, "Name");
		error_intl
		(
			scp2,
			i18n("in substitution \"$message\" variable \"$name\" unused")
		);
		sub_context_delete(scp2);
		str_free(tmp);
		++error_count;
	}
	if (error_count > 0)
	{
		string_ty	*tmp;
		sub_context_ty	*scp2;

		/*
		 * Make sure the variables of this message are optional,
		 * to avoid infinite loops if there is a mistake in the
		 * translation string.
		 */
		scp2 = sub_context_new();
		tmp = wstr_to_str(s);
		sub_var_set_string(scp2, "Message", tmp);
		sub_var_optional(scp2, "Message");
		sub_var_set_format(scp2, "Number", "%d", error_count);
		sub_var_optional(scp2, "Number");
		fatal_intl
		(
			scp2,
			i18n("in substitution \"$message\" found unused variables")
		);
		/* NOTREACHED */
		str_free(tmp);
		sub_context_delete(scp2);
	}

	/*
	 * clear the slate, ready for the next run
	 */
	sub_var_clear(scp);
	result = collect_end(&buf);
	collect_destructor(&buf);
	trace(("return %8.8lX;\n", (long)result));
	trace(("}\n"));
	return result;
}


wstring_ty *
subst_intl_wide(sub_context_ty *scp, const char *msg)
{
	char		*tmp;
	wstring_ty	*s;
	wstring_ty	*result;

	trace(("subst_intl_wide(scp = %08lX, msg = \"%s\")\n{\n",
		(long)scp, msg));
	language_human();
	tmp = gettext(msg);
	language_C();
#ifdef DEBUG
#ifdef HAVE_GETTEXT
	if (tmp == msg)
		error_raw("warning: message \"%s\" has no translation", msg);
#endif /* HAVE_GETTEXT */
#endif /* DEBUG */
	s = wstr_from_c(tmp);
	result = subst(scp, s);
	wstr_free(s);
	trace(("return %8.8lX;\n", (long)result));
	trace(("}\n"));
	return result;
}


string_ty *
subst_intl(sub_context_ty *scp, const char *s)
{
	wstring_ty	*result_wide;
	string_ty	*result;

	trace(("subst_intl(scp = %08lX, s = \"%s\")\n{\n",
		(long)scp, s));
	result_wide = subst_intl_wide(scp, s);
	result = wstr_to_str(result_wide);
	wstr_free(result_wide);
	trace(("return \"%s\";\n", result->str_text));
	trace(("}\n"));
	return result;
}


/*
 * NAME
 *	substitute
 *
 * SYNOPSIS
 *	string_ty *substitute(string_ty *s);
 *
 * DESCRIPTION
 *	The substitute function is used to perform substitutions on
 *	strings.  Usually command strings, but not always.
 *
 *	The format of substitutions, and the commonly available
 *	substitutions, are described in aesub(5).
 *
 * ARGUMENTS
 *	cp	- the aegis change involved with the command
 *		  This may never be NULL.
 *	s	- the string to be substituted.
 *
 * RETURNS
 *	string_ty *; pointer to string in dynamic memory
 */

string_ty *
substitute(sub_context_ty *scp, string_ty *s)
{
	wstring_ty	*ws;
	wstring_ty	*result_wide;
	string_ty	*result;

	assert(s);
	trace(("substitute(scp = %08lX, s = \"%s\")\n{\n",
		(long)scp, s->str_text));
	ws = str_to_wstr(s);
	result_wide = subst(scp, ws);
	wstr_free(ws);
	result = wstr_to_str(result_wide);
	wstr_free(result_wide);
	trace(("return \"%s\";\n", result->str_text));
	trace(("}\n"));
	return result;
}


/*
 * NAME
 *	sub_var_clear
 *
 * SYNOPSIS
 *	void sub_var_clear(void);
 *
 * DESCRIPTION
 *	The sub_var_clear function is used to clear all of
 *	the substitution variables.  Not usually needed manually,
 *	as this is done automatically at the end of every substitute().
 */

void
sub_var_clear(sub_context_ty *scp)
{
	size_t		j;

	trace(("sub_var_clear()\n{\n"));
	for (j = 0; j < scp->sub_var_pos; ++j)
		wstr_free(scp->sub_var_list[j].value);
	scp->sub_var_pos = 0;
	scp->errno_sequester = 0;
	trace(("}\n"));
}


/*
 * NAME
 *	sub_var_set
 *
 * SYNOPSIS
 *	void sub_var_set(char *name, char *fmt, ...);
 *
 * DESCRIPTION
 *	The sub_var_set function is used to set the value of a
 *	substitution variable.  These variables are command specific,
 *	as opposed to the functions which are always present.
 *	The user documentation does NOT make this distinction by
 *	using the names "variable" and "function", they are always referred
 *	to as "substitutions".
 *
 * ARGUMENTS
 *	name	- the name of the variable
 *	fmt,...	- a format string and arguments to construct the value.
 *		  Handed to str_vformat to make a (string_ty *) out of it.
 *
 * CAVEAT
 *	remains in scope until the next invokation of sub_var_clear,
 *	or until the end of the next invokation of substitute.
 */

void
sub_var_set_string(sub_context_ty *scp, const char *name, string_ty *value)
{
	table_ty	*svp;

	trace(("sub_var_set_string(scp = %08lX, name = \"%s\", value = \"%s\")\n{\n",
		(long)scp, name, value->str_text));

	if (scp->sub_var_pos >= scp->sub_var_size)
	{
		size_t		nbytes;

		scp->sub_var_size += 10;
		nbytes = scp->sub_var_size * sizeof(scp->sub_var_list[0]);
		scp->sub_var_list = mem_change_size(scp->sub_var_list, nbytes);
	}
	svp = &scp->sub_var_list[scp->sub_var_pos++];
	svp->name = name;
	svp->func = 0;
	svp->value = str_to_wstr(value);
	svp->must_be_used = 1;
	svp->append_if_unused = 0;
	svp->override = 0;
	svp->resubstitute = !svp->must_be_used;
	trace(("}\n"));
}


void
sub_var_set_format(sub_context_ty *scp, const char *name, const char *fmt, ...)
{
	va_list		ap;
	string_ty	*s;

	trace(("sub_var_set_format(scp = %08lX, name = \"%s\")\n{\n",
		(long)scp, name));
	va_start(ap, fmt);
	s = str_vformat(fmt, ap);
	va_end(ap);
	sub_var_set_string(scp, name, s);
	str_free(s);
	trace(("}\n"));
}


void
sub_var_set_long(sub_context_ty *scp, const char *name, long value)
{
	sub_var_set_format(scp, name, "%ld", value);
}


void
sub_var_set_charstar(sub_context_ty *scp, const char *name, const char *value)
{
	string_ty	*s;

	trace(("sub_var_set_charstar(scp = %08lX, name = \"%s\")\n{\n",
		(long)scp, name));
	s = str_from_c(value);
	sub_var_set_string(scp, name, s);
	str_free(s);
	trace(("}\n"));
}


void
sub_var_resubstitute(sub_context_ty *scp, const char *name)
{
	table_ty	*the_end;
	table_ty	*svp;

	the_end = scp->sub_var_list + scp->sub_var_pos;
	for (svp = scp->sub_var_list; svp < the_end; ++svp)
		if (!strcmp(svp->name, name))
			break;
	assert(svp < the_end);
	if (svp < the_end)
		svp->resubstitute = 1;
}


void
sub_var_override(sub_context_ty *scp, const char *name)
{
	table_ty	*the_end;
	table_ty	*svp;

	the_end = scp->sub_var_list + scp->sub_var_pos;
	for (svp = scp->sub_var_list; svp < the_end; ++svp)
		if (!strcmp(svp->name, name))
			break;
	assert(svp < the_end);
	if (svp < the_end)
		svp->override = 1;
}


void
sub_var_optional(sub_context_ty *scp, const char *name)
{
	table_ty	*the_end;
	table_ty	*svp;

	the_end = scp->sub_var_list + scp->sub_var_pos;
	for (svp = scp->sub_var_list; svp < the_end; ++svp)
		if (!strcmp(svp->name, name))
			break;
	assert(svp < the_end);
	if (svp < the_end)
		svp->must_be_used = 0;
}


void
sub_var_append_if_unused(sub_context_ty *scp, const char *name)
{
	table_ty	*the_end;
	table_ty	*svp;

	the_end = scp->sub_var_list + scp->sub_var_pos;
	for (svp = scp->sub_var_list; svp < the_end; ++svp)
		if (!strcmp(svp->name, name))
			break;
	assert(svp < the_end);
	if (svp < the_end)
	{
		svp->must_be_used = 1;
		svp->append_if_unused = 1;
	}
}


void
sub_errno_setx(sub_context_ty *scp, int x)
{
	scp->errno_sequester = x;
}


void
sub_errno_set(sub_context_ty *scp)
{
	sub_errno_setx(scp, errno);
}


int
sub_context_errno_get(sub_context_ty *scp)
{
	if (scp->errno_sequester == 0)
		scp->errno_sequester = errno;
	return scp->errno_sequester;
}	
