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

#include "bas.h"

int startlineno = 0;
int endlineno = 32768;

/* dump tokens for each line in a BASIC program. easier to read than
   a plain hex dump. */
void print_help(void) {
	printf("Usage: %s [-v] [-s start-lineno] [-e end-lineno] <inputfile>\n", self);
}

unsigned short getlineno(char opt, const char *arg) {
	int lineno;
	char *e;

	lineno = (int)strtol(arg, &e, 10);

	if(*e) {
		fprintf(stderr, "%s: Invalid line number for -%c option: %s is not a number.\n",
				self, opt, arg);
		exit(1);
	}

	if(lineno < 0 || lineno > 32767) {
		fprintf(stderr, "%s: Invalid line number for -%c option: %d > 32767.\n",
				self, opt, lineno);
		exit(1);
	}

	return ((unsigned short)lineno);
}

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

	while( (opt = getopt(argc, argv, "vs:e:l:")) != -1) {
		switch(opt) {
			case 'v': verbose = 1; break;
			case 's': startlineno = getlineno(opt, optarg); break;
			case 'e': endlineno   = getlineno(opt, optarg); break;
			case 'l': startlineno = getlineno(opt, optarg);
						 endlineno   = startlineno; break;
			default:
				print_help();
				exit(1);
		}
	}

	if(optind >= argc)
		die("No input file given (use - for stdin).");
	else
		open_input(argv[optind]);
}

void print_atascii(unsigned char c) {
	if(c & 0x80) {
		putchar('|');
		c &= 0x7f;
	}

	if(c < 32) {
		putchar('^');
		c += 0x40;
	}

	if(c == 0x7f)
		printf("del");
	else
		putchar(c);
	putchar('/');
}

/* REM, DATA, ERROR lines are terminated by $9B, a real EOL, not
   the BASIC token. Since they're strings, print them in ASCII too. */
/*
int handle_text_stmt(int pos) {
	unsigned char c;

	do {
		c = program[pos];
		print_atascii(c);
		printf("%02x ", c);
		pos++;
	} while(c != 0x9b);

	return pos;
}
*/

CALLBACK(handle_text) {
	unsigned char c;

	do {
		c = program[pos];
		print_atascii(c);
		printf("%02x ", c);
		pos++;
	} while(c != 0x9b);
}

CALLBACK(handle_cmd) {
	printf("!%02x ", tok);
}

/*
int handle_cmd(int pos) {
	unsigned char tok = program[pos];

	printf("!%02x ", tok);
	switch(tok) {
		case CMD_REM:
		case CMD_DATA:
		case CMD_ERROR:
			return handle_text_stmt(pos + 1);
		default:
			return pos + 1;
	}
}
*/

/*
void handle_string(int pos) {
	int i, len;
	len = program[pos + 1];
	printf("$%02x =%02x \"", program[pos], len);
	for(i = pos; i < pos + len; i++) {
		unsigned char c = program[i + 2];
		print_atascii(c);
		printf("%02x%c", c, (i == (pos + len - 1) ? '"' : ' '));
	}
	putchar(' ');
}
*/

/*
void handle_num(int pos) {
	int i;
	printf("#%02x [", program[pos]);
	for(i = 0; i < 6; i++)
		printf("%02x%c", program[pos + 1 + i], (i == 5 ? ']' : ' '));
	putchar(' ');
}
*/

CALLBACK(handle_op) {
	switch(tok) {
		case OP_EOS:
			printf("%02x:", tok);
			return;
		case OP_NUMCONST:
			putchar('#'); break;
		case OP_STRCONST:
			putchar('$'); break;
		default: break;
	}
	printf("%02x ", tok);
}

CALLBACK(handle_var) {
	printf("%02x", tok);
	switch(get_vartype(tok)) {
		case TYPE_ARRAY:
			putchar('('); break;
		case TYPE_STRING:
			putchar('$'); break;
		default: break;
	}
	putchar(' ');
}

CALLBACK(handle_string) {
	int i, len;
	len = program[pos];
	printf("=%02x \"", len);
	for(i = pos; i < pos + len; i++) {
		unsigned char c = program[i + 1];
		print_atascii(c);
		printf("%02x%c", c, (i == (pos + len - 1) ? '"' : ' '));
	}
	putchar(' ');
}

CALLBACK(handle_num) {
	int i;
	putchar('[');
	for(i = 0; i < 6; i++)
		printf("%02x%c", program[pos + i], (i == 5 ? ']' : ' '));
	putchar(' ');
}

CALLBACK(handle_start_line) {
	printf("%5d @%04x (%02x %02x): ^%02x\n",
			lineno, pos, program[pos], program[pos + 1], program[pos + 2]);
}

CALLBACK(handle_end_line) {
	putchar('\n');
}

CALLBACK(handle_start_stmt) {
	printf("     >%02x ", tok);
}

CALLBACK(handle_end_stmt) {
	putchar('\n');
}

int main(int argc, char **argv) {
	set_self(*argv);
	parse_general_args(argc, argv, print_help);
	parse_args(argc, argv);

	readfile();
	parse_header();

	on_start_line = handle_start_line;
	on_end_line = handle_end_line;
	on_start_stmt = handle_start_stmt;
	on_end_stmt = handle_end_stmt;
	on_exp_token = handle_op;
	on_cmd_token = handle_cmd;
	on_num_const = handle_num;
	on_string_const = handle_string;
	on_text = handle_text;
	on_var_token = handle_var;

	walk_code(startlineno, endlineno);

	return 0;
}

/* sorry, this is horrid, more like assembly than C. */
#if 0
int main(int argc, char **argv) {
	int linepos, nextpos, offset, soffset, lineno, pos, end, tok;

	set_self(*argv);
	parse_general_args(argc, argv, print_help);
	parse_args(argc, argv);

	readfile();
	parse_header();

	linepos = codestart;
	while(linepos < filelen) { /* loop over lines */
		lineno = getword(linepos);
		offset = program[linepos + 2];
		nextpos = linepos + offset;

		if(offset < 6)
			die("Can't dump a protected program, unprotect it first.");

		if(lineno < startlineno) {
			linepos = nextpos;
			continue;
		}

		/* line header */
		printf("%5d@%04x (%02x %02x): ^%02x ",
				lineno, linepos, program[linepos], program[linepos + 1], offset);

		pos = linepos + 3;
		while(pos < nextpos) { /* loop over statements within a line */
			soffset = program[pos];
			end = linepos + soffset;

			while(pos < end) {  /* loop over tokens within a statement */
				printf("\n    >%02x ", program[pos++]); /* offset */
				pos = handle_cmd(pos++); /* 1st token is the command */

				while(pos < end) {     /* loop over operators */
					tok = program[pos];
					switch(tok) {
						case OP_NUMCONST:
							handle_num(pos);
							pos += 7;
							break;
						case OP_STRCONST:
							handle_string(pos);
							pos += program[pos + 1] + 2;
							break;
						default:
							printf("%02x", program[pos]);
							if(pos == (end - 1) && tok == OP_EOS)
								putchar(':');
							else
								putchar(' ');
							pos++;
							break;
					}
				}
			}
		}

		putchar('\n');

		if(lineno == endlineno) break;
		linepos = nextpos;
	}

	return 0;
}
#endif
