/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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_dom_MutableBlobStorage_h
#define mozilla_dom_MutableBlobStorage_h

#include "mozilla/RefPtr.h"
#include "mozilla/Mutex.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "prio.h"

class nsIEventTarget;
class nsIRunnable;

namespace mozilla {

class TaskQueue;

namespace dom {

class Blob;
class BlobImpl;
class MutableBlobStorage;
class TemporaryIPCBlobChild;
class TemporaryIPCBlobChildCallback;

class MutableBlobStorageCallback {
 public:
  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING

  virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage,
                                  BlobImpl* aBlob, nsresult aRv) = 0;
};

// This class is must be created and used on main-thread, except for Append()
// that can be called on any thread.
class MutableBlobStorage final {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MutableBlobStorage)

  enum MutableBlobStorageType {
    eOnlyInMemory,
    eCouldBeInTemporaryFile,
  };

  explicit MutableBlobStorage(MutableBlobStorageType aType,
                              nsIEventTarget* aEventTarget = nullptr,
                              uint32_t aMaxMemory = 0);

  nsresult Append(const void* aData, uint32_t aLength);

  // This method can be called just once.
  // The callback will be called when the BlobImpl is ready.
  void GetBlobImplWhenReady(const nsACString& aContentType,
                            MutableBlobStorageCallback* aCallback);

  void TemporaryFileCreated(PRFileDesc* aFD);

  void AskForBlob(TemporaryIPCBlobChildCallback* aCallback,
                  const nsACString& aContentType);

  void ErrorPropagated(nsresult aRv);

  nsIEventTarget* EventTarget() {
    MOZ_ASSERT(mEventTarget);
    return mEventTarget;
  }

  // Returns the heap size in bytes of our internal buffers.
  // Note that this intentionally ignores the data in the temp file.
  size_t SizeOfCurrentMemoryBuffer();

  PRFileDesc* GetFD();

  void CloseFD();

 private:
  ~MutableBlobStorage();

  bool ExpandBufferSize(const MutexAutoLock& aProofOfLock, uint64_t aSize);

  bool ShouldBeTemporaryStorage(const MutexAutoLock& aProofOfLock,
                                uint64_t aSize) const;

  bool MaybeCreateTemporaryFile(const MutexAutoLock& aProofOfLock);
  void MaybeCreateTemporaryFileOnMainThread(const MutexAutoLock& aProofOfLock);

  [[nodiscard]] nsresult DispatchToIOThread(
      already_AddRefed<nsIRunnable> aRunnable);

  Mutex mMutex MOZ_UNANNOTATED;

  // All these variables are touched on the main thread only or in the
  // retargeted thread when used by Append(). They are protected by mMutex.

  void* mData;
  uint64_t mDataLen;
  uint64_t mDataBufferLen;

  enum StorageState {
    eKeepInMemory,
    eInMemory,
    eWaitingForTemporaryFile,
    eInTemporaryFile,
    eClosed
  };

  StorageState mStorageState;

  PRFileDesc* mFD;

  nsresult mErrorResult;

  RefPtr<TaskQueue> mTaskQueue;
  nsCOMPtr<nsIEventTarget> mEventTarget;

  nsCString mPendingContentType;
  RefPtr<MutableBlobStorageCallback> mPendingCallback;

  RefPtr<TemporaryIPCBlobChild> mActor;

  // This value is used when we go from eInMemory to eWaitingForTemporaryFile
  // and eventually eInTemporaryFile. If the size of the buffer is >=
  // mMaxMemory, the creation of the temporary file will start.
  // It's not used if mStorageState is eKeepInMemory.
  uint32_t mMaxMemory;
};

}  // namespace dom
}  // namespace mozilla

#endif  // mozilla_dom_MutableBlobStorage_h