#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "bas.h"

#undef DUMP_TABLES

#include "tokens.c"
#include "aplus_tokens.c"
#include "bxl_tokens.c"
#include "bxe_tokens.c"

int errs = 0;

/* there are a few more BXL commands past 0x55, but they have no
   A+ equivalents. */
#define LAST_BXL_CMD 0x55
#define LAST_BXL_OP 0x65

/* cmd_table[basic_token] or op_table[basic_token] gives the equivalent
   A+ token. note that there's no "GO TO" or "COM" in A+, we translate
   those as GOTO and DIM. */
unsigned char cmd_table[] = {
	/* Atari BASIC: */
	/* REM DATA INPUT COLOR LIST ENTER LET IF:*/
	0x00, 0x01, 0x02, 0x3f, 0x03, 0x04, 0x05, 0x06,  /* 0x00 - 0x07 */

	/* FOR NEXT GOTO GO<sp>TO GOSUB TRAP BYE CONT: */
	0x07, 0x08, 0x09, 0x09, 0x0b, 0x0c, 0x0d, 0x0e,  /* 0x08 - 0x0f */

	/* COM CLOSE CLR DEG DIM END NEW OPEN: */
	0x12, 0x0f, 0x10, 0x11, 0x12, 0x19, 0x1a, 0x1b,  /* 0x10 - 0x17 */

	/* LOAD SAVE STATUS NOTE POINT XIO ON POKE: */
	0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,  /* 0x18 - 0x1f */

	/* PRINT RAD READ RESTORE RETURN RUN STOP POP: */
	0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,  /* 0x20 - 0x27 */

	/* ? GET PUT GRAPHICS PLOT POSITION DOS DRAWTO: */
	0x2d, 0x2e, 0x2f, 0x40, 0x41, 0x42, 0x38, 0x43,  /* 0x28 - 0x2f */

	/* SETCOLOR LOCATE SOUND LPRINT CSAVE CLOAD <silent-let> ERROR-: */
	0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x52, 0x53,  /* 0x30 - 0x37 */

	/* BASIC XL: */
	/* WHILE ENDWHILE TRACEOFF TRACE ELSE ENDIF DPOKE LOMEM: */
	0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x24, 0x30,  /* 0x38-0x3f */

	/* DEL RPUT RGET BPUT BGET TAB CP ERASE: */
	0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x39,  /* 0x40-0x47 */

	/* PROTECT UNPROTECT DIR RENAME MOVE MISSILE PMCLR PMCOLOR: */
	0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x4a, 0x4b, 0x4c,  /* 0x48-0x4f */

	/* PMGRAPHICS PMMOVE PMWIDTH SET LVAR RENUM: */
	0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x0a,              /* 0x50-0x55 */

};

/* 0xff means "untranslatable". these are:
   BUMP( FIND( HEX$ RANDOM(
   ...though BUMP( and FIND( do have A+ equivalents (they just need
   a left paren inserted after) */
unsigned char op_table[] = {
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* not used: 0x00-0x07 */
	0xff, 0xff, 0xff, 0xff, 0xff,                   /* not used: 0x08-0x0c */
	0x0e,       /* convert BXL hex const to decimal */
	0x0e, 0x0f, /* numeric and string consts are the same */
	0xff, 0xff, /* these 2 not used: 0x10-0x11 */

	/* these are the same, 0x12-0x1b */
	0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,

	/* 0x1c-0x2a off by one due to USING inserted at 0x1c */
	0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24,
	0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,

	/* 0x2b-0x43 off by 3 due to ! and & */
	0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
	0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d,
	0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
	0x46,

	/* 0x44-0x50 */
	0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51,
	0x52, 0x53, 0x54, 0x55, 0x56,

	/* 0x51-0x54 (last BASIC) */
	0x58, 0x59, 0x5a, 0x5b,

	/* BXL stuff, 0x55-0x5f */
	0x1c, 0xff /* % */, 0x2c, 0x2d,
	0xff, /* string array semicolon */
	0xff, /* BUMP( (TODO: A+ token 0x47 plus a paren) */
	0xff, /* FIND( (TODO: A+ token 0x48 plus a paren) */
	0xff, /* HEX$ */
	0xff, /* RANDOM( */
	0x49, /* DPEEK */
	0x57, /* SYS */
	0x5c, /* VSTICK */
	0x5d, /* HSTICK */
	0x5e, /* PMADR */
	0x5f, /* ERR */
	0x60, /* TAB */
	0x61, /* PEN */

};

#ifdef DUMP_TABLES
void dump_tables(void) {
	int i;

	for(i = 0; i <= LAST_BXL_CMD; i++) {
		const char *b, *ap = aplus_cmds[cmd_table[i]];
		if(i <= last_command)
			b = commands[i];
		else
			b = bxl_cmds[i - 0x38];
		printf("%02x: %s   %s%s\n", i, b, ap, (strcmp(b, ap) == 0) ? " SAME" : "");
	}

	printf("\n\n");

	for(i = 0x12; i <= LAST_BXL_OP; i++) {
		int j = op_table[i];;
		const char *b, *ap;
		if(j == 0xff)
			ap = "(not in A+)";
		else
			ap = aplus_ops[j];
		if(i <= last_operator)
			b = operators[i];
		else
			b = bxl_ops[i - 0x55];
		printf("%02x: %s   %s%s\n", i, b, ap, (strcmp(b, ap) == 0) ? " SAME" : "");
	}
}
#endif

void print_help(void) {
	printf("%s [input-file] [output-file]\n", self);
}

void parse_args(int argc, char **argv) {
	int opt;

	while( (opt = getopt(argc, argv, "v")) != -1) {
		switch(opt) {
			case 'v': verbose =     1; break;
			case 'h': print_help(); exit(0);
			default:
				print_help();
				exit(1);
		}
	}

	if(optind == argc)
		die("No input file given.");
	else
		open_input(argv[optind]);

	if(++optind == argc)
		die("No output file given.");
	else
		output_filename = argv[optind];

	if(argv[++optind])
		die("Only one input and one output file allowed.");
}

const char *get_tok_name(unsigned char tok, int is_cmd) {
	if(is_cmd) {
		if(tok <= last_command) {
			return commands[tok];
		} else if(tok == 0x5a) {
			return "BASIC XL LOCAL/EXIT/PROCEDURE/CALL/SORTUP/SORTDOWN";
		} else if(tok == 0x59 || (tok > 0x5a && tok < 0x65)) {
			return bxe_cmds[tok - 0x57];
		} else if(tok > last_command && tok <= 0x58) {
			return bxl_cmds[tok - 0x38];
		} else {
			return "(maybe Turbo BASIC?)";
		}
	} else {
		if(tok < 0x12) return "";
		if(tok <= last_operator) {
			return operators[tok];
		} else if(tok <= 0x68) {
			return bxl_ops[tok - 0x55];
		} else {
			return "(maybe Turbo BASIC?)";
		}
	}
}

void unsupported_msg(unsigned char tok, int lineno, int is_cmd) {
	fprintf(stderr, "%s: Invalid %s \"%s\" ($%02x) at line %d, not converted.\n",
			self, is_cmd ? "command" : "operator", get_tok_name(tok, is_cmd), tok, lineno);
	errs++;
}

int is_supported_cmd(unsigned char tok, int lineno) {
	if(tok > LAST_BXL_CMD) {
		unsupported_msg(tok, lineno, 1);
		return 0;
	}
	return 1;
}

int is_supported_op(unsigned char tok, int lineno) {
	if(tok > LAST_BXL_OP || op_table[tok] == 0xff) {
		unsupported_msg(tok, lineno, 0);
		return 0;
	}
	return 1;
}

CALLBACK(conv_cmd) {
	if(!is_supported_cmd(tok, lineno))
		return;

	program[pos] = cmd_table[tok];

	if(verbose && tok != program[pos])
		fprintf(stderr, "command \"%s\" ($%02x) converted to $%02x at line %d, pos $%04x\n",
				get_tok_name(tok, 1), tok, program[pos], lineno, pos);
}

CALLBACK(conv_op) {
	if(!is_supported_op(tok, lineno))
		return;

	program[pos] = op_table[tok];

	if(verbose && tok != program[pos])
		fprintf(stderr, "operator \"%s\" ($%02x) converted to $%02x at line %d, pos $%04x\n",
				get_tok_name(tok, 0), tok, program[pos], lineno, pos);
}

int main(int argc, char **argv) {
#ifdef DUMP_TABLES
	dump_tables(); exit(0);
#endif
	set_self(*argv);
	parse_general_args(argc, argv, print_help);
	parse_args(argc, argv);

	readfile();
	parse_header();

	on_cmd_token = conv_cmd;
	on_exp_token = conv_op;

	allow_hex_const = 1;
	walk_all_code();

	open_output(output_filename);
	writefile();

	if(errs) {
		fprintf(stderr, "%s: program has %d invalid tokens; BASIC/A+ won't RUN it.\n",
				self, errs);
		return 1;
	}

	return 0;
}