/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ifndef lint
static	char sccsid[] = "@(#)infocmp.c 1.1 94/10/31 SMI"; /* from S5R3 1.11.1.1 */
#endif

/*
    NAME
	infocmp - compare terminfo descriptions, or dump a terminfo
		  description

    AUTHOR
	Tony Hansen, February 23, 1984.
*/

#include "curses.h"
#include "term.h"
#include "print.h"
#include <fcntl.h>

/* externs from libcurses */
extern char *boolnames[];
extern char *boolcodes[];
extern char *boolfnames[];
extern char *numnames[];
extern char *numcodes[];
extern char *numfnames[];
extern char *strnames[];
extern char *strcodes[];
extern char *strfnames[];
extern char ttytype[];
extern int tgetflag ();
extern int tgetnum ();
extern char *tgetstr ();

/* externs from libc */
extern char *malloc ();
extern void exit ();
extern void qsort ();
extern char *getenv ();
extern int getopt ();
extern int optind;
extern char *optarg;
extern char *strncpy(), *strcpy();
extern int strcmp(), strlen();

/* data structures for this program */

struct boolstruct
    {
    char *infoname;			/* the terminfo capability name */
    char *capname;			/* the termcap capability name */
    char *fullname;			/* the long C variable name */
    char *secondname;			/* the use= terminal w/ this value */
    char val;				/* the value */
    char secondval;			/* the value in the use= terminal */
    char changed;			/* a use= terminal changed the value */
    char seenagain;			/* a use= terminal had this entry */
    };

struct numstruct
    {
    char *infoname;			/* ditto from above */
    char *capname;
    char *fullname;
    char *secondname;
    short val;
    short secondval;
    char changed;
    char seenagain;
    };

struct strstruct
    {
    char *infoname;			/* ditto from above */
    char *capname;
    char *fullname;
    char *secondname;
    char *val;
    char *secondval;
    char changed;
    char seenagain;
    };

/* globals for this file */
char *progname;			/* argv[0], the name of the program */
static struct boolstruct *ibool;/* array of char information */
static struct numstruct *num;	/* array of number information */
static struct strstruct *str;	/* array of string information */
static char *used;		/* usage statistics */
static int numbools;		/* how many booleans there are */
static int numnums;		/* how many numbers there are */
static int numstrs;		/* how many strings there are */
#define TTYLEN 255
static char *firstterm;		/* the name of the first terminal */
static char *savettytype;	/* the synonyms of the first terminal */
static char _savettytype[TTYLEN];/* the place to save those names */
static int devnull;		/* open("/dev/null") for setupterm */
#define trace stderr		/* send trace messages to stderr */

/* options */
static int verbose = 0;		/* debugging printing level */
static int diff = 0;		/* produce diff listing, the default */
static int common = 0;		/* produce common listing */
static int neither = 0;		/* list caps in neither entry */
static int use = 0;		/* produce use= comparison listing */
static enum printtypes printing	/* doing any of above printing at all */
	= pr_none;
enum { none, by_database, by_terminfo, by_longnames, by_cap }
    sortorder = none;		/* sort the fields for printing */
static char *term1info, *term2info;	/* $TERMINFO settings */
static int Aflag = 0, Bflag = 0;	/* $TERMINFO was set with -A/-B */

#define EQUAL(s1,s2)	( ((s1 == NULL) && (s2 == NULL)) || \
			  ((s1 != NULL) && (s2 != NULL) && \
			  (strcmp(s1,s2) == 0) ) )

void badmalloc()
{
    (void) fprintf (stderr, "%s: malloc is out of space!\n", progname);
    exit (-1);
}

/*
    Allocate and initialize the global data structures and variables.
*/
void allocvariables(argc, firstoptind)
int argc, firstoptind;
{
    register int i, nullseen;

    /* find out how many names we are dealing with */
    for (numbools = 0; boolnames[numbools]; numbools++)
	;
    for (numnums = 0; numnames[numnums]; numnums++)
	;
    for (numstrs = 0; strnames[numstrs]; numstrs++)
	;

    if (verbose)
	{
	(void) fprintf (trace, "There are %d boolean capabilities.\n", numbools);
	(void) fprintf (trace, "There are %d numeric capabilities.\n", numnums);
	(void) fprintf (trace, "There are %d string capabilities.\n", numstrs);
	}

    /* Allocate storage for the names and their values */
    ibool = (struct boolstruct  *) malloc ((unsigned) numbools * sizeof (struct boolstruct));
    num = (struct numstruct *) malloc ((unsigned) numnums * sizeof (struct numstruct));
    str = (struct strstruct *) malloc ((unsigned) numstrs * sizeof (struct strstruct));

    /* Allocate array to keep track of which names have been used. */
    if (use)
	used = (char *) malloc ((unsigned) (argc - firstoptind) * sizeof (char));

    if ((ibool == NULL) || (num == NULL) || (str == NULL) ||
	    (use && (used == NULL)))
	badmalloc();

    /* Fill in the names and initialize the structures. */
    nullseen = FALSE;
    for (i = 0; i < numbools; i++)
	{
	ibool[i].infoname = boolnames[i];
	ibool[i].capname = boolcodes[i];
	/* This is necessary until fnames.c is */
	/* incorporated into standard curses. */
	if (nullseen || (boolfnames[i] == NULL))
	    {
	    ibool[i].fullname = "unknown_boolean";
	    nullseen = TRUE;
	    }
	else
	    ibool[i].fullname = boolfnames[i];
	ibool[i].changed = FALSE;
	ibool[i].seenagain = FALSE;
	}
    nullseen = 0;
    for (i = 0; i < numnums; i++)
	{
	num[i].infoname = numnames[i];
	num[i].capname = numcodes[i];
	if (nullseen || (numfnames[i] == NULL))
	    {
	    ibool[i].fullname = "unknown_number";
	    nullseen = TRUE;
	    }
	else
	    num[i].fullname = numfnames[i];
	num[i].changed = FALSE;
	num[i].seenagain = FALSE;
	}
    nullseen = 0;
    for (i = 0; i < numstrs; i++)
	{
	str[i].infoname = strnames[i];
	str[i].capname = strcodes[i];
	if (nullseen || (strfnames[i] == NULL))
	    {
	    str[i].fullname = "unknown_string";
	    nullseen = TRUE;
	    }
	else
	    str[i].fullname = strfnames[i];
	str[i].changed = FALSE;
	str[i].seenagain = FALSE;
	}
}

/*
    Routines to be passed to qsort(3) for comparison of the structures.
*/
int boolcompare (a, b)
struct boolstruct *a, *b;
{
    switch ( (int) sortorder)
	{
	case (int) by_terminfo:	 return strcmp (a->infoname, b->infoname);
	case (int) by_cap:	 return strcmp (a->capname, b->capname);
	case (int) by_longnames: return strcmp (a->fullname, b->fullname);
	default:		 return 0;
	}
}

int numcompare (a, b)
struct numstruct *a, *b;
{
    switch ( (int) sortorder)
	{
	case (int) by_terminfo:	 return strcmp (a->infoname, b->infoname);
	case (int) by_cap:	 return strcmp (a->capname, b->capname);
	case (int) by_longnames: return strcmp (a->fullname, b->fullname);
	default:		 return 0;
	}
}

int strcompare (a, b)
struct strstruct *a, *b;
{
    switch ( (int) sortorder)
	{
	case (int) by_terminfo:	 return strcmp (a->infoname, b->infoname);
	case (int) by_cap:	 return strcmp (a->capname, b->capname);
	case (int) by_longnames: return strcmp (a->fullname, b->fullname);
	default:		 return 0;
	}
}

/*
    Sort the entries by their terminfo name.
*/
sortnames()
{
    if (sortorder != by_database)
	{
	qsort ((char *) ibool, (unsigned) numbools, sizeof (struct boolstruct)  , boolcompare);
	qsort ((char *) num, (unsigned) numnums, sizeof (struct numstruct) , numcompare);
	qsort ((char *) str, (unsigned) numstrs, sizeof (struct strstruct) , strcompare);
	}
}

/*
    Print out a string, or "NULL" if it's not defined.
*/
void PR (stream, string)
FILE *stream;
register char *string;
{
    if (string == NULL)
	(void) fprintf (stream, "NULL");
    else
	tpr (stream, string);
}

/*
    Output the 'ko' termcap string. This is a list of all of the input
    keys that input the same thing as the corresponding output strings.    
*/
int kncounter;
char kobuffer[512];

char *addko (output, input, koptr)
char *output, *input, *koptr;
{
    char *inptr, *outptr, padbuffer[512];
    inptr = tgetstr (input, (char **)0);
    if (inptr == NULL)
        return koptr;
    outptr = tgetstr (output, (char **)0);
    if (outptr == NULL)
        return koptr;
    outptr = rmpadding (outptr, padbuffer, (int *) 0);
    if (strcmp (inptr, outptr) == 0)
        {
        *koptr++ = *output++;
	*koptr++ = *output++;
	*koptr++ = ',';
	kncounter++;
	}
    return koptr;
}

void setupknko()
{
    char *koptr;
    
    kncounter = 0;
    koptr = kobuffer;

    koptr = addko("bs", "kb", koptr);	/* key_backspace */
    koptr = addko("bt", "kB", koptr);	/* key_btab */
    koptr = addko("cl", "kC", koptr);	/* key_clear */
    koptr = addko("le", "kl", koptr);	/* key_left */
    koptr = addko("do", "kd", koptr);	/* key_down */
    koptr = addko("nd", "kr", koptr);	/* key_right */
    koptr = addko("up", "ku", koptr);	/* key_up */
    koptr = addko("dc", "kD", koptr);	/* key_dc */
    koptr = addko("dl", "kL", koptr);	/* key_dl */
    koptr = addko("cd", "kS", koptr);	/* key_eos */
    koptr = addko("ce", "kE", koptr);	/* key_eol */
    koptr = addko("ho", "kh", koptr);	/* key_home */
    koptr = addko("st", "kT", koptr);	/* key_stab */
    koptr = addko("ic", "kI", koptr);	/* key_ic */
    koptr = addko("im", "kI", koptr);	/* key_ic */
    koptr = addko("al", "kA", koptr);	/* key_il */
    koptr = addko("sf", "kF", koptr);	/* key_sf */
    koptr = addko("ll", "kH", koptr);	/* key_ll */
    koptr = addko("sr", "kR", koptr);	/* key_sr */
    koptr = addko("ei", "kM", koptr);	/* key_eic */
    koptr = addko("ct", "ka", koptr);	/* key_catab */

    /* get rid of comma */
    if (koptr != kobuffer)
        *(--koptr) = '\0';
}

void pr_kn()
{
    if (kncounter > 0)
        pr_number ((char *)0, "kn", (char *)0, kncounter);
}

void pr_ko()
{
    if (kncounter > 0)
	pr_string ((char *)0, "ko", (char *)0, kobuffer);
}

void pr_bcaps()
{
    char *retptr;
    char padbuffer[512];

    if (verbose)
        (void) fprintf (trace, "looking at 'bs'\n");
    retptr = cconvert (rmpadding (cursor_left, padbuffer, (int *) 0));
    if (strcmp ("\\b", retptr) == 0)
	pr_boolean ((char *)0, "bs", (char *)0, 1);

    if (verbose)
        (void) fprintf (trace, "looking at 'pt'\n");
    retptr = cconvert (rmpadding (tab, padbuffer, (int *) 0));
    if (strcmp ("\\t", retptr) == 0)
	pr_boolean ((char *)0, "pt", (char *)0, 1);

    if (verbose)
        (void) fprintf (trace, "looking at 'nc'\n");
    retptr = cconvert (rmpadding (carriage_return, padbuffer, (int *) 0));
    if (strcmp ("\\r", retptr) != 0)
	pr_boolean ((char *)0, "nc", (char *)0, 1);

    if (verbose)
        (void) fprintf (trace, "looking at 'ns'\n");
    if (scroll_forward == NULL)
	pr_boolean ((char *)0, "ns", (char *)0, 1);

    /* Ignore "xr": Return acts like ce \r \n (Delta Data) */
}

void pr_ncaps()
{
    char padbuffer[512];
    int padding;

    if (verbose)
        (void) fprintf (trace, "looking at 'ug'\n");
    /* Duplicate sg for ug: Number of blank chars left by us or ue */
    if (magic_cookie_glitch > -1)
	pr_number ((char *)0, "ug", (char *)0, magic_cookie_glitch);

    if (verbose)
        (void) fprintf (trace, "looking at 'dB'\n");
    /* Number of millisec of bs delay needed */
    (void) rmpadding (cursor_left, padbuffer, &padding);
    if (padding > 0)
	pr_number ((char *)0, "dB", (char *)0, padding);

    if (verbose)
        (void) fprintf (trace, "looking at 'dC'\n");
    /* Number of millisec of cr delay needed */
    (void) rmpadding (carriage_return, padbuffer, &padding);
    if (padding > 0)
	pr_number ((char *)0, "dC", (char *)0, padding);

    if (verbose)
        (void) fprintf (trace, "looking at 'dF'\n");
    /* Number of millisec of ff delay needed */
    (void) rmpadding (form_feed, padbuffer, &padding);
    if (padding > 0)
	pr_number ((char *)0, "dF", (char *)0, padding);

    if (verbose)
        (void) fprintf (trace, "looking at 'dN'\n");
    /* Number of millisec of nl delay needed */
    (void) rmpadding (cursor_down, padbuffer, &padding);
    if (padding > 0)
	pr_number ((char *)0, "dN", (char *)0, padding);

    if (verbose)
        (void) fprintf (trace, "looking at 'dT'\n");
    /* Number of millisec of tab delay needed */
    (void) rmpadding (tab, padbuffer, &padding);
    if (padding > 0)
	pr_number ((char *)0, "dT", (char *)0, padding);

    /* Handle "kn": Number of "other" keys */
    setupknko();
    pr_kn();
}

void pr_scaps()
{
    char *retptr;
    char padbuffer[512];

    /* Backspace if not "^H" */
    if (verbose)
        (void) fprintf (trace, "looking at 'bc'\n");
    retptr = cconvert (rmpadding (cursor_left, padbuffer, (int *) 0));
    if (strcmp ("\\b", retptr) != 0)
	pr_string ((char *)0, "bc", (char *)0, cursor_left);

    /* Newline character (default "\n") */
    if (verbose)
        (void) fprintf (trace, "looking at 'nl'\n");
    retptr = cconvert (rmpadding (cursor_down, padbuffer, (int *) 0));
    if (strcmp ("\\n", retptr) != 0)
	pr_string ((char *)0, "nl", (char *)0, cursor_down);

    /* Handle "ko" here: Termcap entries for other non-function keys */
    pr_ko();

    /* Ignore "ma": Arrow key map, used by vi version 2 only */
}

/*
    Set up the first terminal and save the values from it.
*/
void initfirstterm (term)
register char *term;
{
    register int i;

    if (verbose)
	(void) fprintf (trace, "setting up terminal type '%s'.\n", term);

    (void) setupterm (term, devnull, (int *) 0);

    /* Save the name for later use. */
    if (use)
	{
	register unsigned int length;
	savettytype = _savettytype;
	if ((length = strlen(ttytype)) >= TTYLEN)
	    {
	    savettytype = malloc (length);
	    if (savettytype == NULL)
		{
		(void) fprintf (stderr, "%s: malloc is out of space\n",
		    progname);
		(void) strncpy (_savettytype, ttytype, TTYLEN-1);
		_savettytype[TTYLEN] = '\0';
		savettytype = _savettytype;
		}
	    }
	else
	    (void) strcpy (_savettytype, ttytype);
	}

    if (printing != pr_none)
	{
	pr_heading (term, ttytype);
	pr_bheading ();
	}

    /* Save the values for the first terminal. */
    for (i = 0; i < numbools; i++)
	{
	if ((ibool[i].val = tgetflag (ibool[i].capname)) &&
	    printing != pr_none)
	    pr_boolean (ibool[i].infoname, ibool[i].capname, ibool[i].fullname, 1);
	if (verbose)
	    (void) fprintf (trace, "%s=%d.\n", ibool[i].infoname, ibool[i].val);
	}

    if (printing != pr_none)
	{
	if (printing == pr_cap)
	    pr_bcaps();
	pr_bfooting();
	pr_nheading();
	}

    for (i = 0; i < numnums; i++)
	{
	if (((num[i].val = tgetnum (num[i].capname)) > -1) && 
	    printing != pr_none)
	    pr_number (num[i].infoname, num[i].capname, num[i].fullname, num[i].val);
	if (verbose)
	    (void) fprintf (trace, "%s=%d.\n", num[i].infoname, num[i].val);
	}

    if (printing != pr_none)
	{
	if (printing == pr_cap)
	    pr_ncaps();
	pr_nfooting();
	pr_sheading();
	}

    for (i = 0; i < numstrs; i++)
	{
	str[i].val = tgetstr (str[i].capname, (char **)0);
	if ((str[i].val != NULL) && printing != pr_none)
	    pr_string (str[i].infoname, str[i].capname, str[i].fullname, str[i].val);
	if (verbose)
	    {
	    (void) fprintf (trace, "%s='", str[i].infoname);
	    PR (trace, str[i].val);
	    (void) fprintf (trace, "'.\n");
	    }
	}

    if (printing == pr_cap)
	pr_scaps();

    if (printing != pr_none)
	pr_sfooting();
}

/*
    Set up the n'th terminal.
*/
check_nth_terminal (nterm, n)
char *nterm;
int n;
{
    register char boolval;
    register short numval;
    register char *strval;
    register int i;

    if (use)
	used[n] = FALSE;

    if (verbose)
	(void) fprintf (trace, "adding in terminal type '%s'.\n", nterm);

    (void) setupterm (nterm, devnull, (int *) 0);

    if (printing != pr_none)
	{
	pr_heading (nterm, ttytype);
	pr_bheading ();
	}

    if (diff || common || neither)
	{
	if (Aflag && Bflag)
	    (void) printf ("comparing %s (TERMINFO=%s) to %s (TERMINFO=%s).\n",
	        firstterm, term1info, nterm, term2info);
	else if (Aflag)
	    (void) printf ("comparing %s (TERMINFO=%s) to %s.\n",
	        firstterm, term1info, nterm);
	else if (Bflag)
	    (void) printf ("comparing %s to %s (TERMINFO=%s).\n",
	    firstterm, nterm, term2info);
	else
	    (void) printf ("comparing %s to %s.\n", firstterm, nterm);
	(void) printf ("    comparing booleans.\n");
	}

    /* save away the values for the nth terminal */
    for (i = 0; i < numbools; i++)
	{
	boolval = tgetflag (ibool[i].capname);
	if (use)
	    {
	    if (ibool[i].seenagain)
		{
		/* We do not have to worry about this impossible case
		** since booleans can have only two values: true and false.
		** if (boolval && (boolval != ibool[i].secondval))
		**  {
		**  (void) fprintf (trace, "use= order dependency found:\n");
		**  (void) fprintf (trace, "    %s: %s has %d, %s has %d.\n",
		**	ibool[i].capname, ibool[i].secondname,
		**	ibool[i].secondval, nterm, boolval);
		**  }
		*/
		}
	    else
		{
		if (boolval == TRUE)
		    {
		    ibool[i].seenagain = TRUE;
		    ibool[i].secondval = boolval;
		    ibool[i].secondname = nterm;
		    if (ibool[i].val != boolval)
			ibool[i].changed = TRUE;
		    else
			used[n] = TRUE;
		    }
		}
	    }
	if (boolval)
	    {
	    if (printing != pr_none)
		pr_boolean (ibool[i].infoname, ibool[i].capname, ibool[i].fullname, 1);
	    if (common && (ibool[i].val == boolval))
		(void) printf ("\t%s= T.\n", ibool[i].infoname);
	    }
	else if (neither && !ibool[i].val)
	    (void) printf ("\t!%s.\n", ibool[i].infoname);
	if (diff && (ibool[i].val != boolval))
	    (void) printf ("\t%s: %c:%c.\n", ibool[i].infoname,
		    ibool[i].val?'T':'F', boolval?'T':'F');
	if (verbose)
	    (void) fprintf (trace, "%s: %d:%d,changed=%d,seen=%d.\n",
		    ibool[i].infoname, ibool[i].val, boolval,
		    ibool[i].changed, ibool[i].seenagain);
	}

    if (printing != pr_none)
	{
	if (printing == pr_cap)
	    pr_bcaps();
	pr_bfooting();
	pr_nheading();
	}

    if (diff || common || neither)
	(void) printf ("    comparing numbers.\n");

    for (i = 0; i < numnums; i++)
	{
	numval = tgetnum (num[i].capname);
	if (use)
	    {
	    if (num[i].seenagain)
		{
		if ((numval > -1) && (numval != num[i].secondval))
		    {
		    (void) fprintf (stderr,
			"%s: use= order dependency found:\n", progname);
		    (void) fprintf (stderr, "    %s: %s has %d, %s has %d.\n",
			    num[i].capname, num[i].secondname,
			    num[i].secondval, nterm, numval);
		    }
		}
	    else
		{
		if (numval > -1)
		    {
		    num[i].seenagain = TRUE;
		    num[i].secondval = numval;
		    num[i].secondname = nterm;
		    if ((numval > -1) && (num[i].val != numval))
			num[i].changed = TRUE;
		    else
			used[n] = TRUE;
		    }
		}
	    }
	if (numval > -1)
	    {
	    if (printing != pr_none)
		pr_number (num[i].infoname, num[i].capname, num[i].fullname, numval);
	    if (common && (num[i].val == numval))
		(void) printf ("\t%s= %d.\n", num[i].infoname, numval);
	    }
	else if (neither && (num[i].val == -1))
	    (void) printf ("\t!%s.\n", num[i].infoname);
	if (diff && (num[i].val != numval))
	    (void) printf ("\t%s: %d:%d.\n", num[i].infoname, num[i].val,
		    numval);
	if (verbose)
	    (void) fprintf (trace, "%s: %d:%d,changed=%d,seen=%d.\n",
		    num[i].infoname, num[i].val, numval,
		    num[i].changed, num[i].seenagain);
	}

    if (printing != pr_none)
	{
	if (printing == pr_cap)
	    pr_ncaps();
	pr_nfooting();
	pr_sheading();
	}

    if (diff || common || neither)
	(void) printf ("    comparing strings.\n");

    for (i = 0; i < numstrs; i++)
	{
	strval = tgetstr (str[i].capname, (char **)0);
	if (use)
	    {
	    if (str[i].seenagain && (strval != NULL))
		{
		if (!EQUAL (strval, str[i].secondval))
		    {
		    (void) fprintf (stderr, "use= order dependency found:\n");
		    (void) fprintf (stderr, "    %s: %s has '",
			    str[i].capname, str[i].secondname);
		    PR (stderr, str[i].secondval);
		    (void) fprintf (stderr, "', %s has '", nterm);
		    PR (stderr, strval);
		    (void) fprintf (stderr, "'.\n");
		    }
		}
	    else
		{
		if (strval != NULL)
		    {
		    str[i].seenagain = TRUE;
		    str[i].secondval = strval;
		    str[i].secondname = nterm;
		    if (!EQUAL (str[i].val, strval))
			str[i].changed = TRUE;
		    else
			used[n] = TRUE;
		    }
		}
	    }
	if (strval != NULL)
	    {
	    if (printing != pr_none)
		pr_string (str[i].infoname, str[i].capname, str[i].fullname, strval);
	    if (common && EQUAL (str[i].val, strval))
		{
		(void) printf ("\t%s= '", str[i].infoname);
		PR (stdout, strval);
		(void) printf ("'.\n");
		}
	    }
	else if (neither && (str[i].val == NULL))
	    (void) printf ("\t!%s.\n", str[i].infoname);
	if (diff && !EQUAL (str[i].val, strval))
	    {
	    (void) printf ("\t%s: '", str[i].infoname);
	    PR (stdout, str[i].val);
	    (void) printf ("','");
	    PR (stdout, strval);
	    (void) printf ("'.\n");
	    }
	if (verbose)
	    {
	    (void) fprintf (trace, "%s: '", str[i].infoname);
	    PR (trace, str[i].val);
	    (void) fprintf (trace, "':'");
	    PR (trace, strval);
	    (void) fprintf (trace, "',changed=%d,seen=%d.\n",
		    str[i].changed, str[i].seenagain);
	    }
	}

    if (printing == pr_cap)
	pr_scaps();

    if (printing != pr_none)
	pr_sfooting();
}

/*
    A capability gets an at-sign if it no longer exists, but
    one of the relative entries contains a value for it.
    It gets printed if the original value is not seen in ANY
    of the relative entries, or if the FIRST relative entry that has
    the capability gives a DIFFERENT value for the capability.
*/
void dorelative (firstoptind, argc, argv)
register int firstoptind;
register int argc;
register char **argv;
{
    register int i;

    /* turn off printing of termcap and long names */
    pr_init (pr_terminfo);

    /* print out the entry name */
    pr_heading ((char *)0, savettytype);

    pr_bheading();

    /* Print out all bools that are different. */
    for (i = 0; i < numbools; i++)
	if (!ibool[i].val && ibool[i].changed)
	    pr_boolean (ibool[i].infoname, (char *)0, (char *)0, -1);
	else if (ibool[i].val && (ibool[i].changed || !ibool[i].seenagain))
	    pr_boolean (ibool[i].infoname, (char *)0, (char *)0, 1);

    pr_bfooting();
    pr_nheading();

    /* Print out all nums that are different. */
    for (i = 0; i < numnums; i++)
	if (num[i].val < 0 && num[i].changed)
	    pr_number (num[i].infoname, (char *)0, (char *)0, -1);
	else if (num[i].val >= 0 && (num[i].changed || !num[i].seenagain))
	    pr_number (num[i].infoname, (char *)0, (char *)0, num[i].val);

    pr_nfooting();
    pr_sheading();

    /* Print out all strs that are different. */
    for (i = 0; i < numstrs; i++)
	if (str[i].val == NULL && str[i].changed)
	    pr_string (str[i].infoname, (char *)0, (char *)0, (char *)0);
	else if ((str[i].val != NULL) &&
		 (str[i].changed || !str[i].seenagain))
	    pr_string (str[i].infoname, (char *)0, (char *)0, str[i].val);

    pr_sfooting();

    /* Finish it up. */
    for (i = firstoptind; i < argc; i++)
	if (used[i - firstoptind])
	    (void) printf ("\tuse=%s,\n", argv[i]);
	else
	    (void) fprintf (stderr,
		"%s: 'use=%s' did not add anything to the description.\n",
		progname, argv[i]);
}

void setenv (termNinfo)
register char *termNinfo;
{
    extern char **environ;
    static char *newenviron[2] = { 0, 0 };
    static unsigned int termsize = BUFSIZ;
    static char _terminfo[BUFSIZ];
    static char *terminfo = &_terminfo[0];
    register int termlen;

    if (termNinfo && *termNinfo)
	{
	if (verbose)
	    (void) fprintf (trace, "setting TERMINFO=%s.\n", termNinfo);
	termlen = strlen (termNinfo);
	if (termlen + 10 > termsize)
	    {
	    termsize = termlen + 20;
	    terminfo = (char *) malloc (termsize * sizeof (char));
	    }
	if (terminfo == (char *) NULL)
	    badmalloc();
	(void) sprintf (terminfo, "TERMINFO=%s", termNinfo);
	newenviron[0] = terminfo;
	}
    else
	newenviron[0] = (char *) 0;
    environ = newenviron;
}

main (argc, argv)
int argc;
char **argv;
{
    register int i, c, firstoptind;
    char *tempargv[2];
    char *term = getenv ("TERM");

    term1info = term2info = getenv ("TERMINFO");
    progname = argv[0];

    /* parse options */
    while ((c = getopt (argc, argv, "ducnILCvV1rw:s:A:B:")) != EOF)
	switch (c)
	    {
	    case 'v':	verbose++;				break;
	    case '1':	pr_onecolumn(1);			break;
	    case 'w':	pr_width (atoi(optarg));		break;
	    case 'd':	diff++;					break;
	    case 'c':	common++;				break;
	    case 'n':	neither++;				break;
	    case 'u':	use++;					break;
	    case 'L':	pr_init (printing = pr_longnames);	break;
	    case 'I':	pr_init (printing = pr_terminfo);	break;
	    case 'C':	pr_init (printing = pr_cap);		break;
	    case 'A':	term1info = optarg; Aflag++;		break;
	    case 'B':	term2info = optarg; Bflag++;		break;
	    case 'r':	pr_caprestrict(0);			break;
	    case 's':
		if (strcmp(optarg, "d") == 0)
		    sortorder = by_database;
		else if (strcmp(optarg, "i") == 0)
		    sortorder = by_terminfo;
		else if (strcmp(optarg, "l") == 0)
		    sortorder = by_longnames;
		else if (strcmp(optarg, "c") == 0)
		    sortorder = by_cap;
		else
		    goto usage;
		break;
	    case 'V':
		(void) printf ("%s: version %s\n", progname, "infocmp.c 1.10");
		exit (0);
	    case '?':
	    usage:
		(void) fprintf (stderr,
			"usage: %s [-ducn] [-ILC] [-1Vv] [-s d|i|l|c] [-A directory] [-B directory] term-names ...\n",
			progname);
		(void) fprintf (stderr, "\t-d\tprint differences (the default for >1 term-name)\n");
		(void) fprintf (stderr, "\t-u\tproduce relative description\n");
		(void) fprintf (stderr, "\t-c\tprint common entries\n");
		(void) fprintf (stderr, "\t-n\tprint entries in neither\n");
		(void) fprintf (stderr, "\t-I\tprint terminfo entries (the default for 1 term-name)\n");
		(void) fprintf (stderr, "\t-C\tprint termcap entries\n");
		(void) fprintf (stderr, "\t-L\tprint long C variable names\n");
		(void) fprintf (stderr, "\t-1\tsingle column output\n");
		(void) fprintf (stderr, "\t-V\tprint program version\n");
		(void) fprintf (stderr, "\t-v\tverbose debugging output\n");
		(void) fprintf (stderr, "\t-s\tchange sort order\n");
		(void) fprintf (stderr, "\t-A\tset $TERMINFO for first term-name\n");
		(void) fprintf (stderr, "\t-B\tset $TERMINFO for other term-names\n");
		exit (-1);
	    }

    argc -= optind;
    argv += optind;
    optind = 0;

    /* Default to $TERM for -n, -I, -C and -L options. */
    /* This is done by faking argv[][], argc and optind. */
    if (neither && (argc == 0 || argc == 1))
	{
	if (argc == 0)
	    tempargv[0] = term;
	else
	    tempargv[0] = argv[optind];
	tempargv[1] = term;
	argc = 2;
	argv = tempargv;
	optind = 0;
	}

    else if ((printing != pr_none) && (argc == 0))
	{
	tempargv[0] = term;
	argc = 1;
	argv = tempargv;
	optind = 0;
	}

    /* Check for enough names. */
    if ((use || diff || common) && (argc <= 1))
	{
	(void) fprintf (stderr,
	    "%s: must have at least two terminal names for a comparison to be done.\n",
	    progname);
	goto usage;
	}

    /* Set the default of diff -d or print -I */
    if (!use && (printing == pr_none) && !common && !neither)
	{
	if (argc == 0 || argc == 1)
	    {
	    if (argc == 0)
		{
		tempargv[0] = term;
		argc = 1;
		argv = tempargv;
		optind = 0;
		}
	    pr_init (printing = pr_terminfo);
	    }
	else
	    diff++;
	}

    /* Set the default sorting order. */
    if (sortorder == none)
	switch ((int) printing)
	    {
	    case (int) pr_cap:		sortorder = by_cap; break;
	    case (int) pr_longnames:	sortorder = by_longnames; break;
	    case (int) pr_terminfo:
	    case (int) pr_none:		sortorder = by_terminfo; break;
	    }

    firstterm = argv[optind++];
    firstoptind = optind;

    allocvariables (argc, firstoptind);
    sortnames ();

    devnull = open("/dev/null", O_RDWR);
    setenv(term1info);
    initfirstterm (firstterm);
    setenv(term2info);
    for (i = 0; optind < argc; optind++, i++)
	check_nth_terminal (argv[optind], i);

    if (use)
	dorelative (firstoptind, argc, argv);

    return 0;
}
