/* -*- Mode: C++ -*-  */
class Block;
class BlockIterator;
class TmpFile;

class Block {
public:
  Block()
    : data_(NULL),
      data_size_(0),
      size_(0) { }

  ~Block() {
    if (data_) {
      delete [] data_;
    }
  }

  size_t Size() const {
    return size_;
  }

  uint8_t operator[](size_t i) const {
    CHECK_LT(i, size_);
    return data_[i];
  }

  uint8_t* Data() const {
    if (data_ == NULL) {
      CHECK_EQ(0, size_);
      data_size_ = 1;
      data_ = new uint8_t[1];
    }
    return data_;
  }

  // For writing to blocks
  void Append(const uint8_t *data, size_t size) {
    if (data_ == NULL) {
      CHECK_EQ(0, size_);
      CHECK_EQ(0, data_size_);
      data_ = new uint8_t[Constants::BLOCK_SIZE];
      data_size_ = Constants::BLOCK_SIZE;
    }

    if (size_ + size > data_size_) {
      uint8_t *tmp = data_;
      while (size_ + size > data_size_) {
	data_size_ *= 2;
      }
      data_ = new uint8_t[data_size_];
      memcpy(data_, tmp, size_);
      delete tmp;
    }

    memcpy(data_ + size_, data, size);
    size_ += size;
  }

  // For cleaing a block
  void Reset() {
    size_ = 0;
  }

  void Print() const {
    xoff_t pos = 0;
    for (size_t i = 0; i < Size(); i++) {
      if (pos % 16 == 0) {
	DP(RINT "%5"Q"x: ", pos);
      }
      DP(RINT "%02x ", (*this)[i]);
      if (pos % 16 == 15) {
	DP(RINT "\n");
      }
      pos++;
    }
    DP(RINT "\n");
  }

  void WriteTmpFile(TmpFile *f) const {
    f->Append(this);
  }

  void SetSize(size_t size) {
    size_ = size;

    if (data_size_ < size) {
      if (data_) {
	delete [] data_;
      }
      data_ = new uint8_t[size];
      data_size_ = size;
    }
  }
private:
  friend class BlockIterator;

  mutable uint8_t *data_;
  mutable size_t data_size_;
  size_t size_;
};

class FileSpec {
 public:
  FileSpec(MTRandom *rand)
    : rand_(rand) {
  }

  // Generates a file with a known size
  void GenerateFixedSize(xoff_t size) {
    Reset();

    for (xoff_t p = 0; p < size; ) {
      xoff_t t = min(Constants::BLOCK_SIZE, size - p);
      table_.insert(make_pair(p, Segment(t, rand_)));
      p += t;
    }
  }

  // Generates a file with exponential-random distributed size
  void GenerateRandomSize(xoff_t mean) {
    GenerateFixedSize(rand_->ExpRand(mean));
  }

  // Returns the size of the file
  xoff_t Size() const {
    if (table_.empty()) {
      return 0;
    }
    ConstSegmentMapIterator i = --table_.end();
    return i->first + i->second.Size();
  }

  // Returns the number of blocks
  xoff_t Blocks(size_t blksize = Constants::BLOCK_SIZE) const {
    if (table_.empty()) {
      return 0;
    }
    return ((Size() - 1) / blksize) + 1;
  }

  // Returns the number of segments
  xoff_t Segments() const {
    return table_.size();
  }

  // Create a mutation according to "what".
  void ModifyTo(const Mutator &mutator,
		FileSpec *modify) const {
    modify->Reset();
    mutator.Mutate(&modify->table_, &table_, rand_);
    modify->CheckSegments();
  }

  void CheckSegments() const {
    for (ConstSegmentMapIterator iter(table_.begin());
	 iter != table_.end(); ) {
      ConstSegmentMapIterator iter0(iter++);
      if (iter == table_.end()) {
	break;
      }
      CHECK_EQ(iter0->first + iter0->second.Size(), iter->first);
    }
  }

  void Reset() {
    table_.clear();
  }

  void Print() const {
    for (ConstSegmentMapIterator iter(table_.begin());
	 iter != table_.end();
	 ++iter) {
      const Segment &seg = iter->second;
      cerr << "Segment at " << iter->first
	   << " (" << seg.ToString() << ")" << endl;
    }
  }

  void PrintData() const {
    Block block;
    for (BlockIterator iter(*this); !iter.Done(); iter.Next()) {
      iter.Get(&block);
      block.Print();
    }
  }

  void WriteTmpFile(TmpFile *f) const {
    Block block;
    for (BlockIterator iter(*this); !iter.Done(); iter.Next()) {
      iter.Get(&block);
      f->Append(&block);
    }
  }

  void Get(Block *block, xoff_t offset, size_t size) const {
    size_t got = 0;
    block->SetSize(size);

    ConstSegmentMapIterator pos = table_.upper_bound(offset);
    if (pos == table_.begin()) {
      CHECK_EQ(0, Size());
      return;
    }
    --pos;

    while (got < size) {
      CHECK(pos != table_.end());
      CHECK_GE(offset, pos->first);

      const Segment &seg = pos->second;

      // The position of this segment may start before this block starts,
      // and then the position of the data may be offset from the seeding
      // position.
      size_t seg_offset = offset - pos->first;
      size_t advance = min(seg.Size() - seg_offset,
			   size - got);

      seg.Fill(seg_offset, advance, block->Data() + got);

      got += advance;
      offset += advance;
      ++pos;
    }
  }

  typedef BlockIterator iterator;

 private:
  friend class BlockIterator;

  MTRandom *rand_;
  SegmentMap table_;
};

class BlockIterator {
public:
  explicit BlockIterator(const FileSpec& spec)
    : spec_(spec),
      blkno_(0),
      blksize_(Constants::BLOCK_SIZE) { }

  BlockIterator(const FileSpec& spec,
		size_t blksize)
    : spec_(spec),
      blkno_(0),
      blksize_(blksize) { }

  bool Done() const {
    return blkno_ >= spec_.Blocks(blksize_);
  }

  void Next() {
    blkno_++;
  }

  xoff_t Blkno() const {
    return blkno_;
  }

  xoff_t Blocks() const {
    return spec_.Blocks(blksize_);
  }

  xoff_t Offset() const {
    return blkno_ * blksize_;
  }

  void SetBlock(xoff_t blkno) {
    blkno_ = blkno;
  }

  void Get(Block *block) const {
    spec_.Get(block, blkno_ * blksize_, BytesOnBlock());
  }

  size_t BytesOnBlock() const {
    xoff_t blocks = spec_.Blocks(blksize_);
    xoff_t size = spec_.Size();

    DCHECK((blkno_ < blocks) ||
	   (blkno_ == blocks && size % blksize_ == 0));

    if (blkno_ == blocks) {
      return 0;
    }
    if (blkno_ + 1 == blocks) {
      return ((size - 1) % blksize_) + 1;
    }
    return blksize_;
  }

  size_t BlockSize() const {
    return blksize_;
  }

private:
  const FileSpec& spec_;
  xoff_t blkno_;
  size_t blksize_;
};

class ExtFile {
public:
  ExtFile() {
    static int static_counter = 0;
    char buf[32];
    snprintf(buf, 32, "/tmp/regtest.%d", static_counter++);
    filename_.append(buf);
    unlink(filename_.c_str());
  }

  ~ExtFile() {
    unlink(filename_.c_str());
  }

  const char* Name() const {
    return filename_.c_str();
  }

  // Check whether a real file matches a file spec.
  bool EqualsSpec(const FileSpec &spec) const {
    main_file t;
    main_file_init(&t);
    CHECK_EQ(0, main_file_open(&t, Name(), XO_READ));

    Block tblock;
    Block sblock;
    for (BlockIterator iter(spec); !iter.Done(); iter.Next()) {
      iter.Get(&sblock);
      tblock.SetSize(sblock.Size());
      usize_t tread;
      CHECK_EQ(0, main_file_read(&t,
				 tblock.Data(),
				 tblock.Size(), &tread, "read failed"));
      CHECK_EQ(0, CmpDifferentBlockBytes(tblock, sblock));
    }

    CHECK_EQ(0, main_file_close(&t));
    main_file_cleanup(&t);
    return true;
  }

protected:
  string filename_;
};

class TmpFile : public ExtFile {
public:
  TmpFile() {
    main_file_init(&file_);
    CHECK_EQ(0, main_file_open(&file_, Name(), XO_WRITE));
  }

  ~TmpFile() {
    main_file_cleanup(&file_);
  }

  void Append(const Block *block) {
    CHECK_EQ(0, main_file_write(&file_,
				block->Data(), block->Size(),
				"tmpfile write failed"));
  }


  const char* Name() const {
    if (main_file_isopen(&file_)) {
      CHECK_EQ(0, main_file_close(&file_));
    }
    return ExtFile::Name();
  }

private:
  mutable main_file file_;
};

