/* vdisk.c
 *    virtual Atari disk management 
 *    (c) 1994   Frank Barrus
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#define VDISK_GLOBAL
#include "memtype.h"
#include "vdisk.h"
#include "sio2pc.h"
#include "dcm.h"
#include "output.h"
#include "psio.h"
#include "sio.h"


extern char errmsg[];


char *vdisk_info(int drive)
{
static char buf[80];
	switch(vdisk[drive].type) {
	case NONE:
		sprintf(buf, "unmounted");
		break;
	case RAMDISK:
		sprintf(buf, "ramdisk: %dx%d",
			vdisk[drive].nsec, vdisk[drive].secsize);
		break;
	case SIO2PC:
		sprintf(buf, "sio2pc: %s: %dx%d", vdisk[drive].fname,
			vdisk[drive].nsec, vdisk[drive].secsize);
		break;
	case ESIO2PC:
		sprintf(buf, "esio2pc: %s: %dx%d (%d bad)", vdisk[drive].fname,
			vdisk[drive].nsec, vdisk[drive].secsize,
			vdisk[drive].nerr);
		break;
	case XFD:
		sprintf(buf, "xfd: %s: %dx%d", vdisk[drive].fname,
			vdisk[drive].nsec, vdisk[drive].secsize);
		break;
	case DCM:
		sprintf(buf, "dcm: %s: %dx%d", vdisk[drive].fname,
			vdisk[drive].nsec, vdisk[drive].secsize);
		break;
	case PSIO:
		sprintf(buf, "psio: drive %c: %dx%d (%02X/%02X timeout=%d)",
			vdisk[drive].dev, vdisk[drive].nsec,
			vdisk[drive].secsize, vdisk[drive].cstat,
			vdisk[drive].hstat, vdisk[drive].timeout/1000);
		break;
	case RSIO:
		sprintf(buf, "rsio: drive %c: %dx%d (%02X/%02X timeout=%d)",
			vdisk[drive].dev, vdisk[drive].nsec,
			vdisk[drive].secsize, vdisk[drive].cstat,
			vdisk[drive].hstat, vdisk[drive].timeout/1000);
		break;
	default:
		sprintf(buf, "unknown type");
	}
	return buf;
}


int vdisk_mounted(int drive)
{
	return vdisk[drive].type != NONE;
}

int vdisk_get_secsize(int drive, int sec)
{
	if(drive < 0 || drive > NVDISK) {
		sprintf(errmsg, "%d: bad disk unit", drive);
		return False;
	}
	if(vdisk[drive].type == NONE) {
		sprintf(errmsg, "D%d: not mounted", drive);
		return False;
	}
	if(sec < 1 || sec > vdisk[drive].nsec) {
		sprintf(errmsg, "D%d: sector %d out of range (1-%d)",
			drive, sec, vdisk[drive].nsec);
		return False;
	}
	if(sec < 4)
		return 128;
	else
		return vdisk[drive].secsize;
}


int vdisk_get_size(int drive, int *nsec, int *secsize)
{
	*nsec = vdisk[drive].nsec;
	*secsize = vdisk[drive].secsize;
	return True;
}

int vdisk_status(int drive, byte *buf)
{
	if(vdisk[drive].type == NONE)
		return 0;
	buf[0] = vdisk[drive].cstat;
	buf[1] = vdisk[drive].hstat;
	buf[2] = vdisk[drive].timeout/1000;
	buf[3] = 0xff;
	return 4;
}


static void add_bad_sec(int drive, int sec)
{
emaplist **ep;
errormap *emap;
	ep = &vdisk[drive].elist;
	while(*ep) {
		if(GETWORD((*ep)->emap.sec) == sec)
			break;
		ep = &(*ep)->next;
	}
	if(!*ep) 
		*ep = (emaplist*)malloc(sizeof(emaplist));
	emap = &(*ep)->emap;
	memset(emap, 0, sizeof(*emap));
	PUTWORD(emap->sec, sec);
	emap->cstat = vdisk[drive].cstat;
	emap->hstat = vdisk[drive].hstat;
}


static void del_bad_sec(int drive, int sec)
{
emaplist *tp;
emaplist **ep;
	ep = &vdisk[drive].elist;
	while(*ep) {
		if(GETWORD((*ep)->emap.sec) == sec) {
			tp = *ep;
			*ep = tp->next;
			free(tp);
			break;
		}
		ep = &(*ep)->next;
	}
}


static int can_read(int drive, int sec)
{
	if(!vdisk_get_secsize(drive, sec))
		return False;
	return True;
}

static int can_write(int drive, int sec)
{
	if(!can_read(drive, sec))
		return False;
	if(!vdisk[drive].writeable) {
		sprintf(errmsg, "D%d: disk is write protected", drive);
		return False;
	}
	return True;
}


int vdisk_read_sec(int drive, int sec, byte *buf)
{
	if(!can_read(drive, sec))
		return False;
	switch(vdisk[drive].type) {
	case NONE: sprintf(errmsg, "not mounted"); return False;
	case RAMDISK:
		memcpy(buf, &vdisk[drive].ram[(sec-1)*vdisk[drive].secsize],
			vdisk[drive].secsize);
		break;
	case SIO2PC: case ESIO2PC:
		fseek(vdisk[drive].file, 16+(sec-1)*vdisk[drive].secsize,
			SEEK_SET);
		fread(buf, vdisk[drive].secsize, 1, vdisk[drive].file);
		break;
	case XFD:
		fseek(vdisk[drive].file,
			(sec-1)*vdisk[drive].secsize, SEEK_SET);
		fread(buf, vdisk[drive].secsize, 1, vdisk[drive].file);
		break;
	case PSIO:
		psio_read_sec(drive, sec, buf);
		break;
	default:
		sprintf(errmsg, "unsupported type"); 
		return False;
	}
	return vdisk_get_secsize(drive, sec);
}


int vdisk_write_sec(int drive, int sec, byte *buf)
{
	if(!can_write(drive, sec))
		return False;
	switch(vdisk[drive].type) {
	case NONE: sprintf(errmsg, "not mounted"); return False;
	case RAMDISK:
		memcpy(&vdisk[drive].ram[(sec-1)*vdisk[drive].secsize], buf,
			vdisk[drive].secsize);
		break;
	case SIO2PC: case ESIO2PC:
		fseek(vdisk[drive].file, 16+(sec-1)*vdisk[drive].secsize,
			SEEK_SET);
		fwrite(buf, vdisk[drive].secsize, 1, vdisk[drive].file);
		fflush(vdisk[drive].file);
		break;
	case XFD:
		fseek(vdisk[drive].file,
			(sec-1)*vdisk[drive].secsize, SEEK_SET);
		fwrite(buf, vdisk[drive].secsize, 1, vdisk[drive].file);
		fflush(vdisk[drive].file);
		break;
	case PSIO:
		psio_write_sec(drive, sec, buf);
		break;
	default:
		sprintf(errmsg, "unsupported type"); 
		return False;
	}
	del_bad_sec(drive, sec);
	return vdisk_get_secsize(drive, sec);
}


int vdisk_write_bad_sec(int drive, int sec, byte *buf)
{
	if(!vdisk_write_sec(drive, sec, buf))
		return False;
	add_bad_sec(drive, sec);
	return vdisk_get_secsize(drive, sec);
}


int vdisk_mount_file(int drive, char *fname)
{
char *ext;
char *base;
struct sio2pc_image_hdr hdr;
int i;
	if(drive < 0 || drive > NVDISK) {
		sprintf(errmsg, "%d: invalid drive number", drive);
		return False;
	}
	vdisk_unmount(drive);
	strcpy(vdisk[drive].fname, fname);
	if(!(base = strrchr(fname, '/')))
		base = fname;
	ext = strrchr(base, '.');
	vdisk[drive].type = NONE;
	vdisk[drive].nsec = 720;
	vdisk[drive].secsize = 128;
	vdisk[drive].nerr = -1;
	vdisk[drive].elist = NULL;
	if(ext) {
		if(!strcasecmp(ext,".xfd") || !strcasecmp(ext,".sd")) {
			vdisk[drive].type = XFD;
		} else if(!strcasecmp(ext, ".dd")) {
			vdisk[drive].type = XFD;
			vdisk[drive].secsize = 256;
		} else if(!strcasecmp(ext,".dcm")) {
			vdisk[drive].type = DCM;
		}
	}
	vdisk[drive].writeable = True;
	if(!(vdisk[drive].file = fopen(fname, "r+"))) {
		vdisk[drive].writeable = False;
		if(!(vdisk[drive].file = fopen(fname, "r"))) {
			sprintf(errmsg, "%s: %s", fname, strerror(errno));
			return False;
		}
	}	
	if(vdisk[drive].type == NONE) {
		fread(&hdr, sizeof(hdr), 1, vdisk[drive].file);
		if(GETWORD(hdr.code) != SIO2PC_MAGIC) {
			sprintf(errmsg, "%s: unknown file type", fname);
			fclose(vdisk[drive].file);
			return False;
		}
		vdisk[drive].secsize = GETWORD(hdr.secsize);
		vdisk[drive].nsec = GETLONG(hdr.size)/(vdisk[drive].secsize/16);
		vdisk[drive].nerr = GETWORD(hdr.numerr)-1;
		if((i=vdisk[drive].nerr) >= 0) {
			emaplist **ep;
			vdisk[drive].type = ESIO2PC;
			ep = &vdisk[drive].elist;
			fseek(vdisk[drive].file,
				16+vdisk[drive].secsize*vdisk[drive].nsec,
				SEEK_SET);
			while(i--) {
				*ep = (emaplist*)malloc(
					sizeof(emaplist));
				fread(&(*ep)->emap, sizeof(errormap),
					1, vdisk[drive].file);
				ep = &(*ep)->next;
			}
			*ep = NULL;
		} else
			vdisk[drive].type = SIO2PC; 
	}
	vdisk[drive].cstat = CSTAT_READY;
	printf("D%d: mounted %s\n", drive, vdisk_info(drive));
	return True;
}


int vdisk_mount_ram(int drive, int nsec, int secsize)
{
	if(drive < 0 || drive > NVDISK) {
		sprintf(errmsg, "%d: invalid drive number", drive);
		return False;
	}
	vdisk_unmount(drive);
	if(nsec >= 64*K)
		nsec = 64*K-1;
	if(!(vdisk[drive].ram = (byte*)malloc(secsize*nsec))) {
		sprintf(errmsg, "D%d: not enough memory", drive);
		return False;
	}
	vdisk[drive].type = RAMDISK;
	vdisk[drive].writeable = True;
	vdisk[drive].secsize = secsize;
	vdisk[drive].nsec = nsec;
	vdisk[drive].elist = NULL;
	printf("D%d: mounted %s\n", drive, vdisk_info(drive));
	return True;
}

int vdisk_load_ram(int drive, char *fname)
{
	sprintf(errmsg, "vdisk_load_ram not implemented");
	return False;
}

int vdisk_save_ram(int drive, char *fname)
{
	sprintf(errmsg, "vdisk_save_ram not implemented");
	return False;
}


int vdisk_unmount(int drive)
{
emaplist *ep;
struct sio2pc_image_hdr hdr;
int i;
	if(drive < 0 || drive > NVDISK) {
		sprintf(errmsg, "%d: invalid drive number", drive);
		return False;
	}
	switch(vdisk[drive].type) {
	case ESIO2PC:
		ep = vdisk[drive].elist; 
		fseek(vdisk[drive].file,
			16+vdisk[drive].secsize*vdisk[drive].nsec, SEEK_SET);
		i = 0;
		while(ep) {
			fwrite(&ep->emap, sizeof(errormap), 1,
					vdisk[drive].file);
			i++;
			ep = ep->next;
		}
		rewind(vdisk[drive].file);
		fread(&hdr, sizeof(hdr), 1, vdisk[drive].file);
		PUTWORD(hdr.numerr, i+1);
		rewind(vdisk[drive].file);
		fwrite(&hdr, sizeof(hdr), 1, vdisk[drive].file);
	case SIO2PC:
	case XFD:
		fclose(vdisk[drive].file);
		break;
	case RAMDISK:
		free(vdisk[drive].ram);
		break;
	case NONE:
	default:
	}
	if(vdisk[drive].type != NONE)
		printf("D%d: unmounted\n", drive);
	vdisk[drive].type = NONE;
	vdisk[drive].nsec = 0;
	vdisk[drive].secsize = 0;
	return True;
}


int vdisk_init(int argc, char **argv)
{
static int init = True;
int narg = 0;
char **argp = argv;
int drive;
int n, nsec, secsize;
byte buf[512];
	if(init) {
		for(drive=0; drive<=NVDISK; drive++) {
			vdisk[drive].type = NONE;
			vdisk_unmount(drive);
		}
	}
	while(argc--) {
		drive = argv[0][strlen(*argv)-1]-'0';
		if(drive < 0 || drive > 9)
			drive = -1;
		if(!strncmp(*argv, "-m", 2) && argc) {
			if(!vdisk_mount_file(drive, *(++argv)))
				return -1;
			argc--;
		} else if(!strncmp(*argv, "-rsd", 4) && argc) {
			if(!vdisk_mount_ram(drive, atoi(*(++argv)), 128))
				return -1;
			argc--;
		} else if(!strncmp(*argv, "-rdd", 4) && argc) {
			if(!vdisk_mount_ram(drive, atoi(*(++argv)), 256))
				return -1;
			argc--;
		} else if(!strncmp(*argv, "-r", 2) && argc) {
			n = atoi(*(++argv))*K; argc--;
			secsize = 128;
			nsec = n/secsize;
			if(nsec >= 64*K) {
				secsize = 256;
				nsec = n/secsize;
			}	
			if(!vdisk_mount_ram(drive, nsec, secsize))
				return -1;
		} else if(!strncmp(*argv, "-l", 2) && argc) {
			if(!vdisk_load_ram(drive, *(++argv)))
				return -1;
			argc--;
		} else if(!strncmp(*argv, "-s", 2) && argc) {
			if(!vdisk_save_ram(drive, *(++argv)))
				return -1;
			 argc--;
		} else if(!strncmp(*argv, "-u", 2)) {
			if(!vdisk_unmount(drive))
				return -1;
		} else if(!strncmp(*argv, "-p", 2) && argc) {
			if(!psio_mount(drive, (**(++argv))))
				return -1;
			argc--;
		} else if(!strncmp(*argv, "-d", 2) && argc) {
			n = vdisk_read_sec(drive, atoi(*(++argv)), buf);
			if(!n)
				return -1;
			dump_data(buf, n);
			argc--;
		} else if(!strcmp(*argv, "-mount")) {
			for(n=1; n<=NVDISK; n++)
				printf("D%d: %s\n", n, vdisk_info(n));
		} else {
			*argp++ = *argv; narg++;
		}
		if(!strcmp(*argv++, "-h")) {
			printf("Virtual Disk options:\n");
			printf("\t-m<drv> <file>\t\tmount disk from file\n");
			printf("\t-r<drv> <kbytes>\tmount ramdisk\n");
			printf("\t-rsd<drv> <nsec>\tmount SD ramdisk\n");
			printf("\t-rdd<drv> <nsec>\tmount DD ramdisk\n");
			printf("\t-l<drv> <file>\t\tload ramdisk from file\n");
			printf("\t-s<drv> <file>\t\tsave ramdisk to file\n");
			printf("\t-p<drv> <id>\t\tmount PSIOX drive\n");
			printf("\t-d<drv> <sec>\t\tdump specified sector\n"); 
			printf("\t-u<drv>\t\t\tunmount disk\n");
			printf("\t-mount\t\t\tshow disks currently mounted\n");
		}
	}
	init = False;
	return narg;
}


void vdisk_end(void)
{
int drive;
	for(drive=0; drive<=NVDISK; drive++) 
		vdisk_unmount(drive);
}

