#include <vo_stdio.h>
#include <vsos.h>
#include "unistd.h"
#include "vo_fatdirops.h"
#include <vo_fat.h>
#include <swap.h>
#include <stdlib.h>
#include <string.h>
#include <sysmemory.h>
#include <ByteManipulation.h>
#include <time.h>

#if 0
#define VERBOSE
#endif

DirectoryEntry dots[2] = {
  {{0x2e20,0x2020,0x2020,0x2020,0x2020},
   0x2010,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0},
  {{0x2e2e,0x2020,0x2020,0x2020,0x2020},
   0x2010,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0}
};

#define NAMELENGTH 32
char fileName[NAMELENGTH];



int MkDir(const char *pathname) {
  FILE *dirFp = NULL;
  ioresult res = S_ERROR;
  FatFileInfo *dirFi = NULL;
  u_int32 parentStartCluster = 0;
  int i;
  int pathLen = strlen(pathname);
  char *parentOnly = calloc(pathLen+2, sizeof(char));
  u_int16 *sectorBuf = malloc(256*sizeof(u_int16));
  char *nameOnly;
  DEVICE *dirDev;
  time_t tt;
  int patchDirEntry = 0;

  if (!parentOnly || !sectorBuf) {
    res = E_OUT_OF_MEMORY;
    goto finally;
  }

  memcpy(parentOnly, pathname, pathLen+1);
  nameOnly = strrchr(parentOnly, '/');
#if 0
  printf("parentOnly \"%s\", nameOnly \"%s\"\n", parentOnly, nameOnly);
#endif
  if (!nameOnly) {
    if (parentOnly[1] == ':') {
      memmove(parentOnly+2, parentOnly+1, pathLen-1);
      nameOnly = parentOnly+2;
      *nameOnly++ = '\0';
    } else {
      res = E_ILLEGAL_FILE_NAME;
      goto finally;
    }
  } else {
    FILE *fp = NULL;
    *nameOnly = '\0'; /* Remove trailing '/' from path name */
    fp = fopen(parentOnly, "rb");
    if (!fp) {
      res = E_FILE_NOT_FOUND;
      goto finally;
    } else {
      u_int16 attr = fp->ungetc_buffer;
      fclose(fp);
      fp = NULL;
      if (!(attr & __ATTR_DIRECTORY)) {
	res = E_FILE_NOT_FOUND;
	goto finally;
      }
    }
   
    *nameOnly++ = '/'; /* Reinstate '/' */
    memmove(nameOnly+1, nameOnly, strlen(nameOnly)+1);
    *nameOnly++ = '\0'; /* Nul-terminate and move to beginning of new string. */
  }

#if 0
  printf("pathname \"%s\"\nparentOnly \"%s\"\nnameOnly \"%s\"\n",
	  pathname, parentOnly, nameOnly);
#endif

  /* Refuse to create if no name */
  if (!nameOnly[0]) {
    res = E_ILLEGAL_FILE_NAME;
    goto finally;
  }

  {
    FILE *parentFp = NULL;
    struct FatDirExt *fde;
    FILE *parentFp;
    parentFp = fopen(parentOnly, "s");
#ifdef VERBOSE
    printf("parentFp = 0x%x, parentOnly \"%s\", attr 0x%02x\n",
	   parentFp, parentOnly, parentFp->ungetc_buffer);
#endif
    
    if (parentFp) {
      ioresult t = FatFindFirst(parentFp, parentOnly+2, fileName, NAMELENGTH);
      FatFileInfo *parentFi = (FatFileInfo *)parentFp->fileInfo;
      FatDeviceInfo *fatDi;

      dirDev = parentFp->dev;
      fatDi = (FatDeviceInfo *)(dirDev->deviceInfo);
      {
#ifdef VERBOSE
	printf("dirDev %04x, fatDi %04x\n", dirDev, fatDi);
	printf("Parent start clus %lx, root start clus %lx\n",
	       parentFi->startCluster, fatDi->rootStart);
	printf("totClus %lx, fatSecPerClus %x, fatBits %x, fatSt %lx, "
	       "dataSt %lx, rootSt %lx\n",
	       fatDi->totalClusters, fatDi->fatSectorsPerCluster,
	       fatDi->fatBits, fatDi->fatStart, fatDi->dataStart,
	       fatDi->rootStart);
#endif
      }

      if (parentFi->startCluster == fatDi->rootStart && fatDi->fatBits == 32) {
	parentStartCluster = 0;
      } else {
	parentStartCluster = parentFi->startCluster;
      }

      fclose(parentFp);
      parentFp = NULL;

      if (t != S_OK) {
	res = E_FILE_NOT_FOUND;
	goto finally;
      }
    } else {
      res = E_FILE_NOT_FOUND;
      goto finally;
    }
  }

  /* Refuse to do anything if already exists */
  dirFp = fopen(pathname, "rb");
  if (dirFp) {
    res = E_FILE_ALREADY_EXISTS;
    goto finally;
  }

  /* Try to open for writing */
  dirFp = fopen(pathname, "wb");
  if (!dirFp) {
    res = E_FILE_NOT_FOUND;
    goto finally;
  }

  dirFi = (FatFileInfo *)dirFp->fileInfo;
  dirDev = dirFp->dev;

  if ((tt = time(NULL)) != TIME_NOT_FOUND && tt != TIME_NOT_SET) {
    struct tm *tm = localtime(&tt);
    u_int16 dat = ((tm->tm_year-80)<<9) | ((tm->tm_mon+1)<<5) | tm->tm_mday;
    u_int16 tim = (tm->tm_hour<<11) | (tm->tm_min<<5) | (tm->tm_sec>>1);

    dots[0].writeDate = dots[0].lastAccessDate = dots[0].creatDate =
      Swap16(dat);
    dots[0].writeTime = dots[0].creatTime = Swap16(tim);
    memcpy((u_int16 *)(dots+1)+0xe/2, (u_int16 *)(dots)+0xe/2, 6);
  }

  {
    u_int32 t32 = Swap32(dirFi->startCluster);
    dots[0].firstClusLo = (u_int16)(t32 >> 16);
    dots[0].firstClusHi = (u_int16)t32;
    t32 = Swap32(parentStartCluster);
    dots[1].firstClusLo = (u_int16)(t32 >> 16);
    dots[1].firstClusHi = (u_int16)t32;
  }

  memset(sectorBuf, 0, 256);
  if (fwrite(dots, sizeof(dots), 1, dirFp) != 1) goto finally;

  /*
    Now, we have to make sure the whole directory cluster is cleared,
    i.e. filled with zeroes. First start by filling the rest of the first
    sector with zeros. */
  if (fwrite(sectorBuf, 256-sizeof(dots), 1, dirFp) != 1) goto finally; /* First sector is cleared */

  {
    FatDeviceInfo *fatDi = (FatDeviceInfo *)(dirDev->deviceInfo);
    i = fatDi->fatSectorsPerCluster;
  }
  while (--i) { /* Clear sectors from 1 to fatSectorsPerCluster-1. */
    if (fwrite(sectorBuf, 256, 1, dirFp) != 1) goto finally;
  }

  patchDirEntry = 1;
  
  res = S_OK;
 finally:
  if (dirFp) {
    fclose(dirFp);
    dirFp = NULL;
    dirFi = NULL;
  }

  if (patchDirEntry) {
    u_int32 dirDirectoryEntrySector;
    DirectoryEntry *dirEnt;

    /* Try to open for reading.
       NOTE! When closing the file earlier for writing, the long file name
       system may rename the file if it doesn't fit into basic 8.3 upper-case
       format. Thus, we really need to reopen it here to get the updated
       and correct sector and entry numbers. */
    dirFp = fopen(pathname, "rb");
    if (!dirFp) {
      res = E_FILE_NOT_FOUND;
    } else {
      dirFi = (FatFileInfo *)dirFp->fileInfo;
      dirDirectoryEntrySector = dirFi->directoryEntrySector;
      dirEnt = (DirectoryEntry *)sectorBuf+dirFi->directoryEntryNumber;
      fclose(dirFp);
      dirFp = NULL;
      dirFi = NULL;
    
      dirDev->BlockRead(dirDev, dirDirectoryEntrySector, 1, sectorBuf);
      /* Update from file to directory */
      dirEnt->attr |= __ATTR_DIRECTORY;
      /* Directory size must be 0 */
      dirEnt->fileSizeHi = dirEnt->fileSizeLo = 0;
      /* Write back to the file */
      dirDev->BlockWrite(dirDev, dirDirectoryEntrySector, 1, sectorBuf);
      /* Force flush operation */
      dirDev->BlockRead(dirDev, 0, 0, NULL);
    }
  }
  
  if (parentOnly) {
    free(parentOnly);
    parentOnly = NULL;
  }

  if (sectorBuf) {
    free(sectorBuf);
    sectorBuf = NULL;
  }

  return res;
}
