#include "persifs.h"
#include "disksuperblob.h"
#include "happyio.h"
#include "blobindex.h"

#include <stdio.h>
#include <unistd.h>

#define DEBUG 0

// Format of diskSuperblob file
//  magic (DISKSUPERBLOB_MAGIC_NUMBER)    4 bytes
//  entry*
//
// Format of entry:
//  magic (BLOBENTRY_MAGIC_NUMBER)    4 bytes
//  content (variable-length string)

static const unsigned long DISKSUPERBLOB_MAGIC_NUMBER = 0x6824b10b;
static const unsigned long BLOBENTRY_MAGIC_NUMBER = 0x537a59dc;

void
diskSuperblob::create(str filename, ref<blobIndex> bi,
                  callback<void, ref<diskSuperblob> >::ref cb, cbe error)
{
  bool initialize;

  if (access(filename.cstr(), F_OK) == 0) {
    // File exists
    initialize = false;
  } else {
    initialize = true;
  }
  
  FILE *f = fopen(filename.cstr(),
                  initialize ? "w+b" : "r+b");
  if (!f) {
    warn << "diskSuperblob: failed to open " << filename << "\n";
    error(PERR_IO);
  }

  if (initialize) {
    fileWriter w(f);
    w.writeLong(DISKSUPERBLOB_MAGIC_NUMBER);

    if (w.hasError() || fflush(f) || fseek(f, 0, SEEK_SET)) {
      warn << "diskSuperblob: failed to initialize new blobfile\n";
      error(PERR_IO);
      return;
    }
  }

  ref<diskSuperblob> sb = New refcounted<diskSuperblob>(f, bi);
  cb(sb);
}


diskSuperblob::diskSuperblob(FILE *f, ref<blobIndex> bi)
  : f(f), bi(bi)
{
  // Validate header
  fileReader r(f);
  if (r.readLong() != DISKSUPERBLOB_MAGIC_NUMBER) {
    fatal << "DiskSuperblob file invalid -- header not sufficiently magical\n";
  }
}


diskSuperblob::~diskSuperblob()
{
  if (fclose(f) != 0)
  {
    fatal << "diskSuperblob: error " << errno << " on fclose\n";
  }
}


void
diskSuperblob::put(str blobContent,
               callback<void, blockFingerprint, blockAddress>::ref cb,
               cbe error)
{
  blockFingerprint fp;
  
  // Magic SFS incantation to compute the fingerprint of the
  // content. I think. I hope. But who can ever really tell?
  sha1_hash(&fp, blobContent.cstr(), blobContent.len());

#if DEBUG
  warn << "diskSuperblob:: put: blobContent=" << blobContent << "\n";
  warn << "diskSuperblob:: put: fp=" << fp << "\n";
#endif
  
  bi->get(fp, wrap(this, &diskSuperblob::put_after_indexGet,
                   blobContent, fp, cb, error),
          error);
}

void
diskSuperblob::put_after_indexGet(
  str blobContent,
  blockFingerprint fp,
  callback<void, blockFingerprint, blockAddress>::ref cb,
  cbe error,
  blockAddress addr)
{ 
  if (addr != 0) {
    // Block already exists in index, so...
    cb(fp, addr);
  } else {
    // Otherwise, let's add it to the blob...
    fseek(f, 0, SEEK_END);
    addr = ftell(f);
    fileWriter fw(f);

#if DEBUG
    warn << "diskSuperblob: addr=" << addr << "\n";
#endif
    
    // Write the header
    fw.writeLong(BLOBENTRY_MAGIC_NUMBER);

    // Write the data
    fw.writeStr(blobContent);
    

    // Flush to disk
    if (fw.hasError() || fflush(f)) {
      warn << "diskSuperblob: failed to write entry\n";
      error(PERR_IO);
      return;
    }
    // Now add to the index
    bi->put(fp, addr, wrap(this, &diskSuperblob::put_after_indexPut,
                           blobContent, fp, addr, cb, error),
            error);
  }
}

void
diskSuperblob::put_after_indexPut(
  str blobContent,
  blockFingerprint fp,
  blockAddress addr,
  callback<void, blockFingerprint, blockAddress>::ref cb,
  cbe error)
{
  cb(fp, addr);
}


void
diskSuperblob::getDirect(blockAddress addr,
                     callback<void, str, blockAddress>::ref cb, cbe error)
{
  if (addr == 0) {
    error(PERR_INVAL);
    return;
  }
  
#if DEBUG
  warn << "diskSuperblob:: getDirect: addr=" << addr << "\n";
#endif
  
  fseek(f, addr, SEEK_SET);
  fileReader fr(f);

  if (fr.readLong() != BLOBENTRY_MAGIC_NUMBER) {
    warn << "diskSuperblob: blob entry at addr " << addr << " not magical\n";
    error(PERR_IO);
    return;
  }

  str data = fr.readStr();
  
  if (fr.hasError()) {
    warn << "diskSuperblob: error reading block data\n";
    error(PERR_IO);
    return;
  }

  cb(data, addr);
}


void
diskSuperblob::get(blockFingerprint fp,
               callback<void, str, blockAddress>::ref cb, cbe error)
{
#if DEBUG
  warn << "diskSuperblob:: get: fp=" << fp << "\n";
#endif
  
  bi->get(fp, wrap(this, &diskSuperblob::get_after_indexGet,
                   fp, cb, error), error);
}

void
diskSuperblob::get_after_indexGet(blockFingerprint fp,
                            callback<void, str, blockAddress>::ref cb,
                            cbe error,
                            blockAddress addr)
{
  if (addr == 0) {
    warn << "diskSuperblob: block not found\n";
    error(PERR_NOENT);
    return;
  }

  getDirect(addr, cb, error);
}
