/* 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_TeeState_h
#define mozilla_dom_TeeState_h

#include "mozilla/HoldDropJSObjects.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ReadableStream.h"
#include "mozilla/dom/ReadableStreamDefaultReader.h"
#include "nsISupports.h"

namespace mozilla::dom {

class ReadableStreamDefaultTeeSourceAlgorithms;

enum class TeeBranch : bool {
  Branch1,
  Branch2,
};

inline TeeBranch OtherTeeBranch(TeeBranch aBranch) {
  if (aBranch == TeeBranch::Branch1) {
    return TeeBranch::Branch2;
  }
  return TeeBranch::Branch1;
}

// A closure capturing the free variables in the ReadableStreamTee family of
// algorithms.
// https://streams.spec.whatwg.org/#abstract-opdef-readablestreamdefaulttee
// https://streams.spec.whatwg.org/#abstract-opdef-readablebytestreamtee
struct TeeState : public nsISupports {
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TeeState)

  static already_AddRefed<TeeState> Create(ReadableStream* aStream,
                                           bool aCloneForBranch2,
                                           ErrorResult& aRv);

  ReadableStream* GetStream() const { return mStream; }
  void SetStream(ReadableStream* aStream) { mStream = aStream; }

  ReadableStreamGenericReader* GetReader() const { return mReader; }
  void SetReader(ReadableStreamGenericReader* aReader) { mReader = aReader; }

  ReadableStreamDefaultReader* GetDefaultReader() const {
    return mReader->AsDefault();
  }

  bool ReadAgain() const { return mReadAgain; }
  void SetReadAgain(bool aReadAgain) { mReadAgain = aReadAgain; }

  // ReadableByteStreamTee uses ReadAgainForBranch{1,2};
  bool ReadAgainForBranch1() const { return ReadAgain(); }
  void SetReadAgainForBranch1(bool aReadAgainForBranch1) {
    SetReadAgain(aReadAgainForBranch1);
  }

  bool ReadAgainForBranch2() const { return mReadAgainForBranch2; }
  void SetReadAgainForBranch2(bool aReadAgainForBranch2) {
    mReadAgainForBranch2 = aReadAgainForBranch2;
  }

  bool Reading() const { return mReading; }
  void SetReading(bool aReading) { mReading = aReading; }

  bool Canceled1() const { return mCanceled1; }
  void SetCanceled1(bool aCanceled1) { mCanceled1 = aCanceled1; }

  bool Canceled2() const { return mCanceled2; }
  void SetCanceled2(bool aCanceled2) { mCanceled2 = aCanceled2; }

  void SetCanceled(TeeBranch aBranch, bool aCanceled) {
    aBranch == TeeBranch::Branch1 ? SetCanceled1(aCanceled)
                                  : SetCanceled2(aCanceled);
  }
  bool Canceled(TeeBranch aBranch) {
    return aBranch == TeeBranch::Branch1 ? Canceled1() : Canceled2();
  }

  void GetReason1(JSContext* aCx, JS::MutableHandle<JS::Value> aReason,
                  ErrorResult& aRv) const;
  void SetReason1(JS::Handle<JS::Value> aReason1) { mReason1 = aReason1; }

  void GetReason2(JSContext* aCx, JS::MutableHandle<JS::Value> aReason,
                  ErrorResult& aRv) const;
  void SetReason2(JS::Handle<JS::Value> aReason2) { mReason2 = aReason2; }

  void SetReason(TeeBranch aBranch, JS::Handle<JS::Value> aReason) {
    aBranch == TeeBranch::Branch1 ? SetReason1(aReason) : SetReason2(aReason);
  }

  ReadableStream* Branch1() const { return mBranch1; }
  void SetBranch1(already_AddRefed<ReadableStream> aBranch1) {
    mBranch1 = aBranch1;
  }

  ReadableStream* Branch2() const { return mBranch2; }
  void SetBranch2(already_AddRefed<ReadableStream> aBranch2) {
    mBranch2 = aBranch2;
  }

  Promise* CancelPromise() const { return mCancelPromise; }
  void SetCancelPromise(Promise* aCancelPromise) {
    mCancelPromise = aCancelPromise;
  }

  bool CloneForBranch2() const { return mCloneForBranch2; }
  void setCloneForBranch2(bool aCloneForBranch2) {
    mCloneForBranch2 = aCloneForBranch2;
  }

  // Some code is better served by using an enum into various internal slots to
  // avoid duplication: Here we provide alternative accessors for that case.
  ReadableStream* Branch(TeeBranch aBranch) const {
    return aBranch == TeeBranch::Branch1 ? Branch1() : Branch2();
  }

  void SetReadAgainForBranch(TeeBranch aBranch, bool aValue) {
    if (aBranch == TeeBranch::Branch1) {
      SetReadAgainForBranch1(aValue);
      return;
    }
    SetReadAgainForBranch2(aValue);
  }

  MOZ_CAN_RUN_SCRIPT void PullCallback(JSContext* aCx, nsIGlobalObject* aGlobal,
                                       ErrorResult& aRv);

 private:
  TeeState(ReadableStream* aStream, bool aCloneForBranch2);

  // Implicit:
  RefPtr<ReadableStream> mStream;

  // Step 3. (Step Numbering is based on ReadableStreamDefaultTee)
  RefPtr<ReadableStreamGenericReader> mReader;

  // Step 4.
  bool mReading = false;

  // Step 5.
  // (Aliased to readAgainForBranch1, for the purpose of ReadableByteStreamTee)
  bool mReadAgain = false;

  // ReadableByteStreamTee
  bool mReadAgainForBranch2 = false;

  // Step 6.
  bool mCanceled1 = false;

  // Step 7.
  bool mCanceled2 = false;

  // Step 8.
  JS::Heap<JS::Value> mReason1;

  // Step 9.
  JS::Heap<JS::Value> mReason2;

  // Step 10.
  RefPtr<ReadableStream> mBranch1;

  // Step 11.
  RefPtr<ReadableStream> mBranch2;

  // Step 12.
  RefPtr<Promise> mCancelPromise;

  // Implicit:
  bool mCloneForBranch2 = false;

  virtual ~TeeState() { mozilla::DropJSObjects(this); }
};

}  // namespace mozilla::dom
#endif
