// Copyright (c) 2012 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_TEST_TOOLS_SIMULATOR_LINK_H_
#define QUICHE_QUIC_TEST_TOOLS_SIMULATOR_LINK_H_

#include <utility>

#include "quiche/quic/core/crypto/quic_random.h"
#include "quiche/quic/core/quic_bandwidth.h"
#include "quiche/quic/test_tools/simulator/actor.h"
#include "quiche/quic/test_tools/simulator/port.h"
#include "quiche/common/quiche_circular_deque.h"

namespace quic {
namespace simulator {

// A reliable simplex link between two endpoints with constrained bandwidth.  A
// few microseconds of random delay are added for every packet to avoid
// synchronization issues.
class OneWayLink : public Actor, public ConstrainedPortInterface {
 public:
  OneWayLink(Simulator* simulator, std::string name,
             UnconstrainedPortInterface* sink, QuicBandwidth bandwidth,
             QuicTime::Delta propagation_delay);
  OneWayLink(const OneWayLink&) = delete;
  OneWayLink& operator=(const OneWayLink&) = delete;
  ~OneWayLink() override;

  void AcceptPacket(std::unique_ptr<Packet> packet) override;
  QuicTime::Delta TimeUntilAvailable() override;
  void Act() override;

  QuicBandwidth bandwidth() const { return bandwidth_; }
  void set_bandwidth(QuicBandwidth new_bandwidth) {
    bandwidth_ = new_bandwidth;
  }

 protected:
  // Get the value of a random delay imposed on each packet.  By default, this
  // is a short random delay in order to avoid artifical synchronization
  // artifacts during the simulation.  Subclasses may override this behavior
  // (for example, to provide a random component of delay).
  virtual QuicTime::Delta GetRandomDelay(QuicTime::Delta transfer_time);

 private:
  struct QueuedPacket {
    std::unique_ptr<Packet> packet;
    QuicTime dequeue_time;

    QueuedPacket(std::unique_ptr<Packet> packet, QuicTime dequeue_time);
    QueuedPacket(QueuedPacket&& other);
    ~QueuedPacket();
  };

  // Schedule the next packet to be egressed out of the link if there are
  // packets on the link.
  void ScheduleNextPacketDeparture();

  UnconstrainedPortInterface* sink_;
  quiche::QuicheCircularDeque<QueuedPacket> packets_in_transit_;

  QuicBandwidth bandwidth_;
  const QuicTime::Delta propagation_delay_;

  QuicTime next_write_at_;
};

// A full-duplex link between two endpoints, functionally equivalent to two
// OneWayLink objects tied together.
class SymmetricLink {
 public:
  SymmetricLink(Simulator* simulator, std::string name,
                UnconstrainedPortInterface* sink_a,
                UnconstrainedPortInterface* sink_b, QuicBandwidth bandwidth,
                QuicTime::Delta propagation_delay);
  SymmetricLink(Endpoint* endpoint_a, Endpoint* endpoint_b,
                QuicBandwidth bandwidth, QuicTime::Delta propagation_delay);
  SymmetricLink(const SymmetricLink&) = delete;
  SymmetricLink& operator=(const SymmetricLink&) = delete;

  QuicBandwidth bandwidth() { return a_to_b_link_.bandwidth(); }
  void set_bandwidth(QuicBandwidth new_bandwidth) {
    a_to_b_link_.set_bandwidth(new_bandwidth);
    b_to_a_link_.set_bandwidth(new_bandwidth);
  }

 private:
  OneWayLink a_to_b_link_;
  OneWayLink b_to_a_link_;
};

}  // namespace simulator
}  // namespace quic

#endif  // QUICHE_QUIC_TEST_TOOLS_SIMULATOR_LINK_H_
