/*
 *	Z80 - Assembler
 *	Copyright (C) 1987-2006 by Udo Munk
 *
 *	History:
 *	17-SEP-1987 Development under Digital Research CP/M 2.2
 *	28-JUN-1988 Switched to Unix System V.3
 *	21-OCT-2006 changed to ANSI C for modern POSIX OS's
 */

/*
 *	module for output functions to list, object and error files
 */

#include <stdio.h>
#include <string.h>
#include "z80a.h"
#include "z80aglb.h"

void flush_hex(void);
int chksum(void);
void btoh(unsigned char, char **);

extern void fatal(int, char *);

static char *errmsg[] = {		/* error messages for asmerr() */
	"illegal opcode",		/* 0 */
	"illegal operand",		/* 1 */
	"missing operand",		/* 2 */
	"multiply defined symbol",	/* 3 */
	"undefined symbol",		/* 4 */
	"value out of range",		/* 5 */
	"missing )",			/* 6 */
	"missing string separator",	/* 7 */
	"memory override",		/* 8 */
	"missing IF",			/* 9 */
	"IF nesting to deep",		/* 10 */
	"missing ENDIF",		/* 11 */
	"INCLUDE nesting to deep"	/* 12 */
};

#define MAXHEX	32			/* max no bytes/hex record */

static unsigned short hex_adr;		/* current address in hex record */
static int hex_cnt;			/* current no bytes in hex buffer */

static unsigned char hex_buf[MAXHEX];	/* buffer for one hex record */
static char hex_out[MAXHEX*2+11];	/* ASCII buffer for one hex record */

/*
 *	print error message to listfile and increase error counter
 */
void asmerr(int i)
{
	if (pass == 1) {
		fprintf(errfp, "Error in file: %s  Line: %d\n", srcfn, c_line);
		fprintf(errfp, errmsg[i]);
		fprintf(errfp, "\n\n");
	} else
		errnum = i;
	errors++;
}

/*
 *	begin new page in listfile
 */
void lst_header(void)
{
	fprintf(lstfp, "\fZ80-Assembler\t\tRelease %s\t\t\t\tPage %d\n", REL,
		++page);
	fprintf(lstfp, "Source file: %s\n", srcfn);
	fprintf(lstfp, "Title:       %s\n", title);
	p_line = 3;
}

/*
 *	print header for source lines
 */
void lst_attl(void)
{
	fprintf(lstfp, "\nLOC   OBJECT CODE   LINE   STMT SOURCE CODE\n");
	p_line += 2;
}

/*
 *	print one line into listfile, if -l option set
 */
void lst_line(int val, int opanz)
{
	register int i;

	if (!list_flag || sd_flag == 4) {
		sd_flag = 0;
		return;
	}
	if ((p_line >= ppl) || (c_line == 1)) {
		lst_header();
		lst_attl();
	}
	switch (sd_flag) {
	case 0:
		fprintf(lstfp, "%04x  ", val & 0xffff);
		break;
	case 1:
		fprintf(lstfp, "%04x  ", sd_val & 0xffff);
		break;
	case 2:
		fprintf(lstfp, "      ");
		break;
	case 3:
		fprintf(lstfp, "%04x              ", sd_val & 0xffff);
		goto no_data;
	default:
		fatal(F_INTERN, "illegal listflag for function lst_line");
		break;
	}
	if (opanz >= 1) fprintf(lstfp, "%02x ", ops[0] & 0xff);
		else fprintf(lstfp, "   ");
	if (opanz >= 2) fprintf(lstfp, "%02x ", ops[1] & 0xff);
		else fprintf(lstfp, "   ");
	if (opanz >= 3) fprintf(lstfp, "%02x ", ops[2] & 0xff);
		else fprintf(lstfp, "   ");
	if (opanz >= 4) fprintf(lstfp, "%02x ", ops[3] & 0xff);
		else fprintf(lstfp, "   ");
	no_data:
	fprintf(lstfp, "%6d %6d %s", c_line, s_line, line);
	if (errnum) {
		fprintf(errfp, "=> %s", errmsg[errnum]);
		putc('\n', errfp);
		errnum = 0;
		p_line++;
	}
	sd_flag = 0;
	p_line++;
	if (opanz > 4 && sd_flag == 0) {
		opanz -= 4;
		i = 4;
		sd_val = val;
		while (opanz > 0) {
			if (p_line >= ppl) {
				lst_header();
				lst_attl();
			}
			s_line++;
			sd_val += 4;
			fprintf(lstfp, "%04x  ", sd_val & 0xffff);
			if (opanz-- > 0) fprintf(lstfp, "%02x ",
						 ops[i++] & 0xff);
				else fprintf(lstfp, "   ");
			if (opanz-- > 0) fprintf(lstfp, "%02x ",
						 ops[i++] & 0xff);
				else fprintf(lstfp, "   ");
			if (opanz-- > 0) fprintf(lstfp, "%02x ",
						 ops[i++] & 0xff);
				else fprintf(lstfp, "   ");
			if (opanz-- > 0) fprintf(lstfp, "%02x ",
						 ops[i++] & 0xff);
				else fprintf(lstfp, "   ");
			fprintf(lstfp, "%6d %6d\n", c_line, s_line);
			p_line++;
		}
	}
}

/*
 *	print symbol table into listfile unsorted
 */
void lst_sym(void)
{
	register int i, j;
	register struct sym *np;

	p_line = j = 0;
	strcpy(title,"Symboltable");
	for (i = 0; i < HASHSIZE; i++) {
		if (symtab[i] != NULL) {
			for (np = symtab[i]; np != NULL; np = np->sym_next) {
				if (p_line == 0) {
					lst_header();
					fputs("\n", lstfp);
					p_line += 1;
				}
				fprintf(lstfp, "%-8s %04x\t", np->sym_name,
					np->sym_wert & 0xffff);
				if (++j == 4) {
					fprintf(lstfp, "\n");
					if (p_line++ >= ppl)
						p_line = 0;
					j = 0;
				}
			}
		}
	}
}

/*
 *	print sorted symbol table into listfile
 */
void lst_sort_sym(int len)
{
	register int i, j;

	p_line = i = j = 0;
	strcpy(title, "Symboltable");
	while (i < len) {
		if (p_line == 0) {
			lst_header();
			fputs("\n", lstfp);
			p_line += 1;
		}
		fprintf(lstfp, "%-8s %04x\t", symarray[i]->sym_name,
			symarray[i]->sym_wert & 0xffff);
		if (++j == 4) {
			fprintf(lstfp, "\n");
			if (p_line++ >= ppl)
				p_line = 0;
			j = 0;
		}
	i++;
	}
}

/*
 *	write header record into object file
 */
void obj_header(void)
{
	switch (out_form) {
	case OUTBIN:
		break;
	case OUTMOS:
		putc(0xff, objfp);
		putc(prg_adr & 0xff, objfp);
		putc(prg_adr >> 8, objfp);
		break;
	case OUTHEX:
		hex_adr = prg_adr;
		break;
	}
}

/*
 *	write end record into object file
 */
void obj_end(void)
{
	switch (out_form) {
	case OUTBIN:
		break;
	case OUTMOS:
		break;
	case OUTHEX:
		flush_hex();
		fprintf(objfp, ":0000000000\n");
		break;
	}
}

/*
 *	write opcodes in ops[] into object file
 */
void obj_writeb(int opanz)
{
	register int i;

	switch (out_form) {
	case OUTBIN:
		fwrite(ops, 1, opanz, objfp);
		break;
	case OUTMOS:
		fwrite(ops, 1, opanz, objfp);
		break;
	case OUTHEX:
		for (i = 0; opanz; opanz--) {
			if (hex_cnt >= MAXHEX)
				flush_hex();
			hex_buf[hex_cnt++] = ops[i++];
		}
		break;
	}
}

/*
 *	write <count> bytes 0xff into object file
 */
void obj_fill(int count)
{
	switch (out_form) {
	case OUTBIN:
		while (count--)
			putc(0xff, objfp);
		break;
	case OUTMOS:
		while (count--)
			putc(0xff, objfp);
		break;
	case OUTHEX:
		flush_hex();
		hex_adr += count;
		break;
	}
}

/*
 *	create a hex record in ASCII and write into object file
 */
void flush_hex(void)
{
	char *p;
	register int i;

	if (!hex_cnt)
		return;
	p = hex_out;
	*p++ = ':';
	btoh((unsigned char) hex_cnt, &p);
	btoh((unsigned char) (hex_adr >> 8), &p);
	btoh((unsigned char) (hex_adr & 0xff), &p);
	*p++ = '0';
	*p++ = '0';
	for (i = 0; i < hex_cnt; i++)
		btoh(hex_buf[i], &p);
	btoh((unsigned char) chksum(), &p);
	*p++ = '\n';
	*p = '\0';
	fwrite(hex_out, 1, strlen(hex_out), objfp);
	hex_adr += hex_cnt;
	hex_cnt = 0;
}

/*
 *	convert unsigned char into ASCII hex and copy to string at p
 *	increase p by 2
 */
void btoh(unsigned char byte, char **p)
{
	register unsigned char c;

	c = byte >> 4;
	*(*p)++ = (c < 10) ? (c + '0') : (c - 10 + 'A');
	c = byte & 0xf;
	*(*p)++ = (c < 10) ? (c + '0') : (c - 10 + 'A');
}

/*
 *	computer checksum for Intel hex record
 */
int chksum(void)
{
	register int i, j, sum;

	sum = hex_cnt;
	sum += hex_adr >> 8;
	sum += hex_adr & 0xff;
	for (i = 0; i < hex_cnt; i++) {
		j = hex_buf[i];
		sum += j & 0xff;
	}
	return (0x100 - (sum & 0xff));
}
