#include "DFC5.h"

extern struct MsgPort *TaskPort[6], *MainPort, *TPort;
extern struct IMsg IMsg[6];

static char *TaskBuffer[5];			/* local buffers (for doing I/O) */
static char *Buffer[80];				/* pointers to the 80 buffer chunks */
static long CheckSum[80];				/* checksums of the internal RAM buffer */
struct IOStdReq *IOStdReq[4];			/* our I/O request blocks */



/*
 *	  This routine attempts to allocate 80 buffers. Return 0 on failure, otherwise
 *   the denominator of the fraction of a disk allocated (1=entire disk, 2=half
 *   disk, etc. upto 4). Note than when 3 is returned, actually 80/3+1 tracks
 *   were allocated.
 */
int AllocBuffers(void) {

	register int i;

	for (i=0; i<80; i++)
		if ((Buffer[i]=AllocMem((long)TRACKSIZE, MEMF_PUBLIC))==NULL) {
			if (i>=40) {
				FreeBuffers(40);
				return(2);
			}
			else if (i>=27) {
				FreeBuffers(27);
				return(3);
			}
			else if (i>=20) {
				FreeBuffers(20);
				return(4);
			}
			else {
				FreeBuffers(0);
				return(0);
			}
		}
	return(1);
}

/*
 *	  And this routine lets them go from a defined index.
 */
void FreeBuffers(int i) {

	while(i<80) {
		if (Buffer[i]) {
			FreeMem(Buffer[i], TRACKSIZE);
			Buffer[i] = NULL;
		}
		i++;
	}
}

/*
 *	  We attempt to create an I/O request.	 We allocate memory for it and
 *	  for its port.
 */
struct IOStdReq *CreateIOStdReq(void) {

	register struct IOStdReq *IOStdReq;

	if (!(IOStdReq = AllocMem(sizeof(struct IOStdReq), MEMF_CLEAR | MEMF_PUBLIC))) return (NULL);
	IOStdReq->io_Message.mn_Node.ln_Type = NT_MESSAGE;
	IOStdReq->io_Message.mn_Length = sizeof(struct IOStdReq)-sizeof(struct Message);
	if ((IOStdReq->io_Message.mn_ReplyPort = CreatePort(NULL,0))==NULL) {
		FreeMem(IOStdReq, sizeof(struct IOStdReq));
		return(NULL);
	}
	return(IOStdReq);
}

/*
 *	  This routine frees an I/O request.
 */
void DeleteIOStdReq(struct IOStdReq *IOStdReq) {

	DeletePort(IOStdReq->io_Message.mn_ReplyPort);
	FreeMem(IOStdReq, sizeof(struct IOStdReq));
}

/*
 *	  This routine recalculates the checksum for a block, and updates it in
 *	  word 5.  The sum of all the words in a block must be 0.
 */
void ReCheck(long *w) {

	register int i;
	register long CheckSum;

	CheckSum = 0;
	for (i=0; i<128; i++)
		CheckSum += w[i];
	w[5] -= CheckSum;
}

/*
 *	  This routine checksums a track of the internal RAM buffer.
 */

long Check(long *Track) {

	register int i;
	register long CheckSum = 0;

	for (i=0; i<(TRACKSIZE/sizeof(long)); i++) CheckSum += Track[i];
	return(CheckSum);
}


/*
 *	  We simply DateStamp the creation date, modification date, and
 *	  rechecksum the block. A random factor is added because of multiple
 *	  async accesses.
 */
void UpdateRootBlock(char *b) {

	static unsigned char Rand;

	Rand += 7;
	DateStamp((void *)(b + 420));
	DateStamp((void *)(b + 484));
	((struct DateStamp *)(b + 420))->ds_Tick = (((struct DateStamp *)(b + 420))->ds_Tick+Rand)%3000;
	((struct DateStamp *)(b + 484))->ds_Tick = (((struct DateStamp *)(b + 484))->ds_Tick+Rand)%3000;
	ReCheck((void *)b);
}

/*
 *	  This routine creates data for the format option.	 Note the clever
 *	  way the data is built up; this routine should build a disk exactly
 *	  the same way the standard AmigaDOS format does.	If we are on track
 *	  40, some additional work must be done to create a root block and
 *	  bitmap, but it's not too bad. I don't know if this is the right
 *   way to do under FFS, but since it works . . .
 */
void MakeFormatData(int Track, char *b, int UseFFS) {

	register long *p;
	register long cs;
	register long i;
	register unsigned char *q;

	p = (long *)b;
	cs = (((ULONG)('D') << 24) | ((ULONG)('O') << 16) | ((ULONG)('S') << 8) | (UseFFS ? '\1' : '\0')) + (((long)Track & 48) << 16);
	for (i=0; i<TRACKSIZE/4; i++)
		*p++ = cs + (i & 3327);
	if (Track != 40)
		return;
	p = (long *)b;
	for (i=0; i<256; i++)
		*p++ = 0;
	p = (long *)b;
	p[0] = 2;
	p[3] = 0x48;
	p[78] = 1;
	p[79] = 0x371;
	q = (unsigned char *)(p + 108);
	strcpy(q+1, "Empty");
	*q = strlen(q+1);
	p[127] = 1;
	p += 128;
	for (i=1; i<55; i++)
		p[i] = 0xffffffff;
	p[0] = 0xc000c037;
	p[28] = 0xffff3fff;
	p[55] = 0x3fffffff;
}

/*
 * This routine moves the data to write in the suitable buffer. Track is
 * useful only in Unit==4---in this case we write directly in the RAM buffer.
 */

void CopyBuffer(void *p, int Unit, int Track) {
	if (Unit<4) fcpy(p, TaskBuffer[Unit]);
	else if (Track>=0) {
		fcpy(p, Buffer[Track]);
		CheckSum[Track] = Check((long *)Buffer[Track]);
	}
}



/*
 *	  Now we try to allocate a disk.	 First, we attempt to allocate an
 *	  I/O request, and then we actually open the device.	If either of
 *	  these fail, we return 0.	 This can occur if a drive is opened that
 *	  doesn't exist.
 */
int OpenDisk(int Unit) {

	register struct IOStdReq **p = &IOStdReq[Unit];

	if (*p) return(1);

	if ((*p = CreateIOStdReq()) && OpenDevice(TD_NAME, Unit, (void *)*p, 0) == 0) return(1);
	else {
		if (*p) {
			DeleteIOStdReq(*p);
			*p = NULL;
		}
		return(0);
	}
}

/*
 *	  Here we release a disk.	We check that we have it first!	Then, we
 *	  close the device, kill the I/O request, and exit.
 */
void CloseDisk(int Unit) {

	register struct IOStdReq **p = &IOStdReq[Unit];

	if (*p) {
		CloseDevice((void *)*p);
		DeleteIOStdReq(*p);
		*p = NULL;
	}
}

/*
 * Here we do our main track I/O with IOStdReq. The routine returns a parsed error field.
 * Notice that the I/O is sync (much simpler), but the overall design of the program
 * makes it automagically async-like.
 */

int DoTDIO(int Unit, int Track, void *Buffer, int Command, int Size) {
	if (Unit>3) return(0);
	IOStdReq[Unit]->io_Length = Size;
	IOStdReq[Unit]->io_Data = Buffer;
	IOStdReq[Unit]->io_Command = Command;
	IOStdReq[Unit]->io_Offset = Track * (long)TRACKSIZE;
	DoIO((void *)IOStdReq[Unit]);
	if (IOStdReq[Unit]->io_Error == TDERR_DiskChanged) return(NO_DISK);
	else if (IOStdReq[Unit]->io_Error == TDERR_WriteProt) return(WRITE_PROTECTED);
	else if (IOStdReq[Unit]->io_Error) return(GENERIC_ERROR);
	else return(0);
}


/*
 * This code is executed by (max) *FIVE* tasks at the same time. It manages a disk
 * (which is deduced from the UserData of the task structure) using a message passing
 * interface. If the unit number is 4, it manages the internal 80 tracks buffer.
 * This code is executed at priority 1.
 */

__saveds void DiskTask(void) {

	void *VerifyBuffer;
	int AllocFlag, Passes;

	register struct IMsg *Message;
	register int Unit = (int)FindTask(NULL)->tc_UserData;
	register int n;

	AllocFlag = ((TaskPort[Unit] = CreatePort(NULL, 0)) != NULL);

	if (Unit<4) {
		AllocFlag &= OpenDisk(Unit);
		AllocFlag &= ((VerifyBuffer = AllocMem(TRACKSIZE, MEMF_CHIP))!=NULL);
		AllocFlag &= ((TaskBuffer[Unit] = AllocMem(TRACKSIZE, MEMF_CHIP))!=NULL);
	}
	else AllocFlag &= ((Passes = IMsg[Unit].im_n = AllocBuffers()) != 0);

	IMsg[Unit].im_RC = AllocFlag;

	if (!AllocFlag) goto GameOver;

	PutMsg(TPort, (struct Message *)&IMsg[Unit]);
	SetTaskPri(FindTask(NULL), 1);

	FOREVER {
		WaitPort(TaskPort[Unit]);

		while(Message = (void *)GetMsg(TaskPort[Unit])) {

			Message->im_RC = 0;
			n = Message->im_n;
			if (Unit == 4) n = n % (80/Passes+(Passes==3));

			switch(Message->im_Action) {

				case INIT:	if (n<0 && Unit<4) {
									if (!DoTDIO(Unit, 0, VerifyBuffer, CMD_READ, 512) && (*((unsigned long *)VerifyBuffer) != (((ULONG)('D') << 24) | ((ULONG)('O') << 16) | ((ULONG)('S') << 8) | '\0') && *((unsigned long *)VerifyBuffer) != (((ULONG)('D') << 24) | ((ULONG)('O') << 16) | ((ULONG)('S') << 8) | '\1')))
										Message->im_RC = NOT_DOS;
									n = 79;
								}
								DoTDIO(Unit, n, 0, TD_SEEK, TRACKSIZE);
								break;

				case STOP_MOTOR:	DoTDIO(Unit, 0, 0, TD_MOTOR, 0);
										break;

				case READ_TRACK : if (Unit<4) {
											if ((Message->im_RC = DoTDIO(Unit, n, TaskBuffer[Unit], CMD_READ, TRACKSIZE)) == GENERIC_ERROR)
												Message->im_RC = READ_ERROR;
											Message->im_p = TaskBuffer[Unit];
										}
										else {
											if (CheckSum[n] != Check((long *)Buffer[n])) Message->im_RC = READ_ERROR;
											Message->im_p = Buffer[n];
										}
										break;

				case WRITE_TRACK :
				case WRITE_AND_VERIFY_TRACK:
										if (Unit<4) {
											if ((Message->im_RC = DoTDIO(Unit, n, TaskBuffer[Unit], TD_FORMAT, TRACKSIZE)) == GENERIC_ERROR)
												Message->im_RC = WRITE_ERROR;
											if (Message->im_Action == WRITE_TRACK || Message->im_RC) break;
											DoTDIO(Unit, n, VerifyBuffer, CMD_READ, TRACKSIZE);
											Message->im_RC = (fcmp(TaskBuffer[Unit], VerifyBuffer) ? VERIFY_ERROR : 0);
										}
										break;

				case EXIT:	goto GameOver;
			}

			ReplyMsg((void *)Message);
		}
	}

GameOver:
	SetTaskPri(FindTask(NULL), 127);
	if (TaskPort[Unit]) {
		DeletePort(TaskPort[Unit]);
		TaskPort[Unit] = NULL;
	}

	if (Unit<4) {
		if (VerifyBuffer) FreeMem(VerifyBuffer, TRACKSIZE);
		if (TaskBuffer[Unit]) FreeMem(TaskBuffer[Unit], TRACKSIZE);
		TaskBuffer[Unit] = NULL;
		CloseDisk(Unit);
	}
	else FreeBuffers(0);
	PutMsg(TPort, (struct Message *)&IMsg[Unit]);
	return;
}
