/*
 * Copyright (C) 1985-1992  New York University
 * Copyright (C) 1994 George Washington University
 * 
 * This file is part of the GWAdaEd system, an extension of the Ada/Ed-C
 * system.  See the Ada/Ed README file for warranty (none) and distribution
 * info and also the GNU General Public License for more details.
 *
 *
 * Modified for the Mac version of this.  We can't use the C I/O to read
 * the source file because it is currently opened by the same process.
 *
 * This file has some preconditions that did not exist before:
 *
 *	 adafile - must always be provided (in our case it is a text Handle)
 */

/* MacAdaMrg.h - merge error and ada source files to make listing */

#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <time.h>

#include "MacMemory.h"
#include "MacAdaMrgp.h"


#include <types.h>
#include <fcntl.h>
#include <signal.h>


/*
 * Author: Alex Dreyzen Jan,1984
 * Revised: Brian Siritzky, June 1984
 * Revised: J. Chiabaut, November 1984
 * Revised: D. Shields, May 1985
 * Revised: D. Shields, July 1985
 * Revised: G. Schenker, April 1986
 * Revised: J. Chiabaut, May 1986
 * Revised: D. Shields, July 1986
 *	make into separate procedure, use single structure for all
 *	messages, simplify logic
 * Revised: M. Perez, September 1993
 *  made it a stand alone file (copied several functions from misc.c)
 */

/*
 * This program takes two arguments giving the name of an adafile and
 * the msgfile/listfile  and then merges the messages in the file with
 * suffix '.msg' and source in file with suffix '.ada' to produce
 * listing file with suffix '.lis'.
 *
 * Function  main(argc,argv)  activates	 functions   init_msg_map(),
 * merge_ada_msg().
 * Function init_msg_map()  counts  lines in the filename.msg,
 * allocates space to store the keys and offsets of each  message,
 * and sorts offsets in ascending order of the keys. Function
 * printmsg() reads messages
 *
 *
 * Message format is:
 *
 * type_of_msg line_left col_left line_right col_right	<TAB>	message
 *
 * Where type_of_msg is one of :
 *
 *   PRAGMA_LIST_ON 1
 *   PRAGMA_LIST_OFF 2
 *   PRAGMA_LIST_ERR 3
 *   PRAGMA_PAGE 4
 *   ERR_LEXICAL 10
 *   ERR_SYNTAX 11
 *   ERR_SEMANTIC 12
 *   ERR_WARNING 13
 *   INFORMATION 14
 *   ERR_BIND 15
 *   ERR_COMPILER 16
 *
 *  Note that these are defined in config.h
 */

#define MAXLINE 512

#define pagesize 66

#define VERSION_NO "1.11.1"

struct n {
    int     n_error;	       /* Number of errors */
    int     n_lex_err;	       /* Counts of the various kinds of
				        * messages */
    int     n_syntax_err;
    int     n_semantic_err;
    int     n_generator_err;
    int     n_binding_err;
    int     n_warning_err;
    int     n_information;
};


struct l {
    char    header[80];		       /* header for output */
    char    blankl[132];	       /* blank line for underscore */
    char    blanks[132];
    char    dashl[132];		       /* dash line for underscore */
    char    dashes[132];
};

struct f {			       /* Pointers to message, ada, and list
				        * files */
    FILE   *listfile;

    Handle  adafile;
    int     adaeof;

    long    teLength;
    long    tePos;
};

struct m {
    int     msgs_total;
    int     m_type;
    int     msgs_read;
    int     msgs_curr;		       /* always 1 < than msgs_read */
    Msg    *msg_array;
};

struct g {
    int     pageno;		       /* output page number */
    int     realline;	       /* actual number of lines output */

    int     line1;
    int     line2;
    int     col1;
    int     col2;
    int     error_type;

    int     line_msg;
    int     line_ada;
    int     list_on;

    char   *pmsg;
    char   *pada;
    char   *data;
    char   *line;
};

static struct g global;
static struct n number;
static struct l lines;
static struct f files;
static struct m messg;


/* Prototypes */

static void getada(void);
static void getmsg(void);
static void merge_ada_msg(void);
static void pblank(int);
static void underscore(int, int);
static void printada(void);
static void printmsg(void);
static void newpage(void);
static void newlin(void);
static int init_msg_map(char *);
static int compar(Msg *, Msg *);
static char *sindex(char *, char);
static Boolean endoffile(Handle te);   /* mac versions */
static int getnextchar(Handle te);     /* mac versions */
static int getlin(void);
static char *msgtype(int);
static char *strsave(char *s1);
static void initialize(void);

extern OSType gSignature;	       /* Creator for Application's files	 */

/* For Error Handling Control inside of this file:
 *  define a longjump value
 *
 */

static jmp_buf gJumpEnv;	       /* environment for jump */


#ifdef DEBUGGING
#define DEBUGGING
void    showMem(void);

void    showMem(void)
{
    Str255  str;

    NumToString((long) FreeMem(), str);
    DebugStr(str);
}

#endif

/***********************************************************
	<merge_error_source>
		DESC

	Created		: 1/28/94 @ 8:23 PM
 ***********************************************************/
 
int     merge_error_source(Handle text, char *msgfilename, char *listfilename, Msg ** ptr)
{
    char   *s;
    time_t  bintim;
    char   *day, *month, *date, *clock, *year;

    int     i, istatus;
    int     val;

#ifdef DEBUGGING
    showMem();
#endif

    /* These are all static global routines, but in the interactive */
    /* version of this program I must reinitialize them... */

    initialize();

    _fcreator = gSignature;
    _ftype = 'TEXT';
    files.adafile = text;
    files.teLength = GetHandleSize(text);
    files.tePos = 0;

    global. data = mmalloc(MAXLINE);
    global. line = global.data;

    val = setjmp(gJumpEnv);
    if (val == 0) {
	
		/*
		 * returns if the message file is empty and a listing is not required
		 */
		istatus = init_msg_map(msgfilename);
		if (istatus == -1)
		    return -1;		       /* return RC_INTERNAL_ERROR; */
	
		files.listfile = fopen(listfilename, "w");
		if (files.listfile == NULL) {
		    /* fprintf(stderr, "Cannot open file %s.\n", listfilename); */
		    return -1;
		}
	
		/* get the date and split it s = "Sun Apr 21 08:00:00 1985\n"; */
		time(&bintim);
		s = ctime(&bintim);
		day = s;
		s += 3;
		*s++ = '\0';
		month = s;
		s += 3;
		*s++ = '\0';
		date = s;
		s += 2;
		*s++ = '\0';
		clock = s;
		s += 8;
		*s++ = '\0';
		year = s;
		s += 4;
		*s = '\0';
	
		sprintf(lines.header, "NYU Ada/ED-C %s  %-15s  %s  %s %s %s  %s  PAGE   ",
			VERSION_NO, OP_SYS, day, date, month, year, clock);
	
		newpage();
		getada();		       /* this was conditional on
					        * files.adafile not being NULL */
		global. realline++;
	
		getmsg();
		if (global.pmsg == NULL)
		    global. line_msg = 9999;
	
		merge_ada_msg();
		number.n_error = number.n_lex_err + number.n_syntax_err +
		    number.n_semantic_err + number.n_binding_err + number.n_generator_err;
	
		if (number.n_error)
		    fprintf(files.listfile, "\n	 %d error%s detected\n", number.n_error,
			    ((number.n_error == 1) ? "" : "s"));
		else
		    fprintf(files.listfile, "\n    No errors detected\n");
	
		*ptr = messg.msg_array;

    }

    /* else, there was an error at a lower level routine... */
    /* so return as an error situation */
    else {
		*ptr = NULL;
		number.n_error = -1;
    }

    if (files.listfile)
		fclose(files.listfile);

    mfree(global.data);
    global.line = global.data = NULL;

#ifdef DEBUGGING
    showMem();
#endif

    return number.n_error;
}

/***********************************************************
	<free_msg>
		DESC

	Created		: 1/28/94 @ 8:23 PM
 ***********************************************************/
 
void    free_msg(int errors, Msg * ptr)
{
    int     i;
    Msg    *array;

#ifdef DEBUGGING
    showMem();
#endif

    array = ptr;
    for (i = 0; i < errors; i++, ptr++) {
	mfree((*ptr)->msg_text);       /* free strings */
	mfree((*ptr));		       /* free each item */
    }
    mfree(array);		       /* free array of pointers */

#ifdef DEBUGGING
    showMem();
#endif
}

/***********************************************************
	<getada>
		DESC

	Created		: 1/28/94 @ 8:23 PM
 ***********************************************************/
 
static void getada()
{				       /* ;getada */
    global. line_ada++;

    files.adaeof = getlin();
    if (files.adaeof != EOF)
	global. pada = global.data;

    else
	global. pada = NULL;
}

/***********************************************************
	<getmsg>
		DESC

	Created		: 1/28/94 @ 8:23 PM
 ***********************************************************/
 
static void getmsg()
{				       /* ;getmsg */
    Msg     msgp;

    if (messg.msgs_read < messg.msgs_total) {
		msgp = messg.msg_array[messg.msgs_read];
		global. pmsg = msgp->msg_text;
		global. line1 = msgp->msg_line1;
		global. line2 = msgp->msg_line2;
		global. line_msg = global.line2;
		global. col1 = msgp->msg_col1;
		global. col2 = msgp->msg_col2;
		global. error_type = msgp->msg_type;
	
		messg.msgs_curr = messg.msgs_read;
		messg.msgs_read++;
		return;
    }
    else {
		global. line_msg = 9999;
		global. pmsg = NULL;
	
		return;
    }

}

/***********************************************************
	<merge_ada_msg>
		DESC

	Created		: 1/28/94 @ 8:27 PM
 ***********************************************************/
 
static void merge_ada_msg()
{
    int     dummsg;

    while ((global.pmsg != NULL) ||(global.pada != NULL)) {
		dummsg = 0;
		if (global.line_msg < global.line_ada) {

		    while ((global.line_msg < global.line_ada) &&global.pmsg != NULL) {
				printmsg();
				getmsg();
		    }
		}
	
		if (global.line_msg == global.line_ada) {	/* only if adafile is
								 * present */
		    switch (messg.m_type) {
				case PRAGMA_PAGE:
				    dummsg = 1;
				    if ((global.pada != NULL)) {
						if (global.list_on) {
							printada();
						}
						getada();
				    }
				    if (global.list_on)
						newpage();
				    break;
				case PRAGMA_LIST_OFF:
				    dummsg = 1;
				    if (files.adaeof != EOF) {
						if (global.list_on)
						    printada();
						getada();
				    }
				    global. list_on = 0;
		
				    break;
				case PRAGMA_LIST_ON:
				    dummsg = 1;
				    if (files.adaeof != EOF) {
						printada();
						getada();
				    }
				    global. list_on = 1;
		
				    break;
				default:
				    dummsg = 0;
				    if (files.adaeof != EOF) {
						printada();
						getada();
				    }
				    printmsg();
			}
		    getmsg();
		}

		/* if only one message on line */
		if (global.line_msg >= global.line_ada) {
		    if (global.list_on) {
				if (!dummsg)
				    newlin();
				while ((global.line_msg > global.line_ada) &&files.adaeof != EOF) {
				    printada();
				    getada();
				}
		    }
		    else   /* read in lines, until the next message or EOF */
				while ((global.line_msg > global.line_ada) &&files.adaeof != EOF)
				    getada();
	
		    if (files.adaeof == EOF) { /* EOF reached */
				while ((global.pmsg != NULL) && (global.line_msg < 9999)) {
				    printmsg();
				    getmsg();
				}
				newlin();
				while (global.pmsg != NULL) {
				    printmsg();
				    getmsg();
				}
		    }
		}
	}
}


/***********************************************************
	<pblank>
		DESC

	Created		: 1/28/94 @ 8:27 PM
 ***********************************************************/
#define printhat(col) pblank(col-1);fprintf(files.listfile,"^");fprintf(files.listfile,"\n")
 
static void pblank(int col)
{				       /* ;pblank */
    strcpy(lines.blankl, lines.blanks);
    global. realline++;

    if (col > 0) {
		lines.blankl[col] = '\0';
		fprintf(files.listfile, "%s", lines.blankl);
		lines.blankl[col] = ' ';
    }
}

/***********************************************************
	<underscore>
		DESC

	Created		: 1/28/94 @ 8:28 PM
 ***********************************************************/
 
static void underscore(int col1, int col2)
{				       /* ;underscore */
    strcpy(lines.dashl, lines.dashes);
    pblank(col1 - 1);
    fprintf(files.listfile, "<");
    if (col2 - col1 > 1) {
		lines.dashl[col2 - col1 - 1] = '\0';
		fprintf(files.listfile, "%s", lines.dashl);
		lines.dashl[col2 - col1 - 1] = '-';
    }
    fprintf(files.listfile, ">");
    fprintf(files.listfile, "\n");
}

/***********************************************************
	<printada>
		DESC

	Created		: 1/28/94 @ 8:28 PM
 ***********************************************************/
 
static void printada()
{				       /* ;printada */
    if (++global.realline >= pagesize)
		newpage();
    fprintf(files.listfile, "%4d:   %s", global.line_ada, global.pada);
}


/***********************************************************
	<printmsg>
		This function prints error message, underscoring
		the corresponding place in the source line

	Created		: 1/28/94 @ 8:28 PM
 ***********************************************************/
 
static void printmsg(void)
{
    messg.msg_array[messg.msgs_curr]->msg_lineno1 = global.realline;

    if (global.error_type >= ERR_LEXICAL) {

		if (global.error_type < INFORMATION) {
		
		    if (files.adafile != NULL) {	/* always true! */
		
				if (global.line1 == global.line2) {
				    if (global.col2 - global.col1 < 1) {
						printhat(global.col1 + 8);
				    }
				    else {
						underscore(global.col1 + 8, global.col2 + 8);
				    }
				}
			
				else {
				    fprintf(files.listfile,
					    "\n	   Between line %d column %d and line %d column %d\n",
						global.line1, global.col1, global.line2, global.col2);
				    global. realline += 2;
				}
			}

		    else {
				global. realline += 2;
		
				if (global.line1 == global.line2) {
				    fprintf(files.listfile,
					  "\n	   Line %d between column %d and column %d\n",
					    global.line1, global.col1, global.col2);
				}
				else {
				    fprintf(files.listfile,
					    "\n	   Between line %d column %d and line %d column %d\n",
				      global.line1, global.col1, global.line2, global.col2);
				}
		    }
		}
	
		fprintf(files.listfile, "%s %s\n", msgtype(global.error_type), sindex(global.pmsg, '\t'));
		if (global.realline++ >= pagesize)
		    newpage();
    }

    messg.msg_array[messg.msgs_curr]->msg_lineno2 = global.realline;
}


/***********************************************************
	<newpage>
		this procedure outputs a form feed, resets the line
		counter and prints the standard lines.header at the
		top of the page

	Created		: 1/28/94 @ 8:31 PM
 ***********************************************************/
 
static void newpage()
{
    global.pageno++;
    global.realline = 1;

    /* fprintf(files.listfile, "\f"); */
    /* Add the page number to the end of the string */

    fprintf(files.listfile, "%s %d\n\n", lines.header, global.pageno);
}

/***********************************************************
	<newlin>
		DESC

	Created		: 1/28/94 @ 8:32 PM
 ***********************************************************/
 
static void newlin()
{
    if ((++global.realline >= pagesize))
		newpage();
    else
		fprintf(files.listfile, "\n");
}

/***********************************************************
	<init_msg_map>
		DESC

	Created		: 1/28/94 @ 8:33 PM
 ***********************************************************/
 
static int init_msg_map(char *msgfilename)
{
    int     i, ln1, ln2, cl1, cl2, type;
    int     nitems;
    int     tab_found;
    Msg     m_prev;
    char   *dp;
    Msg     msg;
    FILE   *msgfile;


    msgfile = fopen(msgfilename, "r");
    if (msgfile == NULL)	       /* cannot open file */
		return 0;		       /* return (FALSE); */

    m_prev = NULL;

    while (fgets(global.data, MAXLINE, msgfile) !=NULL) {

		/* delete trailing newline */
		global. data[strlen(global.data) -1] = '\0';
	
		/* Allocate memory for array (just 1 item) */
		msg = (Msg) mmalloc(sizeof(Msg_ent));
	
		if (msg == (Msg) NULL)
		    longjmp(gJumpEnv, -1);     /* this won't return */
	
		nitems = sscanf(global.data, "%d %d %d %d %d",
				&type, &ln1, &cl1, &ln2, &cl2);
	
		if (nitems != 5)
		    longjmp(gJumpEnv, -2);     /* this won't return */
	
		messg.msgs_total++;
		msg->msg_num = messg.msgs_total;
		msg->msg_type = type;
		msg->msg_line1 = ln1;
		msg->msg_col1 = cl1;
		msg->msg_line2 = ln2;
		msg->msg_col2 = cl2;
		msg->msg_prev = m_prev;
		m_prev = msg;
	
		/* now find message text */
		dp = global.data;
	
		tab_found = 0;
	
		while (*dp != '\0') {	       /* skip to first tab */
	
		    if (*dp == '\t') {
				tab_found = 1;
				break;
		    }
	
		    dp++;
		}
	
		if (!tab_found)
		    dp = global.data;
	
		msg->msg_text = strsave(dp);   /* copy message text */
    }


    /* now form msg_array, array of pointers to messages */
    if (messg.msgs_total) {
	
		messg.msg_array = (Msg *) mcalloc(messg.msgs_total, sizeof(Msg));
		if (messg.msg_array == NULL) {
		    /* fprintf(stderr,"Cannot read error messages\n"); */
		    /* return -1; */
		    longjmp(gJumpEnv, -3);     /* this won't return */
		}
	
		for (i = messg.msgs_total - 1; i >= 0; i--) {
		    messg.msg_array[i] = m_prev;
		    m_prev = m_prev->msg_prev;
		}
	
		qsort(messg.msg_array, messg.msgs_total, sizeof(Msg),
		      (int (*) (const void *, const void *)) compar);
    }
    else {
		messg.msg_array = NULL;
    }

    fclose(msgfile);
    remove(msgfilename);
    return messg.msgs_total;
}

/***********************************************************
	<compar>
		DESC

	Created		: 1/28/94 @ 8:34 PM
 ***********************************************************/
 
static int compar(Msg * a, Msg * b)
{				       /* ;compar */
    Msg     c, d;

    c = *a;
    d = *b;

    /* compare by lines, then by columns */
    if (c->msg_line2 > d->msg_line2)
		return 1;
    else if (c->msg_line2 < d->msg_line2)
		return -1;

    /* if same line, use message number */
    else if (c->msg_num > d->msg_num)
		return 1;

    else if (c->msg_num < d->msg_num)
		return -1;

    return 0;			       /* if same */
}

/***********************************************************
	<sindex>
		This is probably meant to be 4.2 index(), but give
		it separate name for now

	Created		: 1/28/94 @ 8:34 PM
 ***********************************************************/
 
static char *sindex(char *s, char c)
{				       /* ;sindex */
    while (*s != c) {
		if ((*s == '\0') || (*s == '\n'))
		    return s;
		s++;
    }
    return ++s;
}

/***********************************************************
	<endoffile>
		The next three functions are mac only functions

	Created		: 1/28/94 @ 8:34 PM
 ***********************************************************/
 
static Boolean endoffile(Handle te)
{
    return files.tePos >= files.teLength;
}

/***********************************************************
	<getnextchar>
		DESC

	Created		: 1/28/94 @ 8:34 PM
 ***********************************************************/
 
static int getnextchar(Handle te)
{
    if (endoffile(te))
	return EOF;
    else
	return (*te)[files.tePos++];
}

/***********************************************************
	<getlin>
		This routine had to change... we read characters
		from the text handle

	Created		: 1/28/94 @ 8:35 PM
 ***********************************************************/
 
static int getlin()
{				       /* ;getlin */
    /* this is a stripped down version of getlin in adalex.c */
    int     ch, ind = 0;

    if (endoffile(files.adafile))
		return EOF;
    /* if (feof(files.adafile)) */
    /* return EOF; */

    for (;;) {
		/* ch = getc(files.adafile); */
		ch = getnextchar(files.adafile);
		if (ch == EOF)
		    break;
		if (ch <= 13 && ch >= 10)
		    break;
		if (ind == MAXLINE) {
		    while ((ch = getnextchar(files.adafile)) != EOF && !(10 <= ch && ch <= 13));
		    break;
		}
		else
		    global. data[ind++] = ch;
    }

    global. data[ind++] = '\n';
    global. data[ind] = '\0';

    if (ch == EOF && !ind)
		return EOF;
    return (0);
}

/***********************************************************
	<msgtype>
		DESC

	Created		: 1/28/94 @ 8:36 PM
 ***********************************************************/
 
static char *msgtype(int n)
{				       /* ;msgtype */
    char   *s;

    switch (n) {
		case ERR_LEXICAL:
		    s = "*** ERROR:";
		    number.n_lex_err++;
		    break;
		case ERR_SYNTAX:
		    s = "*** ERROR:";
		    number.n_syntax_err++;
		    break;
		case ERR_SEMANTIC:
		    s = "*** ERROR:";
		    number.n_semantic_err++;
		    break;
		case ERR_COMPILER:
		    s = "*** ERROR:";
		    number.n_generator_err++;
		    break;
		case ERR_WARNING:
		    s = "* WARNING:";
		    number.n_warning_err++;
		    break;
		case INFORMATION:
		    s = "    ";
		    number.n_information++;
		    break;
		case ERR_BIND:
		    s = "*** ERROR:";
		    number.n_binding_err++;
		    break;
    }
    return (s);
}

/***********************************************************
	<strsave>
		DESC

	Created		: 1/28/94 @ 8:36 PM
 ***********************************************************/
 
static char *strsave(char *s1)
{
    char   *s;

    if (s1) {
		s = mmalloc((unsigned) strlen(s1) + 1);
		strcpy(s, s1);
    }
    else {
		s = mmalloc(1);
		s[0] = '\0';
    }

    return s;
}


/***********************************************************
	<initialize>
		DESC

	Created		: 1/28/94 @ 8:36 PM
 ***********************************************************/
 
static void initialize(void)
{
    int     i;

    number.n_error = 0;		       /* Number of errors */
    number.n_lex_err = 0;	       /* Counts of the various kinds of
				        * messages */
    number.n_syntax_err = 0;
    number.n_semantic_err = 0;
    number.n_generator_err = 0;
    number.n_binding_err = 0;
    number.n_warning_err = 0;
    number.n_information = 0;

    for (i = 0; i < 131; i++) {
	lines.blanks[i] = ' ';
	lines.dashes[i] = '-';
    }
    lines.blanks[131] = '\0';
    lines.dashes[131] = '\0';

    lines.header[0] = '\0';
    lines.blankl[0] = '\0';
    lines.dashl[0] = '\0';

    files.listfile = NULL;
    files.adafile = NULL;
    files.adaeof = 0;
    files.teLength = 0;
    files.tePos = 0;

    messg.msgs_total = 0;
    messg.m_type = 0;
    messg.msgs_read = 0;
    messg.msgs_curr = 0;
    messg.msg_array = NULL;

    global. pageno = 0;		       /* output page number */
    global. realline = 0;	       /* actual number of lines output */
    global. line1 = 0;
    global. line2 = 0;
    global. col1 = 0;
    global. col2 = 0;
    global. error_type = 0;
    global. line_msg = 0;
    global. line_ada = 0;
    global. list_on = 1;
    global. pmsg = NULL;
    global. pada = NULL;
    global. data = NULL;
    global. line = NULL;
}
