/*
 *					srctex		insert source specials
 *					Feb. 2000 -..., written by SHIMA
 */

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<ctype.h>
#include	<io.h>

#define	MAXDEF	0x1000			/* maximal number of keys */
#define	MAXLINE	0x2000			/* size of line buffer */
#define	MAXNAME	0x100			/* maximal length of a file name */

#define	TRUE	1
#define	FALSE	0

short int f_plus = FALSE;		/* line number in new source */
short int f_tmp = FALSE;		/* output <foo>.tmp */
short int f_exec = FALSE;		/* execute system() */
short int f_sep = FALSE;		/* insert as a separate line */
short int f_strip = FALSE;		/* delete source specials */
short int f_endskip = FALSE;	/* used for skip/end_skip in config */
short int f_pause = FALSE;		/* user-defined special */
short int f_config = FALSE;		/* use user-defined config */
short int f_null = FALSE;		/* ignore NULL line */
short int f_all = FALSE;		/* insert in all lines */
short int f_print = FALSE;		/* print configulation */

struct KEY {
	char *key;
	short int type;
	short int len;
};

enum {
	UNDEFINED, BEGIN, END, SKIP, END_SKIP, IGNORE, SET_SRC
};

char *keyname[] =
{
	"begin", "end", "skip", "_skip", "ignore", "set", NULL
};

#define	TYPE			0x0f

#define	LOC_HEAD		0x0100
#define	LOC_TAIL		0x0200
#define	LOC_JUST		0x0400

#define	LOC_TOP			0x0500

#define	KEY_SKIP		10

struct KEY table[MAXDEF] =
{
	{"%skip{src}",			SKIP|LOC_HEAD, 0},
	{"%resume{src}",		END_SKIP|LOC_HEAD, 0},
	{"%put{src}",			SET_SRC|LOC_HEAD,0},

	{"%",					IGNORE|LOC_HEAD, 0},

	{"\\document",			BEGIN|LOC_TOP, 0},
	{"\\enddocument",		END|LOC_TOP|LOC_TAIL,0 },

	{"\\verbatim",			SKIP|LOC_TOP, 0},
	{"\\endverbatim",		END_SKIP|LOC_HEAD|LOC_TAIL, 0},

	{"\\comment",			SKIP|LOC_TOP, 0},
	{"\\endcomment",		END_SKIP|LOC_HEAD|LOC_TAIL, 0},

	{"\\begin{document}",	BEGIN|LOC_HEAD, 0},
	{"\\end{document}",		END|LOC_HEAD, 0},

	{"\\begin{verbatim}",	SKIP|LOC_HEAD, 0},
	{"\\end{verbatim}",		END_SKIP|LOC_HEAD|LOC_TAIL, 0},

	{"\\begin{verbatim*}",	SKIP|LOC_HEAD, 0},
	{"\\end{verbatim*}",	END_SKIP|LOC_HEAD|LOC_TAIL, 0},

	{"\\begin{comment}",	SKIP|LOC_HEAD, 0},
	{"\\end{comment}",		END_SKIP|LOC_HEAD|LOC_TAIL, 0},

	{"$$",					SET_SRC|LOC_HEAD|LOC_TAIL, 0},
	{"\\[",					SET_SRC|LOC_HEAD, 0},
	{"\\]",					SET_SRC|LOC_TAIL, 0},

	{"\\begin{equation}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{equation}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{equation*}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{equation*}",	SET_SRC|LOC_TAIL, 0},
	{"\\begin{align}",		SET_SRC|LOC_HEAD, 0},
	{"\\end{align}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{align*}",		SET_SRC|LOC_HEAD, 0},
	{"\\end{align*}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{multline}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{multline}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{multline*}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{multline*}",	SET_SRC|LOC_TAIL, 0},
	{"\\begin{gather}",		SET_SRC|LOC_HEAD, 0},
	{"\\end{gather}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{gather*}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{gather*}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{aligned}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{aligned}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{alignat}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{alignat}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{alignat*}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{alignat*}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{xalignat}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{xalignat}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{xxalignat}",	SET_SRC|LOC_HEAD, 0},
	{"\\end{xxalignat}",	SET_SRC|LOC_TAIL, 0},
	{"\\begin{verse}",		SET_SRC|LOC_HEAD, 0},
	{"\\end{verse}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{cases}",		SET_SRC|LOC_HEAD, 0},
	{"\\end{cases}",		SET_SRC|LOC_TAIL, 0},
	{"\\begin{pf}",			SET_SRC|LOC_HEAD, 0},
	{"\\end{pf}",			SET_SRC|LOC_TAIL, 0},

	{"\\align",				SET_SRC|LOC_HEAD, 0},
	{"\\endalign",			SET_SRC|LOC_TAIL, 0},
//	{"\\alignat",			SET_SRC|LOC_HEAD, 0},
	{"\\endalignat",		SET_SRC|LOC_TAIL, 0},
	{"\\xalignat",			SET_SRC|LOC_HEAD, 0},
	{"\\endxalignat",		SET_SRC|LOC_TAIL, 0},
	{"\\xxalignat",			SET_SRC|LOC_HEAD, 0},
	{"\\endxxalignat",		SET_SRC|LOC_TAIL, 0},
	{"\\gather",			SET_SRC|LOC_HEAD, 0},
	{"\\endgather",			SET_SRC|LOC_TAIL, 0},
	{"\\multline",			SET_SRC|LOC_HEAD, 0},
	{"\\endmultline",		SET_SRC|LOC_TAIL, 0},
//	{"\\aligned",			SET_SRC|LOC_HEAD, 0},
	{"\\endaligned",		SET_SRC|LOC_TAIL, 0},
	{"\\topaligned",		SET_SRC|LOC_HEAD, 0},
	{"\\endtopaligned",		SET_SRC|LOC_TAIL, 0},
	{"\\botaligned",		SET_SRC|LOC_HEAD, 0},
	{"\\endbotaligned",		SET_SRC|LOC_TAIL, 0},
	{"\\cases",				SET_SRC|LOC_HEAD, 0},
	{"\\endcases",			SET_SRC|LOC_TAIL, 0},

	{"\\ref",				SET_SRC|LOC_TOP, 0},
	{"\\demo",				SET_SRC|LOC_TOP, 0},
	{"\\item",				SET_SRC|LOC_TOP, 0},
	{"\\bibitem",			SET_SRC|LOC_TOP, 0},

	{"\\break",				SET_SRC|LOC_TAIL, 0},
	{"\\\\",				SET_SRC|LOC_TAIL, 0},
	{"\\par",				SET_SRC|LOC_TAIL, 0},
	{"\\newpage",			SET_SRC|LOC_TAIL, 0},
	{"\\clearpage",			SET_SRC|LOC_TAIL, 0},
	{"\\cleardoublepage",	SET_SRC|LOC_TAIL, 0},
};

char *msg =
	"\t\tsrctex: utility to insert source specials\n"
	"\t\t  Ver.0.1, Feb. 2000, written by SHIMA\n\n"
	"srctex [-<options>] [<command>] [<file>]\n"
	"  -i<name> : file name to be input\t(default: stdin   or <file>)\n"
	"  -o<name> : file name to be output\t(default: stdout  or <file>)\n"
	"  -s<name> : file name in the special\t(default: i<name> or <file>)\n"
	"  -c<name> : configulation file\t\t(default: add   -C<name>: replace)\n"
	"  -S<special>: insert a <special>\t(ex. -S\\special{pause})\n"
	"  -n: new line for the special\t\t(-N: line number in the new source)\n"
	"  -d: ignore waiting \\begin{document}, \\document\n"
	"  -z: ignore NULL line\t\t\t-a: insert in all lines\n"
	"  -x: strip \\special{src:...}\t\t-p: print configulation\n"
	"  <command> <file> : command to make a DVI file from <file>\n"
	"  %skip{src}, %resume{src} and %put{src} at the head of a line in a TeX file\n"
	"  control to insert souce specials (default)\n\n"
	"Example:  -> set source specials (with the filename fo.tex)  => compile\n"
	"  srctex fo\t\t\t\t(fo.tex -> fo.tmp)\n"
	"  srctex latex fo\t\t\t(fo.tex -> fo.tmp => fo.dvi)\n"
	"  srctex -ifo.tex latex f1\t\t(fo.tex -> f1.tex => f1.dvi)\n"
	"  srctex -d -ifo.tex -of1.tex latex f2\t(fo.tex -> f1.tex, f2.tex => f2.dvi)\n"
	"  srctex -n -S\\special{pause} fo\t(fo.tex -> fo.tmp)\n"
;

FILE *infile;
FILE *outfile;

char lbuf[MAXLINE];			/* line buffer */
char fname[MAXNAME];		/* filename to be compiled */
char iname[MAXNAME];		/* filename to be input */
char oname[MAXNAME];		/* filename to be output */
char special[MAXNAME];		/* special */

int IsKey(unsigned char *s, unsigned char *k)
{
	while(*k){
		if(*s != *k)
			return (int)(*s) - (int)(*k);
		s++;
		k++;
	}
	return 0;
}

int IsSpaceIn(char *s)
{
	if(*s == '\"' && s[strlen(s)-1] == '\"')
		return 0;
	while(*s){
		if(*s++ == ' ')
			return 1;
	}
	return 0;
}

int interpret(char *buf, int count)
{
	int i;
	unsigned char *s;

	s = buf;
	while(*s && *s <= ' ')
		s++;
	if(!*s || *s == '%')
		return count;
	table[count].type = 0;

	while(*s == 'H' || *s == 'T' || *s == 'E'){
		if(*s == 'H')
			table[count].type |= LOC_HEAD;
		else if(*s == 'T')
			table[count].type |= LOC_TAIL;
		else
			table[count].type |= LOC_TOP;
		s++;
	}
	if(*s > ' '){
err:	fprintf(stderr, "Error in configulation file: %s", buf);
		exit(4);
	}
	while(*s && *s <= ' ')
		s++;
	for(i = 0; keyname[i]; i++){
		if(!IsKey(s, keyname[i])){
			table[count].type |= i+1;
			if(f_endskip){
				if(i != END_SKIP - 1){
					fprintf(stderr, 
						"Expect endskip in configlation file:%s", buf);
					exit(4);
				}
				f_endskip = FALSE;
			}
			if(i == SKIP - 1)
				f_endskip = TRUE;
			break;
		}
	}
	if(keyname[i] == NULL)
		goto err;
	s += strlen(keyname[i]);
	while(*s && *s <= ' ')
		s++;
	if(!*s)
		goto err;
	for(i = 0; s[i] > ' '; i++);
	s[i] = 0;
	table[count].key = strdup(s);
	return count + 1;
}

void readcfg(char *name, int count)
{
	FILE *fp;

	fp = fopen(name, "r");
	if(fp == NULL){
		fprintf(stderr, "Cannot open configulation file:%s", name);
		exit(3);
	}
	while(fgets(lbuf, MAXLINE, fp)){
		count = interpret(lbuf, count);
		if(count >= MAXDEF - 1){
			fprintf(stderr, "Too many keys in %s", name);
			exit(4);
		}
	}
	fclose(fp);
	if(f_endskip){
		fprintf(stderr, 
			"Expect endskip in the last of the configlation file:%s", name);
		exit(4);
	}
}

void putsrcsp(int *line)
{
	char tmp[0x200];

	if(f_pause){
		if(f_sep)
			sprintf(tmp, "%s%c\n", special, '%');
		else
			strcpy(tmp, special);
	}else if(f_sep){
		if(f_plus)
			*line = *line+1;
		sprintf(tmp, "\\special{src:%d%s}%c\n", *line, fname, '%');
	}else
		sprintf(tmp, "\\special{src:%d%s}", *line, fname);
	fputs(tmp, outfile);
}

void main( int argc, char *argv[] )
{
	int	line, type, i, f_skip, prog;
	unsigned char *head, *tail, *s;
	short int f_srcsp;					/* src special? */
	int f_document = FALSE;				/* in document? */

	infile = stdin;
	outfile = stdout;

	for(prog = 1; prog < argc; prog++){
		if(argv[prog][0] != '-')
			break;
		i = 1;
		type = 0;

							/* analyze command parameters */
par:	switch(argv[prog][i++]){
			case 0:
				i = -1;
				break;

			case 'S':
				f_pause = TRUE;
				strncpy(special, argv[prog]+i, MAXNAME);
				i = -1;
				break;

			case 's':
				type = 1;
setfname:		s = &fname[0];
				if(argv[prog][i] >= '0' && argv[prog][i] <= '9')
					*s++ = ' ';
				strncpy(s, argv[prog]+i, MAXNAME -1);
				i = -1;
				break;

			case 'o':
				strncpy(oname, argv[prog]+i, MAXNAME);
				i = -1;
				break;

			case 'i':
				strncpy(iname, argv[prog]+i, MAXNAME);
				if(type == 0)
					goto setfname;
				i = -1;
				break;

			case 'c':
				for(line = 0; table[line].type & TYPE != UNDEFINED; line++);
				readcfg(argv[prog]+i, line);
				f_config = TRUE;
				i = -1;
				break;

			case 'C':
				for(line = 0; table[line].type & TYPE != UNDEFINED; line++)
					table[line].type = UNDEFINED;
				readcfg(argv[prog]+i, 0);
				f_config = TRUE;
#if 0
				for(i = 0; table[i].type & TYPE; i++)
					printf("%3d: %03x [%s]\n", i+1, table[i].type, table[i].key); 
#endif							
				i = -1;
				break;

			case 'N':
				f_plus = f_sep = TRUE;
				break;

			case 'd':
				f_document = TRUE;
				break;

			case 'n':
				f_sep = TRUE;
				break;

			case 'x':
				f_strip = TRUE;
				break;

			case 'z':
				f_null = TRUE;
				break;

			case 'a':
				f_all = TRUE;
				break;

			case 'p':
				f_print = TRUE;
				break;
		}
		if(i > 0)
			goto par;
	}

	if(f_print){		/* print configulation */
		for(i = 0; table[i].type & TYPE; i++){
			if((table[i].type & LOC_TOP) == LOC_TOP)
				printf("E");
			else if(table[i].type & LOC_HEAD)
				printf("H");
			if(table[i].type & LOC_TAIL)
				printf("T");
			printf("\t%s\t%s\n", 
				keyname[(table[i].type & 0xf) - 1],
				table[i].key);
		}
		exit(0);
	}
						/* executable or filename */
	if(prog < argc){
		if(!fname[0]){
			s = &fname[0];
			if(argv[argc-1][0] >= '0' && argv[argc-1][0] <= '9')
				*s++ = ' ';
			strncpy(s, argv[argc-1], MAXNAME-1);
		}
		if(prog < argc-1)
			f_exec = TRUE;		/* executable */
	}

	if(!fname[0]){		/* no filename for source specials */
msg:	fputs( msg, stderr );
		exit( 6 );
	}

	i = strlen(fname);		/* analyze filename for source specials */
	while(--i > 0){
		if(fname[i] == '.')
			break;
		if(fname[i] == '\\' || fname[i] == ':'){
			i = 0;
			break;
		}
	}
	if(i == 0)				/* no extension */
		strcat(fname, ".tex");
	s = &fname[0];
	if(*s == ' ')
		s++;
	if(!*s)
		goto msg;

	if(!iname[0] && isatty(fileno(stdin)))
		strncpy(iname, s, MAXNAME);			/* filename to be input */ 
	if(!oname[0] && isatty(fileno(stdout)))
		strncpy(oname, s, MAXNAME);			/* filename to be output */

	if(iname[0] && !strcmp(iname, oname)){	/* infile == outfile */
		i = strlen(oname);
		if(i > 3 && !strcmp(oname + i-4, ".tmp"))
			oname[i-1] = 0;
		else{
			while(--i > 0){
				if(oname[i] == '.')
					break;
				else if(oname[i] == ':' || oname[i] == '\\')
					break;
			}
			if(oname[i] == '.')
				strcpy(oname+i, ".tmp");
			else
				strcat(oname, ".tmp");
		}
		f_tmp = TRUE;
	}

	if(iname[0]){							/* open file to be input */
		infile = fopen(iname, "r");
		if(infile == NULL){
			fprintf(stderr, "Cannot open to read %s", iname);
			exit(1);
		}
	}

	if(oname[0]){							/* open file to be output */
		outfile = fopen(oname, "w");
		if(outfile == NULL){
			fprintf(stderr, "Cannot open to write %s", oname);
			exit(2);
		}
	}

	for(i = 0; (s = table[i].key) != NULL; i++)		/* set strlen */
		table[i].len = strlen(table[i].key);

	if(f_pause && !f_config){
		for(i = KEY_SKIP; (s = table[i].key) != NULL; i++){	/* kill SET_SRC */
			if((table[i].type & TYPE) == SET_SRC)
				table[i].type = UNDEFINED;
		}
	}
rep:
	f_srcsp = FALSE;
	line = f_skip = 0;

	if(f_sep)
		f_document = 1;

	while(fgets(lbuf, MAXLINE, infile)){
		line++;
		if(f_document < 0)		/* \end{document} ... */
			goto out;
		for(head = lbuf; *head && *head <= ' '; head++);	/* search head */
		if(*head){				/* not NULL LINE , search tail */
			for(tail = lbuf + strlen(lbuf); --tail >= lbuf && *tail <= ' ';);
			tail++;
		}
		if(!f_document){		/* search \begin{document} */
			if(!*head)
				goto out;
			for(i = 0; (type = table[i].type) != UNDEFINED; i++){
				if((type & TYPE) != BEGIN)
					continue;
				if((type & LOC_HEAD) && !IsKey(head, table[i].key)){
					if(!(type & LOC_JUST) || !isalpha(head[table[i].len])){
						f_document = 1;
						break;
					}
				}
				if(type & LOC_TAIL){
					s = tail - table[i].len;
					if(s >= head && !IsKey(s, table[i].key)){
						f_document = 1;
						break;
					}
				}
			}
			goto out;
		}

		if(f_skip){			/* \begin{verbatim} ... \end{verbatim} etc. */
			if(!*head)
				goto out;
			if( (  table[f_skip].type & LOC_HEAD)
				&& !IsKey(head, table[f_skip].key)
				&& ( !(table[f_skip].type & LOC_JUST)
				  || !isalpha(head[table[f_skip].len]) ) ){
					f_skip = 0;
					if(f_all)
						goto out;
					goto tail;
			}
			if( table[f_skip].type & LOC_TAIL ){
				s = tail - table[f_skip].len;
				if(s >= head && !IsKey(s, table[f_skip].key))
					f_skip = 0;
			}
			goto out;
		}

		if(!*head){			/* NULL line */
			if(f_srcsp || f_null || f_strip)
				goto out;
			goto outsp;
		}

		for(i = 0; (type = table[i].type) != UNDEFINED; i++){	/* head */
			if(!(type & LOC_HEAD) || IsKey(head, table[i].key)
			  || ((type & LOC_JUST) && isalpha(head[table[i].len])) )
				continue;
			switch(table[i].type & TYPE){
				case END:				/* \end{document} etc. */
					f_document = -1;
					goto out;

				case SKIP:				/* \begin{verbatim} ... etc. */
					f_skip = i+1;
					if(f_all)
						putsrcsp(&line);

				case IGNORE:			/* %... etc. */
					goto out;

				case SET_SRC:			/* \[ ... etc. */
					if(f_strip)
						goto out;
					if(!f_srcsp)
						putsrcsp(&line);
					if(head + table[i].len >= tail){
						f_srcsp = TRUE;
						goto out;
					}
			}

		}	/* head */

		if(f_all){			/* all line */
			putsrcsp(&line);
			goto out;
		}

		if(f_strip){		/* strip \special{src:...} */
			if(f_pause){
				if(!IsKey(head, special)){
					do{
						head += strlen(special);
					}while(!IsKey(head, special));
					goto del;
				}
			}else if(!IsKey(head, "\\special{src:")){
				do{
					head += 13;
					while(*head && *head++ != '}');
				}while(!IsKey(head, "\\specials{src:"));
del:			if(*head == '%' && head[1] == '\n')
					continue;
				fputs(head, outfile);
				continue;
			}
			goto out;
		}

tail:	for(i = 0; (type = table[i].type) != UNDEFINED; i++){	/* tail */
			if(!(type & LOC_TAIL))
				continue;
			s = tail - table[i].len;
			if(s < head || IsKey(s, table[i].key))
				continue;
			switch(table[i].type & TYPE){
				case END:
					f_document = -1;
					goto out;

				case SKIP:
					f_skip = i + 1;

				case IGNORE:
					goto out;

				case SET_SRC:			/* ..\] etc */
outsp:				fputs(lbuf, outfile);
					line++;
					putsrcsp(&line);
					line--;
					f_srcsp = TRUE;
					goto nxt;
			}
		}	/* tail */
		f_srcsp = FALSE;			/* normal line */
out:	fputs(lbuf, outfile);
nxt:	/* next line */ ;
	}
	if(oname[0]){
		fclose(outfile);
		if(!f_document){
			f_document = 1;
			outfile = fopen(oname, "w");
			if(outfile == NULL){
				fprintf(stderr, "Cannot open to write %s", oname);
				exit(2);
			}
			rewind(infile);
			goto rep;
		}
	}
	if(iname[0])
		fclose(infile);
	if(f_exec){
		lbuf[0] = 0;
		for(i = prog; i < argc-1; i++){
			type = IsSpaceIn(argv[i]);
			if(type)
				strcat(lbuf, "\"");
			strcat(lbuf, argv[i]);
			strcat(lbuf, type?"\" ":" ");
		}
		s = f_tmp?oname:argv[argc-1];
		type = IsSpaceIn(s);
		if(type)
			strcat(lbuf, "\"");
		strcat(lbuf, s);
		if(type)
			strcat(lbuf, "\"");
		system(lbuf);
	}
}