/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: option.c,v 1.8.2.1 92/06/24 13:43:26 marker Exp $"

/*
 * Process command line options.
 * Each option is a single letter which controls a program variable.
 * The options have defaults which may be changed via
 * the command line option, or toggled via the "-" command.
 */

#include "less.h"

#define	toupper(c)	((c)-'a'+'A')

#define	END_OPTION_STRING	('$')

/*
 * Types of options.
 */
#define	BOOL		01	/* Boolean option: 0 or 1 */
#define	TRIPLE		02	/* Triple-valued option: 0, 1 or 2 */
#define	NUMBER		04	/* Numeric option */
#define	REPAINT		040	/* Repaint screen after toggling option */
#define	NO_TOGGLE	0100	/* Option cannot be toggled with "-" cmd */

/*
 * Variables controlled by command line options.
 */
public int p_nbufs, f_nbufs;	/* Number of buffers.  There are two values,
				   one used for input from a pipe and 
				   the other for input from a file. */
public int clean_data;		/* Can we assume the data is "clean"? 
				   (That is, free of nulls, etc) */
public int quiet;		/* Should we suppress the audible bell? */
public int top_search;		/* Should forward searches start at the top 
				   of the screen? (alternative is bottom) */
public int top_scroll;		/* Repaint screen from top?
				   (alternative is scroll from bottom) */
public int pr_type;		/* Type of prompt (short, medium, long) */
public int bs_mode;		/* How to process backspaces */
public int know_dumb;		/* Don't complain about dumb terminals */
public int quit_at_eof;		/* Quit after hitting end of file twice */
public int squeeze;		/* Squeeze multiple blank lines into one */
public int tabstop;		/* Tab settings */
public int back_scroll;		/* Repaint screen on backwards movement */
public int twiddle;		/* Display "~" for lines after EOF */

extern char *prproto[];
extern int nbufs;
extern int sc_window;
extern char *first_cmd;
extern char *every_first_cmd;
#if LOGFILE
extern char *namelogfile;
extern int force_logfile;
#endif

#define	DEF_F_NBUFS	5	/* Default for f_nbufs */
#define	DEF_P_NBUFS	12	/* Default for p_nbufs */

static struct option
{
	char oletter;		/* The controlling letter (a-z) */
	char otype;		/* Type of the option */
	int odefault;		/* Default value */
	int *ovar;		/* Pointer to the associated variable */
	char *odesc[3];		/* Description of each value */
} option[] =
{
	{ 'c', TRIPLE, 0, &top_scroll,
		{ "Repaint by scrolling from bottom of screen",
		  "Repaint by clearing each line",
		  "Repaint by painting from top of screen"
		}
	},
	{ 'd', BOOL|NO_TOGGLE, 0, &know_dumb,
		{ NULL, NULL, NULL}
	},
	{ 'e', TRIPLE, 0, &quit_at_eof,
		{ "Don't quit at end-of-file",
		  "Quit at end-of-file",
		  "Quit immediately at end-of-file"
		}
	},
	{ 'f', BOOL, 0, &clean_data,
		{ "Don't assume data is clean",
		  "Assume data is clean",
		  NULL
		}
	},
	{ 'h', NUMBER, -1, &back_scroll,
		{ "Backwards scroll limit is %d lines",
		  NULL, NULL
		}
	},
	{ 'm', TRIPLE, 0, &pr_type,
		{ "Short prompt",
		  "Medium prompt",
		  "Long prompt"
		}
	},
	{ 'q', TRIPLE, 0, &quiet,
		{ "Ring the bell for errors AND at eof/bof",
		  "Ring the bell for errors but not at eof/bof",
		  "Never ring the bell"
		}
	},
	{ 'u', TRIPLE|REPAINT, 0, &bs_mode,
		{ "Underlined text displayed in underline mode",
		  "Backspaces cause overstrike",
		  "Backspaces print as ^H"
		}
	},
	{ 's', BOOL|REPAINT, 0, &squeeze,
		{ "Don't squeeze multiple blank lines",
		  "Squeeze multiple blank lines",
		  NULL
		}
	},
	{ 't', BOOL, 1, &top_search,
		{ "Forward search starts from bottom of screen",
		  "Forward search starts from top of screen",
		  NULL
		}
	},
	{ 'w', BOOL|REPAINT, 1, &twiddle,
		{ "Display nothing for lines after end-of-file",
		  "Display ~ for lines after end-of-file",
		  NULL
		}
	},
	{ 'x', NUMBER|REPAINT, 8, &tabstop,
		{ "Tab stops every %d spaces", 
		  NULL, NULL 
		}
	},
	{ 'z', NUMBER|REPAINT, -1, &sc_window,
		{ "Scroll window size is %d lines",
		  NULL, NULL
		}
	},
	{ '\0' }
};

public char all_options[64];	/* List of all valid options */

/*
 * Initialize each option to its default value.
 */
	public void
init_option()
{
	register struct option *o;
	register char *p;

	/*
	 * First do special cases, not in option table.
	 */
	first_cmd = every_first_cmd = NULL;
	f_nbufs = DEF_F_NBUFS;		/* -bf */
	p_nbufs = DEF_P_NBUFS;		/* -bp */

	p = all_options;
	*p++ = 'b';

	for (o = option;  o->oletter != '\0';  o++)
	{
		/*
		 * Set each variable to its default.
		 * Also make a list of all options, in "all_options".
		 */
		*(o->ovar) = o->odefault;
		*p++ = o->oletter;
		if (o->otype & TRIPLE)
			*p++ = toupper(o->oletter);
	}
	*p = '\0';
}

/*
 * Toggle command line flags from within the program.
 * Used by the "-" command.
 */
	public void
toggle_option(s)
	WCHAR_T *s;
{
	int c;
	register struct option *o;
	char *msg;
	int n;
	int dorepaint;
	char message[100];
	char buf[5];

	c = *s++;

	/*
	 * First check for special cases not handled by the option table.
	 */
	switch (c)
	{
	case 'b':
		sprintf(message, "%d buffers", nbufs);
		error(message);
		return;
	}

	msg = NULL;
	for (o = option;  o->oletter != '\0';  o++)
	{
		if (o->otype & NO_TOGGLE)
			continue;
		dorepaint = (o->otype & REPAINT);
		if ((o->otype & BOOL) && (o->oletter == c))
		{
			/*
			 * Boolean option: 
			 * just toggle it.
			 */
			*(o->ovar) = ! *(o->ovar);
		} else if ((o->otype & TRIPLE) && (o->oletter == c))
		{
			/*
			 * Triple-valued option with lower case letter:
			 * make it 1 unless already 1, then make it 0.
			 */
			*(o->ovar) = (*(o->ovar) == 1) ? 0 : 1;
		} else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c))
		{
			/*
			 * Triple-valued option with upper case letter:
			 * make it 2 unless already 2, then make it 0.
			 */
			*(o->ovar) = (*(o->ovar) == 2) ? 0 : 2;
		} else if ((o->otype & NUMBER) && (o->oletter == c))
		{
			n = getnum(&s, '\0');
			if (n < 0)
			{
				/*
				 * No number; just a query.
				 * No need to repaint screen.
				 */
				dorepaint = 0;
			} else
			{
				/*
				 * Number follows the option letter.
				 * Set the variable to that number.
				 */
				*(o->ovar) = n;
			}
			sprintf(message, o->odesc[0], 
				(o->ovar == &back_scroll) ? 
				get_back_scroll() : *(o->ovar));
			msg = message;
		} else
			continue;

		if (dorepaint)
			repaint();
		if (msg == NULL)
			msg = o->odesc[*(o->ovar)];
		error(msg);
		return;
	}

	if (control_char(c))
		sprintf(buf, "^%c", carat_char(c));
	else
		sprintf(buf, "%c", c);
	sprintf(message, "\"-%s\": no such flag.  Use one of \"%s\"", 
		buf, all_options);
	error(message);
}

/*
 * Scan to end of string or to an END_OPTION_STRING character.
 * In the latter case, replace the char with a null char.
 * Return a pointer to the remainder of the string, if any.
 */
	static char *
optstring(s)
	char *s;
{
	register char *p;

	for (p = s;  *p != '\0';  p++)
		if (*p == END_OPTION_STRING)
		{
			*p = '\0';
			return (p+1);
		}
	return (p);
}

/* 
 * Scan an argument (either from command line or from LESS environment 
 * variable) and process it.
 */
	public void
scan_option(s)
	char *s;
{
	register struct option *o;
	register int c;
	char message[80];

	if (s == NULL)
		return;

    next:
	if (*s == '\0')
		return;
	switch (c = *s++)
	{
	case '-':
	case ' ':
	case '\t':
	case END_OPTION_STRING:
		goto next;
	case '+':
		if (*s == '+')
			every_first_cmd = ++s;
		first_cmd = s;
		s = optstring(s);
		goto next;
	case 'P':
		switch (*s)
		{
		case 'm':  prproto[PR_MEDIUM] = ++s;  break;
		case 'M':  prproto[PR_LONG] = ++s;    break;
		default:   prproto[PR_SHORT] = s;     break;
		}
		s = optstring(s);
		goto next;
#if LOGFILE
	case 'L':
		force_logfile = 1;
		/* fall thru */
	case 'l':
		namelogfile = s;
		s = optstring(s);
		goto next;
#endif
	case 'b':
		switch (*s)
		{
		case 'f':
			s++;
			f_nbufs = getnum(&s, 'b');
			break;
		case 'p':
			s++;
			p_nbufs = getnum(&s, 'b');
			break;
		default:
			f_nbufs = p_nbufs = getnum(&s, 'b');
			break;
		}
		goto next;
	case '0':  case '1':  case '2':  case '3':  case '4':
	case '5':  case '6':  case '7':  case '8':  case '9':
		{
			/*
			 * Handle special "more" compatibility form "-number"
			 * to set the scrolling window size.
			 */
			s--;
			sc_window = getnum(&s, '-');
			goto next;
		}
	}

	if (c == 'p') {
		sprintf(message,
			"The -p option for less has been changed to -c.");
		error(message);
		exit(1);
	}

	for (o = option;  o->oletter != '\0';  o++)
	{
		if ((o->otype & BOOL) && (o->oletter == c))
		{
			*(o->ovar) = ! o->odefault;
			goto next;
		} else if ((o->otype & TRIPLE) && (o->oletter == c))
		{
			*(o->ovar) = (o->odefault == 1) ? 0 : 1;
			goto next;
		} else if ((o->otype & TRIPLE) && (toupper(o->oletter) == c))
		{
			*(o->ovar) = (o->odefault == 2) ? 0 : 2;
			goto next;
		} else if ((o->otype & NUMBER) && (o->oletter == c))
		{
			*(o->ovar) = getnum(&s, c);
			goto next;
		}
	}

	sprintf(message, "\"-%c\": invalid flag", c);
	error(message);
	exit(1);
}

/*
 * Translate a string into a number.
 * Like atoi(), but takes a pointer to a char *, and updates
 * the char * to point after the translated number.
 */
	static int
getnum(sp, c)
	char **sp;
	int c;
{
	register char *s;
	register int n;
	char message[80];

	s = *sp;
	if (*s < '0' || *s > '9')
	{
		if (c == '\0')
			return (-1);
		sprintf(message, "number is required after -%c", c);
		error(message);
		exit(1);
	}

	n = 0;
	while (*s >= '0' && *s <= '9')
		n = 10 * n + *s++ - '0';
	*sp = s;
	return (n);
}
