/* ados.c
 *   Atari DOS emulation
 *   by Frank Barrus
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "memtype.h"
#include "vdisk.h"


struct avtoc {
	byte	dirtype;		/* directory type */
	byte	maxsec_lo, maxsec_hi;	/* maximum # of sectors */
	byte	free_lo, free_hi;	/* # free sectors */
	byte	unknown[5];		/* ?? */
	byte	bitmap[118];	/* usage bitmap: 1 = sector free */
};

struct afile {
	int	dev;		/* device number */
	FILE	*f;		/* native file pointer */
	int	dnum;		/* directory number */
	int	mode;		/* mode file was opened for */
	char	name[12];	/* 8.3-style filename + NULL */
	int	size;		/* total size of file */
	int	nsec;		/* number of sectors in file */
	int	pos;		/* position in file */
	int	bpos;		/* position in buffer */
	int	bsize;		/* buffer size */
	int	cursec;		/* current sector */
	int	nextsec;	/* next sector to read */
	int	start;		/* first sector */
	byte	buf[256];	/* temporary buffer */
};

struct avtoc vtoc[NVDISK+1];
int nsec[NVDISK+1];
int secsize[NVDISK+1];


#define SEC_FREE(v, n)		(v).bitmap[n/8] |= (0x80>>(n&7));
#define SEC_USE(v, n)		(v).bitmap[n/8] &= ~(0x80>>(n&7));
#define SEC_ISFREE(v, n)	((v).bitmap[n/8] & (0x80>>(n&7)))
#define SEC_ISUSED(v, n)	!((v).bitmap[n/8] & (0x80>>(n&7)))

struct adirent {
	byte	flag;		
	byte	nsec_lo;	/* number of sectors */
	byte	nsec_hi;	/* this got misaligned when it was a */
	byte	start_lo;	/* a word type, so bytes have to be separate */
	byte	start_hi;	/* starting sector num */
	char	name[11];
};

/* directory flags: */
#define DIR_DELETED	0x80
#define DIR_INUSE	0x40
#define DIR_LOCKED	0x20
#define DIR_OPENOUT	0x01

/* file opening modes: */
#define DOS_READ	1
#define DOS_WRITE	2


extern char errmsg[];

static int parse_path(char *fname, char *str)
{
int n = 0;
int dev = -1;
	if(str[0] == 'D' || str[0] == 'd') 
		if(str[1] == ':') {
			dev = 1;
			str += 2;
		} else if(str[1] >= '1' && str[1] <= '8' && str[2] == ':') {
			dev = str[1]-'0';
			str += 3;
		}
	if(dev < 0)
		return dev;
	while(*str) {
		if(*str == '.') {
			while(n<8)
				fname[n++] = 0x20;
			n = 8;
			str++;
		} else {
			fname[n++] = *str++;
			if(n >= 11)
				break;
		}
	}
	while(n < 11)
		fname[n++] = 0x20;
	fname[11] = '\0';
	return dev;	
}


static int dos_open(struct afile *a, char *path, int mode)
{
char *modestr;
struct adirent dir[16];
struct adirent match;
char fstr[12];
int i, n;
	a->f = NULL;
	if((a->dev = parse_path(a->name, path)) < 0) {
		if(mode == DOS_WRITE)
			modestr = "w";
		else
			modestr = "r";
		if(!(a->f = fopen(path, modestr))) {
			sprintf(errmsg, "%s: %s", path, strerror(errno));
			return False;
		}
		return True;
	}
	vdisk_get_size(a->dev, &nsec[a->dev], &secsize[a->dev]);
	a->pos = a->bpos = a->size = a->bsize = a->nsec = 0;
	n = -1;
	for(i=0; i<64; i++) {
		if(!(i&7))
			if(!vdisk_read_sec(a->dev, 361+(i/8), (byte*)dir))
				return False;
		strncpy(fstr, dir[i&7].name, 11);
		fstr[11] = '\0';
		if(!strcasecmp(fstr, a->name) && ((dir[i&7].flag&DIR_INUSE)
				|| mode == DOS_READ)) {
			match = dir[i&7];
			n = i;
			break;
		}
		if(n < 0 && mode == DOS_WRITE && !(dir[i&7].flag&DIR_INUSE)) {
			match = dir[i&7];
			PUTWORD(match.start, 0);
			n = i;
		} 
	}
	if(n < 0) {
		if(mode == DOS_WRITE) {
			sprintf(errmsg, "D%d: too many files\n", a->dev);
			return False;
		} else {
			sprintf(errmsg, "D%d: could not open '%s'\n",
						a->dev, a->name);
			return False;
		}
	}
	a->dnum = n;
	a->mode = mode;
	if(mode == DOS_READ) {
		a->nsec = GETWORD(match.nsec);
		a->size = (secsize[a->dev]-3)*a->nsec;
	} else {
		if(!vdisk_read_sec(a->dev, 361+(n/8), (byte*)dir))
			return False;
		strncpy(dir[n&7].name, a->name, 11);
		dir[n&7].flag = DIR_INUSE|DIR_OPENOUT;
		if(!vdisk_write_sec(a->dev, 361+(n/8), (byte*)dir))
			return False;
	}
	a->nextsec = a->start = GETWORD(match.start);
	a->cursec = 0;
	return True;
}


static int dos_read(struct afile *a, byte *buf, int len)
{
byte b;
int n,next;
int size;
	if(a->f) 
		return fread(buf, 1, len, a->f);
	n = 0;
	size = secsize[a->dev];
	while(len > 0 && a->pos < a->size) {
		if(a->bpos >= a->bsize) {
			vdisk_read_sec(a->dev, a->nextsec, a->buf);
			b = a->buf[size-3];
			if((b >> 2) != a->dnum) {
				sprintf(errmsg, 
			"D%d: sec %d: filenum mismatch: %d (expected %d)\n",
					a->dev, a->nextsec, b>>2, a->dnum);
				return -1;
			}
			next = a->buf[size-2] | ((b&3)<<8);
			if((next <= 0 || next >= nsec[a->dev])
			  	  && (a->pos/(size-3) < a->nsec-1)) {
				sprintf(errmsg,
					"D%d: sec %d: invalid link: %d\n",
					a->dev, a->nextsec, next);
				return -1;
			}
			a->nextsec = next;
			b = a->buf[size-1];
			a->bsize = b&0x7f;
			if(a->bsize < size-3)
				a->size -= (size-3-a->bsize);
			a->bpos = 0;
		}
		*buf++ = a->buf[a->bpos++];
		a->pos++; n++; len--;
	}
	return n;
}



static int alloc_sec(int drive)
{
int n;
	for(n=0; n < nsec[drive]; n++) {
		if(SEC_ISFREE(vtoc[drive], n))
			goto gotsec;
	}
	sprintf(errmsg, "D%d: disk full", drive);	
	return False;
gotsec:
	SEC_USE(vtoc[drive], n);
	PUTWORD(vtoc[drive].free, GETWORD(vtoc[drive].free)-1);
	return n;
}


static int dos_write(struct afile *a, byte *buf, int len)
{
byte b;
int size, sec, n;
	if(a->f)
		return fwrite(buf, len, 1, a->f);
	size = secsize[a->dev];
	n = 0;
	while(len > 0) {
		if(!a->cursec) 
		    if(a->nextsec) {
			a->cursec = a->nextsec;
			if(!vdisk_read_sec(a->dev, a->cursec, a->buf))
				return -1;
			b = a->buf[size-3];	
			if((b >> 2) != a->dnum) {
				sprintf(errmsg, 
			"D%d: sec %d: filenum mismatch: %d (expected %d)\n",
					a->dev, a->nextsec, b>>2, a->dnum);
				return -1;
			}
			a->nextsec = ((b&3)<<8) | a->buf[size-2];
		    } else {
			if(!(a->cursec = alloc_sec(a->dev)))
				return -1;
			a->nsec++;
			a->start = a->cursec;
		}	
		if(a->bpos == size - 3) {
			if(!(sec = a->nextsec)) {
				if(!(sec = alloc_sec(a->dev)))
					return -1;
			} 
			a->nsec++;
			a->buf[size-3] = (a->dnum<<2)|HI(sec);
			a->buf[size-2] = LO(sec);
			a->buf[size-1] = size-3;
			if(!vdisk_write_sec(a->dev, a->cursec, a->buf))
				return -1;
			if(!a->nextsec) 
				a->cursec = sec;
			else 
				a->cursec = 0;
			a->bpos = 0;
		}
		a->buf[a->bpos++] = *buf++;
		a->size++;
		n++; len--;
	}
	return n;
}

static int dos_close(struct afile *a)
{
int dev;
int size;
struct adirent dir[16];
	if(a->f) {
		fclose(a->f);
		a->f = NULL;
		return True;
	}
	dev = a->dev;
	a->dev = -1;
	size = secsize[dev];
	if(a->mode == DOS_WRITE && a->bpos) {
		a->buf[size-3] = (a->dnum << 2);
		a->buf[size-2] = 0;
		a->buf[size-1] = 0x80 | a->bpos;
		if(!vdisk_write_sec(dev, a->cursec, a->buf))
			return False;
		if(!vdisk_read_sec(dev, 361+(a->dnum/8), (byte*)dir))
			return False;
		strncpy(dir[a->dnum&7].name, a->name, 11);
		PUTWORD(dir[a->dnum&7].nsec, a->nsec);
		PUTWORD(dir[a->dnum&7].start, a->start);
		dir[a->dnum&7].flag = DIR_INUSE;
		if(!vdisk_write_sec(dev, 361+(a->dnum/8), (byte*)dir))
			return False;
		if(!vdisk_write_sec(dev, 360, (byte*)&vtoc[dev]))
			return False;
	}
	return True;
}


static int dos_format(char *str)
{
int dev, i;
char fname[11];
byte buf[256];
	if((dev = parse_path(fname, str)) < 0) {
		sprintf(errmsg, "%s: can only format Atari disks\n", str);
		return False;
	} else {
		vdisk_get_size(dev, &nsec[dev], &secsize[dev]);
		memset(&vtoc[dev], 0, sizeof(*vtoc));
		vtoc[dev].dirtype = 2;
		PUTWORD(vtoc[dev].maxsec, nsec[dev]-13);
		PUTWORD(vtoc[dev].free, nsec[dev]-13);
		for(i=4; i<360; i++)
			SEC_FREE(vtoc[dev], i);
		for(i=369; i<nsec[dev]; i++)
			SEC_FREE(vtoc[dev], i);
		if(!vdisk_write_sec(dev, 360, (byte*)&vtoc[dev]))
			return False;
		memset(buf, 0, secsize[dev]);
		for(i=361; i<=368; i++) 
			if(!vdisk_write_sec(dev, i, buf))
				return False;
	}
	return True;
}

 
static int dos_dir(char *str)
{
int dev, sec, i, c;
char fname[11];
char cmd[80];
struct adirent dir[16];
char fstr[12];
	if((dev = parse_path(fname, str)) < 0) {
		sprintf(cmd, "ls -la %s", str);
		system(cmd);
	} else {
		if(!vdisk_read_sec(dev, 360, (byte*)&vtoc[dev]))
			return False;
		printf("directory of D%d: (type %02x; %05d/%05d free)\n", dev,
			vtoc[dev].dirtype, GETWORD(vtoc[dev].free),
			GETWORD(vtoc[dev].maxsec));
		c = 0;
		for(sec=361; sec<=368; sec++) {
			if(!vdisk_read_sec(dev, sec, (byte*)dir))
				return False;
			for(i=0; i<8; i++) {
				if(!dir[i].flag)
					continue;
				strncpy(fstr, dir[i].name, 11);
				fstr[11] = '\0';
				printf("%02d: %05d/%02x:  %11s %04d", 
					(sec-361)*8+i,
					WORD(dir[i].start_lo, dir[i].start_hi),
					dir[i].flag, fstr,
					WORD(dir[i].nsec_lo, dir[i].nsec_hi));
				if(dir[i].flag & DIR_DELETED)
					printf(" (D)");
				else if(dir[i].flag & DIR_LOCKED)
					printf(" (L)");
				else if(dir[i].flag & DIR_OPENOUT)
					printf(" (O)");
				else
					printf("\t");
				if(c++&1)
					printf("\n");
				else
					printf("\t");
			}
		}
		if(c&1)
			printf("\n");
	}
	return True;
}



static int dos_trace(char *path)
{
struct afile af;
int n, next;
byte a,b,c;
int size, nb;
	if(!dos_open(&af, path, DOS_READ))
		return False;
	if(af.f) {
		sprintf(errmsg, "trace: must be an Atari DOS file\n");
		return False; 
	}
	n = nb = 0;
	size = secsize[af.dev];
	while(n++ < af.nsec) {
		vdisk_read_sec(af.dev, af.nextsec, af.buf);
		a = af.buf[size-3];
		b = af.buf[size-2];
		c = af.buf[size-1];
		next = b | ((a&3)<<8);
		printf(
	"%05d: %02x %02x %02x: dnum=%02d bytes=%03d eof=%d next=%05d (%06d)\n",
			af.nextsec, a, b, c, a>>2, c&0x7f, c>>7, next, nb);
		if((a>>2) != af.dnum) {
			printf("Error: file number mismatch\n");
			break;
		}
		if((next <= 0 || next >= nsec[af.dev]) && n < af.nsec) {
			printf("Error: invalid sector link\n");
			break;
		}
		nb += c;
		af.nextsec = next;
	}
	return dos_close(&af);
}


static int dos_type(char *path)
{
byte buf[16384];
struct afile af;
int n, i, b;
	if(!dos_open(&af, path, DOS_READ))
		return False;
	while((n = dos_read(&af, buf, sizeof(buf))) > 0) {
		b = 0;
		for(i=0; i<n; i++)
			if(buf[i] == 155) {
				if(i > b)
					fwrite(&buf[b], 1, i-b, stdout);
				printf("\n");
				b = i+1;
			}
		if(i > b)
			fwrite(&buf[b], 1, i-b, stdout);
	}	
	return dos_close(&af) && (n >= 0);
}


static int dos_copy(char *path1, char *path2)
{
char buf[16384];
struct afile af1, af2;
int n;
	if(!dos_open(&af1, path1, DOS_READ)
			|| !dos_open(&af2, path2, DOS_WRITE))
		return False;
	while((n = dos_read(&af1, buf, sizeof(buf))) > 0)
		if(dos_write(&af2, buf, n) < 0)
			return False;
	return dos_close(&af1) && dos_close(&af2) && (n >= 0);
}



int dos_init(int argc, char **argv)
{
static int init = True;
int narg = 0;
char **argp = argv;
char *arg1, *arg2;
	while(argc--) {
		if(!strcmp(*argv, "dir")) {
			if(argc < 1) {
				printf("dir: missing <path>\n"); break;
			}
			if(!dos_dir(*(++argv)))
				return -1;
			argc--;
		} else if(!strcmp(*argv, "copy")) {
			if(argc < 2) {
				printf("copy: missing <src> <dest>\n");
				break;
			}
			arg1 = *(++argv);  arg2 = *(++argv); argc -= 2;
			if(!dos_copy(arg1, arg2))
				return -1;
		} else if(!strcmp(*argv, "type")) {
			if(argc < 1) {
				printf("type: missing <file>\n"); break;
			}
			if(!dos_type(*(++argv)))
				return -1;
			argc--;
		} else if(!strcmp(*argv, "trace")) {
			if(argc < 1) {
				printf("trace: missing <file>\n"); break;
			}
			if(!dos_trace(*(++argv)))
				return -1;
			argc--;
		} else if(!strcmp(*argv, "format")) {
			if(argc < 1) {
				printf("format: missing <drive>\n"); break;
			}
			if(!dos_format(*(++argv)))
				return -1;
			argc--;
		} else {
			*argp++ = *argv; narg++;
		}
		if(!strcmp(*argv++, "-h")) {
			printf("ADOS interface functions:\n");
			printf("\tdir <drive>\t\tlist directory\n");
			printf("\tcopy <src> <dest>\tcopy a file\n");
			printf("\ttype <file>\t\tshow a file\n");
			printf("\ttrace <file>\t\ttrace file links\n");
			printf("\tformat <drive>\t\tinitialize disk\n");
		}
	}
	init = False;
	return narg;
}


void dos_end(void)
{
}

