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

/* Force stdin into binary mode if running cygwin */
#ifdef __CYGWIN__
#include <fcntl.h>
#include <io.h>
void stdin_setup() {
        if (setmode(0,O_BINARY)==EOF) {
                fprintf(stderr,"\nImage read error.\n");
                exit(1);
        }
}
#else
void stdin_setup() {}
#endif

#define min(a,b) (((a)>(b))?(b):(a))

#define TOKEN_RECORD	0
#define TOKEN_NUMBER 	1
#define TOKEN_FUNCTION	2

#define FUNCTION_STARTLIST	0 
#define FUNCTION_ENDLIST	1
#define FUNCTION_STARTFOLD	2
#define FUNCTION_ENDFOLD	3
#define FUNCTION_STARTFILED	4
#define FUNCTION_ENDFILED	5

/* Declaration needed here as they can get called mutually recursively */
void parse_fold_body(void);
void parse_filed_body(void);

/* We hold the current fold name as a global because only one
 * copy is ever needed; it is not used accross recursive calls. 
 * Making it a single global saves a lot of stack space compared
 * to declaring it inside parse_fold_body and parse_filed_body */
unsigned char ns[2048];

int current_byte = 0;

int current_indent;

/* We default current_type to default to a binary type for worst-case
 * readabilityas text */
int current_type=1;

void indent() {
	int i=current_indent;
	while (i--) putchar(' ');
}

void fold_error() {
	fprintf(stderr,"\nFolded file format error at byte %d\n",current_byte);
	exit(1);
}

int safe_getchar() {
	int c;
	if ((c=getchar())==EOF) fold_error();
	current_byte++;
	return c;
}

int get_token(int *value) {
	int c;
	*value = 0;
	/* Handle prefix bytes */
	while (((c=safe_getchar())&0xc0)==0xc0) {
		*value |= c&0x3f;
		*value <<= 6;
		}
	*value |= c&0x3f;
	return (c &0xc0) >> 6;
}

void check_function(int f) {
	int v;
	if (get_token(&v) != TOKEN_FUNCTION || v != f)
		fold_error();
}
	
void parse_record_body(int v) {
	int i;
	switch (current_type) {
		case 1:
		case 2:
			for (i=0;i<v; i++) {
				if (i%16)
					putchar(' ');
				else 
					indent();
				printf("%2x",safe_getchar());
				if ( i%16 == 15)
					putchar('\n');
			}
			break;
		default:
			indent();
			while (v--) putchar(safe_getchar());
			break;
	}
	printf("\n");
}

void parse_record() {
	int v;
	if (get_token(&v) != TOKEN_RECORD) fold_error();
	parse_record_body(v);
}

char * attribute_type[] = {
	"opstext",
	"opsdata",
	"opscode",
	"foldset",
	"voidset"
};

char * attribute_content[] = {
	"comment.text",
	"source.text",
	"code.data",
	"occam1.sc",
	"desc.data",
	"debug.data",
	"occam1.prog",
	"occam1.util",
	"occam1.exe",
	"link.data",
	"occam2.sc",
	"occam2.prog",
	"occam2.util",
	"occam2.exe",
	"occam2.lib",
	"imp.proc",
	"imp.sc",
	"imp.lib",
	"c.proc",
	"c.sc",
	"c.lib",
	"pascal.proc",
	"pascal.sc",
	"pascal.lib",
	"fortran.proc"
	"fortran.sc",
	"fortran.lib",
	"check.data",
	"image.data",
	"map.data",
	"config.info",
	"analyse.info",
};

#define ARRAY_SIZE(f) (sizeof(f)/sizeof(f[0]))

void parse_attributes(int * attr_type, int * attr_content, int * local_indent) {
	int v;
	if (get_token(attr_type) != TOKEN_NUMBER)
		fold_error();
	if (*attr_type<0 || *attr_type >= ARRAY_SIZE(attribute_type)) 
		fold_error();
	if (get_token(attr_content) != TOKEN_NUMBER)
		fold_error();
	if (*attr_content<0 || *attr_content >= ARRAY_SIZE(attribute_content))
		fold_error();
	if (get_token(local_indent) != TOKEN_NUMBER)
		fold_error();
	if (*local_indent<0)
		fold_error();
}



void parse_elements() {
	int v;
	while(1) {
		switch(get_token(&v)) {
			case TOKEN_RECORD:
				parse_record_body(v);
				break;
			case TOKEN_NUMBER:
				printf("N %d\n",v);
				break;
			case TOKEN_FUNCTION:
				switch (v) {
					case FUNCTION_STARTFOLD:
						parse_fold_body();
						break;
					case FUNCTION_STARTFILED:
						parse_filed_body();
						break;
					case FUNCTION_ENDLIST:
						return;
						break;
					default:
						fold_error();
						break;
				}
				break;
			default:
				fold_error();
				break;
		}
	}
}

void parse_list() {
	int v;
	check_function(FUNCTION_STARTLIST);
	parse_elements();
}

void parse_fold_body() {
	unsigned char ns[2048];
	int i, len, attr_type, attr_content, local_indent;
	check_function(FUNCTION_STARTLIST);
	if (get_token(&len) != TOKEN_RECORD) fold_error();
	if (len > 2048) {
		fprintf(stderr,"\nFold header overflow at byte %d\n",current_byte);
		exit(1);
	}
	for (i=0;i<len;i++)
		ns[i]=safe_getchar();
	parse_attributes(&attr_type, &attr_content,&local_indent);
	current_indent += local_indent;
	indent();
	printf("{{{ %s %s ",attribute_type[attr_type], attribute_content[attr_content]);
	fwrite(ns,1,len,stdout);
	printf("\n");
	check_function(FUNCTION_ENDLIST);
	current_type = attr_type;
	parse_list();
	check_function(FUNCTION_ENDFOLD);
	indent();
	printf("}}}\n");
	current_indent -= local_indent;
}

void parse_filed_body() {
	unsigned char ns[2048];
	int i, len, attr_type, attr_content,local_indent, v;
	check_function(FUNCTION_STARTLIST);
	if (get_token(&len) != TOKEN_RECORD) fold_error();
	if (len > 2048) {
		fprintf(stderr,"\nFold header overflow at byte %d\n",current_byte);
		exit(1);
	}
	for (i=0;i<len;i++)
		ns[i]=safe_getchar();
	parse_attributes(&attr_type, &attr_content,&local_indent);
	current_indent += local_indent;
	indent();
	printf("{{{ %s %s ",attribute_type[attr_type], attribute_content[attr_content]);
	fwrite(ns,1,len,stdout);
	printf("\n");
	check_function(FUNCTION_ENDLIST);
	indent();
	printf("FILED FOLD: ");
	switch(get_token(&v)) {
		case TOKEN_RECORD:
			for (i=0;i<len;i++)
				putchar(safe_getchar());
			break;
		case TOKEN_NUMBER:
			printf("%d",v);
			break;
		default:
			fold_error();
			break;
	}
	printf("\n");
	check_function(FUNCTION_ENDFILED);
	indent();
	printf("}}}\n");
	current_indent -= local_indent;
}

main(int argc, char * argv[]) {
	if (argc > 1) {
		if (argc ==2 && !strcmp("-t",argv[1])) 
			current_type = 0;
		else {
			fprintf(stderr,"Usage: tdsfile [-t] < disk_image\n"
				"  -t: Assume data outside any fold is text.\n"
				"Decodes folded file to stdout. Non-text folds and, by default (no -t), the outermost\n"
				"data is dumped as 16 byte hex lines, with additional newline between records.\n");
			exit(1);
		}
	}
	parse_list();
	if (current_indent!=0 )
		/* This is really an internal error---it can't(!) happen */
		fold_error();
	exit(0);
}
