#ifndef NFSPROVIDER_H
#define NFSPROVIDER_H

#include "persifs.h"
#include "fs.h"
#include "fsdir.h"
#include <nfsserv.h>

class nfsProvider
{
public:
  static void create(ref<FS> fs, callback<void, ref<nfsProvider> >::ref cb,
                     cbe error);

  void dispatch(nfscall *nc);

  nfs_fh3 getRootFH();

protected:
  nfsProvider(ref<FS> fs);

private:
  static const int FHSIZE = sizeof(timestamp)+sizeof(inumber);
  ref<FS> fs;

  fattr3 rootAttr;
  nfs_fh3 rootFH;
  fattr3 infoAttr;
  nfs_fh3 infoFH;
  str rootCurrentName;
  str rootInfoName;

  struct readdirEntry
  {
    str name;
    inumber num;
    int cookie;
    tailq_entry<readdirEntry> link;
  };
  typedef tailq<readdirEntry, &readdirEntry::link> readdirEntryList;

  void nfsError(nfscall *nc, pStat err);
  bool ensureNotRoot(nfscall *nc, nfs_fh3 fh);
  bool ensureCurrent(nfscall *nc, nfs_fh3 fh);

  bool isRootFH(nfs_fh3 fh);
  bool isInfoFH(nfs_fh3 fh);
  bool isMagicFH(nfs_fh3 fh);
  timestamp fhToTS(nfs_fh3 fh);
  inumber fhToNum(nfs_fh3 fh);
  nfs_fh3 TSNumToFh(timestamp ts, inumber num, bool allowZeroNum = false);
  uint64 TSNumToFileid(timestamp ts, inumber num);

  fattr3 applySetAttrs(fattr3 base, sattr3 setAttrs);
  wcc_data makeWcc(fattr3 before, fattr3 after);

  fattr3 getInfoAttr();
  str getInfoContents();

  void getattr(nfscall *nc);
  void getattr_after_load(nfscall *nc, ref<fsfile> file);
  void getattr_after_get(nfscall *nc, ref<fsfile> file, fattr3 attr);
  void getattr_done(nfscall *nc, fattr3 attr);

  void setattr(nfscall *nc);
  void setattr_after_load(nfscall *nc, sattr3 newAttrs,
                          ref<fsfile> file);
  void setattr_after_get(nfscall *nc, sattr3 newAttrs, ref<fsfile> file,
                         fattr3 attrs);
  void setattr_after_set(nfscall *nc, ref<fsfile> file,
                         fattr3 oldAttrs, fattr3 newAttrs);

  void lookup(nfscall *nc);
  void lookup_after_loadDir(nfscall *nc, str name, timestamp ts,
                            ref<fsdir> dir);
  void lookup_after_lookup(nfscall *nc, timestamp ts, ref<fsdir> dir,
                           ref<fsdirEntry> ent);
  void lookup_root(nfscall *nc, str name);
  void lookup_done(nfscall *nc, nfs_fh3 fh);

  void access(nfscall *nc);
  void access_after_load(nfscall *nc, uint32 access,
                         ref<fsfile> file);
  void access_after_get(nfscall *nc, uint32 access, ref<fsfile> file,
                        fattr3 attrs);
  void access_done(nfscall *nc, uint32 access, uint32 realAccess,
                   fattr3 attrs);
  
  void read(nfscall *nc);
  void read_after_load(nfscall *nc, uint64 offset, uint32 count,
                       ref<fsfile> file);
  void read_after_read(nfscall *nc, uint64 end, ref<fsfile> file,
                       str data);
  void read_after_getAttr(nfscall *nc, uint64 end, ref<fsfile> file,
                          str data,
                          fattr3 attr);
  void read_done(nfscall *nc, str data, bool eof);

  void write(nfscall *nc);
  void write_after_load(nfscall *nc, uint64 offset, str data,
                        ref<fsfile> file);
  void write_after_getOldAttr(nfscall *nc, uint64 offset, str data,
                              ref<fsfile> file,
                              fattr3 oldAttr);
  void write_after_write(nfscall *nc, uint32 count, ref<fsfile> file,
                         fattr3 oldAttr);
  void write_after_getNewAttr(nfscall *nc, uint32 count,
                              ref<fsfile> file, fattr3 oldAttr,
                              fattr3 newAttr);

  void create(nfscall *nc);
  void mkdir(nfscall *nc);
  void createOrMkdir_after_done(nfscall *nc,
                                ref<fsdir> parent, nfs_fh3 fileFH,
                                wcc_data wcc);

  void remove(nfscall *nc);
  void remove_after_load(nfscall *nc, str name,
                         ref<fsdir> dir);
  void remove_after_getOldAttr(nfscall *nc, str name, ref<fsdir> dir,
                               fattr3 oldAttr);
  void remove_after_remove(nfscall *nc, ref<fsdir> dir, fattr3 oldAttr);
  void remove_after_getNewAttr(nfscall *nc, ref<fsdir> dir,
                               fattr3 oldAttr,
                               fattr3 newAttr);

  void readdir(nfscall *nc);
  void readdir_after_loadDir(nfscall *nc, int cookie, timestamp ts,
                             ref<fsdir> dir);
  bool readdir_on_entry(ref<readdirEntryList> el, int *count,
                        int cookie, ref<fsdirEntry> entry);
  void readdir_after_traverse(nfscall *nc, ref<fsdir> dir, timestamp ts,
                              ref<readdirEntryList> el,
                              bool eof);
  void readdir_root(nfscall *nc, int cookie);

  void fsstat(nfscall *nc);
  void fsinfo(nfscall *nc);

  /**
   * Attempts to create file named name in parent with attributes
   * attrs.  If a file by that name already exists, it calls existsCB.
   * Otherwise, it calls createdCB with the newly created file.  Does
   * not actually add the file to parent.
   */
  //typedef callback<void, ref<fsdir>, nfs_fh3, fattr3, fattr3>::ref mcDoneCB;
  typedef callback<void, ref<fsdir>, nfs_fh3, wcc_data>::ref mcDoneCB;
  struct createState
  {
    str name;
    fattr3 attrs;
    mcDoneCB doneCB;
    cbe error;
    ptr<fsdir> parent;
    fattr3 oldAttr;
    ptr<fsfile> file;
    nfs_fh3 fileFH;
    createState(str name, fattr3 attrs, mcDoneCB doneCB, cbe error)
      : name(name), attrs(attrs), doneCB(doneCB), error(error)
    {}
  };
  void generalCreate(nfs_fh3 parent, str name, fattr3 attrs,
                     mcDoneCB doneCB, cbe error);
  void generalCreate_after_loadDir(ref<createState> s,
                                   ref<fsdir> parent);
  void generalCreate_after_getOldAttr(ref<createState> s,
                                      fattr3 oldAttr);
  void generalCreate_after_lookup(ref<createState> s,
                                  ref<fsdirEntry> ent);
  void generalCreate_after_lookupFail(ref<createState> s,
                                      pStat err);
  void generalCreate_after_createDir(ref<createState> s,
                                     ref<fsdir> dir);
  void generalCreate_after_createFile(ref<createState> s,
                                      ref<fsfile> file);
  void generalCreate_after_add(ref<createState> s,
                               ref<fsdirEntry> ent);
  void generalCreate_after_getNewAttr(ref<createState> s,
                                      fattr3 newAttr);

  void loadDir(nfs_fh3 dir, int flags,
               callback<void, ref<fsdir> >::ref cb, cbe error);
  void loadDir_after_load(callback<void, ref<fsdir> >::ref cb, cbe error,
                          ref<fsfile> file);
};

#endif // NFSPROVIDER_H
