/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed 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 INCLUDE_PERFETTO_PROTOZERO_SCATTERED_HEAP_BUFFER_H_
#define INCLUDE_PERFETTO_PROTOZERO_SCATTERED_HEAP_BUFFER_H_

#include <memory>
#include <string>
#include <vector>

#include "perfetto/base/export.h"
#include "perfetto/base/logging.h"
#include "perfetto/protozero/root_message.h"
#include "perfetto/protozero/scattered_stream_writer.h"

namespace protozero {

class Message;

class PERFETTO_EXPORT_COMPONENT ScatteredHeapBuffer
    : public protozero::ScatteredStreamWriter::Delegate {
 public:
  class PERFETTO_EXPORT_COMPONENT Slice {
   public:
    Slice();
    explicit Slice(size_t size);
    Slice(Slice&& slice) noexcept;
    ~Slice();
    Slice& operator=(Slice&&);

    inline protozero::ContiguousMemoryRange GetTotalRange() const {
      return {buffer_.get(), buffer_.get() + size_};
    }

    inline protozero::ContiguousMemoryRange GetUsedRange() const {
      return {buffer_.get(), buffer_.get() + size_ - unused_bytes_};
    }

    uint8_t* start() const { return buffer_.get(); }
    size_t size() const { return size_; }
    size_t unused_bytes() const { return unused_bytes_; }
    void set_unused_bytes(size_t unused_bytes) {
      PERFETTO_DCHECK(unused_bytes_ <= size_);
      unused_bytes_ = unused_bytes;
    }

    void Clear();

   private:
    std::unique_ptr<uint8_t[]> buffer_;
    size_t size_;
    size_t unused_bytes_;
  };

  ScatteredHeapBuffer(size_t initial_slice_size_bytes = 128,
                      size_t maximum_slice_size_bytes = 128 * 1024);
  ~ScatteredHeapBuffer() override;

  // protozero::ScatteredStreamWriter::Delegate implementation.
  protozero::ContiguousMemoryRange GetNewBuffer() override;

  // Return the slices backing this buffer, adjusted for the number of bytes the
  // writer has written.
  const std::vector<Slice>& GetSlices();

  // Stitch all the slices into a single contiguous buffer.
  std::vector<uint8_t> StitchSlices();

  // Stitch all the slices into a contiguous output buffer.
  std::pair<std::unique_ptr<uint8_t[]>, size_t> StitchAsUniquePtr();

  // Note that the returned ranges point back to this buffer and thus cannot
  // outlive it.
  std::vector<protozero::ContiguousMemoryRange> GetRanges();

  // Note that size of the last slice isn't updated to reflect the number of
  // bytes written by the trace writer.
  const std::vector<Slice>& slices() const { return slices_; }

  void set_writer(protozero::ScatteredStreamWriter* writer) {
    writer_ = writer;
  }

  // Update unused_bytes() of the current |Slice| based on the writer's state.
  void AdjustUsedSizeOfCurrentSlice();

  // Returns the total size the slices occupy in heap memory (including unused).
  size_t GetTotalSize();

  // Reset the contents of this buffer but retain one slice allocation (if it
  // exists) to be reused for future writes.
  void Reset();

 private:
  size_t next_slice_size_;
  const size_t maximum_slice_size_;
  protozero::ScatteredStreamWriter* writer_ = nullptr;
  std::vector<Slice> slices_;

  // Used to keep an allocated slice around after this buffer is reset.
  Slice cached_slice_;
};

// Helper function to create heap-based protozero messages in one line.
// Useful when manually serializing a protozero message (primarily in
// tests/utilities). So instead of the following:
//   protozero::MyMessage msg;
//   protozero::ScatteredHeapBuffer shb;
//   protozero::ScatteredStreamWriter writer(&shb);
//   shb.set_writer(&writer);
//   msg.Reset(&writer);
//   ...
// You can write:
//   protozero::HeapBuffered<protozero::MyMessage> msg;
//   msg->set_stuff(...);
//   msg.SerializeAsString();
template <typename T = ::protozero::Message>
class HeapBuffered {
 public:
  HeapBuffered() : HeapBuffered(4096, 4096) {}
  HeapBuffered(size_t initial_slice_size_bytes, size_t maximum_slice_size_bytes)
      : shb_(initial_slice_size_bytes, maximum_slice_size_bytes),
        writer_(&shb_) {
    shb_.set_writer(&writer_);
    msg_.Reset(&writer_);
  }

  // This can't be neither copied nor moved because Message hands out pointers
  // to itself when creating submessages.
  HeapBuffered(const HeapBuffered&) = delete;
  HeapBuffered& operator=(const HeapBuffered&) = delete;
  HeapBuffered(HeapBuffered&&) = delete;
  HeapBuffered& operator=(HeapBuffered&&) = delete;

  T* get() { return &msg_; }
  T* operator->() { return &msg_; }

  bool empty() const { return shb_.slices().empty(); }

  std::vector<uint8_t> SerializeAsArray() {
    msg_.Finalize();
    return shb_.StitchSlices();
  }

  std::string SerializeAsString() {
    auto vec = SerializeAsArray();
    return std::string(reinterpret_cast<const char*>(vec.data()), vec.size());
  }

  std::pair<std::unique_ptr<uint8_t[]>, size_t> SerializeAsUniquePtr() {
    msg_.Finalize();
    return shb_.StitchAsUniquePtr();
  }

  std::vector<protozero::ContiguousMemoryRange> GetRanges() {
    msg_.Finalize();
    return shb_.GetRanges();
  }

  const std::vector<ScatteredHeapBuffer::Slice>& GetSlices() {
    msg_.Finalize();
    return shb_.GetSlices();
  }

  void Reset() {
    shb_.Reset();
    writer_.Reset(protozero::ContiguousMemoryRange{});
    msg_.Reset(&writer_);
    PERFETTO_DCHECK(empty());
  }

 private:
  ScatteredHeapBuffer shb_;
  ScatteredStreamWriter writer_;
  RootMessage<T> msg_;
};

}  // namespace protozero

#endif  // INCLUDE_PERFETTO_PROTOZERO_SCATTERED_HEAP_BUFFER_H_
