// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef QUICHE_QUIC_CORE_QUIC_WRITE_BLOCKED_LIST_H_
#define QUICHE_QUIC_CORE_QUIC_WRITE_BLOCKED_LIST_H_

#include <cstddef>
#include <cstdint>
#include <utility>

#include "absl/container/inlined_vector.h"
#include "quiche/http2/core/priority_write_scheduler.h"
#include "quiche/http2/core/spdy_protocol.h"
#include "quiche/quic/core/quic_packets.h"
#include "quiche/quic/core/quic_stream_priority.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
#include "quiche/quic/platform/api/quic_export.h"
#include "quiche/quic/platform/api/quic_flags.h"

namespace quic {

// Keeps tracks of the order of QUIC streams that have data to write.
// Static streams come first, in the order they were registered with
// QuicWriteBlockedList.  They are followed by non-static streams, ordered by
// priority.
class QUICHE_EXPORT QuicWriteBlockedListInterface {
 public:
  virtual ~QuicWriteBlockedListInterface() = default;

  virtual bool HasWriteBlockedDataStreams() const = 0;
  virtual size_t NumBlockedSpecialStreams() const = 0;
  virtual size_t NumBlockedStreams() const = 0;
  bool HasWriteBlockedSpecialStream() const {
    return NumBlockedSpecialStreams() > 0;
  }

  // Returns true if there is another stream with higher priority in the queue.
  virtual bool ShouldYield(QuicStreamId id) const = 0;

  // Returns the priority of the specified stream.
  virtual QuicStreamPriority GetPriorityOfStream(QuicStreamId id) const = 0;

  // Pops the highest priority stream, special casing static streams. Latches
  // the most recently popped data stream for batch writing purposes.
  virtual QuicStreamId PopFront() = 0;

  // Register a stream with given priority.
  // `priority` is ignored for static streams.
  virtual void RegisterStream(QuicStreamId stream_id, bool is_static_stream,
                              const QuicStreamPriority& priority) = 0;

  // Unregister a stream.  `stream_id` must be registered, either as a static
  // stream or as a non-static stream.
  virtual void UnregisterStream(QuicStreamId stream_id) = 0;

  // Updates the stored priority of a stream.  Must not be called for static
  // streams.
  virtual void UpdateStreamPriority(QuicStreamId stream_id,
                                    const QuicStreamPriority& new_priority) = 0;

  // TODO(b/147306124): Remove when deprecating
  // reloadable_flag_quic_disable_batch_write.
  virtual void UpdateBytesForStream(QuicStreamId stream_id, size_t bytes) = 0;

  // Pushes a stream to the back of the list for its priority level *unless* it
  // is latched for doing batched writes in which case it goes to the front of
  // the list for its priority level.
  // Static streams are special cased to always resume first.
  // Stream must already be registered.
  virtual void AddStream(QuicStreamId stream_id) = 0;

  // Returns true if stream with |stream_id| is write blocked.
  virtual bool IsStreamBlocked(QuicStreamId stream_id) const = 0;
};

// Default implementation of QuicWriteBlockedListInterface.
class QUICHE_EXPORT QuicWriteBlockedList
    : public QuicWriteBlockedListInterface {
 public:
  explicit QuicWriteBlockedList();
  QuicWriteBlockedList(const QuicWriteBlockedList&) = delete;
  QuicWriteBlockedList& operator=(const QuicWriteBlockedList&) = delete;

  bool HasWriteBlockedDataStreams() const override {
    return priority_write_scheduler_.HasReadyStreams();
  }

  size_t NumBlockedSpecialStreams() const override {
    return static_stream_collection_.num_blocked();
  }

  size_t NumBlockedStreams() const override {
    return NumBlockedSpecialStreams() +
           priority_write_scheduler_.NumReadyStreams();
  }

  bool ShouldYield(QuicStreamId id) const override;

  QuicStreamPriority GetPriorityOfStream(QuicStreamId id) const override {
    return QuicStreamPriority(priority_write_scheduler_.GetStreamPriority(id));
  }

  // Pops the highest priority stream, special casing static streams. Latches
  // the most recently popped data stream for batch writing purposes.
  QuicStreamId PopFront() override;

  // Register a stream with given priority.
  // `priority` is ignored for static streams.
  void RegisterStream(QuicStreamId stream_id, bool is_static_stream,
                      const QuicStreamPriority& priority) override;

  // Unregister a stream.  `stream_id` must be registered, either as a static
  // stream or as a non-static stream.
  void UnregisterStream(QuicStreamId stream_id) override;

  // Updates the stored priority of a stream.  Must not be called for static
  // streams.
  void UpdateStreamPriority(QuicStreamId stream_id,
                            const QuicStreamPriority& new_priority) override;

  // TODO(b/147306124): Remove when deprecating
  // reloadable_flag_quic_disable_batch_write.
  void UpdateBytesForStream(QuicStreamId stream_id, size_t bytes) override;

  // Pushes a stream to the back of the list for its priority level *unless* it
  // is latched for doing batched writes in which case it goes to the front of
  // the list for its priority level.
  // Static streams are special cased to always resume first.
  // Stream must already be registered.
  void AddStream(QuicStreamId stream_id) override;

  // Returns true if stream with |stream_id| is write blocked.
  bool IsStreamBlocked(QuicStreamId stream_id) const override;

 private:
  struct QUICHE_EXPORT HttpStreamPriorityToInt {
    int operator()(const HttpStreamPriority& priority) {
      return priority.urgency;
    }
  };

  struct QUICHE_EXPORT IntToHttpStreamPriority {
    HttpStreamPriority operator()(int urgency) {
      return HttpStreamPriority{urgency};
    }
  };
  http2::PriorityWriteScheduler<QuicStreamId, HttpStreamPriority,
                                HttpStreamPriorityToInt,
                                IntToHttpStreamPriority>
      priority_write_scheduler_;

  // If performing batch writes, this will be the stream ID of the stream doing
  // batch writes for this priority level.  We will allow this stream to write
  // until it has written kBatchWriteSize bytes, it has no more data to write,
  // or a higher priority stream preempts.
  QuicStreamId batch_write_stream_id_[spdy::kV3LowestPriority + 1];
  // Set to kBatchWriteSize when we set a new batch_write_stream_id_ for a given
  // priority.  This is decremented with each write the stream does until it is
  // done with its batch write.
  // TODO(b/147306124): Remove when deprecating
  // reloadable_flag_quic_disable_batch_write.
  size_t bytes_left_for_batch_write_[spdy::kV3LowestPriority + 1];
  // Tracks the last priority popped for UpdateBytesForStream() and AddStream().
  spdy::SpdyPriority last_priority_popped_;

  // A StaticStreamCollection is a vector of <QuicStreamId, bool> pairs plus a
  // eagerly-computed number of blocked static streams.
  class QUICHE_EXPORT StaticStreamCollection {
   public:
    struct QUICHE_EXPORT StreamIdBlockedPair {
      QuicStreamId id;
      bool is_blocked;
    };

    // Optimized for the typical case of 2 static streams per session.
    using StreamsVector = absl::InlinedVector<StreamIdBlockedPair, 2>;

    StreamsVector::const_iterator begin() const { return streams_.cbegin(); }

    StreamsVector::const_iterator end() const { return streams_.cend(); }

    size_t num_blocked() const { return num_blocked_; }

    // Add |id| to the collection in unblocked state.
    void Register(QuicStreamId id);

    // True if |id| is in the collection, regardless of its state.
    bool IsRegistered(QuicStreamId id) const;

    // Remove |id| from the collection.  If it is in the blocked state, reduce
    // |num_blocked_| by 1.  Returns true if |id| was in the collection.
    bool Unregister(QuicStreamId id);

    // Set |id| to be blocked. If |id| is not already blocked, increase
    // |num_blocked_| by 1.
    // Return true if |id| is in the collection.
    bool SetBlocked(QuicStreamId id);

    // Unblock the first blocked stream in the collection.
    // If no stream is blocked, return false. Otherwise return true, set *id to
    // the unblocked stream id and reduce |num_blocked_| by 1.
    bool UnblockFirstBlocked(QuicStreamId* id);

   private:
    size_t num_blocked_ = 0;
    StreamsVector streams_;
  };

  StaticStreamCollection static_stream_collection_;

  // Latched value of reloadable_flag_quic_priority_respect_incremental.
  const bool respect_incremental_;
  // Latched value of reloadable_flag_quic_disable_batch_write.
  const bool disable_batch_write_;
};

}  // namespace quic

#endif  // QUICHE_QUIC_CORE_QUIC_WRITE_BLOCKED_LIST_H_
