#ifndef CHUNKABLE_H
#define CHUNKABLE_H

#include "persifs.h"
#include "superblob.h"
#include "rabinpoly.h"
#include <list.h>

class chunkable 
{
private:
  enum chunkState
  {
                                // Valid fields of chunk:
    CHUNK_NOT_LOADED,           // address, length
    CHUNK_LOADED,               // address, length, content
    CHUNK_DIRTY,                // length, content
  };
  struct chunk
  {
    blockAddress address;
    unsigned long length;
    chunkState state;
    str content;
    tailq_entry<chunk> link;

    void alterContents(str newContent) 
    {
      address = 0;
      length = newContent.len();
      state = CHUNK_DIRTY;
      content = newContent;

      if (length == 0)
        fatal << "BUG: Zero-length chunkable content\n";
    }
  };
  typedef tailq<chunk, &chunk::link> chunkList;

  struct wrapSucksFlushArgs
  {
    window w;
    chunk *c;
    unsigned long chunkPos;
    int fingerprintLen;
    chunk *rechunkStart;
    unsigned long rechunkStartPos;
  };

public:
  /**
   * Marshalled form of a chunkable
   */
  typedef str marshalled;

  /**
   * Create a chunkable string consisting of content. This is a
   * factory function.
   */
  static void create(ref<superblob> blob, str content,
                     callback<void, ref<chunkable> >::ref cb,
                     cbe error);

  /**
   * Create the chunkable object corresponding to the marshalled
   * representation m. This is a facotry function.
   */
  static void unmarshall(ref<superblob> blob, marshalled m,
                         callback<void, ref<chunkable> >::ref cb,
                         cbe error);

  /**
   * Read the substring starting at start for length len from the
   * file, fetching blocks from the superblob as necessary.
   */
  void readSubstr(unsigned long start, unsigned long len,
                  callback<void, str>::ref cb,
                  cbe error);

  /**
   * Write str at position start in the string. If start+len(str)
   * extends beyond the current end of the string, the string length
   * is extended appropriately. If start is greater than len(str), the
   * string is padded with NULLs. Note that this change is not
   * actually committed to the superblob until flush is called.
   */
  void writeSubstr(unsigned long start, str s,
                  callback<void>::ref cb,
                  cbe error);

  /**
   * Set the length of the string to len, truncating or null-filling
   * as appropriate.
   */
  void truncate(unsigned long len,
                callback<void>::ref cb,
                cbe error);

  /**
   * Get the length of this chunkable.
   */
  void length(callback<void, unsigned long>::ref cb, cbe error);

  /**
   * Commit changes by re-performing the chunking operation and
   * writing chunks to the superblob as necessary.
   */
  void flush(callback<void>::ref cb,
             cbe error);

  /**
   * Produce a marshalled representation.  This string must not
   * contain any dirty contents (ie, it must be untouched since the
   * last flush, load, or create).
   */
  void marshall(callback<void, marshalled>::ref cb,
                cbe error);

  /**
   * Dumps a list of the chunks in this chunkable to the warn stream.
   */
  void debug_dumpChunks();

  /**
   * Copy constructor
   */
  chunkable(const chunkable & c);
  
protected:
  chunkable(ref<superblob> blob, chunkList cl);
  ~chunkable();

  static void create_after_write(ref<chunkable> c,
                                 callback<void, ref<chunkable> >::ref cb,
                                 cbe error);

private:
  friend class testChunkable;

  ref<superblob> blob;
  chunkList chunks;

  void loadChunk(chunk *c, callback<void>::ref cb, cbe error);
  void loadChunk_after_get(chunk *c,
                           callback<void>::ref cb, cbe error,
                           str content, blockAddress ba);
  void loadChunks(chunk *first, chunk *last,
                  callback<void>::ref cb, cbe error);
  chunk *findChunk(unsigned long *pos);
  chunk *findChunk(unsigned long pos) 
  {
    return findChunk(&pos);
  }
  /**
   * Splits chunk c into two chunks, the first of length firstLen,
   * where the second half of the split is inserted after c.  If the
   * split would result in one of the two chunks being zero length, no
   * split is performed.  c must already be loaded.  Sets pre and post
   * (if non-NULL) to the chunks preceeding and succeeding the split
   * boundary and returns whether or not a split was performed.  If
   * the split is at the beginning or end of the string, pre or post,
   * respectively, will be set to NULL.
   */
  bool splitChunk(chunk *c, unsigned long firstLen,
                  chunk **pre = NULL, chunk **post = NULL);
  /**
   * Load c if necessary and then split.
   */
  void loadAndSplitChunk(chunk *c, unsigned long firstLen,
                         callback<void, chunk*, chunk*>::ref cb,
                         cbe error);
  /**
   * Removes all of the chunks in the inclusive range [first, last]
   */
  void nukeChunks(chunk *first, chunk *last);
  void insertChunkBefore(chunk *pos, chunk *c);
  void insertChunkAfter(chunk *pos, chunk *c);
  /**
   * Gathers together the contents of a range of chunks,
   * [first:lastPos, last:lastPos)
   */
  str gatherChunks(chunk *first, unsigned long firstPos,
                   chunk *last, unsigned long lastPos);
  void padWithZeros(strbuf buf, unsigned long padLen);

  void readSubstr_after_getLength(unsigned long start, unsigned long len,
                                  callback<void, str>::ref cb,
                                  cbe error,
                                  unsigned long totalLength);
  void readSubstr_after_load(chunk *first, unsigned long firstPos, 
                             chunk *last, unsigned long lastPos,
                             callback<void, str>::ref cb);
  void writeSubstr_after_getLength(unsigned long start, str s,
                                   callback<void>::ref cb,
                                   cbe error,
                                   unsigned long totalLength);
  void writeSubstr_after_split1(chunk *c, unsigned long c2Pos,
                                callback<void>::ref cb, cbe error,
                                chunk *pre1, chunk *post1);
  void writeSubstr_after_split2(chunk *c, chunk *post1,
                                callback<void>::ref cb, cbe error,
                                chunk *pre2, chunk *post2);
  void truncate_after_getLength(unsigned long len,
                                callback<void>::ref cb,
                                cbe error,
                                unsigned long realLength);
  void truncate_after_split(callback<void>::ref cb,
                            cbe error,
                            chunk *pre, chunk *post);
  void flush_preload(chunk *c, unsigned long pos,
                     callback<void>::ref cb, cbe error);
  void flush_preload_loadRange(chunk *start, chunk *end,
                               callback<void>::ref cb, cbe error);
  void flush_slide(wrapSucksFlushArgs s,
                   callback<void>::ref cb, cbe error);
  void flush_flush(chunk *c, callback<void>::ref cb, cbe error);
  void flush_flush_after_put(chunk *c, callback<void>::ref cb, cbe error,
                             blockFingerprint bf, blockAddress ba);
};


inline const strbuf &
strbuf_cat(const strbuf &sb, const chunkable &c)
{
     // This should actually output the first 20 or so characters of
     // the content of the string, but that isn't possible without
     // special implementation support in chunkable because a callback
     // is normally required. Ick.
     sb << "I'm a chunkable and I'm not going to tell you who I am"
        << " because SFS sucks!";
     return sb;
}


#endif // CHUNKABLE_H
