/*
 *	vii - buffer and display output
 *	Copyright (C) 1991-1995, 1999, 2005 Peter Miller
 *
 *	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 provide consistent -Help behaviour
 */

#include <ac/ctype.h>
#include <ac/stdarg.h>
#include <ac/stdio.h>
#include <ac/stdlib.h>
#include <ac/string.h>
#include <ac/unistd.h>

#include <arglex.h>
#include <error.h>
#include <help.h>
#include <mem.h>
#include <option.h>
#include <str.h>
#include <trace.h>
#include <version_stmp.h>


#define PAIR(a, b) ((a) * 256 + (b))


static	FILE	*fp;
static	char	*pager;

static char *cr[] =
{
	"\\*(n) version \\*(v)",
	".br",
	"Copyright (C) \\*(Y) Peter Miller",
	"",
	"The \\*(n) program comes with ABSOLUTELY NO WARRANTY;",
	"for details use the '\\*(n) -VERSion Warranty' command.",
	"The \\*(n) program is free software, and you are welcome to",
	"redistribute it under certain conditions;",
	"for details use the '\\*(n) -VERSion Redistribution' command.",
};

static char *au[] =
{
	".nf",
	"Peter Miller   E-Mail: millerp@canb.auug.org.au",
	"/\\e/\\e*          WWW:    http://www.canb.auug.org.au/~millerp/",
	".fi",
};

static char *so_o__rules[] =
{
#include <../man1/o__rules.h>
};

static char *so_z_exit[] =
{
#include <../man1/z_exit.h>
};

static char *so_copyright[] =
{
	".SH COPYRIGHT",
	".so cr",
	".SH AUTHOR",
	".so au",
};

typedef struct so_list_ty so_list_ty;
struct so_list_ty
{
	char	*name;
	char	**text;
	int	length;
};

static so_list_ty so_list[] =
{
	{ "o__rules.so",	so_o__rules,	SIZEOF(so_o__rules)	},
	{ "z_exit.so",		so_z_exit,	SIZEOF(so_z_exit)	},
	{ "z_name.so",		0,		0			},
	{ "copyright.so",	so_copyright,	SIZEOF(so_copyright)	},
	{ "../doc/version.so",	0,		0			},
	{ "cr",			cr,		SIZEOF(cr),		},
	{ "au",			au,		SIZEOF(au),		},
};


static	int	ocol;
static	int	icol;
static	int	fill;	/* true if currently filling */
static	int	in;	/* current indent */
static	int	ll;	/* line length */
static	long	roff_line;
static	char	*roff_file;
static	int	TP_line;


static void pager_error _((void));

static void
pager_error()
{
	nfatal("write %s", (pager ? pager : "standard output"));
}


static void emit _((int));

static void
emit(c)
	int	c;
{
	switch (c)
	{
	case ' ':
		icol++;
		break;

	case '\t':
		icol = ((icol / 8) + 1) * 8;
		break;
	
	case '\n':
		fputc('\n', fp);
		fflush(fp);
		icol = 0;
		ocol = 0;
		break;

	default:
		if (!isprint(c))
			break;
		while (((ocol / 8) + 1) * 8 <= icol && ocol + 1 < icol)
		{
			fputc('\t', fp);
			ocol = ((ocol / 8) + 1) * 8;
		}
		while (ocol < icol)
		{
			fputc(' ', fp);
			++ocol;
		}
		fputc(c, fp);
		++icol;
		++ocol;
		break;
	}
	if (ferror(fp))
		pager_error();
}


static void emit_word _((char *, long));

static void
emit_word(buf, len)
	char	*buf;
	long	len;
{
	if (len <= 0)
		return;

	/*
	 * if this line is not yet indented, indent it
	 */
	if (!ocol && !icol)
		icol = in;
	
	/*
	 * if there is already something on this line 
	 * and we are in "fill" mode
	 * and this word would cause it to overflow
	 * then wrap the line
	 */
	if (ocol && fill && icol + len >= ll)
	{
		emit('\n');
		icol = in;
	}
	if (ocol)
		emit(' ');
	while (len-- > 0)
		emit(*buf++);
}


static void br _((void));

static void
br()
{
	if (ocol)
		emit('\n');
}


static void sp _((void));

static void
sp()
{
	br();
	emit('\n');
}


static void interpret_line_of_words _((char *));

static void
interpret_line_of_words(line)
	char	*line;
{
	/*
	 * if not filling,
	 * pump the line out literrally.
	 */
	if (!fill)
	{
		if (!ocol && !icol)
			icol = in;
		while (*line)
			emit(*line++);
		emit('\n');
		return;
	}

	/*
	 * in fill mode, a blank line means
	 * finish the paragraph and emit a blank line
	 */
	if (!*line)
	{
		sp();
		return;
	}

	/*
	 * break the line into space-separated words
	 * and emit each individually
	 */
	while (*line)
	{
		char	*start;

		while (isspace(*line))
			++line;
		if (!*line)
			break;
		start = line;
		while (*line && !isspace(*line))
			++line;
		emit_word(start, line - start);

		/*
		 * extra space at end of sentences
		 */
		if
		(
			(line[-1] == '.' || line[-1] == '?')
		&&
			(
				!line[0]
			||
				(
					line[0] == ' '
				&&
					(!line[1] || line[1] == ' ')
				)
			)
		)
			emit(' ');
	}
}


static void roff_error _((char *, ...));

static void
roff_error(s sva_last)
	char		*s;
	sva_last_decl
{
	string_ty	*buffer;
	va_list		ap;

	sva_init(ap, s);
	buffer = str_vformat(s, ap);
	va_end(ap);
	fatal
	(
		"%s: %ld: %S",
		(roff_file ? roff_file : "(noname)"),
		roff_line,
		buffer
	);
}


static void get_name _((char **, char *));

static void
get_name(lp, name)
	char	**lp;
	char	*name;
{
	char	*line;

	line = *lp;
	if (*line == '('/*)*/)
	{
		++line;
		if (*line)
		{
			name[0] = *line++;
			if (*line)
			{
				name[1] = *line++;
				name[2] = 0;
			}
			else
				name[1] = 0;
		}
		else
			name[0] = 0;
	}
	else if (*line)
	{
		name[0] = *line++;
		name[1] = 0;
	}
	else
		name[0] = 0;
	*lp = line;
}


typedef struct string_reg_ty string_reg_ty;
struct string_reg_ty
{
	char	*name;
	char	*value;
};


static	long		string_reg_count;
static	string_reg_ty	*string_reg;



static char *string_find _((char *));

static char *
string_find(name)
	char	*name;
{
	long	j;

	for (j = 0; j < string_reg_count; ++j)
	{
		string_reg_ty	*srp;

		srp = &string_reg[j];
		if (!strcmp(name, srp->name))
			return srp->value;
	}
	return 0;
}


static char *numreg_find _((char *));

static char *
numreg_find(name)
	char	*name;
{
	return 0;
}


static void roff_prepro _((char *, char *));

static void
roff_prepro(buffer, line)
	char	*buffer;
	char	*line;
{
	char	*bp;
	char	*value;
	char	name[4];

	bp = buffer;
	while (*line)
	{
		int c = *line++;
		if (c != '\\')
		{
			*bp++ = c;
			continue;
		}
		c = *line++;
		if (!c)
		{
			roff_error("can't do escaped end-of-line");
			break;
		}
		switch (c)
		{
		default:
			roff_error("unknown \\%c inline directive", c);
			break;

		case '%':
			/* word break info */
			break;

		case '*':
			/* inline string */
			get_name(&line, name);
			value = string_find(name);
			if (value)
			{
				while (*value)
					*bp++ = *value++;
			}
			break;

		case 'n':
			/* inline number register */
			get_name(&line, name);
			value = numreg_find(name);
			if (value)
			{
				while (*value)
					*bp++ = *value++;
			}
			break;

		case 'e':
		case '\\':
			*bp++ = '\\';
			break;

		case '-':
			*bp++ = '-';
			break;

		case 'f':
			/* ignore font directives */
			get_name(&line, name);
			break;

		case '&':
		case '|':
			/* ignore weird space directives */
			break;
		}
	}
	*bp = 0;
}


static void interpret_text _((char *));

static void
interpret_text(line)
	char	*line;
{
	char	buffer[1000];

	roff_prepro(buffer, line);
	interpret_line_of_words(buffer);
	if (TP_line)
	{
		if (icol >= 15)
			br();
		else
			icol = 15;
		TP_line = 0;
		in = 16;
	}
}


static void roff_sub _((char *, int, char **));

static void
roff_sub(buffer, argc, argv)
	char	*buffer;
	int	argc;
	char	**argv;
{
	int	j;
	char	*bp;
	long	len;

	bp = buffer;
	for (j = 0; j < argc; ++j)
	{
		len = strlen(argv[j]);
		if (j)
			*bp++ = ' ';
		memcpy(bp, argv[j], len);
		bp += len;
	}
	*bp = 0;
}


static void interpret_text_args _((int, char **));

static void
interpret_text_args(argc, argv)
	int	argc;
	char	**argv;
{
	char	buffer[1000];

	roff_sub(buffer, argc, argv);
	interpret_text(buffer);
}


static void concat_text_args _((int, char **));

static void
concat_text_args(argc, argv)
	int	argc;
	char	**argv;
{
	int	j;
	char	*bp;
	long	len;
	char	buffer[1000];

	bp = buffer;
	for (j = 0; j < argc; ++j)
	{
		len = strlen(argv[j]);
		if ((bp - buffer) + len + 1 >= sizeof(buffer))
			break;
		memcpy(bp, argv[j], len);
		bp += len;
	}
	*bp = 0;
	interpret_text(buffer);
}


static void interpret _((char **, int)); /* forward */


static void so _((int, char **));

static void
so(argc, argv)
	int	argc;
	char	**argv;
{
	so_list_ty	*sop;

	if (argc != 1)
	{
		roff_error(".so requires one argument");
		return;
	}
	for (sop = so_list; sop < ENDOF(so_list); ++sop)
	{
		if (!strcmp(sop->name, argv[0]))
		{
			interpret(sop->text, sop->length);
			return;
		}
	}
	roff_error("\".so %s\" not known", argv[0]);
}


static void lf _((int, char **));

static void
lf(argc, argv)
	int	argc;
	char	**argv;
{
	if (roff_file)
		mem_free(roff_file);
	if (argc >= 1)
		roff_line = atol(argv[0]) - 1;
	else
		roff_line = 0;
	if (argc >= 2)
		roff_file = mem_copy_string(argv[1]);
	else
		roff_file = 0;
}


static void ds_guts _((char *, char *));

static void
ds_guts(name, value)
	char		*name;
	char		*value;
{
	long		j;
	string_reg_ty	*srp;
	size_t		nbytes;

	for (j = 0; j < string_reg_count; ++j)
	{
		srp = &string_reg[j];
		if (!strcmp(name, srp->name))
		{
			mem_free(srp->value);
			srp->value = mem_copy_string(value);
			return;
		}
	}

	nbytes = (string_reg_count + 1) * sizeof(string_reg_ty);
	string_reg = mem_change_size(string_reg, nbytes);
	srp = &string_reg[string_reg_count++];
	srp->name = mem_copy_string(name);
	srp->value = mem_copy_string(value);
}


static void ds _((int, char **));

static void
ds(argc, argv)
	int	argc;
	char	**argv;
{
#if 1
	/*
	 * ignore .ds directives
	 * values already set appropriately
	 */
#else
	char	buf1[1000];
	char	buf2[1000];

	if (!argc)
		return;
	roff_sub(buf1, argc - 1, argv + 1);
	roff_prepro(buf2, buf1);
	ds_guts(argv[0], buf2);
#endif
}


static void dot_in _((int, char**));

static void
dot_in(argc, argv)
	int	argc;
	char	**argv;
{
	if (argc < 1)
		return;
	switch (argv[0][0])
	{
	case '-':
		in -= atoi(argv[0] + 1);
		break;

	case '+':
		in += atoi(argv[0] + 1);
		break;

	default:
		in = atoi(argv[0] + 1);
		break;
	}
	if (in < 0)
		in = 0;
}


static void interpret _((char **, int)); /* forward */


static void interpret_control _((char *));

static void
interpret_control(line)
	char	*line;
{
	int	c1, c2;
	int	argc;
	char	*argv[20];
	char	temp[1000];
	char	*cp;

	/*
	 * find the directive name
	 */
	line++;
	while (isspace(*line))
		++line;
	if (*line)
		c1 = *line++;
	else
		c1 = ' ';
	if (*line)
		c2 = *line++;
	else
		c2 = ' ';

	/*
	 * break the line into space-separated arguments
	 */
	argc = 0;
	cp = temp;
	while (argc < SIZEOF(argv))
	{
		int quoting;

		while (isspace(*line))
			++line;
		if (!*line)
			break;
		argv[argc++] = cp;
		quoting = 0;
		while (*line)
		{
			if (*line == '"')
			{
				quoting = !quoting;
				++line;
				continue;
			}
			if (!quoting && isspace(*line))
				break;
			*cp++ = *line++;
		}
		*cp++ = 0;
		if (!*line)
			break;
	}

	/*
	 * now do something with it
	 */
	switch (PAIR(c1, c2))
	{
	case PAIR('n', 'e'):
		/* ignore the space needed directive */
		break;

	case PAIR('f', 't'):
		/* ignore the font directive */
		break;

	case PAIR('i', 'n'):
		dot_in(argc, argv);
		break;

	case PAIR('I', ' '):
	case PAIR('I', 'R'):
	case PAIR('I', 'B'):
	case PAIR('R', ' '):
	case PAIR('R', 'I'):
	case PAIR('R', 'B'):
	case PAIR('B', ' '):
	case PAIR('B', 'I'):
	case PAIR('B', 'R'):
		concat_text_args(argc, argv);
		break;

	case PAIR('n', 'f'):
		br();
		fill = 0;
		break;

	case PAIR('f', 'i'):
		br();
		fill = 1;
		break;

	case PAIR('t', 'a'):
		/* ignore tab directive */
		break;

	case PAIR('b', 'r'):
		br();
		break;

	case PAIR('s', 'p'):
		sp();
		break;

	case PAIR('I', 'P'):
		sp();
		emit(' ');
		emit(' ');
		break;

	case PAIR('P', 'P'):
		in = 8;
		sp();
		break;

	case PAIR('T', 'H'):
		break;

	case PAIR('T', 'P'):
		in = 8;
		sp();
		TP_line = 1;
		break;

	case PAIR('S', 'H'):
		in = 0;
		sp();
		interpret_text_args(argc, argv);
		br();
		in = 8;
		break;

	case PAIR('S', 'S'):
		in = 4;
		sp();
		interpret_text_args(argc, argv);
		br();
		in = 8;
		break;

	case PAIR('s', 'o'):
		so(argc, argv);
		break;

	case PAIR('l', 'f'):
		lf(argc, argv);
		break;

	case PAIR('R', 'S'):
		in += 8;
		break;

	case PAIR('R', 'E'):
		in -= 8;
		if (in < 0)
			in = 0;
		break;

	case PAIR('d', 's'):
		ds(argc, argv);
		break;

	case PAIR('r', /*(*/')'):
		cp = string_find(/*(*/"R)");
		if (!cp)
			cp = "";
		if (strcmp(cp, "no") != 0)
		{
			static char *macro[] =
			{
				".PP",
				"See also",
				".IR \\*(n) (1)",
				"for options common to all \\*(n) commands.",
			};

			interpret(macro, SIZEOF(macro));
		}
		break;

	default:
		roff_error("formatting directive \".%c%c\" unknown", c1, c2);
		break;
	}
}


static void interpret _((char **, int));

static void
interpret(text, text_len)
	char	**text;
	int	text_len;
{
	int	j;
	long	hold_line;
	char	*hold_file;

	/*
	 * save position
	 */
	trace(("interpret()\n{\n"/*}*/));
	hold_line = roff_line;
	hold_file = roff_file ? mem_copy_string(roff_file) : (char *)0;

	/*
	 * interpret the text
	 */
	for (j = 0; j < text_len; ++j)
	{
		char *s;

		s = text[j];
		if (*s == '.' || *s == '\'')
			interpret_control(s);
		else
			interpret_text(s);
		++roff_line;
		if (ferror(fp))
			pager_error();
	}

	/*
	 * restore position
	 */
	if (roff_file)
		mem_free(roff_file);
	roff_line = hold_line;
	roff_file = hold_file;
	trace((/*{*/"}\n"));
}


void
help(text, text_len, usage)
	char	**text;
	int	text_len;
	void	(*usage)_((void));
{
	/*
	 * collect the rest of thge command line,
	 * if necessary
	 */
	trace(("help(text = %08lX, text_len = %d, usage = %08lX)\n{\n"/*}*/,
		text, text_len, usage));
	if (usage && arglex() != arglex_token_eoln)
	{
		error
		(
			"misplaced \"%s\" command line argument",
			arglex_value.alv_string
		);
		usage();
	}

	/*
	 * if output is to the terminal,
	 * send the output through a paginator
	 */
	if (isatty(0) && isatty(1))
	{
		pager = getenv("PAGER");
		if (!pager)
			pager = "more";
	}
	else
		pager = 0;
	
	/*
	 * open the paginator
	 */
	if (pager)
	{
		fp = popen(pager, "w");
		if (!fp)
		{
			nerror("%s", pager);
			pager = 0;
			fp = stdout;
		}
	}
	else
		fp = stdout;

	/*
	 * initialize the state of the interpreter
	 */
	ds_guts(/*(*/"n)", option_progname_get());
	ds_guts(/*(*/"v)", version_stamp());
	ds_guts(/*(*/"Y)", copyright_years());
	ll = option_page_width_get() - 1;
	in = 0;
	fill = 1;
	ocol = 0;
	icol = 0;
	lf(0, 0);
	TP_line = 0;

	/*
	 * do what they asked
	 */
	interpret(text, text_len);
	br();

	/*
	 * close the paginator
	 */
	if (pager)
		pclose(fp);
	trace((/*{*/"}\n"));
}


syntax highlighted by Code2HTML, v. 0.9.1