/*
 * Directory module
 *
 * This module provides an abstraction around the disk representation
 * of directories.
 */

#ifndef FSDIR_H
#define FSDIR_H

#include "persifs.h"
#include "fsfile.h"
#include <itree.h>

class fsdirEntry
{
public:
  /**
   * Get the name of this entry.
   */
  str getName() { return name; }
  /**
   * Get the inumber of this entry.
   */
  inumber getInumber() { return num; }

protected:
  fsdirEntry(str name, inumber num)
    : name(name), num(num) {}
  
private:
  friend class fsdir;

  str name;
  inumber num;

  itree_entry<fsdirEntry> link;
};

class fsdir
{
private:
  typedef itree<str, fsdirEntry,
                &fsdirEntry::name, &fsdirEntry::link> entriesTree;

public:
  /**
   * Create a new directory containing only . and .. entries.  Expects
   * fa.mode, fa.atime, and fa.mtime to be set.  This is a factory
   * function.
   */
  static void create(ref<fsdir> parent, fattr3 fa,
                     callback<void, ref<fsdir> >::ref cb, cbe error);

  /**
   * Create a root directory which has no parent.  By convention, the
   * .. points back to this directory.  Expects fa.mode, fa.atime, and
   * fa.mtime to be set.  This is a factory function.
   */
  static void createRoot(ref<FS> fs, fattr3 fa, 
                         callback<void, ref<fsdir> >::ref cb, cbe error);

  /**
   * Load an existing directory from a file.  This is a factory
   * function.
   */
  static void load(ref<fsfile> file,
                   callback<void, ref<fsdir> >::ref cb, cbe error);

  /**
   * Get the file backing this directory.
   */
  ref<fsfile> getFile();
  
  /**
   * Lookup an entry in this directory.  If the entry doesn't exist,
   * this calls error with PERR_NOENT.
   */
  void lookup(str name, callback<void, ref<fsdirEntry> >::ref cb, cbe error);
  /**
   * Add an entry to this directory.  It is a PERR_EXIST error if an
   * entry with this name already exists.
   */
  void add(str name, inumber num,
           callback<void, ref<fsdirEntry> >::ref cb, cbe error);
  /**
   * Add an entry to this directory.  This is a convenience overload
   * for the above add method.
   */
  void add(str name, ref<fsfile> file,
           callback<void, ref<fsdirEntry> >::ref cb, cbe error);
  /**
   * Remove an entry from this directory.  
   */
  void remove(str name, callback<void>::ref cb, cbe error);
  /**
   * Traverse the entries in the directory.  Calls cb for every entry,
   * starting with cookie, passing it the cookie of the next entry
   * (for ease of dealing with NFS quirkiness).  When cb returns
   * false, stops iterating and invokes doneCB.  Set cookie to 0 to
   * start at the beginning of the directory.  If there are no more
   * directory entries, doneCB is passed true.
   */
  void traverse(int cookie, callback<bool, int, ref<fsdirEntry> >::ref cb,
                callback<void, bool>::ref doneCB, cbe error);

protected:
  fsdir(ref<fsfile> file, ref<entriesTree> entries);
  
private:
  ref<fsfile> file;
  ref<entriesTree> entries;

  static void create_create(inumber parent, ref<FS> fs, fattr3 fa, 
                            callback<void, ref<fsdir> >::ref cb, cbe error);
  static void create_after_createFile(inumber parent,
                                      callback<void, ref<fsdir> >::ref cb,
                                      cbe error,
                                      ref<fsfile> file);
  static void create_after_flush(ref<fsdir> dir,
                                 callback<void, ref<fsdir> >::ref cb,
                                 cbe error);
  static void load_after_getAttr(ref<fsfile> file,
                                 callback<void, ref<fsdir> >::ref cb, cbe error,
                                 fattr3 fa);
  static void load_after_read(ref<fsfile> file,
                              callback<void, ref<fsdir> >::ref cb, cbe error,
                              str data);
  void add_after_flush(fsdirEntry *ent, 
                       callback<void, ref<fsdirEntry> >::ref cb, cbe error);
  
  ref<fsdirEntry> getEntryRef(fsdirEntry *ent);
  
  void flush(callback<void>::ref cb, cbe error);
  void flush_after_write(unsigned int dataLen,
                         callback<void>::ref cb, cbe error);
  void flush_after_getAttr(unsigned int dataLen,
                           callback<void>::ref cb, cbe error,
                           fattr3 fa);
  void flush_after_setAttr(callback<void>::ref cb, cbe error,
                           fattr3 oldAttr, fattr3 newAttr);
};

#endif // FSDIR_H

