#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include "defs.h"
#include "externs.h"
#include "protos.h"

/* pseudo instructions section flag */
char pseudo_flag[] = {
	0x0C, 0x0C, 0x0F, 0x0F, 0x0F, 0x0C, 0x0C, 0x0C, 0x0F, 0x0C,
	0x0C, 0x0C, 0x0C, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0C,
	0x0F, 0x0F, 0x0F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0F, 0x0F, 0x0F,
	0x0F, 0x0F, 0x0C, 0x0C, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x04
};


/* ----
 * do_pseudo()
 * ----
 * pseudo instruction processor
 */

void
do_pseudo(int *ip)
{
	char str[80];
	int old_bank;
	int size;

	/* check if the directive is allowed in the current section */
	if (!(pseudo_flag[opval] & (1 << section)))
		fatal_error("Directive not allowed in the current section!");

	/* save current location */
	old_bank = bank;

	/* execute directive */
	opproc(ip);

	/* reset last label pointer */
	switch (opval) {
	case P_VRAM:
	case P_PAL:
		break;

	case P_DB:
	case P_DW:
		if (lastlabl) {
			if(lastlabl->data_type != P_DB)
		 	   lastlabl = NULL;
		}
		break;

	default:
		if (lastlabl) {
			if(lastlabl->data_type != opval)
		 	   lastlabl = NULL;
		}
		break;
	}

	/* bank overflow warning */
	if (pass == LAST_PASS) {
		if (asm_opt[OPT_WARNING]) {
			switch (opval) {
			case P_INCBIN:
			case P_INCCHR:
			case P_INCSPR:
			case P_INCPAL:
			case P_INCBAT:
			case P_INCTILE:
			case P_INCMAP:
				if (bank != old_bank) {
					size = ((bank - old_bank - 1) * 8192) + loccnt;
					if (size) {
						sprintf(str, "Warning, bank overflow by %i bytes!\n", size);
						warning(str);
					}
				}
				break;
			}
		}
	}
}


/* ----
 * do_list()
 * ----
 * .list pseudo
 */

void
do_list(int *ip)
{
	/* check end of line */
	if (!check_eol(ip))
		return;

	asm_opt[OPT_LIST] = 1;
	xlist = 1;
}


/* ----
 * do_mlist()
 * ----
 * .mlist pseudo
 */

void
do_mlist(int *ip)
{
	/* check end of line */
	if (!check_eol(ip))
		return;

	asm_opt[OPT_MACRO] = 1;
}


/* ----
 * do_nolist()
 * ----
 * .nolist pseudo
 */

void
do_nolist(int *ip)
{
	/* check end of line */
	if (!check_eol(ip))
		return;

	asm_opt[OPT_LIST] = 0;
}


/* ----
 * do_nomlist()
 * ----
 * .nomlist pseudo
 */

void
do_nomlist(int *ip)
{
	/* check end of line */
	if (!check_eol(ip))
		return;

	asm_opt[OPT_MACRO] = mlist_opt;
}


/* ----
 * do_db()
 * ----
 * .db pseudo
 */

void
do_db(int *ip)
{
	unsigned char c;

	/* define label */
	labldef(loccnt, 1);

	/* output infos */
	data_loccnt = loccnt;
	data_level  = 2;

	/* skip spaces */
	while (isspace(prlnbuf[++(*ip)]));

	/* get bytes */
	for (;;) {
		/* ASCII string */
		if (prlnbuf[*ip] == '\"') {
			for (;;) {
				c = prlnbuf[++(*ip)];
				if (c == '\"')
					break;
				if (c == '\0') {
					error("Unterminated ASCII string!");
					return;
				}
				if (c == '\\') {
					c = prlnbuf[++(*ip)];
					switch(c) {
					case 'r':
						c = '\r';
						break;
					case 'n':
						c = '\n';
						break;
					case 't':
						c = '\t';
						break;
					}
				}
				/* store char on last pass */
				if (pass == LAST_PASS)
					putbyte(loccnt, c);

				/* update location counter */
				loccnt++;
			}
			(*ip)++;
		}
		/* bytes */
		else {
			/* get a byte */
			if (!evaluate(ip, 0))
				return;

			/* update location counter */
			loccnt++;

			/* store byte on last pass */
			if (pass == LAST_PASS) {
				/* check for overflow */
				if ((value > 0xFF) && (value < 0xFFFFFF80)) {
					error("Overflow error!");
					return;
				}

				/* store byte */
				putbyte(loccnt - 1, value);
			}
		}

		/* check if there's another byte */
		c = prlnbuf[(*ip)++];

		if (c != ',')
			break;
	}

	/* check error */
	if (c != ';' && c != '\0') {
		error("Syntax error!");
		return;
	}

	/* size */
	if (lablptr) {
		lablptr->data_type = P_DB;
		lablptr->data_size = loccnt - data_loccnt;
	}
	else {
		if (lastlabl) {
			if (lastlabl->data_type == P_DB)
				lastlabl->data_size += loccnt - data_loccnt;
		}
	}

	/* output line */
	if (pass == LAST_PASS)
		println();
}


/* ----
 * do_dw()
 * ----
 * .dw pseudo
 */

void
do_dw(int *ip)
{
	char c;

	/* define label */
	labldef(loccnt, 1);

	/* output infos */
	data_loccnt = loccnt;
	data_size   = 2;
	data_level  = 2;

	/* get data */
	for (;;) {
		/* get a word */
		if (!evaluate(ip, 0))
			return;

		/* update location counter */
		loccnt += 2;

		/* store word on last pass */
		if (pass == LAST_PASS) {
			/* check for overflow */
			if ((value > 0xFFFF) && (value < 0xFFFF8000)) {
				error("Overflow error!");
				return;
			}

			/* store word */
			putword(loccnt-2, value);
		}

		/* check if there's another word */
		c = prlnbuf[(*ip)++];

		if (c != ',')
			break;
	}

	/* check error */
	if (c != ';' && c != '\0') {
		error("Syntax error!");
		return;
	}

	/* size */
	if (lablptr) {
		lablptr->data_type = P_DB;
		lablptr->data_size = loccnt - data_loccnt;
	}
	else {
		if (lastlabl) {
			if (lastlabl->data_type == P_DB)
				lastlabl->data_size += loccnt - data_loccnt;
		}
	}

	/* output line */
	if (pass == LAST_PASS)
		println();
}


/* ----
 * do_equ()
 * ----
 * .equ pseudo
 */

void
do_equ(int *ip)
{
	/* get value */
	if (!evaluate(ip, ';'))
		return;

	/* assign value to the label */
	labldef(value, 0);

	/* output line */
	if (pass == LAST_PASS) {
		loadlc(value, 1);
		println();
	}
}


/* ----
 * do_page()
 * ----
 * .page pseudo
 */

void
do_page(int *ip)
{
	/* not allowed in procs */
	if (proc_ptr) {
		fatal_error("PAGE can not be changed in procs!");
		return;
	}

	/* define label */
	labldef(loccnt, 1);

	/* get page index */
	if (!evaluate(ip, ';'))
		return;
	if (value > 7) {
		error("Invalid page index!");
		return;
	}
	page = value;

	/* output line */
	if (pass == LAST_PASS) {
		loadlc(value << 13, 1);
		println();
	}
}


/* ----
 * do_org()
 * ----
 * .org pseudo
 */

void
do_org(int *ip)
{
	/* get the .org value */
	if (!evaluate(ip, ';'))
		return;

	/* check for undefined symbol - they are not allowed in .org */
	if (undef != 0) {
		error("Undefined symbol in operand field!");
		return;
	}

	/* section switch */
	switch (section) {
	case S_ZP:
		/* zero page section */
		if ((value & 0xFFFFFF00) && ((value & 0xFFFFFF00) != machine->ram_base)) {
			error("Invalid address!");
			return;
		}
		break;

	case S_BSS:
		/* ram section */
		if ((value < machine->ram_base) || (value >= (machine->ram_base + machine->ram_limit))) {
			error("Invalid address!");
			return;
		}
		break;

	case S_CODE:
	case S_DATA:
		/* not allowed in procs */
		if (proc_ptr) {
			fatal_error("ORG can not be changed in procs!");
			return;
		}

		/* code and data section */
		if (value & 0xFFFF0000) {
			error("Invalid address!");
			return;
		}
		page = (value >> 13) & 0x07;
		break;
	}

	/* set location counter */
	loccnt = (value & 0x1FFF);

	/* set label value if there was one */
	labldef(loccnt, 1);

	/* output line on last pass */
	if (pass == LAST_PASS) {
		loadlc(value, 1);
		println();
	}
}


/* ----
 * do_bank()
 * ----
 * .bank pseudo
 */

void
do_bank(int *ip)
{
	char name[128];

	/* not allowed in procs */
	if (proc_ptr) {
		fatal_error("Bank can not be changed in procs!");
		return;
	}

	/* define label */
	labldef(loccnt, 1);

	/* get bank index */
	if (!evaluate(ip, 0))
		return;
	if (value > bank_limit) {
		error("Bank index out of range!");
		return;
	}

	/* check if there's a bank name */
	switch (prlnbuf[*ip]) {
	case ';':
	case '\0':
		break;
		
	case ',':
		/* get name */
		(*ip)++;
		if (!getstring(ip, name, 63))
			return;

		/* check name validity */
		if (strlen(bank_name[value])) {
			if (strcasecmp(bank_name[value], name)) {
				error("Different bank names not allowed!");
				return;
			}
		}

		/* copy name */			
		strcpy(bank_name[value], name);

		/* check end of line */
		if (!check_eol(ip))
			return;

		/* ok */
		break;

	default:
		error("Syntax error!");
		return;
	}

	/* backup current bank infos */
	bank_glabl[section][bank]  = glablptr;
	bank_loccnt[section][bank] = loccnt;
	bank_page[section][bank]   = page;

	/* get new bank infos */
	bank     = value;
	page     = bank_page[section][bank];
	loccnt   = bank_loccnt[section][bank];
	glablptr = bank_glabl[section][bank];

	/* update the max bank counter */
	if (max_bank < bank)
		max_bank = bank;

	/* output on last pass */
	if (pass == LAST_PASS) {
		loadlc(bank, 1);
		println();
	}
}


/* ----
 * do_incbin()
 * ----
 * .incbin pseudo
 */

void
do_incbin(int *ip)
{
	FILE *fp;
	char *p;
	char fname[128];
	int  size;

	/* get file name */
	if (!getstring(ip, fname, 127))
		return;

	/* get file extension */
	if ((p = strrchr(fname, '.')) != NULL) {
		if (!strchr(p, PATH_SEPARATOR)) {
			/* check if it's a mx file */
			if (!strcasecmp(p, ".mx")) {
				do_mx(fname);
				return;
			}
			/* check if it's a map file */
			if (!strcasecmp(p, ".fmp")) {
				if (pce_load_map(fname, 0))
					return;
			}
		}
	}

	/* define label */
	labldef(loccnt, 1);

	/* output */
	if (pass == LAST_PASS)
		loadlc(loccnt, 0);

	/* open file */
	if ((fp = open_file(fname, "rb")) == NULL) {
		fatal_error("Can not open file!");
		return;
	}

	/* get file size */
	fseek(fp, 0, SEEK_END);
	size = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	/* check if it will fit in the rom */
	if (((bank << 13) + loccnt + size) > rom_limit) {
		fclose(fp);
		error("ROM overflow!");
		return;
	}

	/* load data on last pass */
	if (pass == LAST_PASS) {
		fread(&rom[bank][loccnt], 1, size, fp);
		memset(&map[bank][loccnt], section + (page << 5), size);

		/* output line */
		println();
	}

	/* close file */
	fclose(fp);

	/* update bank and location counters */
	bank  += (loccnt + size) >> 13;
	loccnt = (loccnt + size) & 0x1FFF;
	if (bank > max_bank) {
		if (loccnt)
			max_bank = bank;
		else
			max_bank = bank - 1;
	}

	/* size */
	if (lablptr) {
		lablptr->data_type = P_INCBIN;
		lablptr->data_size = size;
	}
	else {
		if (lastlabl) {
			if (lastlabl->data_type == P_INCBIN)
				lastlabl->data_size += size;
		}
	}
}


/* ----
 * do_mx()
 * ----
 * load a mx file
 */

void
do_mx(char *fname)
{
	FILE *fp;
	char *ptr;
	char type;
	char line[256];
	unsigned char buffer[128];
	int data;
	int flag = 0;
	int size = 0;
	int cnt, addr, chksum;
	int i;

	/* open the file */
	if ((fp = open_file(fname, "r")) == NULL) {
		fatal_error("Can not open file!");
		return;
	}

	/* read loop */
	while (fgets(line, 254, fp) != NULL) {
		if (line[0] == 'S') {
			/* get record type */
			type = line[1];

			/* error on unsupported records */
			if ((type != '2') && (type != '8')) {
				error("Unsupported S-record type!");
				return;
			}

			/* get count and address */
			cnt  = htoi(&line[2], 2);
			addr = htoi(&line[4], 6);

			if ((strlen(line) < 12) || (cnt < 4) || (addr == -1)) {
				error("Incorrect S-record line!");
				return;
			}

			/* adjust count */
			cnt -= 4;

			/* checksum */
			chksum = cnt + ((addr >> 16) & 0xFF) +
						   ((addr >> 8) & 0xFF) +
						   ((addr) & 0xFF) + 4;

			/* get data */
			ptr = &line[10];

			for (i = 0; i < cnt; i++) {
				data = htoi(ptr, 2);
				buffer[i] = data;
				chksum += data;
				ptr += 2;

				if (data == -1) {
					error("Syntax error in a S-record line!");
					return;
				}
			}

			/* checksum test */
			data = htoi(ptr, 2);
			chksum = (~chksum) & 0xFF;

			if (data != chksum) {
				error("Checksum error!");
				return;
			}

			/* end record */
			if (type == '8')
				break;

			/* data record */
			if (type == '2') {
				/* set the location counter */
				if (addr & 0xFFFF0000) {
					error("Invalid address!");
					return;
				}
				page   = (addr >> 13) & 0x07;
				loccnt = (addr & 0x1FFF);

				/* define label */
				if (flag == 0) {
					flag  = 1;
					labldef(loccnt, 1);

					/* output */
					if (pass == LAST_PASS)
						loadlc(loccnt, 0);
				}

				/* copy data */
				if (pass == LAST_PASS) {
					for (i = 0; i < cnt; i++)
						putbyte(loccnt + i, buffer[i]);
				}

				/* update location counter */
				loccnt += cnt;
				size   += cnt;
			}
		}
	}

	/* close file */
	fclose(fp);

	/* define label */
	if (flag == 0) {
		labldef(loccnt, 1);

		/* output */
		if (pass == LAST_PASS)
			loadlc(loccnt, 0);
	}

	/* size */
	if (lablptr) {
		lablptr->data_type = P_INCBIN;
		lablptr->data_size = size;
	}
	else {
		if (lastlabl) {
			if (lastlabl->data_type == P_INCBIN)
				lastlabl->data_size += size;
		}
	}

	/* output line */
	if (pass == LAST_PASS)
		println();
}


/* ----
 * do_include()
 * ----
 * .include pseudo
 */

void
do_include(int *ip)
{
	char fname[128];

	/* define label */
	labldef(loccnt, 1);

	/* get file name */
	if (!getstring(ip, fname, 127))
		return;

	/* open file */
	if (open_input(fname) == -1) {
		fatal_error("Can not open file!");
		return;
	}

	/* output line */
	if (pass == LAST_PASS)
		println();
}


/* ----
 * do_rsset()
 * ----
 * .rsset pseudo
 */

void
do_rsset(int *ip)
{
	/* define label */
	labldef(loccnt, 1);

	/* get value */
	if (!evaluate(ip, ';'))
		return;
	if (value & 0xFFFF0000) {
		error("Address out of range!");
		return;
	}

	/* set 'rs' base */
	rsbase = value;

	/* output line */
	if (pass == LAST_PASS) {
		loadlc(rsbase, 1);
		println();
	}
}


/* ----
 * do_rs()
 * ----
 * .rs pseudo
 */

void
do_rs(int *ip)
{
	/* define label */
	labldef(rsbase, 0);

	/* get the number of bytes to reserve */
	if (!evaluate(ip, ';'))
		return;

	/* ouput line */
	if (pass == LAST_PASS) {
		loadlc(rsbase, 1);
		println();
	}

	/* update 'rs' base */
	rsbase += value;
	if (rsbase & 0xFFFF0000)
		error("Address out of range!");
}


/* ----
 * do_ds()
 * ----
 * .ds pseudo
 */

void
do_ds(int *ip)
{
	int limit = 0;
	int addr;

	/* define label */
	labldef(loccnt, 1);

	/* get the number of bytes to reserve */
	if (!evaluate(ip, ';'))
		return;

	/* section switch */
	switch (section) {
	case S_ZP:
		/* zero page section */
		limit = machine->zp_limit;
		break;	

	case S_BSS:
		/* ram section */
		limit = machine->ram_limit;
		break;

	case S_CODE:
	case S_DATA:
		/* code and data sections */
		limit = 0x2000;
		break;
	}

	/* check range */
	if ((loccnt + value) > limit) {
		error("Out of range!");
		return;
	}

	/* update max counter for zp and bss sections */
	addr = loccnt + value;

	switch (section) {
	case S_ZP:
		/* zero page */
		if (addr > max_zp)
			max_zp = addr;
		break;

	case S_BSS:
		/* ram page */
		if (addr > max_bss)
			max_bss = addr;
		break;
	}

	/* output line on last pass */
	if (pass == LAST_PASS) {
		switch (section) {
		case S_CODE:
		case S_DATA:
			memset(&rom[bank][loccnt], 0, value);
			memset(&map[bank][loccnt], section + (page << 5), value);
			if (bank > max_bank)
				max_bank = bank;
			break;
		}
		loadlc(loccnt, 0);
		println();
	}

	/* update location counter */
	loccnt += value;
}


/* ----
 * do_fail()
 * ----
 * .fail pseudo
 */

void
do_fail(int *ip)
{
	fatal_error("Compilation failed!");
}


/* ----
 * do_section()
 * ----
 * .zp/.bss/.code/.data pseudo
 */

void
do_section(int *ip)
{
	if (proc_ptr) {
		if (optype == S_DATA) {
			fatal_error("No data segment in procs!");
			return;
		}
	}
	if (section != optype) {
		/* backup current section data */
		section_bank[section] = bank;
		bank_glabl[section][bank] = glablptr;
		bank_loccnt[section][bank] = loccnt;
		bank_page[section][bank] = page;

		/* change section */
		section = optype;

		/* switch to the new section */
		bank = section_bank[section];
		page = bank_page[section][bank];
		loccnt = bank_loccnt[section][bank];
		glablptr = bank_glabl[section][bank];
	}

	/* output line */
	if (pass == LAST_PASS) {
		loadlc(loccnt + (page << 13), 1);
		println();
	}
}


/* ----
 * do_incchr()
 * ----
 * .inchr pseudo - convert a PCX to 8x8 character tiles
 */

void
do_incchr(int *ip)
{
	unsigned char buffer[32];
	unsigned int i, j;
	unsigned int x, y, w, h;
	unsigned int tx, ty;
	int total = 0;
	int size;

	/* define label */
	labldef(loccnt, 1);

	/* output */
	if (pass == LAST_PASS)
		loadlc(loccnt, 0);

	/* get args */
	if (!pcx_get_args(ip))
		return;
	if (!pcx_parse_args(0, pcx_nb_args, (int *)&x, (int *)&y, (int *)&w, (int *)&h, 8))
		return;

	/* pack data */
	for (i = 0; i < h; i++) {
		for (j = 0; j < w; j++) {
			/* tile coordinates */
			tx = x + (j << 3);
			ty = y + (i << 3);

			/* get tile */
			size   = pcx_pack_8x8_tile(buffer, tx, ty);
			total += size;

			/* store tile */
			putbuffer(buffer, size);
		}
	}

	/* size */
	if (lablptr) {
		lablptr->data_type = P_INCCHR;
		lablptr->data_size = total;
	}
	else {
		if (lastlabl) {
			if (lastlabl->data_type == P_INCCHR)
				lastlabl->data_size += total;
		}
	}

	/* output */
	if (pass == LAST_PASS)
		println();
}


/* ----
 * do_opt()
 * ----
 * .opt pseudo - compilation options
 */

void
do_opt(int *ip)
{
	char c;
	char flag;
	char name[32];
	int  opt;
	int  i;

	for (;;) {
		/* skip spaces */
		while (isspace(prlnbuf[*ip]))
			(*ip)++;

		/* get char */
		c = prlnbuf[(*ip)++];

		/* no option */
		if (c == ',')
			continue;

		/* end of line */	
		if (c == ';' || c == '\0')
			break;

		/* extract option */
		i = 0;
		for (;;) {
			if (c == ' ')
				continue;
			if (c == ',' || c == ';' || c == '\0')
				break;
			if (i > 31) {
				error("Syntax error!");
				return;
			}
			name[i++] = c;
			c = prlnbuf[(*ip)++];
		}

		/* get option flag */
		name[i] = '\0';
		flag = name[--i];
		name[i] = '\0';

		/* search option */
		if (!strcasecmp(name, "l"))
			opt = OPT_LIST;
		else if (!strcasecmp(name, "m"))
			opt = OPT_MACRO;
		else if (!strcasecmp(name, "w"))
			opt = OPT_WARNING;
		else if (!strcasecmp(name, "o"))
			opt = OPT_OPTIMIZE;
		else {
			error("Unknown option!");
			return;
		}

		/* set option */
		if (flag == '+')
			asm_opt[opt] = 1;
		if (flag == '-')
			asm_opt[opt] = 0;
	}

	/* output */
	if (pass == LAST_PASS)
		println();
}


/* ----
 * htoi()
 * ----
 */

int
htoi(char *str, int nb)
{
	char c;
	int val;
	int i;

	val = 0;
	
	for (i = 0; i < nb; i++) {
		 c = toupper(str[i]);

		if ((c >= '0') && (c <= '9'))
			val = (val << 4) + (c - '0');
		else if ((c >= 'A') && (c <= 'F'))
			val = (val << 4) + (c - 'A' + 10);
		else
			return (-1);
	}

	/* ok */
	return (val);
}

