/**
 * Logging inode log implementation
 *
 * This implements an inode log which uses a simple time log with
 * periodic snapshots and replay.
 */

#ifndef LOGGINGINODELOG_H
#define LOGGINGINODELOG_H

#include "inodelog.h"
#include "superblob.h"
#include <stdio.h>
#include <qhash.h>

/**
 * The time, in seconds, between pondering creating a snapshot.  This
 * is used if periodic snapshotted is enabled.
 */
//static const int SNAPSHOT_TIME = 60;
static const int SNAPSHOT_TIME = 10;
static const bool SNAPSHOT_PERIODICALLY = false;

/**
 * The minimum number of entries since the last snapshot in order to
 * write a new snapshot.  If not periodically snapshotting, this is
 * precisely how often snapshots will be taken.
 */
static const int SNAPSHOT_MIN_CHANGES = 100;
//static const int SNAPSHOT_MIN_CHANGES = 5;

class loggingInodeLog;

/**
 * The inode map maps inumbers to block addresses at one point in
 * time.
 */
class inodeMap
{
public:
  inodeMap(loggingInodeLog *log);
  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();
  unsigned long getNumSnapshotEntries();
  inumber getRootInumber();
  void setRootInumber(inumber num);

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

  loggingInodeLog *log;
  FILE *fp;
  timestamp latest;
  inumber nextInumber;
  unsigned long lastSnapshot;
  inumber rootInumber;
  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(loggingInodeLog *log);
  void clear();
  void load(unsigned long lastSnapshotOffset);
  void add(timestamp ts, unsigned long offset);

  unsigned long findSnapshot(timestamp ts);

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

class loggingInodeLog : public 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);

  void put(inode node,
           callback<void, timestamp>::ref cb, cbe error);
  void del(inumber num,
           callback<void, timestamp>::ref cb, cbe error);
  void get(timestamp ts, inumber num,
           callback<void, inode>::ref cb, cbe error);
  void newInumber(callback<void, inumber>::ref cb, cbe error);
  void setRootInumber(inumber num, callback<void>::ref cb, cbe error);
  void getRootInumber(callback<void, inumber>::ref cb, cbe error);

  unsigned long blockTransfers();
  void resetBlockTransfers();

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

private:
  friend class inodeMap;
  friend class snapshotMap;

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

  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(bool scheduled = false);
  void takeSnapshot();

  // Stats gathering
  void startRead();
  void endRead();
  unsigned long readStartPos;
  unsigned long bytesRead;
};

#endif // LOGGINGINODELOG_H
