/*
 * main.c -- calls mainline, trace calling sequences of C programs
 */

#ifdef pdp11
#include <sys/types.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <strings.h>
#ifdef __amigados__
#include <stdlib.h>
#include <unistd.h>
#endif
#include "scan.h"
#include "getopt.h"
#include "main.h"

/* globals */
char
	sbCmd[] = "cmd line";   /* kludge to notify user of error       */
int
	Allp = 0,		/* flag to show *all* function calls	*/
	Tersep = 0,		/* only requested trees 		*/
	Externp = 0,		/* include externs in index		*/
	Indexp = 0,		/* output functions index		*/
	Verbosep = 1;		/* index functions not output		*/

/* locals */
static int
	linect = 0,		/* line number				*/
	activep = 0,		/* current function being output	*/
	iWidth = PAPERWIDTH;	/* default paper width			*/
static char
	*pchProg,		/* argv[0]				*/
	cppcommand[1024] =	/* cpp command string			*/
#ifndef __amigados__
		"/lib/cpp -P ";
#else
		"/bin/gcc -E -P ";
#endif
static HASH
	*activelist[MAXDEPTH];	/* list of current output names 	*/


char *
syserrlist()    /*  the routine returns a string for a system error     */
{
	extern char *sys_errlist[];
	extern int sys_nerr;
	extern int errno;
	register char *pchErr =
	    errno == 0 ? "errno = 0" :
		errno < sys_nerr ? sys_errlist[errno] : "errno out of range";

	errno = 0;
	return pchErr;
}

void
process(filename, outname)      /* invoke cpp on file, call level1      */
register char *filename, *outname;
{
	extern FILE *popen();
	register char *sbNull;
	register int ret;

	if (access(filename, 04) != 0) {
		(void) fprintf(stderr, "%s: cannot open file '%s' (%s).\n", pchProg, filename, syserrlist());
		return;
	}
	sbNull = cppcommand + strlen(cppcommand);
	strcpy(sbNull, filename);
	if (NULL == (input = popen(cppcommand, "r"))) {
		(void) fprintf(stderr, "%s: fork of CPP command '%s' failed on file '%s' (%s).\n", pchProg, cppcommand, filename, syserrlist());
	} else {
		level1(outname);
		if (0 != (ret = pclose(input)))
			(void) fprintf(stderr, "%s: CPP command '%s' failed on file '%s' with return code %d.\n", pchProg, cppcommand, filename, ret);
	}
	*sbNull = '\0';
	return;
}

void
dostdin()       /* copy stdin to temp file, call process on file        */
{
	extern char *mktemp();
	register int cc;
	register char *filename = mktemp("/tmp/callsXXXXXX");
	register FILE *ofileptr = fopen(filename, "w");
	register char *sbNull;

	if (NULL == ofileptr) {
		(void) fprintf(stderr, "%s: cannot open tempfile '%s' for writing (%s).\n", pchProg, filename, syserrlist());
	} else {
		while (EOF != (cc = getchar()))
			putc(cc, ofileptr);
		fclose(ofileptr);
		sbNull = cppcommand + strlen(cppcommand);
		strcpy(sbNull, "-I. ");
		process(filename, "stdin");
		*sbNull = '\000';
		unlink(filename);
	}
}

int
active(func)    /* check for recursive calls, prevents endless output   */
register HASH *func;
{
	register int i;

	for (i = 0; i < activep-1; i++)
		if (func == activelist[i])
			return 1;
	return 0;
}

void
output(pHTFunc, tabc)   /* output a (sub)tree in pretty form            */
register HASH *pHTFunc;
register int tabc;
{
	static char dashes[] = "\n----------";
	register INST *pINTemp;
	register int i;

	++linect;
	(void) printf("\n%5d\t", linect);
	if (activep < MAXDEPTH) {
		activelist[activep++] = pHTFunc;
	} else {
		(void) printf("   * nesting is too deep");
		return;
	}

	for (i = 0; i < tabc; i++ )
		putchar('\t');
	printf("%s", pHTFunc->pchname);

	if (active(pHTFunc)) {
		(void) printf(" <<< recursive >>>");
	} else if (pHTFunc->pchfile) {
		pINTemp = pHTFunc->pINcalls;
		if (pHTFunc->listp && tabc && 0 == pHTFunc->iline) {
			(void) printf(" [%s] [see below]", pHTFunc->pchfile);
		} else if (! pHTFunc->iline) {
			(void) printf(pHTFunc->localp ? " [static in %s]" : " [%s]", pHTFunc->pchfile);
			pHTFunc->iline = linect;
			if ((++tabc) * TABWIDTH >= iWidth) {
				(void) printf(dashes);
				tabc = 0;
			}
			while (pINTemp) {
				output(pINTemp->pHTname, tabc);
				pINTemp = pINTemp->pINnext;
			}
			if (! tabc)
				(void) printf(dashes);
		} else if (pINTemp || pHTFunc->localp) {
			(void) printf(" [see line %d]", pHTFunc->iline);
		}
	}
	activelist[activep--] = nilHASH;
}

int
main(argc, argv)        /* parse args, add files, call output           */
int argc;
char *argv[];
{
	extern int atoi();
	static char sbOpts[] = "aehitvw:D:f:F:U:I:"; /* valid options   */
	static char sbTemp[200];
	static LIST *pCLRoot;
	register HASH *pHTList;
	register int cOption;
	register LIST **ppCL;
	register char *pchSplit;

#ifndef pdp11
	setlinebuf(stdout);
#endif
	pchProg = argv[0];
	ppCL = & pCLRoot;

	while (EOF != (cOption = getopt(argc, argv, sbOpts))) {
		switch (cOption) {
		case 'a':
			Allp = 1;
			break;
		case 'e':
			Externp = 1;
			Indexp = 1;
			break;
		case 'F':
			if (0 != (pchSplit = index(optarg, '/'))) {
				*pchSplit++ = '\000';
			} else {
		case 'f':
				pchSplit = sbCmd;
			}
			pHTList = search(optarg, 'F' == cOption, pchSplit);
			pHTList->listp = 1;
			pHTList->pchfile = pchSplit;
			*ppCL = newCL();
			(*ppCL)->pCLnext = pCLRoot;
			(*ppCL)->pHTlist = pHTList;
			ppCL = & (*ppCL)->pCLnext;
			break;
		case 't':
			Tersep = 1;
			break;
		case 'v':
			Verbosep = 0;
			/*fallthrough*/
		case 'i':
			Indexp = 1;
			break;
		case 'w':
			if (0 >= (iWidth = atoi(optarg)))
				iWidth = PAPERWIDTH;
			break;
#ifdef OLD_AND_BROKEN_SPRINTF
		case 'D':
			strcat(cppcommand, (const char *)
						sprintf(sbTemp, "-D%s ", optarg));
			break;
		case 'I':
			strcat(cppcommand, (const char *)
						sprintf(sbTemp, "-I%s ", optarg));
			break;
		case 'U':
			strcat(cppcommand, (const char *)
						sprintf(sbTemp, "-U%s ", optarg));
			break;
#else
		case 'D':
			sprintf(sbTemp, "-D%s ", optarg);
			strcat(cppcommand,sbTemp);
			break;
		case 'I':
			sprintf(sbTemp, "-I%s ", optarg);
			strcat(cppcommand,sbTemp);
			break;
		case 'U':
			sprintf(sbTemp, "-U%s ", optarg);
			strcat(cppcommand,sbTemp);
			break;
#endif /* sprintf */
		case '?':
		case 'h':
			(void) fprintf(stderr, "usage: %s [-aehitv] [-f function] [-F function[/file.c]] [-w width]\n\
    [-D define] [-U undefine] [-I include-dir] [filename|-]*\n\
\ta\tprint all calls in every function body\n\
\te\tindex external functions too\n\
\tf,F\tstart calling trace at given function\n\
\th\tprint this message\n\
\ti\tprint an index of defined functions\n\
\tv\tlist only called functions in index output\n\
\tt\tterse, list only trees that are requested\n\
\tw\tset ouptut width\n\
\tD,U,I\tas in cpp\n", pchProg);
			exit('h' != cOption);
		}
	}
	*ppCL = nilCL;

	while (EOF != getarg(argc, argv)) {
		if ('-' == optarg[0] && '\000' == optarg[1])
			dostdin();
		else
			process(optarg, optarg);
	}

	while (pCLRoot) {               /* print requested trees        */
		output(pCLRoot->pHTlist, 0);
		putchar('\n');
		pCLRoot = pCLRoot->pCLnext;
	}

	if (!Tersep) {                  /* print other trees            */
		for (cOption = 0; cOption < 2; ++cOption) {
			for (pHTList = pHTRoot[cOption]; pHTList; pHTList = pHTList->pHTnext) {
				if (!pHTList->calledp && NULL != pHTList->pchfile && 0 == pHTList->iline) {
					output(pHTList, 0);
					putchar('\n');
				}
			}
		}
	}

	if (Indexp) {                   /* print index                  */
		printf("\fIndex:\n");
		while (nilHASH != pHTRoot[0] || nilHASH != pHTRoot[1]) {

			if (nilHASH == pHTRoot[0] || (nilHASH != pHTRoot[1] && strcmp(pHTRoot[0]->pchname, pHTRoot[1]->pchname) >= 0)) {
				pHTList = pHTRoot[1];
				pHTRoot[1] = pHTRoot[1]->pHTnext;
			} else {
				pHTList = pHTRoot[0];
				pHTRoot[0] = pHTRoot[0]->pHTnext;
			}
			if (!Externp && NULL == pHTList->pchfile)
				continue;
			if (!Verbosep && 0 == pHTList->iline)
				continue;
			putchar('\t');
			fputs(pHTList->pchname, stdout);
			if (pHTList->localp) {
				printf(" [static in %s]", pHTList->pchfile);
			} else if (((char *) 0) != pHTList->pchfile) {
				printf(" [%s]", pHTList->pchfile);
			}
			if (0 != pHTList->iline) {
				printf(" [see line %d]", pHTList->iline);
			}
			putchar('\n');
		}
	}
	exit(0);
}
