#include "disk.h"
#include "rdb.h"
#include "filesystem.h"

struct inblock {
	daddr_t b[MAXBSIZE/sizeof(daddr_t)];
};

static struct fs super;
static struct dinode dino;
static UBYTE *diskbuf, *diskbufend;
static ULONG dbufsize;
static struct inblock inblock0;
static daddr_t ibno;

BOOL openFilesystem(STRPTR driver, LONG unit, STRPTR volume)
{
	UBYTE *sbuf;
	struct Partition pinfo;
	int ok = 0;

	if (openDisk(driver, unit, 0)) {
		if (getPartitionRDB(volume, &pinfo)) {
			setMapping(DEV_BSIZE, pinfo.first_block);
			sbuf = getBuffer(SBSIZE);
			if (sbuf) {
				ok = readDisk(SBLOCK, sbuf, SBSIZE);
				super = *(struct fs *)sbuf;
				if (super.fs_magic != FS_MAGIC)
					ok = FALSE;
				freeBuffer(sbuf);
				if (ok) {
					dbufsize = super.fs_bsize;
					diskbuf = getBuffer(dbufsize);
					if (diskbuf) {
						diskbufend = diskbuf + dbufsize;
						return TRUE;
					}
				}
			}
		}
		closeDisk();
	}
	return FALSE;
}

VOID closeFilesystem(VOID)
{
	freeBuffer(diskbuf);
	closeDisk();
}

BOOL readInode(ULONG i)
{
	daddr_t ia;
	long ib;

	ia = ino_to_fsba(&super, i);
	ib = ino_to_fsbo(&super, i);
	if (!readDisk(fsbtodb(&super, ia), diskbuf, dbufsize))
		return FALSE;
	dino = ((struct dinode *)diskbuf)[ib];
	return TRUE;
}

ULONG lookupName(STRPTR name, LONG typ)
{
	struct direct *di;
	daddr_t ia;
	int i, n;

	n = lblkno(&super, dino.di_size.lo-1);
	if (n > NDADDR) n = NDADDR;

	for (i=0; i<=n; ++i) {
		ia = dino.di_db[i];
		if (ia != 0) {
			if (!readDisk(fsbtodb(&super, ia), diskbuf, dbufsize))
				break;
			di = (struct direct *)diskbuf;
			while (di->d_reclen != 0) {
				if (typ == di->d_type && !strcmp(name, di->d_name))
					return di->d_ino;
				di = (struct direct *)(((u_char *)di) + di->d_reclen);
				if ((u_char *)di >= diskbufend)
					break;
			}
		}
	}
	return 0;
}

ULONG readFileBlock(ULONG offset, UBYTE **locp)
{
	daddr_t ia;
	ULONG bytes;
	ULONG blockno;

	blockno = offset / super.fs_bsize;
	offset -= blockno * super.fs_bsize;

	if (dino.di_size.hi > 0)
		bytes = super.fs_bsize;
	else {
		if (blockno * super.fs_bsize >= dino.di_size.lo)
			return 0;
		bytes = dino.di_size.lo - blockno * super.fs_bsize;
		if (bytes > super.fs_bsize)
			bytes = super.fs_bsize;
	}

	if (bytes <= offset)
		return 0;

	bytes -= offset;

	if (blockno < NDADDR)
		ia = dino.di_db[blockno];
	else {
		blockno -= NDADDR;
		/* won't handle more than first indirection */
		if (blockno >= super.fs_bsize/sizeof(daddr_t))
			return 0;
		ia = dino.di_ib[0];
		if (ia == 0)
			return 0;
		if (ia != ibno) {
			if (!readDisk(fsbtodb(&super,ia), diskbuf, dbufsize))
				return 0;
			inblock0 = *(struct inblock *)diskbuf;
			ibno = ia;
		}
		ia = inblock0.b[blockno];
	}
	if (ia == 0)
		return 0;

	if (!readDisk(fsbtodb(&super,ia), diskbuf, dbufsize))
		return 0;

	*locp = &diskbuf[offset];

	return bytes;
}
