#include "persifs.h"
#include "fsfile.h"
#include "inodelog.h"
#include "fs.h"
#include "chunkable.h"
#include "writegrouper.h"
#include "lockmanager.h"

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


void
fsfile::create(ref<FS> fs, fattr3 fa,
               callback<void, ref<fsfile> >::ref cb, cbe error) 
{
  // Start by making an empty chunkable
  chunkable::create(fs->blob, "",
                    wrap(&fsfile::create_after_chunkableCreate,
                         fs, fa, cb, error),
                    error);
}

void
fsfile::create_after_chunkableCreate(ref<FS> fs, fattr3 fa,
                                     callback<void, ref<fsfile> >::ref cb,
                                     cbe error,
                                     ref<chunkable> chunk)
{
  // Make an inode
  inode ino;
  ino.setFromFattr(fa);
  // I have to make a callback just for this? I hate libasync.
  fs->log->newInumber(wrap(&fsfile::create_after_newInumber,
                           fs, chunk, ino, cb, error), error);
}

void
fsfile::create_after_newInumber(ref<FS> fs, ref<chunkable> chunk,
                                inode ino,
                                callback<void, ref<fsfile> >::ref cb,
                                cbe error, inumber num)
{
  ino.num = num;
  fs->grouper->write(num, ino, chunk);

  ref<fsfile> file = New refcounted<fsfile>(fs, chunk, ino,
                                            TIMESTAMP_LATEST);
  file->writable = true;
  file->acquireLock(false, wrap(&fsfile::create_after_acquireLock,
                                cb, error, file), error);
}

void
fsfile::create_after_acquireLock(callback<void, ref<fsfile> >::ref cb,
                                 cbe error, ref<fsfile> file)
{
  cb(file);
}


void
fsfile::load(ref<FS> fs, timestamp ts, inumber num, int flags,
             callback<void, ref<fsfile> >::ref cb, cbe error)
{
  if (ts == TIMESTAMP_LATEST) {
    loadLatest(fs, num, flags, cb, error);
  } else {
    loadPast(fs, ts, num, flags, cb, error);
  }
}

void
fsfile::loadLatest(ref<FS> fs, inumber num, int flags,
                   callback<void, ref<fsfile> >::ref cb, cbe error)
{
  inode dummyInode;             // Don't actually need to load the
                                // real inode yet,
  dummyInode.num = num;         // but we need to know the inumber
                                // somehow.
  ref<fsfile> file = New refcounted<fsfile>(fs,
                                            (ptr<chunkable>) NULL,
                                            dummyInode,
                                            TIMESTAMP_LATEST);

  file->writable = (flags & L_WRITE);
  
  if (flags & L_LOCKNOW) {
    file->acquireLock(true, wrap(&fsfile::loadLatest_after_acquireLock,
                          file, cb, error), error);
  } else {
    cb(file);
  }
}

void
fsfile::loadLatest_after_acquireLock(
  ref<fsfile> file,
  callback<void, ref<fsfile> >::ref cb,
  cbe error)
{
  cb(file);
}


void
fsfile::loadPast(ref<FS> fs, timestamp ts, inumber num, int flags,
                 callback<void, ref<fsfile> >::ref cb, cbe error)
{
  // If it's a past timestamp, there's no need to worry about
  // locking etc. since we can't write it
  if (flags & L_WRITE || flags & L_LOCKNOW) {
    error(PERR_INVAL);
    return;
  }

  fs->log->get(ts, num, wrap(&fsfile::loadPast_after_logGet,
                             fs, ts, flags, cb, error), error);
}

void
fsfile::loadPast_after_logGet(ref<FS> fs, timestamp ts, int flags,
                              callback<void, ref<fsfile> >::ref cb,
                              cbe error, inode ino)
{
  // Unmarshal the chunkable
  chunkable::unmarshall(fs->blob, ino.content,
                        wrap(&fsfile::loadPast_after_unmarshal,
                             fs, ts, ino, cb, error), error);
}

void
fsfile::loadPast_after_unmarshal(ref<FS> fs, timestamp ts,
                                 inode ino,
                                 callback<void, ref<fsfile> >::ref cb,
                                 cbe error,  ref<chunkable> content)
{
  ref<fsfile> file = New refcounted<fsfile>(fs, content, ino, ts);

  file->writable = false;
  file->lockHeld = true;        // There is no lock.
  
  cb(file);
}



ref<FS>
fsfile::getFS()
{
  return fs;
}


inumber
fsfile::getInumber()
{
  return ino.num;
}


void
fsfile::read(uint pos, uint len, callback<void, str>::ref cb, cbe error)
{
  acquireLock(true, WRAP(read_after_acquireLock, pos, len, cb, error), error);
  
}

void
fsfile::read_after_acquireLock(uint pos, uint len,
                       callback<void, str>::ref cb, cbe error)
{
  content->readSubstr(pos, len, cb, error);
}


void
fsfile::write(uint pos, str data, callback<void>::ref cb, cbe error)
{
  if (!writable) {
    fatal << "fsfile: attempt to write to read-only file\n";
  }

  acquireLock(true, WRAP(write_after_acquireLock, pos, data, cb, error), error);
}

void
fsfile::write_after_acquireLock(uint pos, str data,
                                callback<void>::ref cb, cbe error)
{
  content->writeSubstr(pos, data, WRAP(write_after_writeSubstr,
                                       pos, data, cb, error), error);
}

void
fsfile::write_after_writeSubstr(uint pos, str data,
                                callback<void>::ref cb, cbe error)
{
  content->length(WRAP(write_after_length, pos, data, cb, error), error);
}

void
fsfile::write_after_length(uint pos, str data,
                   callback<void>::ref cb, cbe error,
                   unsigned long len)
{
  ino.size = len;
  ino.mtime = ino.ctime = nfstime();
  fs->grouper->write(ino.num, ino, content);
  cb();
}


void
fsfile::getAttr(callback<void, fattr3>::ref cb, cbe error)
{
  acquireLock(true, WRAP(getAttr_after_acquireLock, cb, error), error);
}

void
fsfile::getAttr_after_acquireLock(callback<void, fattr3>::ref cb, cbe error)
{
  cb(ino);
}


void
fsfile::setAttr(fattr3 fa,
                callback<void, fattr3, fattr3>::ref cb, cbe error)
{
  if (!writable) {
    fatal << "fsfile: attempt to setattr a read-only file\n";
  }

  acquireLock(true, WRAP(setAttr_after_acquireLock,
                   fa, cb, error), error);
}

void
fsfile::setAttr_after_acquireLock(
  fattr3 fa,
  callback<void, fattr3, fattr3>::ref cb, cbe error)
{
  content->truncate(fa.size, WRAP(setAttr_after_truncate,
                                  fa, cb, error), error);
}

void
fsfile::setAttr_after_truncate(
  fattr3 fa,
  callback<void, fattr3, fattr3>::ref cb, cbe error)
{
  inode oldInode = ino;
  ino.setFromFattr(fa);
  ino.ctime = nfstime();
  fs->grouper->write(ino.num, ino, content);
  cb(oldInode, ino);
}


void
fsfile::acquireLock(bool load, callback<void>::ref cb, cbe error)
{
  if (lockHeld) {
    cb();
    return;
  }

  // Grab the lock
  fs->lockMgr->acquire(ino.num, WRAP(acquireLock_after_acquire,
                                     load, cb, error));
}

void
fsfile::acquireLock_after_acquire(bool load,
                                  callback<void>::ref cb, cbe error)
{
  if (!load) {
    lockHeld = true;
    cb();
    return;
  }
  
  // See if the write grouper knows anything about this file
  const fileState * state = fs->grouper->read(ino.num);

  if (state != NULL) {
    ino = state->ino;
    content = state->content;
    lockHeld = true;
    cb();   
  } else {
    // Nope, better go fetch the inode
    fs->log->get(ts, ino.num, WRAP(acquireLock_after_inodeGet,
                                   cb, error),
                 error);
  }
}

void
fsfile::acquireLock_after_inodeGet(callback<void>::ref cb, cbe error,
                                   inode ino)
{
  this->ino = ino;
  // Unmarshal the chunkable
  chunkable::unmarshall(fs->blob, ino.content,
                        WRAP(acquireLock_after_unmarshall,
                             cb, error),
                        error);
}

void
fsfile::acquireLock_after_unmarshall(callback<void>::ref cb, cbe error,
                                     ref<chunkable> ch)
{
  content = ch;
  lockHeld = true;
  cb();
}

fsfile::fsfile(ref<FS> fs, ptr<chunkable> content, inode ino, timestamp ts)
  : fs(fs), content(content), ino(ino), ts(ts)
{
  writable = false;
  lockHeld = false;
}

fsfile::~fsfile()
{
  if (lockHeld && ts == TIMESTAMP_LATEST) {
    fs->lockMgr->release(ino.num);
  }
}
