#include "persifs.h"
#include "testutils.h"
#include "testsuperblob.h"
#include "memblobindex.h"

#include <amisc.h>
#include <async.h>
#include <arpc.h>
#include <stdlib.h>
#include <unistd.h>

const int TEST_COUNT = 1024;
const unsigned long MAX_SIZE = 16384;
const str FILENAME = "testblob.blb";

void
testsDone(ref<superblob> sb, ref<blobContents> c)
{
  warn << "Superblob test passes... hooray!\n";
  if (unlink(FILENAME.cstr()) != 0)
    perror("unlink");
  exit(0);
}


void
testSuperblob(callback<void,ref<superblob>, ref<blobContents> >::ref cb,
              ref<blobContents> c, ref<superblob> sb)
{
  testSuperblobAdd(wrap(testSuperblobGet, cb),
                   sb, c, TEST_COUNT);
}

void
testSuperblobAdd(callback<void,ref<superblob>, ref<blobContents> >::ref cb,
                 ref<superblob> sb, ref<blobContents> c, int count)
{
  bool ok = false;
  str data;
  blockFingerprint fp;

  TRACE();
  
  while (!ok) {
    strbuf databuf;
     unsigned long len = random() % MAX_SIZE;
     for (unsigned long i = 0; i < len; i++) {
       char c = (char) random();
       databuf << str(&c);      // This can't be a good way to do
                                // this.
     }

    databuf << "foo";
    
    data = databuf;
    sha1_hash(&fp, data.cstr(), data.len());
    blobEnt *e = (*c)[fp];
    ok = 1  || (e == NULL);
  }

  sb->put(data,
          wrap(testSuperblobAdd_after_put, cb, sb, c, count, data),
          wrap(failCBE));
}

void
testSuperblobAdd_after_put(
  callback<void,ref<superblob>, ref<blobContents> >::ref cb,
  ref<superblob> sb, ref<blobContents> c, int count,
  str data, blockFingerprint fp,
  blockAddress addr)
{
  // Can't actually do this check because we can't squeeze this many
  // arguments through a callback.
//   if (fp != expFp) {
//     fail("block fingerprint returned by put was not as expected");
//   }
  TRACE();

  blobEnt *ent = New blobEnt;
  ent->data = data;
  ent->fp = fp;
  ent->addr = addr;
  c->insert(ent);

  sb->put(data,
          wrap(testSuperblobAdd_after_putAgain, cb, sb, c, count, addr),
          wrap(failCBE));

}

void
testSuperblobAdd_after_putAgain(
  callback<void,ref<superblob>, ref<blobContents> >::ref cb,
  ref<superblob> sb, ref<blobContents> c, int count,
  blockAddress oldAddr, blockFingerprint fp,
  blockAddress addr)
{
  TRACE();

  if (addr != oldAddr) {
    fail("chunk fusion did not occur!");
  }
  
  if (--count == 0) {
    cb(sb, c);
  } else {
    delaycb(0, wrap(testSuperblobAdd, cb, sb, c, count));
  }
}


void
testSuperblobGet(callback<void,ref<superblob>, ref<blobContents> >::ref cb,
                 ref<superblob> sb, ref<blobContents> c)
{
  testSuperblobGetItem(cb, sb, c, c->first());
}

void
testSuperblobGetItem(callback<void,ref<superblob>, ref<blobContents> >::ref cb,
                     ref<superblob> sb, ref<blobContents> c, blobEnt *ent)
{
  if (ent == NULL) {
    cb(sb, c);
    return;
  }

  sb->get(ent->fp,
          wrap(testSuperblobGetItem_after_get, cb, sb, c, ent),
          wrap(failCBE));
}

void
testSuperblobGetItem_after_get(
  callback<void,ref<superblob>, ref<blobContents> >::ref cb,
  ref<superblob> sb, ref<blobContents> c, blobEnt *ent,
  str data, blockAddress addr)
{
  if (data != ent->data)
    fail("get data was not as expected!\n");
  if (addr != ent->addr)
    fail("get address was not as expected!\n");

  sb->getDirect(ent->addr,
                wrap(testSuperblobGetItem_after_getDirect,
                     cb, sb, c, ent),
                wrap(failCBE));
}

void
testSuperblobGetItem_after_getDirect(
  callback<void,ref<superblob>, ref<blobContents> >::ref cb,
  ref<superblob> sb, ref<blobContents> c, blobEnt *ent,
  str data, blockAddress addr)
{
  if (data != ent->data)
    fail("getDirect data was not as expected!\n");
  if (addr != ent->addr)
    fail("getDirect address was not as expected!\n");

  delaycb(0, wrap(testSuperblobGetItem,
                  cb, sb, c, c->next(ent)));
}

void
testMemSuperblob(callback<void,ref<superblob>, ref<blobContents> >::ref cb,
                 ref<blobContents> c, ref<memSuperblob> sb)
{
  warn << "Testing memSuperBlob\n";
  testSuperblob(cb, c, sb);
}

void
testDiskSuperblob(callback<void,ref<superblob>, ref<blobContents> >::ref cb,
                  ref<blobContents> c, ref<diskSuperblob> sb)
{
  warn << "Testing diskSuperBlob\n";
  testSuperblob(cb, c, sb);
}

void
runDiskSuperblobTests(
  callback<void,ref<superblob>, ref<blobContents> >::ref cb,
  ref<blobContents> realC,
  ptr<blobContents> dummyC, ptr<superblob> dummySb)
{
  memBlobIndex::create(str("ignore"),
                       wrap(runDiskSuperblobTests_after_indexCreate,
                            cb, realC), wrap(failCBE));
}

void
runDiskSuperblobTests_after_indexCreate(
  callback<void,ref<superblob>, ref<blobContents> >::ref cb,
  ref<blobContents> realC,
  ref<memBlobIndex> ind)
{
  diskSuperblob::create(FILENAME, ind,
                        wrap(testDiskSuperblob,
                             wrap(runDiskSuperblobTestsAgain, cb, ind),
                             realC),
                        wrap(failCBE));
}

void
runDiskSuperblobTestsAgain(
  callback<void,ref<superblob>, ref<blobContents> >::ref cb,
  ref<memBlobIndex> ind, ref<superblob> oldBlob, ref<blobContents> c)
{
  diskSuperblob::create(FILENAME, ind, wrap(testDiskSuperblob, cb, c),
                        wrap(failCBE));
}


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

  ref<blobContents> c = New refcounted<blobContents>();
  ref<blobContents> c2 = New refcounted<blobContents>();

//    memSuperblob::create(wrap(testMemSuperblob,
//                              wrap(testsDone),
//                              c),
//                         wrap(failCBE));

  runDiskSuperblobTests(wrap(testsDone), c2, 0, 0);
  amain();
}
