#include "fs.h"
#include "fsutil.h"

using namespace fsutil;

// fs::fs(blockdbc *xdb)
// {
//   db = xdb;
  
//   // try to get some random bits so we can create
//   // unique file handles.
//   struct timeval tv;
//   gettimeofday(&tv, 0);
//   seq = ((long long) tv.tv_usec << 30) ^ tv.tv_sec;
//   int fd = open("/dev/urandom", 0);
//   if(fd != -1){
//     read(fd, &seq, sizeof(seq));
//     close(fd);
//   }
// }

fs::fs(ref<tagged_blockdbc> xbdb) : bdb(xbdb)
{
  // try to get some random bits so we can create
  // unique file handles.
  struct timeval tv;
  gettimeofday(&tv, 0);
  seq = ((long long) tv.tv_usec << 30) ^ tv.tv_sec;
  int fd = open("/dev/urandom", 0);
  if(fd != -1){
    read(fd, &seq, sizeof(seq));
    close(fd);
  }
}


ref<tagged_blockdbc>
fs::get_bdb()
{
  return bdb;
}


void
fs::flush(str lock, callback<void,void>::ref cb)
{
  // lock_client is telling us that it's about to release
  // a lock. Here's our chance to flush cached blocks.
  // Call (cb)() when done.
  (cb)();
}

void
fs::dispatch(nfscall *nc)
{
  switch(nc->proc()){
  case NFSPROC3_NULL:
    nc->reply(nc->getvoidres());
    break;
  case NFSPROC3_GETATTR:
    nfs3_getattr(nc);
    break;
  case NFSPROC3_SETATTR:
    nfs3_setattr(nc);
    break;
  case NFSPROC3_LOOKUP:
    nfs3_lookup(nc);
    break;
  case NFSPROC3_ACCESS:
    nfs3_access(nc);
    break;
  case NFSPROC3_READ:
    nfs3_read(nc);
    break;
  case NFSPROC3_WRITE:
    nfs3_write(nc);
    break;
  case NFSPROC3_CREATE:
    nfs3_create(nc);
    break;
  case NFSPROC3_MKDIR:
    nfs3_mkdir(nc);
    break;
  case NFSPROC3_REMOVE:
    nfs3_remove(nc);
    break;
  case NFSPROC3_READDIR:
    nfs3_readdir(nc);
    break;
  case NFSPROC3_FSSTAT:
    nfs3_fsstat(nc);
    break;
  case NFSPROC3_FSINFO:
    nfs3_fsinfo(nc);
    break;
  default:
    fprintf(stderr, "fs:dispatch unknown proc %d\n", nc->proc());
    nc->reject(SYSTEM_ERR);
  }
}


// implementation of the NFS GETATTR RPC
// called by fs::dispatch()
// fetch the attributes from the block server using the
// file handle as key
void
fs::nfs3_getattr(nfscall *nc)
{
  // ask the RPC system to parse the RPC arguments (a file handle)
  nfs_fh3 *fh = nc->template getarg<nfs_fh3> ();

  ref<fsfile> file = New refcounted<fsfile>(this, fh2hex(*fh));
  file->get_fattr(wrap(this, &fs::nfs3_getattr_on_get_fattr,
                       nc, file));
}

void
fs::nfs3_getattr_on_get_fattr(nfscall *nc, ref<fsfile> file,
                              bool ok, fattr3 fa)
{
  if (ok)
  {
    // ask the RPC system for a place to put the RPC reply
    getattr3res *res = nc->template getres<getattr3res> ();

    res->set_status(NFS3_OK);
    *(res->attributes) = fa;

    // send the RPC reply
    nc->reply(nc->getvoidres());
  } else {
    fprintf(stderr, "getattr: stale file handle\n");

    // send an RPC error reply
    nc->error(NFS3ERR_STALE);
  }
}


void
fs::nfs3_setattr(nfscall *nc)
{
  setattr3args *a = nc->template getarg<setattr3args> ();

}


void
fs::nfs3_access(nfscall *nc)
{
  access3args *aa = nc->template getarg<access3args> ();

  ref<fsfile> file = New refcounted<fsfile>(this, fh2hex(aa->object));
  file->get_fattr(wrap(this, &fs::nfs3_access_on_get_fattr,
                       nc, file));
}

void
fs::nfs3_access_on_get_fattr(nfscall *nc, ref<fsfile> file,
                             bool ok, fattr3 fa)
{
  access3args *aa = nc->template getarg<access3args> ();
  if(ok){
    access3res *res = nc->template getres<access3res> ();
    res->set_status(NFS3_OK);
    res->resok->access = aa->access; // XXX
    res->resok->obj_attributes.set_present(true);
    *res->resok->obj_attributes.attributes = fa;
    nc->reply(nc->getvoidres());
  } else {
    fprintf(stderr, "access: stale file handle\n");
    nc->error(NFS3ERR_STALE);
  }
}


// handle a LOOKUP RPC.
void
fs::nfs3_lookup(nfscall *nc)
{
  // Get a pointer to the un-marshalled arguments.
  // The diropargs3 structure is defined in nfs3_prot.x
  // nfs_fh3 a->dir : directory file handle
  // str a->name : the file name the client wants to find
  diropargs3 *a = nc->template getarg<diropargs3> ();

  ref<fsdir> dir = New refcounted<fsdir>(this, fh2hex(a->dir));

  dir->get_fattr(wrap(this, &fs::nfs3_lookup_on_get_fattr,
                      nc, dir));
}

void
fs::nfs3_lookup_on_get_fattr(nfscall *nc, ref<fsdir> dir,
                             bool ok, fattr3 fa)
{
  if (!ok) 
  {
    nc->error(NFS3ERR_IO);
    return;
  }

  if (fa.type != NF3DIR) 
  {
    nc->error(NFS3ERR_NOTDIR);
    return;
  }

  dir->get_dircontents(wrap(this, &fs::nfs3_lookup_on_get_dircontents,
                            nc, dir));
}

void
fs::nfs3_lookup_on_get_dircontents(nfscall *nc, ref<fsdir> dir,
                                   int r, ptr<fsdirlist> lst)
{
  if (r != 0) 
  {
    // XXX should distinguish error cases
    nc->error(NFS3ERR_IO);
    return;
  }

  diropargs3 *a = nc->template getarg<diropargs3> ();
  fsdirent *ent = (*lst)[a->name];

  if (ent == NULL)
  {
    nc->error(NFS3ERR_NOENT);
    return;
  }

  lookup3res *res = nc->template getres<lookup3res> ();
  res->set_status(NFS3_OK);
  res->resok->object = hex2fh(ent->fileid);
  res->resok->obj_attributes.set_present(false);
  res->resok->dir_attributes.set_present(false);
  nc->reply(nc->getvoidres());
}
    

// use the first 32 bits of the file handle as the file ID.
// it's not clear what the client really depends on about
// the file ID, I think it's just for use in stat().
// but file IDs should not be zero, they should be unique,
// and they should stay the same for the same file.
uint64
fs::fh2fileid(nfs_fh3 fh)
{
  uint64 id;
  bcopy(fh.data.base(), &id, sizeof(id));
  return id;
}

wcc_data
fs::make_wcc(fattr3 before, fattr3 after)
{
  wcc_data d;

#if 0
  // because there's no locking, we cannot guarantee that
  // there were no changes between the before and after
  // attributes. so don't send any. XXX
  d.before.set_present(false);
  d.after.set_present(false);
#else
  d.before.set_present(true);
  d.before.attributes->size = before.size;
  d.before.attributes->mtime = before.mtime;
  d.before.attributes->ctime = before.ctime;
  d.after.set_present(true);
  (*d.after.attributes) = after;
#endif

  return d;
}

void
fs::nfs3_write(nfscall *nc)
{
  write3args *a = nc->template getarg<write3args> ();

}


void
fs::nfs3_read(nfscall *nc)
{
  read3args *a = nc->template getarg<read3args> ();

}


void
fs::nfs3_create(nfscall *nc)
{
  create3args *a = nc->template getarg<create3args> ();

  // create3args has these fields:
  // nfs_fh3 a->where.dir
  // str a->where.name
  // don't worry about a->how
  
  // don't try to implement EXCLUSIVE, which is supposed to
  // be atomic w.r.t. server crashes.
  if(a->how.mode == EXCLUSIVE){
    nc->error(NFS3ERR_NOTSUPP);
    return;
  }

  ref<fsdir> dir = New refcounted<fsdir>(this, fh2hex(a->where.dir));
  dir->get_fattr(wrap(this, &fs::nfs3_create_on_get_fattr,
                      nc, dir));

// once you've created the new file (or discovered that the
  // file already exists), and you have put the file's file
  // handle in fh, here's how to format and send the reply.
  // you'll probably have to move this code to a new function.
  //
  // diropres3 *res = nc->template getres<diropres3> ();
  // res->set_status(NFS3_OK);
  // res->resok->obj.set_present(true);
  // *res->resok->obj.handle = fh;
  // res->resok->obj_attributes.set_present(false);
  // res->resok->dir_wcc = make_wcc(dfa_before, dfa_after);
  // nc->reply(nc->getvoidres());
  //
  // dfa_before and dfa_after are fattr3 structures describing
  // the directory's attributes before and after the create.
  // this is part of the NFS v3 cache consistency machinery.
}

void
fs::nfs3_create_on_get_fattr(nfscall *nc, ref<fsdir> dir,
                             bool ok, fattr3 fa)
{
  if (!ok) 
  {
    nc->error(NFS3ERR_IO);
    return;
  }

  if (fa.type != NF3DIR) 
  {
    nc->error(NFS3ERR_NOTDIR);
    return;
  }

  dir->get_dircontents(wrap(this, &fs::nfs3_create_on_get_dircontents,
                             nc, dir, fa));
}

void
fs::nfs3_create_on_get_dircontents(nfscall *nc, ref<fsdir> dir, fattr3 dirfa,
                                   int r, ptr<fsdirlist> lst)
{
  if (r != 0) 
  {
    // XXX should distinguish error cases
    nc->error(NFS3ERR_IO);
    return;
  }

  create3args *a = nc->template getarg<create3args> ();
  fsdirent *ent = (*lst)[a->where.name];

  if (ent != NULL)
  {
    if (a->how.mode == GUARDED) 
    {
      nc->error(NFS3ERR_EXIST);
    } else {
      diropres3 *res = nc->template getres<diropres3> ();
      res->set_status(NFS3_OK);
      res->resok->obj.set_present(true);
      *res->resok->obj.handle = hex2fh(ent->fileid);
      res->resok->obj_attributes.set_present(false);
      res->resok->dir_wcc = make_wcc(dirfa, dirfa);
      nc->reply(nc->getvoidres());      
    }
  } else {
    nfs_fh3 fh;
    new_fh(&fh);
    ref<fsfile> file = new refcounted<fsfile>(this, fh2hex(fh));

    fattr3 fa;
    bzero(&fa, sizeof(fa));
    fa.type = NF3REG;
    fa.mode = 0777; // rwxrwxrwx
    fa.nlink = 1;
    fa.fileid = fh2fileid(fh);
    fa.atime = fa.mtime = fsutil::nfstime();

    file->create(fa, wrap(this, &fs::nfs3_create_on_create,
                          nc, dir, dirfa, file));
  }
}

void
fs::nfs3_create_on_create(nfscall *nc, ref<fsdir> dir, fattr3 dirfa,
                          ref<fsfile> file, int r, fattr3 fa)
{
  if (r != 0) 
  {
    // XXX should distinguish error cases
    nc->error(NFS3ERR_IO);
    return;
  }

  create3args *a = nc->template getarg<create3args> ();
  dir->add_file(a->where.name, file->get_fileid(),
                wrap(this, &fs::nfs3_create_on_add_file,
                     nc, dir, dirfa, file));
}

void
fs::nfs3_create_on_add_file(nfscall *nc, ref<fsdir> dir, fattr3 dirfa,
                            ref<fsfile> file, int r)
{
  if (r != 0) 
  {
    // XXX should distinguish error cases
    nc->error(NFS3ERR_IO);
    return;
  }

  dir->get_fattr(wrap(this, &fs::nfs3_create_on_final_get_fattr,
                      nc, dir, dirfa, file));
}

void
fs::nfs3_create_on_final_get_fattr(nfscall *nc, ref<fsdir> dir, fattr3 dirfa,
                                   ref<fsfile> file, bool ok, fattr3 newfa)
{
  if (!ok) 
  {
    nc->error(NFS3ERR_IO);
    return;
  }

  diropres3 *res = nc->template getres<diropres3> ();
  res->set_status(NFS3_OK);
  res->resok->obj.set_present(true);
  *res->resok->obj.handle = hex2fh(file->get_fileid());
  res->resok->obj_attributes.set_present(false);
  res->resok->dir_wcc = make_wcc(dirfa, newfa);
  nc->reply(nc->getvoidres());
}


void
fs::nfs3_mkdir(nfscall *nc)
{
  mkdir3args *a = nc->template getarg<mkdir3args> ();

}


void
fs::nfs3_remove(nfscall *nc)
{
  diropargs3 *a = nc->template getarg<diropargs3> ();

}


void
fs::nfs3_readdir(nfscall *nc)
{
  readdir3args *a = nc->template getarg<readdir3args> ();
  
  // arguments:
  // nfs_fh3 a->dir
  // int a->cookie
  // the cookie indicates where the previous READDIR left off,
  // and where this READDIR should start. that is, it tells
  // you how many directory entries to skip.
  // the cookie is just a copy of whatever you put in the reply.
  
  ref<fsdir> dir = New refcounted<fsdir>(this, fh2hex(a->dir));

  dir->get_fattr(wrap(this, &fs::nfs3_readdir_on_get_fattr,
                      nc, dir));
}

void
fs::nfs3_readdir_on_get_fattr(nfscall *nc, ref<fsdir> dir,
                             bool ok, fattr3 fa)
{
  if (!ok) 
  {
    nc->error(NFS3ERR_IO);
    return;
  }

  if (fa.type != NF3DIR) 
  {
    nc->error(NFS3ERR_NOTDIR);
    return;
  }

  dir->get_dircontents(wrap(this, &fs::nfs3_readdir_on_get_dircontents,
                            nc, dir));
}

void
fs::nfs3_readdir_on_get_dircontents(nfscall *nc, ref<fsdir> dir,
                                   int r, ptr<fsdirlist> lst)
{
  if (r != 0) 
  {
    // XXX should distinguish error cases
    nc->error(NFS3ERR_IO);
    return;
  }

  readdir3args *a = nc->template getarg<readdir3args> ();

  // skip the number of entries indicated by the cookie
  fsdirent *ent = lst->first();
  unsigned int i;
  for (i = 0; i < a->cookie; i++) 
  {
    if (ent != NULL) 
      ent = lst->next(ent);
  }

  if (ent != NULL) 
  {
    // once you've read the directory entry you want to return
    // from the block server, and have the information in
    // name and ffh (the file's file handle), here's how to
    // format and send the RPC reply. this code sends back
    // on directory entry at a time
  
    readdir3res *res = nc->template getres<readdir3res> ();
    res->set_status(NFS3_OK);
    res->resok->reply.eof = false;
    res->resok->dir_attributes.set_present(false);
    rpc_ptr<entry3> *e = &res->resok->reply.entries;
    (*e).alloc();
    (*e)->name = ent->name;
    (*e)->fileid = fh2fileid(hex2fh(ent->fileid));
    (*e)->cookie = i + 1;
    nc->reply(nc->getvoidres());
  } else {  
    // if there are no more entries to return:
    readdir3res *res = nc->template getres<readdir3res> ();
    res->set_status(NFS3_OK);
    res->resok->dir_attributes.set_present(false);
    res->resok->reply.eof = true;
    nc->reply(nc->getvoidres());
  }

}


void
fs::nfs3_fsstat(nfscall *nc)
{
  fsstat3res *res = nc->template getres<fsstat3res> ();
  res->set_status(NFS3_OK);
  res->resok->tbytes = 0;
  res->resok->fbytes = 0;
  res->resok->abytes = 0;
  res->resok->tfiles = 0;
  res->resok->ffiles = 0;
  res->resok->afiles = 0;
  res->resok->invarsec = 0;
  nc->reply(nc->getvoidres());
}

void
fs::nfs3_fsinfo(nfscall *nc)
{
  //  nfs_fh3 *fh = nc->template getarg<nfs_fh3> ();
  fsinfo3res *res = nc->template getres<fsinfo3res> ();
  res->set_status(NFS3_OK);
  res->resok->obj_attributes.set_present(false);
  res->resok->rtmax = 8192;  // max read size
  res->resok->rtpref = 8192; // preferred read size
  res->resok->rtmult = 512;  // reads should be multiple of this (?)
  res->resok->wtmax = 8192;
  res->resok->wtpref = 8192;
  res->resok->wtmult = 8192;
  res->resok->dtpref = 512;  // preferred readdir size
  res->resok->maxfilesize = 0x7fffffff;
  res->resok->time_delta.seconds = 0;
  res->resok->time_delta.nseconds = 1;
  res->resok->properties = FSF3_HOMOGENEOUS;
  // res->resok->properties = (FSF3_LINK | FSF3_SYMLINK | FSF3_HOMOGENEOUS);
  nc->reply(nc->getvoidres());
}

// allocate a new file handle with the next available number
void
fs::new_fh(nfs_fh3 *fh)
{
  fh->data.setsize(sizeof(seq));
  bcopy(&seq, fh->data.base(), sizeof(seq));
  seq+=2304;                    // there is absolutely no reason
                                // whatsoever for this choice of
                                // number.
}


// If root file handle (fhname) specified, just use it,
// since we've been started to serve an existing file system.
// Otherwise create a new root i-node for a new file system,
// insert it into the db, and call cb w/ root file handle.
void
fs::new_root(char *fhname, callback<void, bool, nfs_fh3>::ref cb)
{
  nfs_fh3 fh;
  if(fhname){
    fh = hex2fh(fhname);
  } else {
    new_fh(&fh);
  }
  
    // an i-node just contains the NFS attributes
    fattr3 fa;
    bzero(&fa, sizeof(fa));
    fa.type = NF3DIR;
    fa.mode = 0777; // rwxrwxrwx
    fa.nlink = 2;   // why 2? maybe . and .. both point here?
    fa.fileid = fh2fileid(fh);
    fa.atime = fa.mtime = fsutil::nfstime();

    fsroot = New refcounted<fsdir>(this, fh2hex(fh));
    warn << "looking for root\n";
    fsroot->get_fattr(wrap(this, &fs::new_root_on_get_fattr, cb, fh, fa));
}

void
fs::new_root_on_get_fattr(callback<void, bool, nfs_fh3>::ref cb,
                          nfs_fh3 fh, fattr3 newfa,
                          bool ok, fattr3 oldfa)
{
  if (ok)
  {
    warn << "found pre-existing root\n";
    cb(true, fh);
  } else {
    warn << "root not found; generating new root\n";
    fsroot->create(newfa, wrap(this, &fs::new_root_on_create, cb, fh));
  }
}

void
fs::new_root_on_create(callback<void, bool, nfs_fh3>::ref cb,
                       nfs_fh3 fh,
                       int r, fattr3 fa)
{
  if (r == 0) 
  {
    cb(true, fh);
  } else {
    warn << "failed to create new root with errno=" << r << " on create\n";
    cb(false, fh);
  }
}


void
fs::test_rw(nfs_fh3 fh, callback<void, bool>::ref cb)
{
  ref<fsfile> file = New refcounted<fsfile>(this, fh2hex(fh));
  str teststr = "an anchovy walks at midnight.";
  file->write(1, teststr.len(), teststr,
              wrap(this, &fs::test_rw_on_write, fh, cb, file, teststr));
}

void
fs::test_rw_on_write(nfs_fh3 fh, callback<void, bool>::ref cb,
                     ref<fsfile> file, str teststr,
                     bool ok)
{
  if (!ok)
  {
    warn << "test: write failed\n";
    cb(false);
    return;
  }

  file->read(0, teststr.len()+1,
             wrap(this, &fs::test_rw_on_read, fh, cb, file, teststr));
}

void
fs::test_rw_on_read(nfs_fh3 fh, callback<void, bool>::ref cb,
                     ref<fsfile> file, str teststr,
                     bool ok, str data)
{
  if (!ok)
  {
    warn << "test: read failed\n";
    cb(false);
    return;
  }

  if (data.len() != teststr.len() + 1)
  {
    warn << "test: read returned wrong length\n";
    warn << "test: expected " << teststr.len()+1 << ", got " << data.len();
    cb(false);
    return;
  }

  if (data[0] != '\0')
  {
    warn << "test: first char was not null\n";
    printf("first char was %x\n", data[0]);
    
    cb(false);
    return;
  }

  if (substr(data, 1, teststr.len()) != teststr)
  {
    warn << "test: strings didn't match\n";
    warn << "expected: " << teststr;
    warn << ", got " << substr(data, 1, teststr.len());
    cb(false);
    return;
  }

  cb(true);
}


void
fs::test_dir(nfs_fh3 fh, callback<void, bool>::ref cb)
{
  ref<fsdir> dir = New refcounted<fsdir>(this, fh2hex(fh));

  dir->add_file("foo", "bar",
                wrap(this, &fs::test_dir_on_add1, fh, cb, dir));
}

void
fs::test_dir_on_add1(nfs_fh3 fh, callback<void, bool>::ref cb,
                     ref<fsdir> dir,
                     int r)
{
  if (r != 0)
  {
    warn << "testdir: add1 failed: " << r << "\n";
    cb(false);
    return;
  }

  dir->add_file("baz", "blarf",
                wrap(this, &fs::test_dir_on_add2, fh, cb, dir));
}

void
fs::test_dir_on_add2(nfs_fh3 fh, callback<void, bool>::ref cb,
                     ref<fsdir> dir,
                     int r)
{
  if (r != 0)
  {
    warn << "testdir: add2 failed: " << r << "\n";
    cb(false);
    return;
  }

  dir->get_dircontents(wrap(this, &fs::test_dir_on_get_dircontents,
                            fh, cb, dir));
}

void
fs::test_dir_on_get_dircontents(nfs_fh3 fh, callback<void, bool>::ref cb,
                                ref<fsdir> dir,
                                int r, ptr<fsdirlist> lst)
{
  if (r != 0)
  {
    warn << "testdir: get failed: " << r << "\n";
    cb(false);
    return;
  }

  fsdirent *e;
  e = (*lst)["foo"];
  if (!e || e->name != "foo" || e->fileid != "bar")
  {
    warn << "testdir: failed to get foo\n";
    cb(false);
    return;
  }
  e = (*lst)["baz"];
  if (!e || e->name != "baz" || e->fileid != "blarf")
  {
    warn << "testdir: failed to get baz\n";
    cb(false);
    return;
  }
  
  cb(true);
}
