/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ORC_BLOCK_BUFFER_HH
#define ORC_BLOCK_BUFFER_HH

#include "orc/MemoryPool.hh"

#include <vector>

namespace orc {

  class OutputStream;
  struct WriterMetrics;
  /**
   * BlockBuffer implements a memory allocation policy based on
   * equal-length blocks. BlockBuffer will reserve multiple blocks
   * for allocation.
   */
  class BlockBuffer {
   private:
    MemoryPool& memoryPool_;
    // current buffer size
    uint64_t currentSize_;
    // maximal capacity (actual allocated memory)
    uint64_t currentCapacity_;
    // unit for buffer expansion
    const uint64_t blockSize_;
    // pointers to the start of each block
    std::vector<char*> blocks_;

    // non-copy-constructible
    BlockBuffer(BlockBuffer& buffer) = delete;
    BlockBuffer& operator=(BlockBuffer& buffer) = delete;
    BlockBuffer(BlockBuffer&& buffer) = delete;
    BlockBuffer& operator=(BlockBuffer&& buffer) = delete;

   public:
    BlockBuffer(MemoryPool& pool, uint64_t blockSize);

    ~BlockBuffer();

    /**
     * Block points to a section of memory allocated by BlockBuffer,
     * containing the corresponding physical memory address and available size.
     */
    struct Block {
      // the start of block
      char* data;
      // number of bytes available at data
      uint64_t size;

      Block() : data(nullptr), size(0) {}
      Block(char* data, uint64_t size) : data(data), size(size) {}
      Block(const Block& block) = default;
      ~Block() = default;
    };

    /**
     * Get the allocated block object.
     * The last allocated block size may be less than blockSize,
     * and the rest of the blocks are all of size blockSize.
     * @param blockIndex the index of blocks
     * @return the allocated block object
     */
    Block getBlock(uint64_t blockIndex) const;

    /**
     * Get a empty block or allocate a new block to write.
     * If the last allocated block size is less than blockSize,
     * the size of empty block is equal to blockSize minus the size of
     * the last allocated block size. Otherwise, the size of
     * the empty block is equal to blockSize.
     * @return a empty block object
     */
    Block getNextBlock();

    /**
     * Get the number of blocks that are fully or partially occupied
     */
    uint64_t getBlockNumber() const {
      return (currentSize_ + blockSize_ - 1) / blockSize_;
    }

    uint64_t size() const {
      return currentSize_;
    }

    uint64_t capacity() const {
      return currentCapacity_;
    }

    void resize(uint64_t size);

    /**
     * Requests the BlockBuffer to contain at least newCapacity bytes.
     * Reallocation happens if there is need of more space.
     * @param newCapacity new capacity of BlockBuffer
     */
    void reserve(uint64_t newCapacity);

    /**
     * Write the BlockBuffer content into OutputStream
     * @param output the output stream to write to
     * @param metrics the metrics of the writer
     */
    void writeTo(OutputStream* output, WriterMetrics* metrics);
  };
}  // namespace orc

#endif
