#include "benchilog.h"
#include "testutils.h"
#include "arborescentinodelog.h"
#include "logginginodelog.h"
#include <math.h>

const int BATCHES = 25;
const int BATCH_SIZE = 200;
const str TEST_IMAP_FILENAME = "/tmp/benchilog.ili";
const str TEST_IMAP_ROOT_MAP_FILENAME = "/tmp/benchilog.ilr";
const unsigned long TEST_IMAP_CACHE_SIZE = 10;
const unsigned long TEST_IMAP_BRANCHT = 8;
const str TEST_TMAP_FILENAME = "/tmp/benchilog.ilt";
const str TEST_LOGGING_ILOG_FILENAME = "/tmp/benchilog.ilg";
const unsigned long BLOCK_SIZE =
  PBPlusInternalNode::padLen(TEST_IMAP_BRANCHT,
                             marshaller<inumber>().maxLength());

tailq<event, &event::link> allPutEvents;
tailq<event, &event::link> recentPutEvents;

unsigned long blockSize;
ref<strbuf> putCurrentPoints = New refcounted<strbuf>;
ref<strbuf> getCurrentPoints = New refcounted<strbuf>;
ref<strbuf> getPastPoints = New refcounted<strbuf>;



int
main(int argc, const char **argv)
{
  srandom(0x6824);

  setup();

  amain();
}

void
setup()
{
  // Create a superblob so we have somewhere to put the file contents
  // that we're putting in the inode and so the logging inode log has
  // somewhere to put its inodes.  In order to cut down on variables,
  // this uses the memsuperblob
  memSuperblob::create(wrap(&setup_after_superblob), fatalCB);
}

void
setup_after_superblob(ref<memSuperblob> blob)
{
  // Create a chunkable so we have something to put in these inodes
  chunkable::create(blob, str(""),
                    wrap(&setup_after_chunkable, blob), fatalCB);
}

void
setup_after_chunkable(ref<superblob> blob, ref<chunkable> chunk)
{
  // Marshal said chunkable
  chunk->marshall(wrap(&setup_after_marshal, blob), fatalCB);
}

void
setup_after_marshal(ref<superblob> blob, chunkable::marshalled m)
{
  // Create a dummy inode
  inode node;
  fattr3 fa;

  bzero(&fa, sizeof(fa));
  fa.type = NF3REG;
  node.setFromFattr(fa);
  node.content = m;

  // Start the benchmark
  doBench(0, blob, node);
}

void
strToFile(str fname, str buf)
{
    FILE *fp = fopen(fname.cstr(), "w");
    if (fwrite(buf.cstr(), buf.len(), 1, fp) != 1)
      fatal << "Failed to write\n";
    fclose(fp);
}

void
doBench(int bench, ref<superblob> blob, inode node)
{
  callback<void>::ref nextDoBench = wrap(&doBench, bench+1, blob, node);
  int prevBench = bench-1;

  if (prevBench == 0 || prevBench == 1) {
//     warn << "Put current points:\n";
//     printf(str(*putCurrentPoints).cstr());
//     warn << "Get current points:\n";
//     printf(str(*getCurrentPoints).cstr());
//     warn << "Get past points:\n";
//     printf(str(*getPastPoints).cstr());
    strToFile(strbuf("output.put.") << prevBench, *putCurrentPoints);
    strToFile(strbuf("output.get.") << prevBench, *getPastPoints);
  }

  // Set up strbufs
  putCurrentPoints = New refcounted<strbuf>;
  getCurrentPoints = New refcounted<strbuf>;
  getPastPoints = New refcounted<strbuf>;

  // Set up events
  clearAllEvents();

  if (bench == 0) {
    // Benchmark arborescent inode log
    warn << "!!! Benchmarking arborescent inode log\n";
    blockSize = 1;
    arborescentInodeLog::create(strbuf(TEST_IMAP_FILENAME) << getpid(),
                                strbuf(TEST_IMAP_ROOT_MAP_FILENAME)
                                  << getpid(),
                                TEST_IMAP_CACHE_SIZE,
                                strbuf(TEST_TMAP_FILENAME) << getpid(),
                                wrap(&after_create, node, nextDoBench),
                                fatalCB,
                                TEST_IMAP_BRANCHT);
  } else if (bench == 1) {
    // Benchmark logging inode log
    warn << "!!! Benchmarking logging inode log\n";
    blockSize = BLOCK_SIZE;
    loggingInodeLog::create(strbuf(TEST_LOGGING_ILOG_FILENAME) << getpid(),
                            blob,
                            wrap(&after_create, node, nextDoBench), fatalCB);
  } else
    // Done with benchmarks
    benchDone();
}

void
after_create(inode node, callback<void>::ref cb, ref<inodeLog> log)
{
  // Perform batches
  iterate2(0, BATCHES, wrap(&doBatch, node, log), cb);
}

void
doBatch(inode node, ref<inodeLog> log, int i, callback<void>::ref next)
{
  warn << "!!! Beginning batch " << i << "\n";
  warn << "Starting put of " << BATCH_SIZE << " inodes\n";

  log->resetBlockTransfers();
  clearRecentEvents();
  iterate(0, BATCH_SIZE, wrap(&putInode, node, log),
          wrap(&doBatch_after_putInodes, log, i, next));
}

void
putInode(inode node, ref<inodeLog> log,
         callback<void>::ref next)
{
  // Generate a new inumber
  log->newInumber(wrap(&putInode_after_newInumber, node, log, next), fatalCB);
}

void
putInode_after_newInumber(inode node, ref<inodeLog> log,
                          callback<void>::ref next,
                          inumber num)
{
  node.num = num;

  // Record number of files versus block transfers to put a single
  // file to current
  (*putCurrentPoints) << num << " " << blockTransfers(log) << "\n";
  log->resetBlockTransfers();
  
  // Put the inode
  log->put(node, wrap(&putInode_after_put, num, next), fatalCB);
}

void
putInode_after_put(inumber num, callback<void>::ref next,
                   timestamp ts)
{
  addEvent(num, ts);
  next();
}

void
doBatch_after_putInodes(ref<inodeLog> log, int i, callback<void>::ref next)
{
  warn << "Done putting inodes\n";

  // Record number of files versus block transfers to put a single
  // file to current
//   (*putCurrentPoints) << (i+1)*BATCH_SIZE << " "
//                       << ((double)blockTransfers(log))/BATCH_SIZE << "\n";

  // Mark the end of the batch
  addEvent(0, 0);

  // Skip to the next test
  doBatch_after_readInodes(log, i, next);
  return;

  // Skip other tests
//   next();
//   return;
  
  // Read back last 100 inodes from LATEST
  warn << "Starting get of last " << BATCH_SIZE
       << " inodes from LATEST\n";
  log->resetBlockTransfers();
  foreach(recentPutEvents.first,
          wrap(readInode, log, TIMESTAMP_LATEST),
          wrap(doBatch_after_readInodes, log, i, next));
}

void
readInode(ref<inodeLog> log, timestamp ts,
          event *e, callback<void>::ref next)
{
  log->get(ts, e->num, wrap(&readInode_after_get, next), fatalCB);
}

void
readInode_after_get(callback<void>::ref next,
                    inode node)
{
  next();
}

void
doBatch_after_readInodes(ref<inodeLog> log, int i, callback<void>::ref next)
{
  // Skip this test
  doBatch_after_readPastInodes(log, i, next);
  return;
  
  warn << "Done reading recent inodes from LATEST\n";
  warn << "Block transfers: " << blockTransfers(log) << "\n";

  // Record number of files versus block transfers to read a single
  // file from current
  (*getCurrentPoints) << (i+1)*BATCH_SIZE << " "
                      << ((double)blockTransfers(log))/BATCH_SIZE << "\n";

  // This is a tad funky.  Find all of the end markers and set the
  // batch timestamp of all of the events in that batch to the
  // timestamp right before the end marker.  Also set the batch
  // numbers as we go.
  event *batchFirst, *batchCur;
  timestamp lastTS;
  int batchNum = 0;

  for (batchFirst = batchCur = allPutEvents.first,
         lastTS = batchFirst->batchTS;
       batchCur;
       batchCur = batchCur->link.next) {
    if (batchCur->ts == 0) {
      // End marker
      for (; batchFirst != batchCur->link.next;
           batchFirst = batchFirst->link.next) {
        batchFirst->batchTS = lastTS;
        batchFirst->batchNum = batchNum;
      }
      ++batchNum;
    } else {
      lastTS = batchCur->ts;
    }    
  }
  
  // Read 100 times from each of the past timestamps
  warn << "Reading from past batches\n";
  log->resetBlockTransfers();
  foreach(allPutEvents.first,
          wrap(&readPastInode, log, i),
          wrap(&doBatch_after_readPastInodes, log, i, next));
}

void
readPastInode(ref<inodeLog> log, int curBatch,
              event *e, callback<void>::ref next)
{
  if (e->ts == 0) {
    // Batch end marker.  Record total files in file system (ie the
    // time in the file system) versus files at the time read versus
    // versus the number of transfers required to read a file from
    // that point in the past
    warn << "Block transfers: " << blockTransfers(log) << "\n";
    (*getPastPoints) << (curBatch+1)*BATCH_SIZE << " "
                     << (e->batchNum+1)*BATCH_SIZE << " "
                     << ((double)blockTransfers(log))/BATCH_SIZE << "\n";
    log->resetBlockTransfers();
    next();
  } else 
    log->get(e->batchTS, e->num,
             wrap(&readPastInode_after_get, next), fatalCB);
}

void
readPastInode_after_get(callback<void>::ref next,
                        inode node)
{
  next();
}

void
doBatch_after_readPastInodes(ref<inodeLog> log, int i,
                             callback<void>::ref next)
{
//   next();
//   return;

  // Read each inode at its associated time
  foreach(recentPutEvents.first,
          wrap(&readInodeAtTS, log),
          wrap(&doBatch_after_readRecentInodesRandomly, log, i, next));
}

void
readInodeAtTS(ref<inodeLog> log,
          event *e, callback<void>::ref next)
{
  //warn << "Reading " << e->num << " at ts " << e->ts << "\n";
  
  log->get(e->ts, e->num, wrap(&readInodeAtTS_after_get, log, next), fatalCB);
}

void
readInodeAtTS_after_get(ref<inodeLog> log, callback<void>::ref next,
                        inode node)
{
  //warn << blockTransfers(log) << "\n";
  next();
}

void
doBatch_after_readRecentInodesRandomly(ref<inodeLog> log, int i,
                                       callback<void>::ref next)
{
  warn << "Done reading recent inodes from their times\n";
  warn << "Block transfers: " << blockTransfers(log) << "\n";
  // The 1+ below is for the reads of the actual inodes
  (*getPastPoints) << (i+1)*BATCH_SIZE << " "
                   << 1+((double)blockTransfers(log))/BATCH_SIZE << "\n";
  log->resetBlockTransfers();

  next();
}

void
benchDone()
{
  warn << "Benchmark done... hooray!\n";

  exit(0);
}

unsigned long
blockTransfers(ref<inodeLog> log)
{
  if (blockSize == 1)
    return log->blockTransfers();
  else
    return (unsigned long)ceil(((double)log->blockTransfers()) / blockSize);
}

//
// event stuff
//

void
addEvent(inumber num, timestamp ts)
{
  event *e = New event, *e2 = New event;
  e->num = e2->num = num;
  e->ts = e2->ts = ts;
  e->batchTS = e2->batchTS = 0;
  allPutEvents.insert_tail(e);
  if (e->num != 0 || e->ts != 0)
    recentPutEvents.insert_tail(e2);
}

void
clearRecentEvents()
{
  while (recentPutEvents.first)
    delete recentPutEvents.remove(recentPutEvents.first);
}

void
clearAllEvents()
{
  clearRecentEvents();
  while (allPutEvents.first)
    delete allPutEvents.remove(allPutEvents.first);
}

//
// iterate
//

void
iterate(int i, int end,
        callback<void, callback<void>::ref>::ref p,
        callback<void>::ref done)
{
  if (i+1 == end)
    p(done);
  else
    delaycb(0, wrap(&iterate_next, p, wrap(&iterate, i+1, end, p, done)));
}

void
iterate_next(callback<void, callback<void>::ref>::ref p,
             callback<void>::ref next)
{
  p(next);
}

void
iterate2(int i, int end,
         callback<void, int, callback<void>::ref>::ref p,
         callback<void>::ref done)
{
  if (i+1 == end)
    p(i, done);
  else
    delaycb(0, wrap(&iterate2_next, i, p, wrap(&iterate2, i+1, end, p, done)));
}

void
iterate2_next(int i,
              callback<void, int, callback<void>::ref>::ref p,
              callback<void>::ref next)
{
  p(i, next);
}

//
// foreach
//

void
foreach(event *e, callback<void, event*, callback<void>::ref>::ref cb,
        callback<void>::ref done)
{
  if (e->link.next)
    delaycb(0, wrap(&foreach_next, e, cb, done));
  else {
    cb(e, done);
  }
}

void
foreach_next(event *e, callback<void, event*, callback<void>::ref>::ref cb,
             callback<void>::ref done)
{
  // delaycb doesn't return void or I could just compose the wraps
  cb(e, wrap(&foreach, e->link.next, cb, done));
}
