/*
 * Inode log module
 *
 * This module maintains the inode log on disk and provides the
 * interface to the inode map internally.
 */

#ifndef INODELOG_H
#define INODELOG_H

#include "persifs.h"
#include "chunkable.h"
#include "superblob.h"
#include <nfs3_prot.h>
#include <time.h>
#include <stdio.h>
#include <qhash.h>

/**
 * The time, in seconds, between pondering creating a snapshot.
 */
static const int SNAPSHOT_TIME = 60;

/**
 * The minimum number of entries since the last snapshot in order to
 * write a new snapshot.
 */
static const int SNAPSHOT_MIN_CHANGES = 200;


typedef time_t timestamp;
static const timestamp TIMESTAMP_LATEST = ((time_t)-1);

typedef unsigned long long inumber;
class inode;

/**
 * The inode map maps inumbers to block addresses at one point in
 * time.
 */
class inodeMap
{
public:
  inodeMap(FILE *fp);
  void clear();
  void replayFromSnapshot(unsigned long snapshotOffset, timestamp to);
  unsigned long writeSnapshot(timestamp its);

  void modify(timestamp its, inumber num, blockAddress ba);
  void remove(timestamp its, inumber num);
  blockAddress get(inumber num);
  inumber newInumber();
  int getModificationCount();

private:
  qhash<inumber, blockAddress> imap;
  timestamp ts;

  FILE *fp;
  timestamp latest;
  inumber nextInumber;
  unsigned long lastSnapshot;
  int modifications;

  void readSnapshot();
  void replay(timestamp to);

  void writeSnapshotEntry(const inumber &num, blockAddress *ba);

  void verifyWritable(timestamp its);
};

/**
 * The snapshot map keeps track of where all of the snapshots are
 * located in the inode log.
 */
class snapshotMap
{
public:
  snapshotMap(FILE *fp);
  void clear();
  void load(unsigned long lastSnapshotOffset);
  void add(timestamp ts, unsigned long offset);

  unsigned long findSnapshot(timestamp ts);

private:
  FILE *fp;
  struct timeOffsetPair
  {
    timestamp ts;
    unsigned long offset;
  };
  vec<timeOffsetPair> snapshots;
};

/**
 * Inodes are variable length and include the standard inode file
 * metadata, the inumber, and the marshalling of the chunkable
 * representing the file's content.
 */
class inode : public fattr3
{
public:
  inumber num;
  chunkable::marshalled content;

  typedef str marshalled;

  static void unmarshall(marshalled m,
                         callback<void, inode>::ref cb,
                         cbe error);
  void marshall(callback<void, marshalled>::ref cb, cbe error);
};

/**
 * Inode log object.  This collects inode information and can be
 * queried for the inode mapping for any inode at any point in time.
 */
class inodeLog
{
public:
  /**
   * Create an empty inode log if filename does not exist.  Otherwise,
   * use the file in filename for the inode log.  This is a factory
   * function.
   */
  static void create(str filename, ref<superblob> blob,
                     callback<void, ref<inodeLog> >::ref cb, cbe error);

  /**
   * Append a new entry to the inode log.
   */
  void put(inode node,
           callback<void, timestamp>::ref cb, cbe error);

  /**
   * Append an inode deletion to the log.
   */
  void del(inumber num,
           callback<void, timestamp>::ref cb, cbe error);

  /**
   * Load an inode from the inode log, given its timestamp and
   * inumber.  timestamp can be TIMESTAMP_LATEST to load the inode
   * from the current version of the file system.
   */
  void get(timestamp ts, inumber num,
           callback<void, inode>::ref cb, cbe error);

  /**
   * Generate a new, unique inumber.  There are no guarantees on the
   * unguessability of this inumber.
   */
  void newInumber(callback<void, inumber>::ref cb, cbe error);

protected:
  inodeLog(FILE *fp, ref<superblob> blob);
  ~inodeLog();

private:
  FILE *fp;
  ref<superblob> blob;
  inodeMap currentMap;
  snapshotMap snapshots;

  timestamp now();

  void put_afterMarshall(inode node,
                         callback<void, timestamp>::ref cb, cbe error,
                         inode::marshalled m);
  void put_afterBlobPut(inode node,
                        callback<void, timestamp>::ref cb, cbe error,
                        blockFingerprint fingerprint,
                        blockAddress address);
  void get_afterBlobGet(callback<void, inode>::ref cb, cbe error,
                        str content, blockAddress ba);

  void scheduleNextSnapshot();
  void maybeTakeSnapshot();
  void takeSnapshot();
};

#endif // INODELOG_H
