/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "webrtc/test/fake_network_pipe.h"

#include <assert.h>
#include <math.h>
#include <string.h>

#include <algorithm>
#include <cmath>

#include "webrtc/call.h"
#include "webrtc/system_wrappers/include/clock.h"

namespace webrtc {

FakeNetworkPipe::FakeNetworkPipe(Clock* clock,
                                 const FakeNetworkPipe::Config& config)
    : FakeNetworkPipe(clock, config, 1) {}

FakeNetworkPipe::FakeNetworkPipe(Clock* clock,
                                 const FakeNetworkPipe::Config& config,
                                 uint64_t seed)
    : clock_(clock),
      packet_receiver_(NULL),
      random_(seed),
      config_(config),
      dropped_packets_(0),
      sent_packets_(0),
      total_packet_delay_(0),
      bursting_(false),
      next_process_time_(clock_->TimeInMilliseconds()) {
  double prob_loss = config.loss_percent / 100.0;
  if (config_.avg_burst_loss_length == -1) {
    // Uniform loss
    prob_loss_bursting_ = prob_loss;
    prob_start_bursting_ = prob_loss;
  } else {
    // Lose packets according to a gilbert-elliot model.
    int avg_burst_loss_length = config.avg_burst_loss_length;
    int min_avg_burst_loss_length = std::ceil(prob_loss / (1 - prob_loss));

    RTC_CHECK_GT(avg_burst_loss_length, min_avg_burst_loss_length)
        << "For a total packet loss of " << config.loss_percent << "%% then"
        << " avg_burst_loss_length must be " << min_avg_burst_loss_length + 1
        << " or higher.";

    prob_loss_bursting_ = (1.0 - 1.0 / avg_burst_loss_length);
    prob_start_bursting_ = prob_loss / (1 - prob_loss) / avg_burst_loss_length;
  }
}

FakeNetworkPipe::~FakeNetworkPipe() {
  while (!capacity_link_.empty()) {
    delete capacity_link_.front();
    capacity_link_.pop();
  }
  while (!delay_link_.empty()) {
    delete *delay_link_.begin();
    delay_link_.erase(delay_link_.begin());
  }
}

void FakeNetworkPipe::SetReceiver(PacketReceiver* receiver) {
  packet_receiver_ = receiver;
}

void FakeNetworkPipe::SetConfig(const FakeNetworkPipe::Config& config) {
  rtc::CritScope crit(&lock_);
  config_ = config;  // Shallow copy of the struct.
}

void FakeNetworkPipe::SendPacket(const uint8_t* data, size_t data_length) {
  // A NULL packet_receiver_ means that this pipe will terminate the flow of
  // packets.
  if (packet_receiver_ == NULL)
    return;
  rtc::CritScope crit(&lock_);
  if (config_.queue_length_packets > 0 &&
      capacity_link_.size() >= config_.queue_length_packets) {
    // Too many packet on the link, drop this one.
    ++dropped_packets_;
    return;
  }

  int64_t time_now = clock_->TimeInMilliseconds();

  // Delay introduced by the link capacity.
  int64_t capacity_delay_ms = 0;
  if (config_.link_capacity_kbps > 0)
    capacity_delay_ms = data_length / (config_.link_capacity_kbps / 8);
  int64_t network_start_time = time_now;

  // Check if there already are packets on the link and change network start
  // time forward if there is.
  if (!capacity_link_.empty() &&
      network_start_time < capacity_link_.back()->arrival_time())
    network_start_time = capacity_link_.back()->arrival_time();

  int64_t arrival_time = network_start_time + capacity_delay_ms;
  NetworkPacket* packet = new NetworkPacket(data, data_length, time_now,
                                            arrival_time);
  capacity_link_.push(packet);
}

float FakeNetworkPipe::PercentageLoss() {
  rtc::CritScope crit(&lock_);
  if (sent_packets_ == 0)
    return 0;

  return static_cast<float>(dropped_packets_) /
      (sent_packets_ + dropped_packets_);
}

int FakeNetworkPipe::AverageDelay() {
  rtc::CritScope crit(&lock_);
  if (sent_packets_ == 0)
    return 0;

  return static_cast<int>(total_packet_delay_ /
                          static_cast<int64_t>(sent_packets_));
}

void FakeNetworkPipe::Process() {
  int64_t time_now = clock_->TimeInMilliseconds();
  std::queue<NetworkPacket*> packets_to_deliver;
  {
    rtc::CritScope crit(&lock_);
    // Check the capacity link first.
    while (!capacity_link_.empty() &&
           time_now >= capacity_link_.front()->arrival_time()) {
      // Time to get this packet.
      NetworkPacket* packet = capacity_link_.front();
      capacity_link_.pop();

      // Drop packets at an average rate of |config_.loss_percent| with
      // and average loss burst length of |config_.avg_burst_loss_length|.
      if ((bursting_ && random_.Rand<double>() < prob_loss_bursting_) ||
          (!bursting_ && random_.Rand<double>() < prob_start_bursting_)) {
        bursting_ = true;
        delete packet;
        continue;
      } else {
        bursting_ = false;
      }

      int arrival_time_jitter = random_.Gaussian(
          config_.queue_delay_ms, config_.delay_standard_deviation_ms);

      // If reordering is not allowed then adjust arrival_time_jitter
      // to make sure all packets are sent in order.
      if (!config_.allow_reordering && !delay_link_.empty() &&
          packet->arrival_time() + arrival_time_jitter <
              (*delay_link_.rbegin())->arrival_time()) {
        arrival_time_jitter =
            (*delay_link_.rbegin())->arrival_time() - packet->arrival_time();
      }
      packet->IncrementArrivalTime(arrival_time_jitter);
      if (packet->arrival_time() < next_process_time_)
        next_process_time_ = packet->arrival_time();
      delay_link_.insert(packet);
    }

    // Check the extra delay queue.
    while (!delay_link_.empty() &&
           time_now >= (*delay_link_.begin())->arrival_time()) {
      // Deliver this packet.
      NetworkPacket* packet = *delay_link_.begin();
      packets_to_deliver.push(packet);
      delay_link_.erase(delay_link_.begin());
      // |time_now| might be later than when the packet should have arrived, due
      // to NetworkProcess being called too late. For stats, use the time it
      // should have been on the link.
      total_packet_delay_ += packet->arrival_time() - packet->send_time();
    }
    sent_packets_ += packets_to_deliver.size();
  }
  while (!packets_to_deliver.empty()) {
    NetworkPacket* packet = packets_to_deliver.front();
    packets_to_deliver.pop();
    packet_receiver_->DeliverPacket(MediaType::ANY, packet->data(),
                                    packet->data_length(), PacketTime());
    delete packet;
  }
}

int64_t FakeNetworkPipe::TimeUntilNextProcess() const {
  rtc::CritScope crit(&lock_);
  const int64_t kDefaultProcessIntervalMs = 30;
  if (capacity_link_.empty() || delay_link_.empty())
    return kDefaultProcessIntervalMs;
  return std::max<int64_t>(next_process_time_ - clock_->TimeInMilliseconds(),
                           0);
}

}  // namespace webrtc