#include "persifs.h"
#include "fs.h"
#include "diskblobindex.h"
#include "disksuperblob.h"
#include "logginginodelog.h"
#include "arborescentinodelog.h"
#include "fsdir.h"
#include "lockmanager.h"
#include "writegrouper.h"

static const bool DEBUG = true;

void
FS::create(fsCreateArgs args,
           callback<void, ref<FS> >::ref cb, cbe error)
{
  strbuf idxName(args.filebase);
  idxName << ".bdx";
  if (DEBUG)
    warn << "fs: Creating blob index " << idxName << "\n";

  diskBlobIndex::create(idxName,
                        wrap(create_afterBlobIndex,
                             args, cb, error),
                        error);
}

void
FS::create_afterBlobIndex(fsCreateArgs args,
                          callback<void, ref<FS> >::ref cb, cbe error,
                          ref<diskBlobIndex> bi)
{
  strbuf blobName(args.filebase);
  blobName << ".blb";
  if (DEBUG)
    warn << "fs: Creating superblob " << blobName << "\n";
  diskSuperblob::create(blobName, bi,
                        wrap(create_afterSuperblob,
                             args, cb, error),
                        error);
}

void
FS::create_afterSuperblob(fsCreateArgs args,
                          callback<void, ref<FS> >::ref cb, cbe error,
                          ref<diskSuperblob> blob)
{
  if (!args.arborescent) {
    strbuf logName(args.filebase);
    logName << ".ilg";
    if (DEBUG)
      warn << "fs: Creating logging inode log " << logName << "\n";

    loggingInodeLog::create(logName, blob,
                            wrap(create_afterInodeLog,
                                 args, blob, cb, error),
                            error);
  } else {
    strbuf imapName(args.filebase);
    imapName << ".ili";
    strbuf rootMapName(args.filebase);
    rootMapName << ".ilr";
    strbuf tmapName(args.filebase);
    tmapName << ".ilt";
    if (DEBUG) {
      warn << "fs: Creating arborescent inode log\n";
      warn << "fs:   inode map " << imapName << "\n";
      warn << "fs:   root map  " << rootMapName << "\n";
      warn << "fs:   time map  " << tmapName << "\n";
    }

    arborescentInodeLog::create(imapName, rootMapName, args.imapCacheSize,
                                tmapName,
                                wrap(create_afterInodeLog,
                                     args, blob, cb, error),
                                error);
  }
}

void
FS::create_afterInodeLog(fsCreateArgs args, ref<diskSuperblob> blob,
                         callback<void, ref<FS> >::ref cb, cbe error,
                         ref<inodeLog> log)
{
  ref<FS> fs = New refcounted<FS>(blob, log, args.writeGrouping);

  // Check if a root already exists
  log->getRootInumber(wrap(create_afterGetRoot, fs, cb, error),
                      error);
}

void
FS::create_afterGetRoot(ref<FS> fs,
                        callback<void, ref<FS> >::ref cb, cbe error,
                        inumber rootNum)
{
  if (rootNum == 0) {
    // Create the root of the file system
    if (DEBUG)
      warn << "fs: Creating new FS root\n";

    fattr3 rootAttr;
    bzero(&rootAttr, sizeof(rootAttr));
    rootAttr.mode = 0777;
    rootAttr.atime = rootAttr.mtime = nfstime();
    fsdir::createRoot(fs, rootAttr,
                      wrap(create_afterCreateRoot, fs, cb, error), error);
  } else {
    // Root already exists
    if (DEBUG)
      warn << "fs: Root already exists at inumber " << rootNum << "\n";

    fs->setRootInumber(rootNum);

    cb(fs);
  }
}

void
FS::create_afterCreateRoot(ref<FS> fs,
                           callback<void, ref<FS> >::ref cb, cbe error,
                           ref<fsdir> rootDir)
{
  inumber rootNum = rootDir->getFile()->getInumber();

  if (DEBUG)
    warn << "fs: Root created at inumber " << rootNum << "\n";

  fs->setRootInumber(rootNum);
  fs->log->setRootInumber(rootNum, wrap(create_afterSetRoot, fs, cb, error),
                          error);
}

void
FS::create_afterSetRoot(ref<FS> fs,
                        callback<void, ref<FS> >::ref cb, cbe error)
{
  cb(fs);
}

FS::FS(ref<superblob> blob, ref<inodeLog> log, int writeGrouping)
  : blob(blob), log(log),
    lockMgr(New refcounted<lockManager>()),
    grouper(New refcounted<writeGrouper>(this, writeGrouping))
{
}

inumber
FS::getRootInumber()
{
  return rootInumber;
}

void
FS::setRootInumber(inumber num)
{
  rootInumber = num;
}
