/* Creates an AppleSoft BASIC shape table                    */
/* See the AppleSoft manual for info on how this works       */
/* Other online info (I'm looking at you, atariarchives.org) */
/* is inaccurate                                             */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define MAX_SIZE 8192 /* not really, but anything larger would be crazy */

static int debug=0,line=1;

static unsigned char table[MAX_SIZE];

static void set_offset(int current_shape,int current_offset) {

	table[2+(current_shape*2)]=current_offset&0xff;
	table[2+(current_shape*2)+1]=(current_offset>>8)&0xff;
}

#define LOC_A 0
#define LOC_B 1
#define LOC_C 2

static void warn_if_zero(unsigned char byte, int line) {

	/* Check to see if we're accidentally ignoring bytes */
	if (byte==0) {
		fprintf(stderr,
			"Warning, all-0 byte will be ignored on line %d!\n",
			line);
	}

	if ((byte&0xf8)==0) {
		fprintf(stderr,
			"Warning, ignoring C and B due to 0 on line %d!\n",
			line);
	}
}

static void print_usage(char *exe) {

	printf("Usage:\t%s [-h] [-a] [-b]\n\n",exe);
	printf("\t-h\tprint this help message\n");
	printf("\t-a\toutput shape table in applesoft BASIC format\n");
	printf("\t-b\toutput shape table in binary format for BLOADing\n");
	printf("\n");
	exit(1);
}

static int get_token(char *token, FILE *fff) {

	int ch;
	int ptr=0;

	/* skip leading spaces/comments */
	while(1) {
		ch=fgetc(fff);
		if (ch<0) return -1;

		if (ch=='#') {
			while(ch!='\n') ch=fgetc(fff);
		}
		if ((ch==' ') || (ch=='\t') || (ch=='\n')) {
			if (ch=='\n') line++;
			continue;
		}

		break;
	}

	while(1) {
		token[ptr]=ch;
		ptr++;

		ch=fgetc(fff);
		if (ch<0) return -1;

		if ((ch==' ') || (ch=='\t') || (ch=='\n')) {
			if (ch=='\n') line++;
			break;
		}
	}
	token[ptr]=0;


	return 0;

}

int main(int argc, char **argv) {

	char string[BUFSIZ];
	int result;
	int table_size=0;
	int num_shapes=0;
	int current_offset=0,current_shape=0;
	int i;

	int command=0,sub_pointer;

	int output_binary=0;

	if (argc<2) {
		output_binary=0;
	}
	else {
		if (argv[1][0]=='-') {

			switch(argv[1][1]) {

				case 'h':
					print_usage(argv[0]);
					break;
				case 'b':
					output_binary=1;
					break;
				case 'a':
					output_binary=0;
					break;
				default:
					printf("Unknown options %s\n",argv[1]);
					print_usage(argv[0]);
			}
		}
	}

	result=get_token(string,stdin);
	if (result<0) {
		fprintf(stderr,"Error getting number\n");
		return -1;
	}

	num_shapes=atoi(string);
	if (num_shapes<1) {
		fprintf(stderr,"Error getting numshapes\n");
		return -2;
	}
	if (debug) fprintf(stderr,"Number of shapes = %d\n",num_shapes);

	table[0]=num_shapes;
	table[1]=0;

	current_shape=0;
	current_offset=2+2*(num_shapes);

	for(current_shape=0;current_shape<num_shapes;current_shape++) {

		set_offset(current_shape,current_offset);

		/* Find START */

		while(1) {
			result=get_token(string,stdin);
			if (result<0) {
				fprintf(stderr,"Unexpected EOF!\n");
				return -1;
			}

			if (!strcmp(string,"START")) {
				if (debug) fprintf(stderr,"START\n");
				break;
			}
		}

		/* READ DATA */
		sub_pointer=LOC_A;

		while(1) {
			result=get_token(string,stdin);
			if (result<0) {
				fprintf(stderr,"Unexpected end of file!\n");
				return -2;
			}

			if (!strcmp(string,"STOP")) {
				if (debug) fprintf(stderr,"STOP\n");
				break;
			}

			/* yes, this is inefficient... */

			if (!strcmp(string,"NUP")) command=0;
			else if (!strcmp(string,"NRT")) command=1;
			else if (!strcmp(string,"NDN")) command=2;
			else if (!strcmp(string,"NLT")) command=3;
			else if (!strcmp(string,"UP")) command=4;
			else if (!strcmp(string,"RT")) {
				if (debug) fprintf(stderr,"RT\n");
				command=5;
			}
			else if (!strcmp(string,"DN")) command=6;
			else if (!strcmp(string,"LT")) command=7;
			else fprintf(stderr,"Unknown command '%s'",string);

			if (sub_pointer==LOC_A) {
				table[current_offset]=(command&0x7);
				sub_pointer=LOC_B;
			}
			else if (sub_pointer==LOC_B) {
				table[current_offset]|=((command&0x7)<<3);
				sub_pointer=LOC_C;
			}
			else {
				/* Try to fit in LOC_C.  This can only hold no-draw moves */
				/* Also a LOC_C of 0 is ignored                           */
				if ((command&0x4) || (command==0)) {

					/* Write to LOC_A instead */

					warn_if_zero(table[current_offset],line);

					current_offset++;
					table[current_offset]=(command&0x7);
					sub_pointer=LOC_B;
				}
				else {
					/* write to LOC_C */
					table[current_offset]|=((command&0x3)<<6);

					warn_if_zero(table[current_offset],line);

					current_offset++;
					sub_pointer=LOC_A;
				}
			}
		}

		if (sub_pointer!=LOC_A) current_offset++;

		table[current_offset]=0; current_offset++;

	}

	table_size=current_offset;

	if (output_binary) {
		unsigned char header[4];
		int offset=0x6000;

		header[0]=offset&0xff;
		header[1]=(offset>>8)&0xff;
		header[2]=table_size&0xff;
		header[3]=(table_size>>8)&0xff;

		fprintf(stderr,"Be sure to POKE 232,%d : POKE 233,%d\n"
			"\tto let applesoft know the location of the table\n",
			offset&0xff,(offset>>8)&0xff);

		fwrite(header,sizeof(unsigned char),4,stdout);

		fwrite(table,sizeof(unsigned char),table_size,stdout);
	}
	else {

		/* put near highmem */
		int address=0x1ff0-table_size;

		printf("10 HIMEM:%d\n",address);
		printf("20 POKE 232,%d:POKE 233,%d\n",(address&0xff),(address>>8)&0xff);
		printf("30 FOR L=%d TO %d: READ B:POKE L,B:NEXT L\n",
			address,(address+table_size)-1);
		printf("35 HGR:ROT=0:SCALE=2\n");
		printf("40 FOR I=1 TO %d: XDRAW I AT I*10,100:NEXT I\n",
			num_shapes);
		printf("90 END\n");

		for(i=0;i<current_offset;i++) {
			if(i%10==0) printf("%d DATA ",100+i/10);
			printf("%d",table[i]);
			if ((i%10==9)||(i==current_offset-1)) printf("\n");
			else printf(",");
		}

	}

	return 0;
}
