/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_layout_printing_DrawEventRecorder_h
#define mozilla_layout_printing_DrawEventRecorder_h

#include <memory>

#include "mozilla/gfx/DrawEventRecorder.h"
#include "mozilla/gfx/RecordingTypes.h"
#include "nsTArray.h"
#include "prio.h"

namespace mozilla {
namespace layout {

class PRFileDescStream final : public mozilla::gfx::EventStream {
  // Most writes, as seen in the print IPC use case, are very small (<32 bytes),
  // with a small number of very large (>40KB) writes. Writes larger than this
  // value are not buffered.
  static const size_t kBufferSize = 1024;

 public:
  PRFileDescStream()
      : mFd(nullptr), mBuffer(nullptr), mBufferPos(0), mGood(true) {}
  PRFileDescStream(const PRFileDescStream& other) = delete;
  ~PRFileDescStream() { Close(); }

  void OpenFD(PRFileDesc* aFd) {
    MOZ_DIAGNOSTIC_ASSERT(!IsOpen());
    mFd = aFd;
    mGood = !!mFd;
    mBuffer.reset(new uint8_t[kBufferSize]);
    mBufferPos = 0;
  }

  void Close() {
    // We need to be API compatible with std::ostream, and so we silently handle
    // closes on a closed FD.
    if (IsOpen()) {
      Flush();
      PR_Close(mFd);
      mFd = nullptr;
      mBuffer.reset();
      mBufferPos = 0;
    }
  }

  bool IsOpen() { return mFd != nullptr; }

  void Flush() {
    // See comment in Close().
    if (IsOpen() && mBufferPos > 0) {
      PRInt32 length =
          PR_Write(mFd, static_cast<const void*>(mBuffer.get()), mBufferPos);
      mGood = length >= 0 && static_cast<size_t>(length) == mBufferPos;
      mBufferPos = 0;
    }
  }

  void Seek(PRInt64 aOffset, PRSeekWhence aWhence) {
    Flush();
    PRInt64 pos = PR_Seek64(mFd, aOffset, aWhence);
    mGood = pos != -1;
  }

  void write(const char* aData, size_t aSize) override {
    if (!good()) {
      return;
    }

    // See comment in Close().
    if (IsOpen()) {
      // If we're writing more data than could ever fit in our buffer, flush the
      // buffer and write directly.
      if (aSize > kBufferSize) {
        Flush();
        PRInt32 length = PR_Write(mFd, static_cast<const void*>(aData), aSize);
        mGood = length >= 0 && static_cast<size_t>(length) == aSize;
        // If our write could fit in our buffer, but doesn't because the buffer
        // is partially full, write to the buffer, flush the buffer, and then
        // write the rest of the data to the buffer.
      } else if (aSize > AvailableBufferSpace()) {
        size_t length = AvailableBufferSpace();
        WriteToBuffer(aData, length);
        Flush();

        WriteToBuffer(aData + length, aSize - length);
        // Write fits in the buffer.
      } else {
        WriteToBuffer(aData, aSize);
      }
    }
  }

  [[nodiscard]] bool read(char* aOut, size_t aSize) override {
    if (!good()) {
      return false;
    }

    Flush();
    PRInt32 res = PR_Read(mFd, static_cast<void*>(aOut), aSize);
    mGood = res >= 0 && (static_cast<size_t>(res) == aSize);
    return true;
  }

  bool good() final { return mGood; }

  void SetIsBad() final { mGood = false; }

 private:
  size_t AvailableBufferSpace() { return kBufferSize - mBufferPos; }

  void WriteToBuffer(const char* aData, size_t aSize) {
    MOZ_ASSERT(aSize <= AvailableBufferSpace());
    memcpy(mBuffer.get() + mBufferPos, aData, aSize);
    mBufferPos += aSize;
  }

  PRFileDesc* mFd;
  std::unique_ptr<uint8_t[]> mBuffer;
  size_t mBufferPos;
  bool mGood;
};

class DrawEventRecorderPRFileDesc final : public gfx::DrawEventRecorderPrivate {
 public:
  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawEventRecorderPRFileDesc, override)
  explicit DrawEventRecorderPRFileDesc() = default;
  ~DrawEventRecorderPRFileDesc();

  gfx::RecorderType GetRecorderType() const final {
    return gfx::RecorderType::PRFILEDESC;
  }

  void RecordEvent(const gfx::RecordedEvent& aEvent) override;

  /**
   * Returns whether a recording file is currently open.
   */
  bool IsOpen();

  /**
   * Opens the recorder with the provided PRFileDesc *.
   */
  void OpenFD(PRFileDesc* aFd);

  /**
   * Closes the file so that it can be processed. The recorder does NOT forget
   * which objects it has recorded. This can be used with OpenNew, so that a
   * recording can be processed in chunks. The file must be open.
   */
  void Close();

  void AddDependentSurface(uint64_t aDependencyId) override;
  nsTArray<uint64_t>&& TakeDependentSurfaces();

 private:
  void Flush() override;

  PRFileDescStream mOutputStream;
  nsTArray<uint64_t> mDependentSurfaces;
};

}  // namespace layout

}  // namespace mozilla

#endif /* mozilla_layout_printing_DrawEventRecorder_h */
