#include "fsdir.h"
#include "happyio.h"

// XXX Deal with reference counting

#define WRAP(args...) wrap(this, &fsdir::args)

static const unsigned int DIR_MAGIC_NUMBER = 0xE803A0CB;

void
fsdir::create(ref<fsdir> parent, fattr3 fa,
              callback<void, ref<fsdir> >::ref cb, cbe error)
{
  create_create(parent->getFile()->getInumber(), parent->getFile()->getFS(),
                fa, cb, error);
}

void
fsdir::createRoot(ref<FS> fs, fattr3 fa, 
                  callback<void, ref<fsdir> >::ref cb, cbe error)
{
  create_create(0, fs, fa, cb, error);
}

void
fsdir::create_create(inumber parent, ref<FS> fs, fattr3 fa, 
                     callback<void, ref<fsdir> >::ref cb, cbe error)
{
  fattr3 fa2;

  bzero(&fa2, sizeof(fa2));
  fa2.type = NF3DIR;
  fa2.nlink = 2;
  fa2.atime = fa.atime;
  fa2.mtime = fa.mtime;
  fa2.mode = fa.mode;

  fsfile::create(fs, fa2,
                 wrap(create_after_createFile, parent, cb, error), error);
}

void
fsdir::create_after_createFile(inumber parent,
                               callback<void, ref<fsdir> >::ref cb, cbe error,
                               ref<fsfile> file)
{
  ref<entriesTree> entries = New refcounted<entriesTree>;
  fsdirEntry *entry;

  // Create . and ..
  entry = New fsdirEntry(str("."), file->getInumber());
  entries->insert(entry);
  entry = New fsdirEntry(str(".."), parent);
  entries->insert(entry);

  ref<fsdir> dir = New refcounted<fsdir>(file, entries);

  dir->flush(wrap(create_after_flush, dir, cb, error), error);
}

void
fsdir::create_after_flush(ref<fsdir> dir,
                          callback<void, ref<fsdir> >::ref cb, cbe error)
{
  cb(dir);
}

void
fsdir::load(ref<fsfile> file,
            callback<void, ref<fsdir> >::ref cb, cbe error)
{
  // Get attributes so I can make sure this is a directory and just
  // read in the whole string
  file->getAttr(wrap(load_after_getAttr, file, cb, error), error);
}

void
fsdir::load_after_getAttr(ref<fsfile> file,
                          callback<void, ref<fsdir> >::ref cb, cbe error,
                          fattr3 fa)
{
  if (fa.type != NF3DIR)
    error(PERR_NOTDIR);
  else
    file->read(0, fa.size, wrap(load_after_read, file, cb, error), error);
}

void
fsdir::load_after_read(ref<fsfile> file,
                       callback<void, ref<fsdir> >::ref cb, cbe error,
                       str data)
{
  // Unmarshall data
  strReader r(data);
  ref<entriesTree> entries = New refcounted<entriesTree>;
  fsdirEntry *entry;

  if (r.readLong() != DIR_MAGIC_NUMBER) {
    fatal << "BUG: Directory didn't start with magic number\n";
  }
  
  while (true) {
    inumber num;
    r.read(&num, sizeof(inumber));
    if (r.eos())
      break;
    str name = r.readStr();
    entry = New fsdirEntry(name, num);
    entries->insert(entry);
  }

  ref<fsdir> dir = New refcounted<fsdir>(file, entries);

  cb(dir);
}

fsdir::fsdir(ref<fsfile> file, ref<entriesTree> entries)
  : file(file), entries(entries)
{
}

ref<fsfile>
fsdir::getFile()
{
  return file;
}

void
fsdir::lookup(str name, callback<void, ref<fsdirEntry> >::ref cb, cbe error)
{
  fsdirEntry *ent = (*entries)[name];

  if (ent) {
    ref<fsdirEntry> rent = getEntryRef(ent);
    cb(rent);
  } else {
    error(PERR_NOENT);
  }
}

void
fsdir::add(str name, inumber num,
           callback<void, ref<fsdirEntry> >::ref cb, cbe error)
{
  if ((*entries)[name])
    error(PERR_EXISTS);
  else {
    fsdirEntry *ent = New fsdirEntry(name, num);
    entries->insert(ent);
    flush(WRAP(add_after_flush, ent, cb, error), error);
  }
}

void
fsdir::add_after_flush(fsdirEntry *ent, 
                       callback<void, ref<fsdirEntry> >::ref cb, cbe error)
{
  ref<fsdirEntry> rent = getEntryRef(ent);
  cb(rent);
}

void
fsdir::add(str name, ref<fsfile> file,
           callback<void, ref<fsdirEntry> >::ref cb, cbe error)
{
  add(name, file->getInumber(), cb, error);
}

void
fsdir::remove(str name, callback<void>::ref cb, cbe error)
{
  fsdirEntry *ent = (*entries)[name];

  if (!ent)
    error(PERR_NOENT);
  else {
    entries->remove(ent); // XXX Leak?
    flush(cb, error);
  }
}

void
fsdir::traverse(int cookie, callback<bool, int, ref<fsdirEntry> >::ref cb,
                callback<void, bool>::ref doneCB, cbe error)
{
  fsdirEntry *ent;

  // Skip the first cookie elements in entries
  int i;
  for (i = 0, ent = entries->first();
       ent && i < cookie;
       ent = entries->next(ent), ++i);

  if (!ent) {
    doneCB(true);
  } else {
    // Traverse the remaining elements
    for (; ent; ent = entries->next(ent), ++i) {
      ref<fsdirEntry> rent = getEntryRef(ent);
      if (!cb(i+1, rent))
        break;
    }
  }

  doneCB(!ent || !entries->next(ent));
}

ref<fsdirEntry>
fsdir::getEntryRef(fsdirEntry *ent)
{
  ref<fsdirEntry> rent = New refcounted<fsdirEntry>(ent->getName(),
                                                    ent->getInumber());
  return rent;
}

void
fsdir::flush(callback<void>::ref cb, cbe error)
{
  strWriter w;

  w.writeLong(DIR_MAGIC_NUMBER);

  fsdirEntry *ent;
  for (ent = entries->first(); ent; ent = entries->next(ent)) {
    inumber num = ent->getInumber();
    w.write(&num, sizeof(inumber));
    w.writeStr(ent->getName());
  }

  str data = w.getStr();

  file->write(0, data, WRAP(flush_after_write, data.len(), cb, error),
              error);
}

void
fsdir::flush_after_write(unsigned int dataLen,
                         callback<void>::ref cb, cbe error)
{
  // Need to truncate fsfile to length of data
  file->getAttr(WRAP(flush_after_getAttr, dataLen, cb, error), error);
}

void
fsdir::flush_after_getAttr(unsigned int dataLen,
                           callback<void>::ref cb, cbe error,
                           fattr3 fa)
{
  fa.size = dataLen;
  file->setAttr(fa, WRAP(flush_after_setAttr, cb, error), error);
}

void
fsdir::flush_after_setAttr(callback<void>::ref cb, cbe error,
                           fattr3 oldAttr, fattr3 newAttr)
{
  cb();
}
