/*
	FS1541

	volume.c


	Volume, BAM and lock handling.

*/

#include <stdio.h> /* contains decl of sprintf, but will be linked from amiga.lib! */
#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 <devices/timer.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
};

static struct MsgPort *UDStimerport;
struct timerequest *UDStimer;
static int timeropen = 0;

static void CreateDollar(struct VolumeNode *node);

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

LONG InitVolumeSS(void)
{
	LONG error = 0;

	if((UDStimerport = CreateMsgPort()))
	{
		if((UDStimer = CreateIORequest(UDStimerport, sizeof(struct timerequest))))
		{
		    if((timeropen = (!OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)UDStimer,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;
			} else error = ERROR_DEVICE_NOT_MOUNTED;
		} 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);

	if(timeropen) {
		StopUDSTimer();
		CloseDevice((struct IORequest*)UDStimer);
	}

	if(UDStimer)
		DeleteIORequest(UDStimer);

	if(UDStimerport)
		DeleteMsgPort(UDStimerport);
}

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

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();
		wprotected = hardwprot;

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

		if(bam->id != 'A')
			wprotected = TRUE;

		/* Read directory */
		for(i=0, dirsize=0, t=18/*bam->dirt*/, s=1/*bam->dirs*/; /* 1541 always uses 18,1 */
			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);

			dirsize += 8;

			if(!directory[8*i].t)
				break;
		}

		/* Strip trailing type 0x00 entries */
		for(i=dirsize; --i>=0 && directory[i].type == 0x00; dirsize--);

		/* Check if this is a volume we know */
		copy64name(diskname, bam->name, 16);
		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)MKBADDR(&node->name);

				copy64name(&node->name[1], bam->name, 16);
				node->name[0] = strlen(&node->name[1]);

				node->volnode = newvol;
				AddHead((struct List*)&volumelist, (struct Node*)node);

				while(!AttemptLockDosList(LDF_VOLUMES|LDF_WRITE))
					DoPackets();
				rc = AddDosEntry(newvol);
				UnLockDosList(LDF_VOLUMES|LDF_WRITE);
				if(rc)
				{
					curvolumenode = node;
					curdoslist = newvol; 

					CreateDollar(node);

					SendEvent(TRUE);
					MotorOff();
					return;
				}
				FreeVec(newvol);
			}
			FreeVec(node);
		}
	}

	MotorOff();
}

void DoDiskRemove(void)
{
	disk_inserted = FALSE;

	if(curvolumenode)
	{
		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;
	}
}

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

static void CreateDollar(struct VolumeNode *node)
{
	UBYTE buf[32], *p=node->dollarbuf;
	int i,j;
	static char *types[8] = { "DEL", "SEQ", "PRG", "USR", "REL", "???", "???", "???" };

	copy64name(buf, bam->name2, 5);
	if(bam->name2[2] == 0xA0)
	{
		buf[2] = ' ';
		copy64name(&buf[3], &bam->name2[3], 2);
	}

	sprintf(p, "0 \033[7m\"%-16s\" %-5s\033[0m\n", &node->name[1], buf);
	p += strlen(p);

	for(i=0;i<dirsize;i++)
	{
		UBYTE type;

		if((type=directory[i].type) == 0x00)
			continue;

		copy64name(buf, directory[i].name, 16);
		sprintf(p, "%-5ld\"%s\"", (ULONG)(directory[i].lengthh<<8)|directory[i].lengthl, buf);
		p += strlen(p);
		for(j=0;j<=(16-strlen(buf));j++)
			*p++ = ' ';
		sprintf(p, "%s%lc\n", types[type&7], type&0x80 ? (type&0x40 ? '<' : ' ') : '*');
		p += strlen(p);
	}

	sprintf(p, "%ld BLOCKS FREE.\n", (ULONG)(683-UsedBlocks()));
	p += strlen(p);

	node->dollarlen = (ULONG)(p - node->dollarbuf);
}

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

static ULONG bam_secmask[21] = {
	1<<16, 1<<17, 1<<18, 1<<19, 1<<20, 1<<21, 1<<22, 1<<23,
	1<<8,  1<<9,  1<<10, 1<<11, 1<<12, 1<<13, 1<<14, 1<<15,
    1<<0,  1<<1,  1<<2,  1<<3,  1<<4
};

static BYTE SearchFreeBlockOnTrack(UBYTE track, UBYTE froms)
{
	UBYTE i,s,numsecs;
	ULONG mask, plan=bam->tracks[track-1];

	if(plan>>24 > 0)
	{
		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++,s++)
		{
			s = s % numsecs;
			mask = bam_secmask[s];

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

		/* If we reach this point, the BAM is corrupted... */
	}

	return(-1);
}

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

	while(1)
	{
		if(track == 18)
			track += dir;

		if((sector = SearchFreeBlockOnTrack(track, track==fromt ? froms : 0)) >= 0)
			return((track<<8)|sector);

		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 |= bam_secmask[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 OptimizeDirectory(void)
{
	int i, newdirsize=0;

	/* Throw 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));

	/* Clear all Track/Sector fields */
	for(i=0;i<dirsize;i++) {
		directory[i].t = 0;
		directory[i].s = 0;
	}
}

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

void StopUDSTimer(void)
{
	if(!CheckIO((struct IORequest*)UDStimer)) {
		AbortIO((struct IORequest*)UDStimer);
		WaitIO((struct IORequest*)UDStimer);
	}
	SetSignal(0, 1<<(UDStimerport->mp_SigBit));
}

void StartUDSTimer(void)
{
	StopUDSTimer();

	UDStimer->tr_time.tv_secs = 1;
	UDStimer->tr_time.tv_micro = 0;
	UDStimer->tr_node.io_Command = TR_ADDREQUEST;
	SendIO((struct IORequest*)UDStimer);
}

void UpdateDiskStructure(void)
{
	int i,n;
	UBYTE s,s2;

	if(!CheckIO((struct IORequest*)UDStimer))
		return;

	StopUDSTimer();

	/* Strip trailing type 0x00 entries */
	for(i=dirsize; --i>=0 && directory[i].type == 0x00; dirsize--);

	/* Write BAM and directory blocks */
	bam->dirt = 18;
	bam->dirs = 1;
	bam->tracks[18-1] = 0x11fcff07; /* Block 0 is BAM and Block 1 is first directory block */
	i = dirsize>>3;
	s2 = 1;
	for(n=0;n<=i;n++)
	{
		s = s2;

		if(n<i)
		{
			s2 = SearchFreeBlockOnTrack(18,s); /* will always succeed */
			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]);
	}
	putblock_ts(18,0,bam);
	MotorOff();

	CreateDollar(curvolumenode);
}

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

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 = MKBADDR(curdoslist);
		fl->fl_Link = (BPTR)curvolumenode->locklist;
		curvolumenode->locklist = fl;
	}

	return(MKBADDR(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);
}

