/* fs.c */

#include "chunks.h"
#include "debug.h"
#include "swis.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

/*********************************************************************************/
/* */
/* Support routines
 *  */
/* */
/*********************************************************************************/

static const char *xFiles_Leaf(const char *pName)
{
	const char *lastDot;

	if (lastDot = strrchr(pName, '.'), lastDot)
		return lastDot + 1;

	return pName;
}

static xFiles_fileInfo *xFiles_FindOpenFile(xFiles_info * pInfo, unsigned cnkNum)
{
	xFiles_fileInfo *pFile;

	for (pFile = (xFiles_fileInfo *) pInfo->openList.head; pFile; pFile = (xFiles_fileInfo *) pFile->li.next)
	{
		if (pFile->cnkNum == cnkNum)
			return pFile;
	}

	return NULL;
}

static _kernel_oserror *xFiles_FindObject(xFiles_info * pInfo, const char *fileName, xFiles_dirHash
		* pDirHash, xFiles_dirEntry * pDirEnt, unsigned *pParent)
{
	_kernel_oserror *err;
	unsigned hashOffset;

	if (err = xFiles_parsePath(pInfo, fileName, pParent, &hashOffset, NULL, pDirHash, pDirEnt, FALSE)
			,err)
		return err;

	if (hashOffset == 0)
		return &xFiles_NotFound;

	return NULL;
}

static _kernel_oserror *xFiles_FindDir(xFiles_info * pInfo, const char *fileName, xFiles_dirHash * pDirHash,
		xFiles_dirEntry * pDirEnt)
{
	_kernel_oserror *err;

	if (err = xFiles_FindObject(pInfo, fileName, pDirHash, pDirEnt, NULL), err)
		return err;

	if ((pDirEnt->attr & xFiles_isDir) == 0)
		return &xFiles_NotADir;

	return NULL;
}

static _kernel_oserror *xFiles_Rename(xFiles_info * pInfo, const char *from, const char *to)
{
	_kernel_oserror *err;

	unsigned toDir, toHashOffset;
	unsigned fromDir, fromHashOffset, fromEntryOffset;
	xFiles_dirEntry fromDirEnt;
	xFiles_dirHash fromDirHash;

	/*TRACE("Rename %s %s\n", from, to); */

	if (err = xFiles_parsePath(pInfo, to, &toDir, &toHashOffset, NULL, NULL, NULL, TRUE), err)
		return err;

	if (err = xFiles_parsePath(pInfo, from, &fromDir, &fromHashOffset, &fromEntryOffset, &fromDirHash,
					&fromDirEnt, FALSE), err)
		return err;

	/* Create the new entry */

	if (err = xFiles_createDirEntry(pInfo, toDir, &fromDirHash, &fromDirEnt, xFiles_Leaf(to)), err)
		return err;

	/* If they are in the same directory the offsets might not be valid anymore, so
	 * do another scan of the directory to get the latest info.
	 */

	if (err = xFiles_dirLookup(pInfo, fromDir, xFiles_Leaf(from),
					&fromHashOffset, &fromEntryOffset, NULL, NULL), err)
		return err;

	if (err = xFiles_deleteDirEntry(pInfo, fromDir, fromHashOffset), err)
		return err;

	return NULL;
}

static _kernel_oserror *xFiles_ReadDirEntries(xFiles_info * pInfo, const char *dirName, void *buffer, int *pCount,
		int *pNext, int bufSize, BOOL infoToo)
{
	_kernel_oserror *err;
	xFiles_dirHeader dirHdr;
	xFiles_dirHash dirHash;
	xFiles_dirEntry dirEnt;
	unsigned dirObject;
	unsigned hashPos;
	int nFiles;
	char *bufPtr, *bufLimit;
	int spaceNeeded;
	BOOL fakeName;

	struct fileInfo
	{
		unsigned load, exec, size, attr, type;
	}
	*pFileInfo;

	if (err = xFiles_FindDir(pInfo, dirName, &dirHash, &dirEnt), err)
		return err;

	dirObject = dirHash.node;

	if (err = xFiles_getDirHeader(pInfo, dirObject, &dirHdr), err)
		return err;

	nFiles = *pCount;
	*pCount = 0;
	bufPtr = (char *) buffer;
	bufLimit = bufPtr + bufSize;

	while (nFiles > 0 && *pNext < dirHdr.used)
	{
		hashPos = sizeof (xFiles_dirHeader) + sizeof (xFiles_dirHash) * (*pNext);
		fakeName = FALSE;

		if (err = xFiles_readChunk(pInfo, &dirHash, dirObject, hashPos, sizeof (dirHash)), err)
			return err;

		if (err = xFiles_readChunk(pInfo, &dirEnt, dirObject, dirHash.entryPos, sizeof (dirEnt)), err)
			return err;

		if (dirEnt.nameLen > 256 || dirEnt.nameLen == 0)
		{
			TRACE("Fixing broken name (length = %08x)\n", dirEnt.nameLen);

			fakeName = TRUE;
			dirEnt.nameLen = 12;
		}

		if (infoToo)
			spaceNeeded = 20 + (dirEnt.nameLen + 4) & ~3;
		else
			spaceNeeded = dirEnt.nameLen + 1;

		if (bufPtr + spaceNeeded > bufLimit)
			break;

		if (infoToo)
		{
			pFileInfo = (struct fileInfo *) bufPtr;

			if (fakeName)
			{
				xFiles_chunk cnk;

				if (err = xFiles_getChunkInfo(pInfo, dirHash.node, &cnk), err)
					return err;

				dirEnt.load =
						dirEnt.exec = 0xFFFFFFFF;
				dirEnt.size = cnk.size;
				dirEnt.attr = xFiles_meRead | xFiles_meWrite;
			}

			pFileInfo->load = dirEnt.load;
			pFileInfo->exec = dirEnt.exec;
			pFileInfo->size = dirEnt.size;
			pFileInfo->attr = dirEnt.attr & 0xFF;
			pFileInfo->type = (dirEnt.attr & xFiles_isDir) ? 2 : 1;
			bufPtr = (char *) (pFileInfo + 1);
		}

		if (fakeName)
		{
			memcpy(bufPtr, dirHash.nameStart, 4);
			sprintf(bufPtr + 4, "%08d", dirHash.node);
		}
		else
		{
			if (err = xFiles_readChunk(pInfo, bufPtr, dirObject,
							dirHash.entryPos + sizeof (dirEnt), dirEnt.nameLen + 1), err)
				return err;
		}

		bufPtr += dirEnt.nameLen + 1;

		if (infoToo)
		{
			while (((int) bufPtr & 3) != 0)
				*bufPtr++ = 0;
		}

		(*pCount)++;
		(*pNext)++;
		nFiles--;
	}

	if (*pNext >= dirHdr.used)
		*pNext = -1;

	return NULL;
}

static _kernel_oserror *xFiles_FileInfo(xFiles_info * pInfo, _kernel_swi_regs * regs)
{
	_kernel_oserror *err;
	xFiles_dirEntry dirEnt;
	unsigned hashOffset;

	if (err = xFiles_parsePath(pInfo, (const char *) regs->r[1], NULL, &hashOffset, NULL, NULL, &dirEnt,
					FALSE), err)
		return err;

	if (hashOffset == 0)
		regs->r[0] = 0;
	else
	{
		regs->r[0] = (dirEnt.attr & xFiles_isDir) ? 2 : 1;
		regs->r[2] = dirEnt.load;
		regs->r[3] = dirEnt.exec;
		regs->r[4] = dirEnt.size;
		regs->r[5] = dirEnt.attr & 0xFF;
	}

	return NULL;
}

static _kernel_oserror *xFiles_UpdateInfo(xFiles_info * pInfo, _kernel_swi_regs * regs)
{
	_kernel_oserror *err;
	xFiles_dirEntry dirEnt;
	unsigned hashOffset;
	unsigned entryOffset;
	unsigned parent;

	const char *objName = (const char *) regs->r[1];

	if (*objName == '\0')										/* don't even bother trying with the root */
		return NULL;

	if (err = xFiles_parsePath(pInfo, objName, &parent, &hashOffset, &entryOffset, NULL, &dirEnt, FALSE)
			,err)
		return err;

	if (hashOffset == 0)
		return NULL;

	dirEnt.load = regs->r[2];
	dirEnt.exec = regs->r[3];
	dirEnt.attr = (dirEnt.attr & 0xFFFFFF00) | (regs->r[5] & 0xFF);

	if (err = xFiles_writeChunk(pInfo, &dirEnt, parent, entryOffset, sizeof (dirEnt)), err)
		return err;

	return NULL;
}

static _kernel_oserror *xFiles_NewImage(_kernel_swi_regs * regs)
{
	xFiles_info *pInfo;
	_kernel_oserror *err;

	if (pInfo = malloc(sizeof (xFiles_info)), !pInfo)
		return &xFiles_NoMemory;

	xFiles_InitItem(&pInfo->li);
	xFiles_InitList(&pInfo->openList);

	pInfo->fileHandle = regs->r[1];

	/* Should this perhaps take some notice of the sage advice which appears
	 * on page 4-46 of the PRM?
	 */

	if (err = xFiles_OpenImage(pInfo), err)
		return err;

	regs->r[1] = (int) pInfo;
	return NULL;
}

static _kernel_oserror *xFiles_Delete(xFiles_info * pInfo, const char *objName)
{
	_kernel_oserror *err;
	xFiles_fileInfo *pFile;
	xFiles_dirHash dirHash;
	xFiles_dirEntry dirEnt;
	unsigned hashOffset;
	unsigned entryOffset;
	unsigned parent;

	/*TRACE("Delete %s\n", objName); */

	if (*objName == '\0')										/* don't even bother trying with the root */
		return NULL;

	if (err = xFiles_parsePath(pInfo, objName, &parent, &hashOffset, &entryOffset, &dirHash, &dirEnt,
					FALSE), err)
		return err;

	/* Don't delete an open file */

	if (pFile = xFiles_FindOpenFile(pInfo, dirHash.node), pFile)
		return &xFiles_IsOpen;

	if (hashOffset == 0)
		return NULL;

	if (dirEnt.attr & xFiles_locked)
		return &xFiles_Locked;

	if (dirEnt.attr & xFiles_isDir)
	{
		xFiles_dirHeader dirHdr;

		if (err = xFiles_getDirHeader(pInfo, dirHash.node, &dirHdr), err)
			return err;

		if (dirHdr.used != 0)
			return NULL;										/* &xFiles_DirNotEmpty; */
	}

	if (err = xFiles_deleteDirEntry(pInfo, parent, hashOffset), err)
		return err;

	if (err = xFiles_freeChunk(pInfo, dirHash.node), err)
		return err;

	return NULL;

}

static _kernel_oserror *xFiles_ObjectExists(xFiles_info * pInfo, const char *pName, BOOL * pExists)
{
	_kernel_oserror *err;
	unsigned hashOffset;

	if (err = xFiles_parsePath(pInfo, pName, NULL, &hashOffset, NULL, NULL, NULL, FALSE), err)
		return err;

	if (pExists)
		*pExists = (hashOffset != 0);

	return NULL;
}

static _kernel_oserror *xFiles_CDir(xFiles_info * pInfo, _kernel_swi_regs * regs)
{
	_kernel_oserror *err;
	xFiles_dirEntry dirEnt, newDir;
	xFiles_dirHash dirHash, newHash;
	const char *dirName = (const char *) regs->r[1];
	unsigned hashOffset;
	BOOL exists;

	/*TRACE("CDir %s\n", dirName); */

	if (err = xFiles_ObjectExists(pInfo, dirName, &exists), err)
		return err;

	if (exists)
		return NULL;

	if (err = xFiles_parsePath(pInfo, dirName, NULL, &hashOffset, NULL, &dirHash, &dirEnt, TRUE), err)
		return err;

	if (hashOffset == 0)
		return &xFiles_NotFound;

	if (err = xFiles_cDir(pInfo, dirHash.node, &newHash.node), err)
		return err;

	newDir.load = regs->r[2];
	newDir.exec = regs->r[3];
	newDir.size = 0;
	newDir.attr = xFiles_meRead | xFiles_meWrite | xFiles_isDir;

	if (err = xFiles_createDirEntry(pInfo, dirHash.node, &newHash, &newDir, xFiles_Leaf(dirName)), err)
		return err;

	return NULL;
}

static _kernel_oserror *xFiles_Create(xFiles_info * pInfo, _kernel_swi_regs * regs, unsigned *pCnk)
{
	_kernel_oserror *err;
	const char *fileName = (const char *) regs->r[1];
	xFiles_dirEntry dirEnt, newFile;
	xFiles_dirHash dirHash, newHash;
	unsigned hashOffset, entryOffset, parent;

	/*TRACE("Create %s\n", fileName); */

#if 0
	if (err = xFiles_Delete(pInfo, fileName), err)
		return err;
#else
	if (err = xFiles_parsePath(pInfo, fileName, &parent, &hashOffset, &entryOffset, &dirHash, &dirEnt,
					FALSE), err)
		return err;

	if (hashOffset != 0)										/* File already exists */
	{
		if (err = xFiles_setChunkSize(pInfo, dirHash.node, regs->r[5] - regs->r[4]), err)
			return err;

		dirEnt.load = regs->r[2];
		dirEnt.exec = regs->r[3];
		dirEnt.size = regs->r[5] - regs->r[4];

		if (err = xFiles_writeChunk(pInfo, &dirEnt, parent, entryOffset, sizeof (dirEnt)), err)
			return err;

		if (pCnk)
			*pCnk = dirHash.node;
		return NULL;
	}
#endif

	if (err = xFiles_parsePath(pInfo, fileName, NULL, &hashOffset, NULL, &dirHash, &dirEnt, TRUE), err)
		return err;

	if (hashOffset == 0)
		return &xFiles_NotFound;

	if (err = xFiles_newChunk(pInfo, &newHash.node), err)
		return err;

	if (err = xFiles_setChunkSize(pInfo, newHash.node, regs->r[5] - regs->r[4]), err)
		return err;

	newFile.load = regs->r[2];
	newFile.exec = regs->r[3];
	newFile.size = regs->r[5] - regs->r[4];
	newFile.attr = xFiles_meRead | xFiles_meWrite;

	if (err = xFiles_createDirEntry(pInfo, dirHash.node, &newHash, &newFile, xFiles_Leaf(fileName))
			,err)
		return err;

	if (pCnk)
		*pCnk = newHash.node;

	return NULL;
}

static _kernel_oserror *xFiles_Save(xFiles_info * pInfo, _kernel_swi_regs * regs)
{
	_kernel_oserror *err;
	unsigned newChunk;

	if (err = xFiles_Create(pInfo, regs, &newChunk), err)
		return err;

	return xFiles_writeChunk(pInfo, (void *) regs->r[4], newChunk, 0, regs->r[5] - regs->r[4]);
}

static _kernel_oserror *xFiles_FindFile(xFiles_info * pInfo, const char *fileName, xFiles_dirHash *
		pDirHash, xFiles_dirEntry * pDirEnt, unsigned *pParent)
{
	_kernel_oserror *err;

	if (err = xFiles_FindObject(pInfo, fileName, pDirHash, pDirEnt, pParent), err)
		return err;

	if ((pDirEnt->attr & xFiles_isDir) != 0)
		return &xFiles_NotAFile;

	return NULL;
}

/* Locate an open file's directory information by looking it up * in it's
 * parent directory. */

static _kernel_oserror *xFiles_LocateOpenFile(xFiles_info * pInfo, xFiles_fileInfo * pFileInfo, unsigned *pEntryOffset,
		xFiles_dirEntry * pDirEnt)
{
	_kernel_oserror *err;
	unsigned hashOffset;
	xFiles_dirHash dirHash;

	/* Might want to change this to return the hash too? */

	if (err = xFiles_dirLookupByNode(pInfo, pFileInfo->parentDir, pFileInfo->cnkNum,
					&hashOffset, pEntryOffset, &dirHash), err)
		return err;

	if (hashOffset == 0)
		return &xFiles_LostTrack;

	if (pDirEnt)
	{
		if (err = xFiles_readChunk(pInfo, (void *) pDirEnt, pFileInfo->parentDir,
						dirHash.entryPos, sizeof (xFiles_dirEntry)), err)
			return err;
	}

	return NULL;
}

/*********************************************************************************/
/* */
/* Filing system functionality (at last)
 *  */
/* */
/*********************************************************************************/

_kernel_oserror *xFiles_Open(_kernel_swi_regs * regs)
{
	_kernel_oserror *err;
	const char *fileName = (const char *) regs->r[1];
	xFiles_info *pInfo = (xFiles_info *) regs->r[6];
	xFiles_fileInfo *pFileInfo;
	xFiles_dirEntry dirEnt;
	xFiles_dirHash dirHash;

	if (pFileInfo = malloc(sizeof (xFiles_fileInfo)), !pFileInfo)
		return &xFiles_NoMemory;

	xFiles_InitItem(&pFileInfo->li);

	pFileInfo->fileSwitchHandle = regs->r[3];
	pFileInfo->pOwner = pInfo;

	/*TRACE("Open(%s)\n", leafName); */

	if (err = xFiles_FindFile(pInfo, fileName, &dirHash, &dirEnt, &pFileInfo->parentDir), err)
		goto failed;

	pFileInfo->cnkNum = dirHash.node;
	pFileInfo->fileSize = dirEnt.size;

	regs->r[0] = dirEnt.attr << 30;
	regs->r[1] = (int) pFileInfo;
	regs->r[2] = pInfo->fileHeader.allocationUnit;
	regs->r[3] = dirEnt.size;
	regs->r[4] = (dirEnt.size + 1023) & ~1023;

	if (err = xFiles_Commit(pInfo), err)
		return err;

	/* Link it in to it's owner */

	xFiles_AddAtHead(&pInfo->openList, &pFileInfo->li);

	return NULL;

  failed:
	free(pFileInfo);
	regs->r[1] = 0;
	return err;
}

_kernel_oserror *xFiles_GetBytes(_kernel_swi_regs * regs)
{
	xFiles_fileInfo *pFileInfo = (xFiles_fileInfo *) regs->r[1];
	xFiles_info *pInfo = pFileInfo->pOwner;
	unsigned pos = regs->r[4];
	unsigned size = regs->r[3];

	if (pos + size > pFileInfo->fileSize)
		size = pFileInfo->fileSize - pos;

	/*TRACE("GetBytes() %08x, %08x, %08x\n", regs->r[2], regs->r[3], regs->r[4]); */

	return xFiles_readChunk(pInfo, (void *) regs->r[2], pFileInfo->cnkNum, pos, size);
}

_kernel_oserror *xFiles_PutBytes(_kernel_swi_regs * regs)
{
	_kernel_oserror *err;
	xFiles_fileInfo *pFileInfo = (xFiles_fileInfo *) regs->r[1];
	xFiles_info *pInfo = pFileInfo->pOwner;
	unsigned pos = regs->r[4];
	unsigned size = regs->r[3];

	/*TRACE("PutBytes() %08x, %08x, %08x\n", regs->r[2], regs->r[3], regs->r[4]);
	 * TRACE("pFileInfo->cnkNum = %08x\n", pFileInfo->cnkNum); */

	if (err = xFiles_writeChunk(pInfo, (void *) regs->r[2], pFileInfo->cnkNum, pos, size), err)
		return err;

	return xFiles_Commit(pInfo);
}

_kernel_oserror *xFiles_Args(_kernel_swi_regs * regs)
{
	_kernel_oserror *err;
	unsigned entryOffset;
	xFiles_fileInfo *pFileInfo = (xFiles_fileInfo *) regs->r[1];
	xFiles_info *pInfo = pFileInfo->pOwner;
	xFiles_dirEntry dirEnt;
	BOOL update = FALSE;

	if (err = xFiles_LocateOpenFile(pInfo, pFileInfo, &entryOffset, &dirEnt), err)
		return err;

	switch (regs->r[0])
	{
	case 7:													/* ensure file size */
		/*TRACE("Ensure file is >= %08x (currently %08x)\n", regs->r[2], dirEnt.size); */
		if (regs->r[2] <= dirEnt.size)
			break;

	case 3:													/* write file extent */
		if (err = xFiles_setChunkSize(pInfo, pFileInfo->cnkNum, regs->r[2]), err)
			return err;
		dirEnt.size = regs->r[2];
		pFileInfo->fileSize = regs->r[2];
		update = TRUE;
		break;

	case 4:
		regs->r[2] = dirEnt.size;
		break;

	case 9:
		regs->r[2] = dirEnt.load;
		regs->r[3] = dirEnt.exec;
		break;

	case 6:
	case 8:													/* write zeros */
	case 10:
		break;

	default:
		TRACE("Args(%d) called\n", regs->r[0]);
		return &xFiles_NotImplemented;
	}

	if (update)
	{
		if (err = xFiles_writeChunk(pInfo, &dirEnt, pFileInfo->parentDir,
						entryOffset, sizeof (dirEnt)), err)
			return err;
	}

	return xFiles_Commit(pInfo);
}

_kernel_oserror *xFiles_Close(_kernel_swi_regs * regs)
{
	_kernel_oserror *err;
	unsigned entryOffset;
	xFiles_fileInfo *pFileInfo = (xFiles_fileInfo *) regs->r[1];
	xFiles_info *pInfo = pFileInfo->pOwner;
	xFiles_dirEntry dirEnt;

	/*TRACE("Close()\n"); */

	if (err = xFiles_LocateOpenFile(pInfo, pFileInfo, &entryOffset, &dirEnt), err)
		return err;

	if (regs->r[2] != 0)
		dirEnt.load = regs->r[2];
	if (regs->r[3] != 0)
		dirEnt.exec = regs->r[3];

	if (regs->r[2] != 0 || regs->r[3] != 0)
	{
		if (err = xFiles_writeChunk(pInfo, &dirEnt, pFileInfo->parentDir,
						entryOffset, sizeof (dirEnt)), err)
			return err;
	}

	if (err = xFiles_Commit(pInfo), err)
		return err;

	/* Can't error now, so unlink it and get rid of it */

	xFiles_Remove(&pInfo->openList, &pFileInfo->li);
	free(pFileInfo);

	return NULL;
}

_kernel_oserror *xFiles_File(_kernel_swi_regs * regs)
{
	_kernel_oserror *err;
	xFiles_info *pInfo = (xFiles_info *) regs->r[6];

	switch (regs->r[0])
	{
	case 0:
		if (err = xFiles_Save(pInfo, regs), err)
			return err;
		break;

	case 1:
		if (err = xFiles_UpdateInfo(pInfo, regs), err)
			return err;
		break;

	case 5:
		if (err = xFiles_FileInfo(pInfo, regs), err)
			return err;
		break;

	case 6:
		if (err = xFiles_Delete(pInfo, (const char *) regs->r[1]), err)
			return err;
		break;

	case 7:
		if (err = xFiles_Create(pInfo, regs, NULL), err)
			return err;
		break;

	case 8:
		if (err = xFiles_CDir(pInfo, regs), err)
			return err;
		break;

	default:
		TRACE("File(%d) called\n", regs->r[0]);
		return &xFiles_NotImplemented;
	}

	return xFiles_Commit(pInfo);
}

_kernel_oserror *xFiles_Func(_kernel_swi_regs * regs)
{
	xFiles_info *pInfo = (xFiles_info *) regs->r[6];
	_kernel_oserror *err;

	/*TRACE("Func(%d) called\n", regs->r[0]); */

	switch (regs->r[0])
	{
	case 8:													/* rename object */
		if (err = xFiles_Rename(pInfo, (const char *) regs->r[1], (const char *) regs->r[2]), err)
			return err;
		regs->r[1] = 0;
		return xFiles_Commit(pInfo);

	case 14:													/* read directory entries */
	case 15:
		return xFiles_ReadDirEntries(pInfo, (const char *) regs->r[1],
				(void *) regs->r[2], &regs->r[3], &regs->r[4], regs->r[5],
				regs->r[0] == 15);

	case 21:													/* new image notification */
		return xFiles_NewImage(regs);

	case 22:
		return xFiles_CloseImage((xFiles_info *) regs->r[1]);

	case 27:													/* return boot option */
		regs->r[2] = 0;
		return NULL;

	default:
		TRACE("Func(%d) called\n", regs->r[0]);
		return &xFiles_NotImplemented;
	}

	return NULL;
}
