#include "axe.h"

extern int all, total_sec, translate;

static int atr_offset;

int load_disk(unsigned char *diskbuf, char *filename) {
	FILE *file;
	int vtoc_sec;
	unsigned char tmpbuf[16];
	char tmp[128];

	if((file = fopen(filename, "r")) == NULL) {
		sprintf(tmp, "load_disk(): %s", filename);
		perror(tmp);
		exit(1);
	}
	fread(tmpbuf, 16, 1, file);
	atr_offset = 16;
	memcpy(diskbuf, tmpbuf, 16);
	if((tmpbuf[0] != 0x96) || (tmpbuf[1] != 0x02)) {	/* not an atr */
		atr_offset = 0;
		rewind(file);
		total_sec = 720;
	}

	/* do some sanity checking of images: */
	if(atr_offset) {
		total_sec = (diskbuf[2] + 256 * diskbuf[3]) >> 3;
		if(!total_sec)
			total_sec = (diskbuf[6] + 256 * diskbuf[7]) * 8192;
		if(total_sec < 720) {
			printf("Total sectors (%d) < 720 - aborting\n", total_sec);
			exit(1);
		}
		if(total_sec > 720) {
			printf
				 ("Warning: Total sectors (%d) > 720 - only reading first 720 sectors\n",
				  total_sec);
		}
		if((diskbuf[4] + 256 * diskbuf[5]) != 128) {
			printf("Fatal: sector size not 128 bytes!\n");
			exit(1);
		}
	} else {
		printf
			 ("Warning: blindly assuming xfd image is 720 sectors, 128 bytes/sector!\n");
	}

	if((total_sec = fread(diskbuf + atr_offset, 128, 720, file)) < 720) {
		printf("Error reading image file, only got %d sectors, aborting\n", total_sec);
		perror("fread");
		exit(1);
	}

	vtoc_sec =
		 diskbuf[atr_offset + 128 * 359 + 1] + 256 * diskbuf[atr_offset +
																			  128 * 359 + 2];
	if(vtoc_sec != 707)
		printf
			 ("Warning: VTOC sector count (%d) not 707, are you sure this is an AtariDOS 2.0 image?\n",
			  vtoc_sec);
	return 0;
}

int write_disk(unsigned char *diskbuf, char *filename) {
	FILE *file;
	unsigned char tmpbuf[16];

	file = fopen(filename, "w");
	memcpy(tmpbuf, diskbuf, 16);
	atr_offset = 0;
	if((tmpbuf[0] == 0x96) && (tmpbuf[1] == 0x02)) {	/* is an atr */
		fwrite(tmpbuf, 16, 1, file);
		atr_offset = 16;
	}
	fwrite(diskbuf + atr_offset, 128, 720, file);
	fclose(file);
	return 0;
}


void usage(char *name) {
	printf
		 ("Usage: %s [options] [-D filename] [-w filename] [-e filename]\n\t\t [-b filename] [-c dirname] [-d sector] disk_image\n\n",
		  name);

	printf("\t-a\tlist all dir entries, even deleted/empty ones\n");
	printf("\t-l\ttrace & print links for all files on disk\n");
	printf("\t-v\tdump VTOC (sector 360) in decimal, hex, and binary\n");
	printf
		 ("\t-t\t'tar xf' style - create dir filled with files in image\n");
	printf("\t-u\tUn*x <-> Atari newline/EOL translation\n");
	printf("\t-D\tdelete file from image\n");
	printf("\t-w\twrite file to image\n");
	printf("\t-c\tcreate new image from directory\n");
	printf("\t-x\textract (read) file from image, write to current dir\n");
	printf("\t-b\tcreate blank image called 'filename'\n");
	printf("\t-d\tdump a sector in decimal, hex, and binary\n");
	printf
		 ("\ndisk_image must be a 720 sector, single density .atr or .xfd image\n");
	printf("\tin Atari DOS 2.0 or compatible format\n");
	printf
		 ("\nAll UN*X filenames must conform to Atari 8.3 filename.ext format\n");
	printf("\t(though they are NOT treated case-sensitively)\n");
	printf("\nEnjoy!\n\n");
	exit(1);
}

void print_entry(unsigned char *buf) {
#ifdef DEBUG
	static int ents;
	int i;
#endif
	int s;
	unsigned char tmpbuf[32];

	memcpy(tmpbuf, buf, 16);
	tmpbuf[16] = '\0';
#ifdef DEBUG
	printf("successfully read dir. entry #%d\n", ents++);
#endif
	if((tmpbuf[0] & 64) || all) {
#ifdef DEBUG
		for (i = 0; i < 16; i++) {
			printf(" 0x%X", tmpbuf[i]);
		}
#endif
		total_sec += (s = tmpbuf[1] + 256 * tmpbuf[2]);
		printf("%c %s\t%03d\n", (tmpbuf[0] & 32) ? '*' : ' ', tmpbuf + 5,
				 s);
	}
}

void read_sector(unsigned char *disk, int sector, unsigned char *buf) {
	memcpy(buf, disk + (sector - 1) * 128 + atr_offset, 128);
}

void write_sector(unsigned char *disk, int sector, unsigned char *buf) {
	memcpy(disk + (sector - 1) * 128 + atr_offset, buf, 128);
}

void traverse_file(unsigned char *disk, unsigned char *ent, int action) {
	int i, next_sec, data_bytes, curfile;
	unsigned char buf[128];
	unsigned char vtoc[128];

#ifdef DEBUG
	printf("traverse_file called with %s\n",
			 (action - 1) ? "TF_DELETE" : "TF_PRINT");
#endif

	if(action == TF_DELETE)
		read_sector(disk, 360, vtoc);

	next_sec = ent[3] + 256 * ent[4];
	while(next_sec) {
		if(action == TF_DELETE)
			mark_free(vtoc, next_sec);
		if(action == TF_PRINT)
			printf("%d: ", next_sec);
		read_sector(disk, next_sec, buf);
		data_bytes = buf[127];
		curfile = buf[125] >> 2;
		next_sec = buf[126] + 256 * (buf[125] & 3);
		if(action == TF_PRINT)
			printf("data_bytes=%d filenum=%d next_sec=%d\n", data_bytes,
					 curfile, next_sec);
	}
	if(action == TF_DELETE) {
		i = (vtoc[3] + 256 * vtoc[4]);
#ifdef DEBUG
		printf("deleting file, old vtoc free count=%d", i);
#endif
		i += ent[1] + 256 * ent[2];
#ifdef DEBUG
		printf(", new=%d\n", i);
#endif
		vtoc[3] = i % 256;
		vtoc[4] = i / 256;
		write_sector(disk, 360, vtoc);
		if(action == TF_DELETE) {
			printf("Deleted file, now %d free sectors on image\n", i);
		}
	}
}

void dump_file(unsigned char *disk, unsigned char *ent, char *outfile) {
#ifdef DEBUG
	int curfile;
#endif
	int i, next_sec, data_bytes, total_written = 0;
	unsigned char buf[128];
	FILE *out;

/*   fseek(handle,atr_seek(361)+16*filenum,SEEK_SET);
   fread(buf,16,1,handle); */
	next_sec = ent[3] + 256 * ent[4];
	out = fopen(outfile, "wb");
/*   #ifdef DEBUG
   printf("First sector of filenum %d, ",filenum);
   #endif */
	while(next_sec) {
#ifdef DEBUG
		printf("%d: ", next_sec);
#endif
		read_sector(disk, next_sec, buf);
		data_bytes = buf[127];
		next_sec = buf[126] + 256 * (buf[125] & 3);
#ifdef DEBUG
		curfile = buf[125] >> 2;
		printf("data_bytes=%d filenum=%d next_sec=%d\n", data_bytes, curfile,
				 next_sec);
#endif
		if(translate)
			for (i = 0; i <= data_bytes; i++)
				if(buf[i] == 0x9b)
					buf[i] = '\n';
		fwrite(buf, data_bytes, 1, out);
		total_written += data_bytes;
		fflush(out);
	}
	printf("Wrote %d bytes to file %s\n", total_written, outfile);
}


int parse_filename(char *filename, char *result) {
	int i = 0, j = 0;
	char ext[4] = "   \0";
	char fn[9] = "        \0";
#ifdef DEBUG
	printf("parse_filename called, filename=%s\n", filename);
#endif
	if(strlen(filename) > 12) {
		printf("filename too long: %s\n", filename);
		exit(1);
	}
/*   if(strlen(strstr(filename,"."))>4) {
      printf("extension too long: %s\n",filename);
      exit(1);
   } */
	while((filename[i]) && (filename[i] != '.')) {
		fn[i++] = toupper(filename[i]);
	}
	if(filename[i] == '.')
		while(filename[++i]) {
			ext[j++] = toupper(filename[i]);
		}

	strcpy(result, fn);
	strncat(result, ext, 3);
#ifdef DEBUG
	printf("parse_filename exiting, result=%s\n", result);
#endif
	return (0);
}

int make_filename(char *filename, char *result) {
	int i = 0, j = 8;
	char fn[13] = { '\0' };

	while((filename[i] && filename[i] != ' ') && (i < 8)) {
		fn[i++] = tolower(filename[i]);
	}
	if(filename[j] != ' ') {
		fn[i++] = '.';
		while(filename[j]) {
			fn[i++] = tolower(filename[j++]);
		}
	}
#ifdef DEBUG
	printf("made filename %s from %s\n", fn, filename);
#endif
	strcpy(result, fn);
	return 0;
}

int make_dir(char *filename) {
	char tmp[15] = { '\0' };
	char *p;

	strcpy(tmp, filename);
	if(!(p = strrchr(tmp, '.'))) {
		strcat(tmp, ".dir");
	} else {
		*p = '\0';
	}
#ifdef DEBUG
	printf("made dir: %s\n", tmp);
#endif
	if(mkdir(tmp, 0777)) {
		perror("Can't make directory: ");
		exit(1);
	}
	chdir(tmp);
	return 0;
}

void dump_sector(unsigned char *disk, int sector) {
	unsigned char buf[256];
	int i, j, k;
	printf("Dump of sector %d in decimal:\n", sector);
	read_sector(disk, sector, buf);
	for (i = 0; i < 128; i += 16) {
		printf("%03d: ", i);
		for (j = 0; j < 16; j++) {
			printf("%03d ", buf[i + j]);
		}
		printf("\n");
	}
	printf("Dump of sector %d in hex:\n", sector);
	for (i = 0; i < 128; i += 16) {
		printf("%03x: ", i);
		for (j = 0; j < 16; j++) {
			printf(" %02x ", buf[i + j]);
		}
		printf("\n");
	}
	printf("Dump of sector %d in binary:\n", sector);
	for (i = 0; i < 128; i += 16) {
		printf("%03x: ", i);
		for (j = 0; j < 16; j++) {
			for (k = 7; k > -1; k--) {
				if(buf[i + j] & (1 << k))
					printf("1");
				else
					printf("0");
			}
			printf(" ");
			if(j == 7)
				printf("\n     ");
		}
		printf("\n");
	}
}


/* please, ignore the ugliness of this function:*/
void write_blank_disk(char *filename) {
	unsigned char atr_header[16] = { 0x96, 0x02, 0x80, 0x16, 0x80, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	};
	unsigned char disk[128 * 720 + 16] = { '\0' };
	int i;
	unsigned char vtoc[128] =
		 { 0x02, 0xc3, 0x02, 0xc3, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f
	};

	for (i = 11; i < 100; i++)
		vtoc[i] = 0xff;
	vtoc[55] = 0x00;
	vtoc[56] = 0x7f;
	for (i = 100; i < 128; i++)
		vtoc[i] = 0x00;
	atr_offset = 16;
	memcpy(disk, atr_header, 16);
	write_sector(disk, 360, vtoc);
	write_disk(disk, filename);
	printf("Wrote formatted blank image to %s\n", filename);
}

static void xlate_eofs(unsigned char *buf, int len) {
	int i;
	for (i = 0; i < len; i++)
		if(buf[i] == '\n')
			buf[i] = 0x9b;
}

void write_file(unsigned char *disk, char *filename) {
	unsigned char vtoc[128];
	unsigned char buf[128];
	char fnbuf[15];
/*   unsigned char dentry[16];*/
	unsigned char *dindex;
	int i, dentryno, first_sec, secno, next_sec, count = 0, bytes;
	FILE *handle;

	printf("Writing %s to image...", filename);
	parse_filename(filename, fnbuf);
	if((handle = fopen(filename, "r")) == NULL) {
		perror("write_file::fopen");
		exit(1);
	}
#ifdef DEBUG
	printf("write_file: parsed %s as \"%s\"\n", filename, fnbuf);
#endif
	if((dentryno = get_dentry(disk)) == -1) {
		printf("write_file: no free directory entries on image\n");
		exit(1);
	}
#ifdef DEBUG
	printf("dentryno=%d\n", dentryno);
#endif
	read_sector(disk, 360, vtoc);
	if(!(first_sec = secno = get_free_sector(vtoc))) {
		printf("write_file: no free sectors in image\n");
		exit(1);
	}
#ifdef DEBUG
	printf("first_sec=%d\n", first_sec);
#endif
	do {
		int tmp, eof = 0;
		count++;
		bytes = fread(buf, 1, 125, handle);

		if(bytes == 125) {
			/* test for EOF on handle */
			tmp = fgetc(handle);
			if(tmp == EOF)
				eof = 1;
			else
				ungetc(tmp, handle);
		}

		if(translate) xlate_eofs(buf, 125);
		mark_used(vtoc, secno);

		if(bytes == 125 && !eof) {
			/* full sector, not at EOF */
			next_sec = get_free_sector(vtoc);
			buf[125] = (dentryno << 2) | (next_sec >> 8);
			buf[126] = next_sec & 255;
			buf[127] = 125;
			write_sector(disk, secno, buf);
			secno = next_sec;
		} else {
			/* partial sector or full sector at EOF */
			buf[125] = dentryno << 2;
			buf[126] = 0;
			buf[127] = bytes;
			write_sector(disk, secno, buf);
			next_sec = 0;
		}
	} while(next_sec);
	/* update dentry */
	read_sector(disk, 361 + (dentryno / 8), buf);
	dindex = buf + (dentryno % 8) * 16;
	dindex[0] = 66;				  /* taken from a dos 2.0s disk, hope its right */
	dindex[1] = count & 255;
	dindex[2] = count / 256;
	dindex[3] = first_sec & 255;
	dindex[4] = first_sec / 256;
	memcpy(dindex + 5, fnbuf, 11);
	write_sector(disk, 361 + (dentryno / 8), buf);
	/*all thats left to do is update free sector count in vtoc: */
	i = (vtoc[3] + 256 * vtoc[4]) - count;
	vtoc[3] = i & 255;
	vtoc[4] = i / 256;
	write_sector(disk, 360, vtoc);
	printf("Wrote %d sectors.\n", count);
}

int get_dentry(unsigned char *disk) {
	int i = 361, j, dentryno = 0, done = 0;
	unsigned char buf[128];

	while(!done) {
		read_sector(disk, i, buf);
		for (j = 0; j < 128; j += 16) {
			if((buf[j] == 0) || (buf[j] & 128)) {
				return dentryno;
			}
			dentryno++;
		}
		done = (++i == 369);
	}
	return -1;
}

int get_free_sector(unsigned char *vtoc) {
	int i;

	for (i = 1; i < 720; i++) {  /* atari DOS doesn't use sector 720 */
		if(vtoc[vtoc_byte(i)] & vtoc_bit(i))
			return i;
	}

	return 0;
}

int vtoc_byte(int sector) {
	return 10 + (sector) / 8;
}

int vtoc_bit(int sector) {
	return 128 >> ((sector) % 8);
}

void mark_used(unsigned char *vtoc_sector, int sector) {
	vtoc_sector[vtoc_byte(sector)] &= ~vtoc_bit(sector);
}

void mark_free(unsigned char *vtoc_sector, int sector) {
	vtoc_sector[vtoc_byte(sector)] |= vtoc_bit(sector);
}

/*int get_confirm(char *prompt) {
   printf("%s[y/N]? ",prompt);
   return ((toupper(getc(stdin)))=='Y');
}*/

int check_dir(unsigned char *disk, char *filename) {
	int i = 361, j, done = 0, dentryno = 0;
	unsigned char buf[128];

	printf("check_dir: passed %s\n", filename);
	while(!done) {
		read_sector(disk, i, buf);
		for (j = 0; j < 128; j += 16) {
			if((strncmp(filename, (char*)(buf + j + 5), 11) == 0)) {
				return dentryno;
			}
			dentryno++;
		}
		done = (++i == 369);
	}

	return -1;
}

/* these routines suck, and are not actually used any more.
 * maybe someday they'll become useful again.
 * 
   int delete_file(unsigned char *disk,char *filename) {
   int first_sec,next_sec,i,dentryno;
   char fnbuf[15];
   char buf[128];
   char *debuf;
   char vtoc[128];
   
   printf("delete_file doesn't work yet. passed: %s\n",filename);
   parse_filename(filename,fnbuf);
   if((dentryno=check_dir(disk,fnbuf))==-1) {
      printf("delete_file: file %s not found on image\n",fnbuf);
      exit(1);
   }
   printf("delete_file: would delete %s\n",fnbuf);
   read_sector(disk,361+dentryno/8,buf);
   read_sector(disk,360,vtoc);
   debuf=buf+(dentryno%8)*16;
   next_sec=debuf[3]+256*debuf[4];
   traverse_file(disk,debuf,TF_DELETE);
   return 0;
}
*/
