/*
	FS1541

	volume.c


	Volume, BAM and lock handling.

*/

#include <string.h>

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <devices/trackdisk.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/alib.h>
#include <proto/utility.h>

#include "main.h"
#include "volume.h"
#include "disk.h"
#include "packet.h"
#include "support.h"

BYTE diskchgintbit = -1;
struct VolumeNode *curvolumenode = NULL;
struct DosList *curdoslist = NULL;

int disk_inserted = FALSE;

struct BAM *bam;
struct DirEntry directory[144];
int dirsize;

UBYTE interleave = 4;

static struct MinList volumelist;

static struct IOExtTD *diskchgint_req;
static void diskchginthandler(void)
{
	Signal(ourtask, 1<<diskchgintbit);
}
static struct Interrupt diskchgint =
{
	{ NULL, NULL, NT_INTERRUPT, 0, "FS1541" },
	NULL,
	(APTR)&diskchginthandler
};

/*-------------------------------------------------------------------------*/

LONG InitVolumeSS(void)
{
	LONG error = 0;

	if((diskchgintbit = AllocSignal(-1))>=0)
	{
		if((diskchgint_req = AllocVec(sizeof(struct IOExtTD), MEMF_PUBLIC)))
		{
			NewList((struct List*)&volumelist);

			CopyMem(diskreq, diskchgint_req, sizeof(struct IOExtTD));
			diskchgint_req->iotd_Req.io_Command = TD_ADDCHANGEINT;
			diskchgint_req->iotd_Req.io_Data = &diskchgint;
			diskchgint_req->iotd_Req.io_Length = sizeof(struct Interrupt);
			diskchgint_req->iotd_Req.io_Flags = 0;
			SendIO((struct IORequest*)diskchgint_req);

			return(0);

		} else error = ERROR_NO_FREE_STORE;
	} else error = ERROR_NO_FREE_STORE;

	QuitVolumeSS();
	return(error);
}

void QuitVolumeSS(void)
{
	if(diskchgint_req)
	{
		diskchgint_req->iotd_Req.io_Command = TD_REMCHANGEINT;
		diskchgint_req->iotd_Req.io_Data = &diskchgint;
		diskchgint_req->iotd_Req.io_Length = sizeof(struct Interrupt);
		diskchgint_req->iotd_Req.io_Flags = 0;
		DoIO((struct IORequest*)diskchgint_req);

		FreeVec(diskchgint_req);
	}

	if(diskchgintbit >= 0)
		FreeSignal(diskchgintbit);
}

/*-------------------------------------------------------------------------*/

void DoDiskInsert(void)
{
	diskreq->iotd_Req.io_Command = TD_CHANGESTATE;
	diskreq->iotd_Req.io_Flags = IOF_QUICK;
	DoIO((struct IORequest*)diskreq);

	if(diskreq->iotd_Req.io_Actual)
	{
		/* Switch off the motor. */
		diskreq->iotd_Req.io_Command = TD_MOTOR;
		diskreq->iotd_Req.io_Flags = 0;
		diskreq->iotd_Req.io_Length = 0;
		DoIO((struct IORequest*)diskreq);
	}
	else
	{
		/* Disk has been inserted. */
		int i,t,s;
		UBYTE diskname[20];
		struct VolumeNode *node;

		disk_inserted = TRUE;

		ResetDisk();

		/* Read Block Allocation Map */
		if(!(bam = (struct BAM*)getblock_ts(18,0)))
			return;

		if(bam->id == 'A')
		{
			/* Read directory */
			for(i=0, dirsize=0, t=bam->dirt, s=bam->dirs;
				i<18;
				t=directory[8*i].t, s=directory[8*i].s, i++)
			{
				struct DataBlock *block;
	
				if(!(block = getblock_ts(t, s)))
					return;
				CopyMem(block, &directory[8*i], 256);
	
				if(directory[8*i].t)
					dirsize += 8;
				else
				{
					/* I thought this would be correct, but s is always $ff here */
					dirsize += ((directory[8*i].s)+1)/32;
					break;
				}
			}
	
			/* Check if this is a volume we know */
			copy64name(diskname, bam->name, 18);
			for(node=(struct VolumeNode*)volumelist.mlh_Head;
				node->node.mln_Succ;
				node=(struct VolumeNode*)(node->node.mln_Succ))
			{
				if(!Stricmp(diskname,&node->name[1]))
				{
					while(!AttemptLockDosList(LDF_VOLUMES|LDF_WRITE))
						DoPackets();
					curvolumenode = node;
					curdoslist = node->volnode;
					curdoslist->dol_Task = ourport;
					curdoslist->dol_misc.dol_volume.dol_LockList = NULL;
					UnLockDosList(LDF_VOLUMES|LDF_WRITE);
					SendEvent(TRUE);
					return;
				}
			}
	
			/* Create a new volume node */
			if((node = AllocVec(sizeof(struct VolumeNode), MEMF_PUBLIC|MEMF_CLEAR)))
			{
				struct DosList *newvol;
	
				if((newvol = AllocVec(sizeof(struct DosList), MEMF_PUBLIC|MEMF_CLEAR)))
				{
					/* Generate DosList entry (Volume) */
					LONG rc;
	
					newvol->dol_Type = DLT_VOLUME;
					newvol->dol_Task = ourport;
					newvol->dol_misc.dol_volume.dol_DiskType = ID_DOS_DISK;
					newvol->dol_Name = (BSTR)CTOB(&node->name);
	
					copy64name(&node->name[1], bam->name, 18);
					node->name[0] = strlen(&node->name[1]);
	
					node->volnode = newvol;
					AddHead((struct List*)&volumelist, (struct Node*)node);
	
					curvolumenode = node;
					curdoslist = newvol; 
	
					while(!AttemptLockDosList(LDF_VOLUMES|LDF_WRITE))
						DoPackets();
					rc = AddDosEntry(newvol);
					UnLockDosList(LDF_VOLUMES|LDF_WRITE);
					if(rc)
					{
						SendEvent(TRUE);
						MotorOff();
						return;
					}
					FreeVec(newvol);
				}
				FreeVec(node);
			}
		}
		else
		{
			/* No DOS disk, will be treated as ID_UNREADABLE_DISK. */
		}
	}

	MotorOff();
}

void DoDiskRemove(void)
{
	if(curvolumenode)
	{
		/* Disk has been removed. */
		disk_inserted = FALSE;

		if(!curvolumenode->locklist)
		{
			/* No locks -> remove completely */
			while(!AttemptLockDosList(LDF_VOLUMES|LDF_WRITE))
				DoPackets();
			RemDosEntry(curdoslist);
			Remove((struct Node*)curvolumenode);
			FreeVec(curvolumenode);
			UnLockDosList(LDF_VOLUMES|LDF_WRITE);
			SendEvent(FALSE);
		}
		else
		{
			/* Do not remove completely, leave disk icon on Workbench. */
			while(!AttemptLockDosList(LDF_VOLUMES|LDF_WRITE))
				DoPackets();
			curdoslist->dol_Task = NULL;
			curdoslist->dol_misc.dol_volume.dol_LockList = (BPTR)curvolumenode->locklist;
			UnLockDosList(LDF_VOLUMES|LDF_WRITE);
			SendEvent(FALSE);
		}

		curvolumenode = NULL;
		curdoslist = NULL;
	}
}

/*-------------------------------------------------------------------------*/

/* This function allocates a block which is located optimally to the one
   specified, i.e. for optimum 1541 loading speed */

UWORD AllocBlock(UBYTE fromt, UBYTE froms)
{
	UBYTE track = fromt;
	BOOL flag = 0;
	BYTE dir = track<18 ? -1 : 1;

	while(1)
	{
		ULONG plan;

		if(track == 18)
			track++;

		if((plan = (bam->tracks[track-1]>>24)) > 0)
		{
			/* Track with free sectors found. */
			UBYTE i,s,numsecs;

			if(track<=17)
				numsecs = 21;
			else if(track<=24)
				numsecs = 19;
			else if(track<=30)
				numsecs = 18;
			else
				numsecs = 17;

			s = froms + interleave;

			for(i=0;i<numsecs;i++)
			{
				if(s>numsecs)
					s -= numsecs;

				if(plan & (1<<(23-s)))
				{
					/* Free sector found. */
					plan &= ~(1<<(23-s));
					plan -= (1<<24);
					bam->tracks[track-1] = plan;
					return((track<<8)|s);
				}

				s++;
			}
		}

		track += dir;

		if(track == 0)
		{
			if(flag)
				return(0);
			flag++;
			dir = 1;
			track = fromt+1;
		}

		if(track == 36)
		{
			if(flag)
				return(0);
			flag++;
			dir = -1;
			track = fromt-1;
		}
	}
}

void FreeBlock(UBYTE t, UBYTE s)
{
	if(t>1 && t<35)
	{
		UBYTE numsecs;

		if(t<=17)
			numsecs = 21;
		else if(t<=24)
			numsecs = 19;
		else if(t<=30)
			numsecs = 18;
		else
			numsecs = 17;

		if(s<numsecs)
		{
			ULONG plan = bam->tracks[t-1];

			plan |= (1<<(23-s));
			plan += (1<<24);
			bam->tracks[t-1] = plan;
		}
	}
}

UWORD UsedBlocks(void)
{
	int i;
	UWORD r=683;

	if(curvolumenode)
	{
		for(i=1;i<=17;i++)
			r -= bam->tracks[i-1]>>24;
		for(i=19;i<=35;i++)
			r -= bam->tracks[i-1]>>24;
	}

	return(r);
}

/*-------------------------------------------------------------------------*/

void UpdateDiskStructure(void)
{
	int i, newdirsize=0;

	/* Optimize directory by throwing out all DEL files */
	for(i=0;i<dirsize;i++)
		if(((directory[i].type) & 0x7) != 0x00)
			memmove(&directory[newdirsize++], &directory[i], sizeof(struct DirEntry));

	dirsize = newdirsize;
	memset(&directory[dirsize], 0, (144-newdirsize)*sizeof(struct DirEntry));

	/* Write BAM and directory blocks */
	bam->dirt = 18;
	bam->dirs = 1;
	if(putblock_ts(18,0,bam))
	{
		int n, i = dirsize>>3;
		UBYTE s=1, s2;

		for(n=0;n<=i;n++)
		{
			s2 = s+interleave;
			if(s2>19)
				s2 -= 19;

			if(n<i)
			{
				directory[n<<3].t = 18;
				directory[n<<3].s = s2;
			}
			else
			{
				directory[n<<3].t = 0;
				directory[n<<3].s = 0xff;
			}

			putblock_ts(18,s,&directory[n<<3]);
			s = s2;
		}
	}
}

/*-------------------------------------------------------------------------*/

BPTR makelock(LONG flkey, LONG axs)
{
	struct FileLock *fl;

	if((fl = AllocVec(sizeof(struct FileLock), MEMF_PUBLIC|MEMF_CLEAR)))
	{
		fl->fl_Key = flkey;
		fl->fl_Access = axs;
		fl->fl_Task = ourport;
		fl->fl_Volume = CTOB(curdoslist);
		fl->fl_Link = (BPTR)curvolumenode->locklist;
		curvolumenode->locklist = fl;
	}

	return(CTOB(fl));
}

void freelock(struct FileLock *fl)
{
	struct VolumeNode *node;

	for(node=(struct VolumeNode*)volumelist.mlh_Head;
		node->node.mln_Succ;
		node=(struct VolumeNode*)(node->node.mln_Succ))
	{
		struct FileLock *cur,*last;

		for(cur=node->locklist, last=(struct FileLock*)&node->locklist;
			cur;
			last=cur, cur=(struct FileLock*)last->fl_Link)
		{
			if(cur == fl)
			{
				last->fl_Link = cur->fl_Link;
				fl->fl_Task = NULL;
				FreeVec(fl);

				if(!node->locklist && !node->volnode->dol_Task)
				{
					/* An unmounted volume does not have any locks open
					   any more, so we can safely remove the volume node. */

					while(!AttemptLockDosList(LDF_VOLUMES|LDF_WRITE))
						DoPackets();
					RemDosEntry((struct DosList*)node->volnode);
					Remove((struct Node*)node);
					FreeVec(node);
					UnLockDosList(LDF_VOLUMES|LDF_WRITE);
					SendEvent(FALSE);
				}

				return;
			}
		}
	}
}

/* Is an object still lockable with the respective access mode? */
BOOL lockable(UBYTE t, UBYTE s, LONG mode)
{
	struct FileLock *fl;
	LONG searchkey = t<<8|s;

	for(fl=curvolumenode->locklist; fl; fl=(struct FileLock*)fl->fl_Link)
	{
		if(fl->fl_Key == searchkey)
		{
			if(mode == EXCLUSIVE_LOCK)
				return(FALSE);
			else
			{
				if(fl->fl_Access == EXCLUSIVE_LOCK)
					return(FALSE);
			}
		}
	}

	return(TRUE);
}

