/////////////////////////////////////////////////////////////////////////////
//
// vfs - Virtual filesystem management
//
/////////////////////////////////////////////////////////////////////////////

#ifndef EMU_COMPILE
#error "Hi I forgot to set EMU_COMPILE"
#endif

#include "vfs.h"

/////////////////////////////////////////////////////////////////////////////
//
// Static information
//

sint32 EMU_CALL vfs_init(void) { return 0; }

/////////////////////////////////////////////////////////////////////////////
//
// State information
//
#define VFS_MAXPATH (250)
#define VFS_MAXOPENFILES (32)

struct VFS_STATE {
  psx_readfile_t readfile_cb;
  void *readfile_context;

  sint32 ofs[VFS_MAXOPENFILES];
  sint32 length[VFS_MAXOPENFILES];
  char path[VFS_MAXOPENFILES][VFS_MAXPATH];
};

#define VFSSTATE ((struct VFS_STATE*)(state))

uint32 EMU_CALL vfs_get_state_size(void) {
  return sizeof(struct VFS_STATE);
}

void EMU_CALL vfs_clear_state(void *state) {
  memset(VFSSTATE, 0, sizeof(struct VFS_STATE));
}

void EMU_CALL vfs_set_readfile(void *state, psx_readfile_t readfile, void *context) {
  VFSSTATE->readfile_cb = readfile;
  VFSSTATE->readfile_context = context;
}

/////////////////////////////////////////////////////////////////////////////
//
// check if the given fd is valid
//
static sint32 EMU_CALL isvalidfd(struct VFS_STATE *state, sint32 fd) {
  if(fd < 0 || fd >= VFS_MAXOPENFILES) return 0;
  if(state->path[fd][0] == 0) return 0;
  return 1;
}

/////////////////////////////////////////////////////////////////////////////
//
// open
//
sint32 EMU_CALL vfs_open(void *state, const char *path) {
  sint32 l;
  sint32 fd;
  char tempbuf[4];
  if(!(VFSSTATE->readfile_cb)) return -5; // EIO if no callback was set
  if(!path) return -22; // EINVAL if this was NULL for some reason
  if(!path[0]) return -2; // ENOENT if the path is empty
  l = (VFSSTATE->readfile_cb)(
    VFSSTATE->readfile_context,
    path,
    0,
    tempbuf,
    0
  );
  if(l < -1) return -5; // EIO for fatal errors
  if(l == -1) return -2; // ENOENT for not found
  // Otherwise, find a free fd and keep it
  for(fd = 0; fd < VFS_MAXOPENFILES; fd++) {
    if(VFSSTATE->path[fd][0] == 0) break;
  }
  if(fd >= VFS_MAXOPENFILES) return -24; // EMFILE too many open files
  VFSSTATE->ofs[fd] = 0;
  VFSSTATE->length[fd] = l;
  strncpy(VFSSTATE->path[fd], path, VFS_MAXPATH);
  VFSSTATE->path[fd][VFS_MAXPATH-1] = 0;
  return fd;
}

/////////////////////////////////////////////////////////////////////////////
//
// close
//
sint32 EMU_CALL vfs_close(void *state, sint32 fd) {
  if(!(VFSSTATE->readfile_cb)) return -5; // EIO if no callback was set
  if(!isvalidfd(VFSSTATE, fd)) return -9; // EBADF
  VFSSTATE->path[fd][0] = 0;
  return 0;
}

/////////////////////////////////////////////////////////////////////////////
//
// read
//
sint32 EMU_CALL vfs_read(void *state, sint32 fd, char *buffer, sint32 length) {
  sint32 r;
  if(!(VFSSTATE->readfile_cb)) return -5; // EIO if no callback was set
  if(!isvalidfd(VFSSTATE, fd)) return -9; // EBADF
  // if length is 0, just return 0
  if(!length) return 0;
  // if we're past end-of-file, return 0
  if(VFSSTATE->ofs[fd] >= VFSSTATE->length[fd]) return 0;
  // make sure we don't read past the end
  { sint32 remain = VFSSTATE->length[fd] - VFSSTATE->ofs[fd];
    if(length > remain) length = remain;
  }
  // attempt actual read
  r = (VFSSTATE->readfile_cb)(
    VFSSTATE->readfile_context,
    VFSSTATE->path[fd],
    VFSSTATE->ofs[fd],
    buffer,
    length
  );
  if(r < -1) return -5; // EIO for fatal errors
  if(r == -1) return -2; // ENOENT for not found - weird, but should work
  VFSSTATE->ofs[fd] += r;
  return r;
}

/////////////////////////////////////////////////////////////////////////////
//
// lseek
//
sint32 EMU_CALL vfs_lseek(void *state, sint32 fd, sint32 offset, sint32 whence) {
  if(!(VFSSTATE->readfile_cb)) return -5; // EIO if no callback was set
  if(!isvalidfd(VFSSTATE, fd)) return -9; // EBADF
  switch(whence) {
  case 0: // SEEK_SET
    break;
  case 1: // SEEK_CUR
    offset += VFSSTATE->ofs[fd];
    break;
  case 2: // SEEK_END
    offset += VFSSTATE->length[fd];
    break;
  default:
    return -22; // EINVAL if whence isn't right
  }
  if(offset < 0) return -22; // EINVAL if offset ends up negative
  VFSSTATE->ofs[fd] = offset;
  return offset;
}

/////////////////////////////////////////////////////////////////////////////
