/* fs.c */

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

#include <stdlib.h>
#include <string.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;

   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);

      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 (infoToo)
         spaceNeeded = 20 + (dirEnt.nameLen + 4) & ~3;
      else
         spaceNeeded = dirEnt.nameLen + 1;

      if (bufPtr + spaceNeeded > bufLimit)
         break;

      if (infoToo)
      {
         pFileInfo = (struct fileInfo *) bufPtr;
         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 (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;
}

