/* 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_net_Http2WebTransportSession_h
#define mozilla_net_Http2WebTransportSession_h

#include "CapsuleParser.h"
#include "Http2StreamTunnel.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Queue.h"
#include "nsRefPtrHashtable.h"
#include "nsTHashMap.h"
#include "nsHashKeys.h"
#include "WebTransportFlowControl.h"
#include "WebTransportSessionBase.h"
#include "WebTransportStreamBase.h"

namespace mozilla::net {

class CapsuleEncoder;
class Http2WebTransportStream;

// A handler used exclusively by Http2WebTransportSessionImpl for capsule I/O,
// primarily responsible for sending capsules.
class CapsuleIOHandler {
 public:
  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING

  virtual void HasCapsuleToSend() = 0;
  virtual void SetSentFin() = 0;
  virtual void StartReading() = 0;
  virtual void OnCapsuleParseFailure(nsresult aError) = 0;

 protected:
  virtual ~CapsuleIOHandler() = default;
};

struct Http2WebTransportInitialSettings {
  // Initial session-level data limit.
  uint32_t mInitialMaxData = 0;
  // Initial stream-level data limit for outgoing unidirectional streams.
  uint32_t mInitialMaxStreamDataUni = 0;
  // Initial stream-level data limit for outgoing bidirectional streams.
  uint32_t mInitialMaxStreamDataBidi = 0;
  // Initial max unidirectional streams per session.
  uint32_t mInitialMaxStreamsUni = 0;
  // Initial max bidirectional streams per session.
  uint32_t mInitialMaxStreamsBidi = 0;
  // Initial limit on unidirectional streams that the peer creates.
  uint32_t mInitialLocalMaxStreamsUnidi = 16;
  // Initial limit on bidirectional streams that the peer creates.
  uint32_t mInitialLocalMaxStreamsBidi = 16;
  // Initial flow control limit for receiving data on unidirectional streams
  // that the peer creates.
  uint32_t mInitialLocalMaxStreamDataUnidi = 0x100000;
  // Initial flow control limit for receiving data on bidirectional streams
  // that the peer creates.
  uint32_t mInitialLocalMaxStreamDataBidi = 0x100000;
  // Initial session-level flow control limit.
  uint64_t mInitialLocalMaxData = 0x3FFFFFFFFFFFFFFF;
};

enum class CapsuleTransmissionPriority : uint8_t {
  Critical = 0,
  Important = 1,
  High = 2,
  Normal = 3,
  Low = 4,
};

// Core implementation of the logic behind Http2WebTransportSession.
// It's designed to be independently instantiated, which makes it easier to
// test.
class Http2WebTransportSessionImpl final : public WebTransportSessionBase,
                                           public CapsuleParser::Listener {
 public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Http2WebTransportSessionImpl, override)

  explicit Http2WebTransportSessionImpl(
      CapsuleIOHandler* aHandler, Http2WebTransportInitialSettings aSettings);

  void CloseSession(uint32_t aStatus, const nsACString& aReason) override;
  uint64_t GetStreamId() const override;
  void GetMaxDatagramSize() override;
  void SendDatagram(nsTArray<uint8_t>&& aData, uint64_t aTrackingId) override;
  void CreateOutgoingBidirectionalStream(
      std::function<void(Result<RefPtr<WebTransportStreamBase>, nsresult>&&)>&&
          aCallback) override;
  void CreateOutgoingUnidirectionalStream(
      std::function<void(Result<RefPtr<WebTransportStreamBase>, nsresult>&&)>&&
          aCallback) override;
  bool OnCapsule(Capsule&& aCapsule) override;
  void OnCapsuleParseFailure(nsresult aError) override;
  void StartReading() override;
  void Close(nsresult aReason);

  void OnStreamClosed(Http2WebTransportStream* aStream);
  void PrepareCapsulesToSend(
      mozilla::Queue<UniquePtr<CapsuleEncoder>>& aOutput);
  SenderFlowControlSession& SessionDataFc() { return mSessionDataFc; }
  ReceiverFlowControlSession& ReceiverFc() { return mReceiverFc; }

  void StreamHasCapsuleToSend();
  void OnStreamDataSent(StreamId aId, size_t aCount);
  void OnError(uint64_t aError);

 private:
  virtual ~Http2WebTransportSessionImpl();

  void CreateOutgoingStreamInternal(
      StreamId aStreamId,
      std::function<void(Result<RefPtr<WebTransportStreamBase>, nsresult>&&)>&&
          aCallback);

  class PendingStreamCallback {
   public:
    explicit PendingStreamCallback(
        std::function<void(
            Result<RefPtr<WebTransportStreamBase>, nsresult>&&)>&& aCallback)
        : mCallback(std::move(aCallback)) {}
    std::function<void(Result<RefPtr<WebTransportStreamBase>, nsresult>&&)>
    TakeCallback() {
      return std::move(mCallback);
    }

   private:
    std::function<void(Result<RefPtr<WebTransportStreamBase>, nsresult>&&)>
        mCallback;
  };
  void ProcessPendingStreamCallbacks(
      mozilla::Queue<UniquePtr<PendingStreamCallback>>& aCallbacks,
      WebTransportStreamType aStreamType);
  bool ProcessIncomingStreamCapsule(Capsule&& aCapsule, StreamId aID,
                                    WebTransportStreamType aStreamType);
  void SendMaintenanceCapsules(CapsuleTransmissionPriority aPriority);
  already_AddRefed<Http2WebTransportStream> GetStream(StreamId aId);
  bool HandleMaxStreamDataCapsule(StreamId aId, Capsule&& aCapsule);
  bool HandleStreamStopSendingCapsule(StreamId aId, Capsule&& aCapsule);
  bool HandleStreamResetCapsule(StreamId aId, Capsule&& aCapsule);

  class CapsuleQueue final {
   public:
    CapsuleQueue();
    mozilla::Queue<UniquePtr<CapsuleEncoder>>& operator[](
        CapsuleTransmissionPriority aPriority);

   private:
    mozilla::Queue<UniquePtr<CapsuleEncoder>> mCritical;
    mozilla::Queue<UniquePtr<CapsuleEncoder>> mImportant;
    mozilla::Queue<UniquePtr<CapsuleEncoder>> mHigh;
    mozilla::Queue<UniquePtr<CapsuleEncoder>> mNormal;
    mozilla::Queue<UniquePtr<CapsuleEncoder>> mLow;
  };
  void EnqueueOutCapsule(CapsuleTransmissionPriority aPriority,
                         UniquePtr<CapsuleEncoder>&& aData);

  uint64_t mStreamId = 0;
  nsRefPtrHashtable<nsUint64HashKey, Http2WebTransportStream> mOutgoingStreams;
  nsRefPtrHashtable<nsUint64HashKey, Http2WebTransportStream> mIncomingStreams;

  mozilla::Queue<UniquePtr<PendingStreamCallback>> mBidiPendingStreamCallbacks;
  mozilla::Queue<UniquePtr<PendingStreamCallback>> mUnidiPendingStreamCallbacks;
  Http2WebTransportInitialSettings mSettings;
  LocalStreamLimits mLocalStreamsFlowControl;
  RemoteStreamLimits mRemoteStreamsFlowControl;

  RefPtr<CapsuleIOHandler> mHandler;
  CapsuleQueue mCapsuleQueue;
  SenderFlowControlSession mSessionDataFc;
  ReceiverFlowControlSession mReceiverFc;
};

class Http2WebTransportSession final : public Http2StreamTunnel,
                                       public nsIOutputStreamCallback,
                                       public nsIInputStreamCallback,
                                       public CapsuleIOHandler {
 public:
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_NSIOUTPUTSTREAMCALLBACK
  NS_DECL_NSIINPUTSTREAMCALLBACK

  Http2WebTransportSession(Http2Session* aSession, int32_t aPriority,
                           uint64_t aBcId,
                           nsHttpConnectionInfo* aConnectionInfo,
                           Http2WebTransportInitialSettings aSettings);
  Http2WebTransportSession* GetHttp2WebTransportSession() override {
    return this;
  }
  Http2WebTransportSessionImpl* GetHttp2WebTransportSessionImpl() {
    return mImpl;
  }

  void CloseStream(nsresult aReason) override;
  void HasCapsuleToSend() override;
  void SetSentFin() override;
  void StartReading() override;
  void OnCapsuleParseFailure(nsresult aError) override;

 private:
  virtual ~Http2WebTransportSession();
  nsresult GenerateHeaders(nsCString& aCompressedData,
                           uint8_t& aFirstFrameFlags) override;

  size_t mWriteOffset{0};
  mozilla::Queue<UniquePtr<CapsuleEncoder>> mOutgoingQueue;
  RefPtr<Http2WebTransportSessionImpl> mImpl;
  UniquePtr<CapsuleParser> mCapsuleParser;
  UniquePtr<CapsuleEncoder> mCurrentOutCapsule;
};

}  // namespace mozilla::net

#endif  // mozilla_net_Http2WebTransportSession_h
