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

#define DEBUG 0

fsfile::fsfile(fs * xfs, str xfileid)
{
  ffs = xfs;
  bdb = xfs->get_bdb();  
  fileid = xfileid;
}

str
fsfile::get_fileid()
{
  return fileid;
}

void
fsfile::get_fattr(callback<void, bool, fattr3>::ref cb) 
{
  bdb->get(TYPE_FATTR, fileid,
           wrap(this, &fsfile::get_fattr_on_bdbget, cb));
}

void
fsfile::get_fattr_on_bdbget(callback<void, bool, fattr3>::ref cb,
                            bool ok, str val) 
{
  if(ok && val.len() == sizeof(fattr3)){
    fattr3 *fap = (fattr3*) val.cstr();
    cb(true, *fap);
  } else {
    fattr3 dummy;
    cb(false, dummy);
  }
}


void
fsfile::set_fattr(fattr3 fattr, callback<void, bool, fattr3>::ref cb)
{
  // Update ctime to current time
  // fattr.ctime = fsutil::nfstime();

  bdb->put(TYPE_FATTR, fileid, str((char *) &fattr, sizeof(fattr)),
           wrap(this, &fsfile::set_fattr_on_bdbput, cb, fattr));
}

void
fsfile::set_fattr_on_bdbput(callback<void, bool, fattr3>::ref cb,
                            fattr3 fa, bool ok)
{
  cb(ok, fa);
}


void
fsfile::read(uint64 offset, uint64 length,
             callback<void, bool, str>::ref cb)
{
#if DEBUG
  warn << "read: " << fileid << " offset=" << offset << " len= " << length << "\n";
#endif
  
  get_fattr(wrap(this, &fsfile::read_on_get_fattr, offset, length, cb));
}

void
fsfile::read_on_get_fattr(uint64 offset, uint64 length,
                          callback<void, bool, str>::ref cb,
                          bool ok, fattr3 fa)
{
#if DEBUG
  warn << "read-1: fa.size=" << fa.size << "\n";
#endif
    
  if (ok) {
    if (fa.size <= offset) { 
         // attempt to read entirely beyond end of file
         cb(true, "");
    } else {
         if (fa.size <= offset + length + 1) {
              // attempt to read partly beyond end of file
              length = fa.size - offset;
         }
         bdb->get(TYPE_FDATA, fileid,
                  wrap(this, &fsfile::read_on_bdbget,
                       offset, length, cb));
    }
  } else {
    cb(false, "");
  }
}

void
fsfile::read_on_bdbget(uint64 offset, uint64 length,
                       callback<void, bool, str>::ref cb,
                       bool ok, str data)
{
#if DEBUG
  warn << "read-2: " << fileid << " offset=" << offset << " len=" << length << "\n";
  warn << "read-2: data: " << data << "\n";
#endif
  if (ok) {
       cb(ok, substr(data, offset, length));
  } else {
    cb(ok, "");
  }
}


void
fsfile::write(uint64 offset, uint64 length, str data,
              callback<void, bool>::ref cb)
{
#if DEBUG
  warn << "write: " << fileid << " offset=" << offset << " len= " << length << "\n";
  warn << "write: data: " << data << "\n";
#endif
  
  if (data.len() < length) {
    cb(false);
    return;
  }

  get_fattr(wrap(this, &fsfile::write_on_get_fattr,
                 offset, length, data, cb));
}

void
fsfile::write_on_get_fattr(uint64 offset, uint64 length, str data,
                           callback<void, bool>::ref cb,
                           bool ok, fattr3 fa)
{
  if (!ok) {
       cb(false);
       return;
  }

  bdb->get(TYPE_FDATA, fileid,
           wrap(this, &fsfile::write_on_bdb_get,
                offset, length, data, cb, fa));
}

void
fsfile::write_on_bdb_get(uint64 offset, uint64 length, str data,
                         callback<void, bool>::ref cb,
                         fattr3 fa, bool ok, str filedata)
{
  if (!ok) {
       cb(false);
       return;
  }

  strbuf newdata;

  if (filedata.len() != fa.size) 
  {
    // XXX
//     warn << "file " << fileid << " had incorrect size: expected "
//          << fa.size << ", got " << filedata.len() << "\n";
  }
  
  if (length <= data.len())
     length = data.len();

  str nstr = str("\0", 1);
  newdata << substr(filedata, 0, offset);
  if (offset > filedata.len()) {
    for (uint64 i = 0; i < offset - filedata.len(); i++)
      newdata << nstr;
  }
  
  newdata << substr(data, 0, length);
  newdata << substr(filedata, offset+length, filedata.len());

  fa.size = MAX(fa.size, offset+length);
  fa.mtime = fsutil::nfstime();
  fa.ctime = fsutil::nfstime();

#if DEBUG
  warn << "write-3: " << fileid << " oldlen=" << filedata.len()
       << "newlen=" << fa.size << "\n";
#endif
  
  set_fattr(fa, wrap(this, &fsfile::write_on_set_fattr, cb, newdata));
}

void
fsfile::write_on_set_fattr(callback<void, bool>::ref cb, str newdata,
                                bool ok, fattr3 fa)
{
  if (!ok) {
       cb(false);
       return;
  }

  bdb->put(TYPE_FDATA, fileid, newdata,
           wrap(this, &fsfile::write_on_bdb_put, cb));
}

void
fsfile::write_on_bdb_put(callback<void, bool>::ref cb, bool ok)
{
  cb(ok);
}


void
fsfile::set_size(uint64 size, callback<void, bool>::ref cb)
{
  get_fattr(wrap(this, &fsfile::set_size_on_get_fattr, size, cb));
}

void
fsfile::set_size_on_get_fattr(uint64 size, callback<void, bool>::ref cb,
                              bool ok, fattr3 fa)
{
  if (!ok) 
  {
    cb(false);
    return;
  }
  
  fa.size = size;
  set_fattr(fa, wrap(this, &fsfile::set_size_on_set_fattr, size, cb));
}

void
fsfile::set_size_on_set_fattr(uint64 size, callback<void, bool>::ref cb,
                              bool ok, fattr3 fa)
{
  if (!ok || fa.size != size)
  {
    cb(false);
    return;
  }

  cb(true);
}


void
fsfile::create(fattr3 newfa, callback<void, int, fattr3>::ref cb)
{
  get_fattr(wrap(this, &fsfile::create_on_get_fattr, newfa, cb));
}

void
fsfile::create_on_get_fattr(fattr3 newfa, callback<void, int, fattr3>::ref cb,
                            bool ok, fattr3 oldfa)
{
  if (ok)
  {
    cb(EEXIST, oldfa);
    return;
  }

  set_fattr(newfa, wrap(this, &fsfile::create_on_set_fattr, cb));
}

void
fsfile::create_on_set_fattr(callback<void, int, fattr3>::ref cb,
                            bool ok, fattr3 setfa)
{
  if (!ok) 
  {
    cb(EIO, setfa);
    return;
  }

  strbuf buf;
  for (uint64 i = 0; i < setfa.size; i++)
    buf << '\0';
  bdb->put(TYPE_FDATA, fileid, buf,
           wrap(this, &fsfile::create_on_bdb_put, cb, setfa));
}

void
fsfile::create_on_bdb_put(callback<void, int, fattr3>::ref cb,
                          fattr3 setfa, bool ok)
{
  if (ok)
    cb(0, setfa);
  else
    cb(EIO, setfa);
}
