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


/* external functions */
void			Skipline(void);
void			WriteLong(long fptr, FILE * fileid);
void			LinkModules(void);
void			DefineOrigin(void);
void			ParseIdent(enum	flag interpret);
void			ReportError(char *filename, short linenr, int errnum);
void			RemovePfixlist(struct expr * pfixexpr);
void			StoreExpr(struct expr *	pfixexpr, char range);
int			DefineSymbol(char *identifier, long value, unsigned char symboltype);
long			EvalPfixExpr(struct expr * pass2expr);
struct expr	       *ParseNumExpr(void);
enum symbols		GetSym(void);
symbol		       *FindSymbol(char	*identifier, avltree *treeptr);
char		       *AllocIdentifier(size_t len);

/* local functions */
void			ifstatement(enum flag interpret);
void			parseline(enum flag interpret);
void			getline(void);
void			Pass2info(struct expr *	expression, char constrange, long lfileptr);
void			Z80pass1(void);
void			Z80pass2(void);
void			WriteSymbolTable(char *msg, avltree * root);
void			WriteListFile(void);
void			WriteHeader(void);
void			LineCounter(void);
void			PatchListFile(struct expr * pass2expr, long c);
void			WriteMapFile(void);
void			StoreName(symbol *node,	unsigned char symscope);
void			StoreLibReference(symbol* node);
void			StoreGlobalName(symbol *node);
void			StoreLocalName(symbol *node);
long			Evallogexpr(void);
struct sourcefile      *Prevfile(void);
struct sourcefile      *Newfile(struct sourcefile * curfile, char *fname);
struct sourcefile      *Setfile(struct sourcefile * curfile, struct sourcefile * newfile, char *fname);
struct sourcefile      *AllocFile(void);
struct usedfile	       *AllocUsedFile(void);


/* global variables */
extern FILE	       *z80asmfile, *listfile, *objfile, *mapfile;
extern char	       *date, line[], ident[], separators[];
extern char	       *lstfilename, *objfilename, objext[], binext[];
extern enum symbols	sym;
extern enum flag	listing, listing_CPY, verbose, writeline, symtable, z80bin, deforigin, EOL;
extern unsigned	short	PC, oldPC;
extern long		EXPLICIT_ORIGIN;
extern unsigned	char   *codearea, *codeptr, PAGELEN;
extern size_t		CODESIZE;
extern int		ASSEMBLE_ERROR;
extern long		listfileptr, TOTALLINES;
extern struct modules  *modulehdr;	/* pointer to module header */
extern struct module   *CURRENTMODULE;
extern int		PAGENR,	LINENR,	TOTALERRORS;
extern int		COLUMN_WIDTH, TAB_DIST;
extern avltree	       *globalroot;

#ifdef QDOS
extern char		_prog_name[], _version[], _copyright[];
#else
extern char		copyrightmsg[];
#endif


void			Z80pass1(void)
{
	line[0]	= '\0';		/* reset contents of list buffer */

	while (!feof(z80asmfile)) {
		
		if (listing) writeline = ON;
		parseline(ON);	/* before parsing it */

		switch (ASSEMBLE_ERROR)	{
			case 3:
			case 12:
				return;	/* Fatal errors, return	immediatly... */
		}
	}
}



void			getline(void)
{
	long			fptr;

	fptr = ftell(z80asmfile);	/* remember file position */
	fgets(line, 255, z80asmfile);	/* get a copy of line for listing file */
	fseek(z80asmfile, fptr,	SEEK_SET);	/* resume file position	*/
}



void			parseline(enum flag interpret)
{
	FindSymbol(ASSEMBLERPC,	globalroot)->symvalue =	PC;	/* update assembler program counter */
	if (PC <= MAXCODESIZE-1) {      /* room for z80 machine code? */
		++CURRENTFILE->line;
		++TOTALLINES;
		if (listing) getline();	     /*	get a copy of current source line */

		EOL = OFF;	/* reset END OF	LINE flag */
		GetSym();
		if (sym	== fullstop) {
			if (interpret == ON) {  /* Generate only possible label declaration if line parsing is allowed */
				if (GetSym() ==	name) {
					DefineSymbol(ident, PC,	SYMADDR	| SYMTOUCHED);	/* labels must always be
											 * touched due to forward */
					GetSym();	/* check for another identifier	*//* referencing problems in
							 * expressions */
				} else {
					ReportError(CURRENTFILE->fname,	CURRENTFILE->line, 11);	/* a name must follow a
												 * label declaration */
					return;	/* read	in a new source	line */
				}
			} else {
				Skipline();
				sym = newline;	      /* ignore	label and rest of line */
			}
		}
		switch (sym) {
			case name:
				ParseIdent(interpret);
				break;

			case newline:
				break;	/* empty line, get next... */

			default:
				ReportError(CURRENTFILE->fname,	CURRENTFILE->line, 1);	/* Syntax error	*/
		}

		if (listing && writeline) WriteListFile();	  /* Write current source line to list file, if	allowed	*/
	} else
		ReportError(NULL, 0, 12);	/* no more room	in machine code	area */
}



/* multilevel contitional assembly logic */

void			ifstatement(enum flag interpret)
{
	if (interpret == ON) {  /* evaluate #if expression */
		if (Evallogexpr() != 0)	{
			do {    /* expression is TRUE, interpret lines until #else or #endif */
				if (!feof(z80asmfile)) {
					writeline = ON;
					parseline(ON);
				} else
					return;	/* end of file - exit from this	#if level */
			} while	((sym != elsestatm) && (sym != endifstatm));

			if (sym	== elsestatm) {
				do {    /* then ignore lines until #endif ... */
					if (!feof(z80asmfile)) {
						writeline = OFF;
						parseline(OFF);
					} else
						return;
				} while	(sym !=	endifstatm);
			}
		} else {
			do {    /* expression is FALSE, ignore until #else or #endif */
				if (!feof(z80asmfile)) {
					writeline = OFF;
					parseline(OFF);
				} else
					return;
			} while	((sym != elsestatm) && (sym != endifstatm));

			if (sym	== elsestatm) {
				do {
					if (!feof(z80asmfile)) {
						writeline = ON;
						parseline(ON);
					} else
						return;
				} while	(sym !=	endifstatm);
			}
		}
	} else {
		do {            /* don't evaluate #if expression and ignore all lines until #endif */
			if (!feof(z80asmfile)) {
				writeline = OFF;
				parseline(OFF);
			} else
				return;	/* end of file - exit from this	IF level */
		} while	(sym !=	endifstatm);
	}

	sym = nil;
}



long			Evallogexpr(void)
{
	struct expr	       *postfixexpr;
	long			constant = 0;

	GetSym();		/* get logical expression */
	if ((postfixexpr = ParseNumExpr()) != NULL) {
		constant = EvalPfixExpr(postfixexpr);
		RemovePfixlist(postfixexpr);	/* remove linked list, expression evaluated */
	}
	return constant;
}




void			Z80pass2(void)
{
	struct expr	       *pass2expr, *prevexpr;
	struct JRPC	       *curJR, *prevJR;
	long			constant, i;
	long			fptr_exprdecl, fptr_namedecl, fptr_modname, fptr_modcode, fptr_libnmdecl;
	unsigned char	       *patchptr;


	if ((pass2expr = CURRENTMODULE->mexpr->firstexpr) != NULL) {
		curJR =	CURRENTMODULE->JRaddr->firstref;	/* point at first JR PC	address	in this	module */
		do {
			constant = EvalPfixExpr(pass2expr);
			if (pass2expr->stored == OFF) {
				if ((pass2expr->rangetype & EXPREXTERN)	|| (pass2expr->rangetype & EXPRADDR)) {
					/*
					 * Expression contains symbol declared as external or defined as a relocatable
					 * address,
					 */
					/* store expression in relocatable file	*/
					switch (pass2expr->rangetype & RANGE) {
						case RANGE_32SIGN:
							StoreExpr(pass2expr, 'L');
							break;

						case RANGE_16CONST:
							StoreExpr(pass2expr, 'C');
							break;

						case RANGE_8UNSIGN:
							StoreExpr(pass2expr, 'U');
							break;

						case RANGE_8SIGN:
							StoreExpr(pass2expr, 'S');
							break;
					}
				}
			}
			if (pass2expr->rangetype & NOTEVALUABLE) {
				if ((pass2expr->rangetype & RANGE) == RANGE_JROFFSET) {
					if (pass2expr->rangetype & EXPREXTERN)
						ReportError(pass2expr->srcfile,	pass2expr->curline, 25);	/* JR, DJNZ used an
														 * external label - */
					else
						ReportError(pass2expr->srcfile,	pass2expr->curline, 2);
					prevJR = curJR;
					curJR =	curJR->nextref;	/* get ready for next JR instruction */
					free(prevJR);
				} else
					ReportError(pass2expr->srcfile,	pass2expr->curline, 2);
			} else {
				patchptr = codearea + pass2expr->codepos;	/* absolute patch pos. in memory buffer	*/
				switch (pass2expr->rangetype & RANGE) {
					case RANGE_JROFFSET:
						constant -= curJR->PCaddr;	/* get module PC at JR instruction */
						if (constant >=	-128 &&	constant <= 127) {
							*patchptr = (char) constant;	/* opcode is stored, now store
											 * relative jump */
						} else
							ReportError(pass2expr->srcfile,	pass2expr->curline, 7);
						prevJR = curJR;
						curJR =	curJR->nextref;	/* get ready for JR instruction	*/
						free(prevJR);
						break;

					case RANGE_8UNSIGN:
						if (constant >=	-128 &&	constant <= 255) {
							*patchptr = (unsigned char) constant;	/* opcode is stored, now
												 * store byte */
						} else
							ReportError(pass2expr->srcfile,	pass2expr->curline, 7);
						break;

					case RANGE_8SIGN:
						if (constant >=	-128 &&	constant <= 255) {
							*patchptr = (char) constant;	/* opcode is stored, now store
											 * signed operand */
						} else
							ReportError(pass2expr->srcfile,	pass2expr->curline, 7);
						break;

					case RANGE_16CONST:
						if (constant >=	-32768 && constant <= 65535) {
							*patchptr++ = (unsigned	short) constant	& 255;	/* modulus 256 */
							*patchptr = (unsigned short) constant >> 8;	/* div 256 */
						} else
							ReportError(pass2expr->srcfile,	pass2expr->curline, 7);
						break;

					case RANGE_32SIGN:
						if (constant >=	LONG_MIN && constant <=	LONG_MAX) {
							for (i = 0; i <	4; i++)	{
								*patchptr++ = (unsigned	char) constant & 255;
								constant >>= 8;
							}
						} else
							ReportError(pass2expr->srcfile,	pass2expr->curline, 7);
						break;
				}
			}

			if (listing_CPY)
				PatchListFile(pass2expr, constant);

			prevexpr = pass2expr;
			pass2expr = pass2expr->nextexpr;	/* get next pass2 expression */
			RemovePfixlist(prevexpr);	/* release current expression */
		}
		while (pass2expr != NULL);	/* re-evaluate expressions and patch in	code */

		free(CURRENTMODULE->mexpr);	/* Release header of expressions list */
		free(CURRENTMODULE->JRaddr);	/* Release header of relative jump address list	*/
		CURRENTMODULE->mexpr = NULL;
		CURRENTMODULE->JRaddr =	NULL;
	}
	if ((TOTALERRORS == 0) && symtable) {
		WriteSymbolTable("Local Module Symbols:", CURRENTMODULE->localroot);
		WriteSymbolTable("Global Module Symbols:", globalroot);
	}
	fptr_namedecl =	ftell(objfile);
	inorder(CURRENTMODULE->localroot, (void	(*)()) StoreLocalName);	/* Store Local Name declarations to relocatable	file */
	inorder(globalroot, (void (*)()) StoreGlobalName); /* Store Global name	declarations to	relocatable file */

	fptr_libnmdecl = ftell(objfile);	/* Store library reference names */
	inorder(globalroot, (void (*)()) StoreLibReference); /*	Store library reference	name declarations to relocatable file */

	fptr_modname = ftell(objfile);
	constant = strlen(CURRENTMODULE->mname);
	fputc(constant,	objfile);	/* write length	of module name to relocatable file */
	fwrite(CURRENTMODULE->mname, sizeof(char), (size_t) constant, objfile);	/* write module	name to	relocatable
										 * file	*/
	if ((constant =	codeptr	- codearea) == 0)
		fptr_modcode = -1;	/* no code generated!  */
	else {
		fptr_modcode = ftell(objfile);
		fputc(constant % 256, objfile);	/* low byte of module code size	*/
		fputc(constant / 256, objfile);	/* high	byte of	module code size */
		fwrite(codearea, sizeof(char), (size_t)	constant, objfile);
	}
	CODESIZE += constant;

	if (verbose)
		printf("Size of module is %ld bytes\n",	constant);

	fseek(objfile, 8, SEEK_SET);	/* set file pointer to point at	ORG */
	if ((modulehdr->first == CURRENTMODULE)) {
		if (deforigin)
			CURRENTMODULE->origin =	EXPLICIT_ORIGIN;	/* use origin from command line	*/
	}
	fputc(CURRENTMODULE->origin % 256, objfile);	/* low byte of origin */
	fputc(CURRENTMODULE->origin / 256, objfile);	/* high	byte of	origin */

	fptr_exprdecl =	30;	/* expressions always begins at	file position 24 */

	if (fptr_namedecl == fptr_exprdecl)
		fptr_exprdecl =	-1;	/* no expressions */
	if (fptr_libnmdecl == fptr_namedecl)
		fptr_namedecl =	-1;	/* no name declarations	*/
	if (fptr_modname == fptr_libnmdecl)
		fptr_libnmdecl = -1;	/* no library reference	declarations */

	WriteLong(fptr_modname,	objfile);	/* write fptr. to module name */
	WriteLong(fptr_exprdecl, objfile);	/* write fptr. to name declarations */
	WriteLong(fptr_namedecl, objfile);	/* write fptr. to name declarations */
	WriteLong(fptr_libnmdecl, objfile);	/* write fptr. to library name declarations */
	WriteLong(fptr_modcode,	objfile);	/* write fptr. to module code */
}


void	StoreGlobalName(symbol *node)
{
	if ((node->type	& SYMXDEF) && (node->type & SYMTOUCHED)) StoreName(node, SYMXDEF);
}


void	StoreLocalName(symbol *node)
{
	if ((node->type	& SYMLOCAL) && (node->type & SYMTOUCHED)) StoreName(node, SYMLOCAL);
}


void	StoreName(symbol *node,	unsigned char scope)
{
	int	b;

	switch (scope) {
		case SYMLOCAL:
			fputc('L', objfile);
			break;

		case SYMXDEF:
			if (node->type & SYMDEF)
				fputc('X', objfile);
			else
				fputc('G', objfile);
			break;
	}
	if (node->type & SYMADDR) /* then write	type of	symbol */
		fputc('A', objfile);	/* either a relocatable	address	*/
	else
		fputc('C', objfile);	/* or a	constant */

	WriteLong(node->symvalue, objfile);

	b = strlen(node->symname);
	fputc(b, objfile);	/* write length	of symbol name to relocatable file */
	fwrite(node->symname, sizeof(char), (size_t) b,	objfile);  /* write symbol name	to relocatable file */
}


void	StoreLibReference(symbol *node)
{
	size_t			b;

	if ((node->type	& SYMXREF) && (node->type & SYMDEF) && (node->type & SYMTOUCHED)) {
		b = strlen(node->symname);
		fputc((int) b, objfile);	/* write length	of symbol name to relocatable file */
		fwrite(node->symname, sizeof(char), b, objfile);	/* write symbol	name to	relocatable file */
	}
}


void			Pass2info(struct expr *	pfixexpr,	/* pointer to header of	postfix	expression linked list */
							  char constrange,	/* allowed size	of value to be parsed */
							  long byteoffset)
{                               /* position in listing file to patch */



	if (listing)
		byteoffset = listfileptr + 12 +	3 * byteoffset + (LFsize + 6) *	((byteoffset) /	32);
	/*
	 * |	  |   |	 |   | add extra line, if hex bytes more than 1	line start of	   |   |  | no.	of hex bytes
	 * rel.	to start current line in    |	length of hex number + space listfile	   | linenumber	& 2 spaces +
	 * hex address and 2 spaces
	 */
	else
		byteoffset = -1;/* indicate that this expression is not	going to be patched in listing file */


	pfixexpr->nextexpr = NULL;
	pfixexpr->rangetype = constrange;
	pfixexpr->srcfile = CURRENTFILE->fname;	/* pointer to record containing	current	source file name */
	pfixexpr->curline = CURRENTFILE->line;	/* pointer to record containing	current	line number */
	pfixexpr->listpos = byteoffset;	/* now calculated as absolute file pointer */

	if (CURRENTMODULE->mexpr->firstexpr == NULL) {
		CURRENTMODULE->mexpr->firstexpr	= pfixexpr;
		CURRENTMODULE->mexpr->currexpr = pfixexpr;	/* Expression header points at first expression	*/
	} else {
		CURRENTMODULE->mexpr->currexpr->nextexpr = pfixexpr;	/* Current expr. node points to	new expression
									 * node	*/
		CURRENTMODULE->mexpr->currexpr = pfixexpr;	/* Pointer to current expr. node updated */
	}
}



struct sourcefile      *Prevfile(void)
{
	struct usedfile	       *newusedfile;
	struct sourcefile      *ownedfile;

	if ((newusedfile = AllocUsedFile()) == NULL) {
		ReportError(NULL, 0, 3);	/* No room in operating	system - stop assembler	*/
		return (CURRENTFILE);	/* return parameter pointer - nothing happended! */
	}
	ownedfile = CURRENTFILE;
	CURRENTFILE = CURRENTFILE->prevsourcefile;	/* get back to owner file - now	the current */
	CURRENTFILE->newsourcefile = NULL;	/* current file	is now the last	in the list */
	ownedfile->prevsourcefile = NULL;	/* pointer to owner now	obsolete... */

	newusedfile->nextusedfile = CURRENTFILE->usedsourcefile;	/* set ptr to next record to current ptr to
									 * another used	file */
	CURRENTFILE->usedsourcefile = newusedfile;	/* new used file now inserted into list	*/
	newusedfile->ownedsourcefile = ownedfile;	/* the inserted	record now points to previously	owned file */
	return (CURRENTFILE);
}


struct sourcefile      *Newfile(struct sourcefile * curfile, char *fname)
{
	struct sourcefile      *nfile;

	if (curfile == NULL) {  /* file record has not yet been created */
		if ((curfile = AllocFile()) == NULL) {
			ReportError(NULL, 0, 3);
			return (NULL);
		} else
			return (Setfile(NULL, curfile, fname));
	} else if ((nfile = AllocFile()) == NULL) {
		ReportError(NULL, 0, 3);
		return (curfile);
	} else
		return (Setfile(curfile, nfile,	fname));
}


struct sourcefile      *Setfile(struct sourcefile * curfile,	/* pointer to record of	current	source file */
							struct sourcefile * nfile,	/* pointer to record of	new
											 * source file */
							char *filename)
{                               /* pointer to filename string */
	if ((nfile->fname = AllocIdentifier(strlen(filename) + 1)) == NULL) {
		ReportError(NULL, 0, 3);
		return (nfile);
	}
	nfile->prevsourcefile =	curfile;
	nfile->newsourcefile = NULL;
	nfile->usedsourcefile =	NULL;
	nfile->filepointer = 0;
	nfile->line = 0;	/* Reset to 0 as line counter during parsing */
	nfile->fname = strcpy(nfile->fname, filename);
	return (nfile);
}




void			PatchListFile(struct expr * pass2expr, long c)
{
	int			i;

	if (pass2expr->listpos == -1)
		return;		/* listing wasn't switched ON during pass1 */
	else {
		fseek(listfile,	pass2expr->listpos, SEEK_SET);	/* set file pointer in list file */
		switch (pass2expr->rangetype & RANGE) { /* look only at range bits */
			case RANGE_JROFFSET:
			case RANGE_8UNSIGN:
			case RANGE_8SIGN:
				fprintf(listfile, "%02X", c);
				break;

			case RANGE_16CONST:
				fprintf(listfile, "%02X %02X", c % 256,	c / 256);
				break;

			case RANGE_32SIGN:
				for (i = 0; i <	4; i++)	{
					fprintf(listfile, "%02X ", c & 255);
					c >>= 8;
				}
				break;
		}
	}
}



/*
 * Write current source	line to	list file with Hex dump	of assembled instruction
 */
void			WriteListFile(void)
{
	int			l, k;

	if (strlen(line) == 0)
		strcpy(line, "\n");

	l = PC - oldPC;
	if (l == 0)
		fprintf(listfile, "%-4d  %04X%14s%s", CURRENTFILE->line, oldPC,	"", line);	/* no bytes generated */
	else if	(l <= 4) {
		fprintf(listfile, "%-4d  %04X  ", CURRENTFILE->line, oldPC);
		for (; l; l--)
			fprintf(listfile, "%02X ", *(codeptr - l));
		fprintf(listfile, "%*s%s", (4 -	(PC - oldPC)) *	3, "", line);
	} else {
		while (l) {
			LineCounter();
			if (l)
				fprintf(listfile, "%-4d  %04X  ", CURRENTFILE->line, PC	- l);
			for (k = (l - 32 > 0) ?	32 : l;	k; k--)
				fprintf(listfile, "%02X ", *(codeptr - l--));
			fprintf(listfile, "\n");
		}
		fprintf(listfile, "%18s%s", "",	line);
		LineCounter();
	}
	LineCounter();		/* Update list file line counter - check page boundary */
	listfileptr = ftell(listfile);	/* Get file position for beginning of next line	in list	file */

	oldPC =	PC;
}


void			LineCounter(void)
{
	if (++LINENR > PAGELEN)	{
		fprintf(listfile, "\x0C\n");	/* send	FORM FEED to file */
		WriteHeader();
		LINENR = 6;
	}
}


void			WriteHeader(void)
{
#ifdef QDOS
	fprintf(listfile, "%s %s, %s", _prog_name, _version, _copyright);
	fprintf(listfile, "%*.*s", 122 - strlen(_prog_name) - strlen(_version) - strlen(_copyright) - 3, strlen(date), date);
#else
	fprintf(listfile, "%s",	copyrightmsg);
	fprintf(listfile, "%*.*s", 122 - strlen(copyrightmsg), strlen(date), date);
#endif
	fprintf(listfile, "Page %03d%*s'%s'\n\n\n", ++PAGENR, 122 - 9 -	2 - strlen(lstfilename), "", lstfilename);
}


void	WriteSymbol(symbol *n)
{
	int			k;
	int			space, tabulators;
	struct pageref	       *page;

	if (n->owner ==	CURRENTMODULE) {        /* Write only symbols related to current module */
		if ((n->type & SYMLOCAL) || (n->type & SYMXDEF)) {
			if ((n->type & SYMTOUCHED)) {
				fprintf(listfile, "%s",	n->symname);
				space =	COLUMN_WIDTH - strlen(n->symname);
				for (tabulators	= space	% TAB_DIST ? space / TAB_DIST +	1 : space / TAB_DIST; tabulators > 0; tabulators--)
					fputc('\t', listfile);
				fprintf(listfile, "= %08lX", n->symvalue);
				if (n->references != NULL) {
					page = n->references->firstref;
					fprintf(listfile, " : %3d* ", page->pagenr);
					page = page->nextref;
					k = 17;
					while (page != NULL) {
						if (k--	== 0) {
							fprintf(listfile, "\n%45s", "");
							k = 16;
							LineCounter();
						}
						fprintf(listfile, "%3d ", page->pagenr);
						page = page->nextref;
					}
				}
				fprintf(listfile, "\n");
				LineCounter();
			}
		}
	}
}


void			WriteSymbolTable(char *msg, avltree * root)
{

	fseek(listfile,	0, SEEK_END);	/* File	position to end	of file	*/
	fputc('\n', listfile);
	LineCounter();
	fputc('\n', listfile);
	LineCounter();
	fprintf(listfile, "%s",	msg);
	LineCounter();
	fputc('\n', listfile);
	LineCounter();
	fputc('\n', listfile);
	LineCounter();

	inorder(root, (void (*)()) WriteSymbol);	/* write symbol	table */
}



struct usedfile	       *AllocUsedFile(void)
{
	return (struct usedfile	*) malloc(sizeof(struct	usedfile));
}


struct sourcefile      *AllocFile(void)
{
	return (struct sourcefile *) malloc(sizeof(struct sourcefile));
}
