#include "linetab.h"

lineref_t *linerefs[32769];
int refcounts[32769];
int lines_exist[32769];
unsigned char last_cmd, on_op;
int last_cmd_pos;

void add_lineref(unsigned short from, unsigned short pos) {
	lineref_t *p;
	int c;
	unsigned short to;

	to = fp2int(program + pos);
	if(to > 32767) return;

	p = linerefs[to];
	c = refcounts[to];

	if(c) {
		p = realloc(p, sizeof(lineref_t) * (c + 1));
	} else {
		p = malloc(sizeof(lineref_t));
	}

	if(!p) die("Out of memory.");

	linerefs[to] = p;
	linerefs[to][c].lineno = from;
	linerefs[to][c].pos = pos;
	linerefs[to][c].cmd = last_cmd;
	c++;
	refcounts[to] = c;
}

/* makes sure a numeric constant isn't start of an expression. */
int is_standalone_num(unsigned short pos) {
	if(program[pos] != OP_NUMCONST) return 0;
	switch(program[pos + 7]) {
		case OP_EOS:
		case OP_EOL:
		case OP_COMMA:
			return 1;
		default:
			return 0;
	}
}

CALLBACK(start_stmt) {
	lines_exist[lineno] = 1;
}

CALLBACK(got_cmd) {
	last_cmd = tok;
	last_cmd_pos = pos;
	on_op = 0;
}

void computed_msg(unsigned short lineno) {
	static int last_lineno = -1;
	char *cmd;

	/* avoid duplicate warnings */
	if(lineno == last_lineno) return;
	last_lineno = lineno;

	switch(last_cmd) {
		case CMD_GOTO:
			cmd = "GOTO"; break;
		case CMD_GO_TO:
			cmd = "GO TO"; break;
		case CMD_GOSUB:
			cmd = "GOSUB"; break;
		case CMD_RESTORE:
			cmd = "RESTORE"; break;
		case CMD_TRAP:
			cmd = "TRAP"; break;
		case CMD_ON:
			if(on_op == OP_GOSUB)
				cmd = "ON/GOSUB";
			else
				cmd = "ON/GOTO";
			break;
		case CMD_LIST:
			cmd = "LIST";
			break;
		default: /* should never happen! */
			cmd = "???"; break;
	}

	fprintf(stderr, "%s: Warning: Computed %s at line %d.\n", self, cmd, lineno);
}

CALLBACK(got_var) {
	switch(last_cmd) {
		/* any use of a variable in the arguments to these means
		   we can't renumber that argument. */
		case CMD_GOTO:
		case CMD_GO_TO:
		case CMD_GOSUB:
		case CMD_RESTORE:
		case CMD_TRAP:
		case CMD_LIST:
			computed_msg(lineno);
			break;
		case CMD_ON:
			/* vars are OK in ON, before the GOTO or GOSUB */
			if(on_op) computed_msg(lineno);
			break;
		default:
			break;
	}
}

CALLBACK(got_exp) {
	unsigned char last_tok = program[pos - 1];
	int standalone;

	if(last_cmd == CMD_ON) {
		if(tok == OP_GOTO || tok == OP_GOSUB)
			on_op = tok;
	}

	if(tok != OP_NUMCONST) return;

	/* beware: standalone only means nothing *follows* the constant
	   in the same expression. still have to check last_tok to see
	   what came before. */
	standalone = is_standalone_num(pos);

	switch(last_cmd) {
		/* these take a single argument */
		case CMD_GOTO:
		case CMD_GO_TO:
		case CMD_GOSUB:
		case CMD_RESTORE:
		case CMD_TRAP:
			if((pos == last_cmd_pos + 1) && standalone) {
				add_lineref(lineno, pos + 1);
			} else {
				computed_msg(lineno);
			}
			break;
		case CMD_IF:
			/* this only applies to bare line numbers, like IF A THEN 1000,
			   not IF A THEN GOTO 1000 (or anything else after THEN). */
			if(last_tok == OP_THEN) {
				add_lineref(lineno, pos + 1);
			}
			break;
		case CMD_ON: {
				/* takes arbitrary number of arguments */
				switch(last_tok) {
					case OP_GOTO:
					case OP_GOSUB:
					case OP_COMMA:
						if(standalone)
							add_lineref(lineno, pos + 1);
						else
							computed_msg(lineno);
						break;
					default:
						break;
				}
			}
			break;
		case CMD_LIST: {
				/* takes one or two arguments */
				switch(last_tok) {
					case CMD_LIST:
					case OP_COMMA:
						if(standalone)
							add_lineref(lineno, pos + 1);
						else
							computed_msg(lineno);
						break;
					default:
						break;
				}
			}
		default:
			break;
	}
}

void build_ref_table(void) {
	on_start_stmt = start_stmt;
	on_cmd_token = got_cmd;
	on_exp_token = got_exp;
	on_var_token = got_var;
	walk_all_code();
	on_start_stmt = on_cmd_token = on_exp_token = on_var_token = 0;
}

void free_ref_table(void) {
	int i;
	for(i = 0; i < 32768; i++)
		if(linerefs[i]) free(linerefs[i]);
}
