/*
 *  Copyright (c) 2013 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 "testing/gtest/include/gtest/gtest.h"

#include "webrtc/common_types.h"
#include "webrtc/modules/remote_bitrate_estimator/inter_arrival.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"

namespace webrtc {
namespace testing {

enum {
  kTimestampGroupLengthUs = 5000,
  kMinStep = 20,
  kTriggerNewGroupUs = kTimestampGroupLengthUs + kMinStep,
  kBurstThresholdMs = 5,
  kAbsSendTimeFraction = 18,
  kAbsSendTimeInterArrivalUpshift = 8,
  kInterArrivalShift = kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift,
};

const double kRtpTimestampToMs = 1.0 / 90.0;
const double kAstToMs = 1000.0 / static_cast<double>(1 << kInterArrivalShift);

class InterArrivalTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    inter_arrival_rtp_.reset(new InterArrival(
        MakeRtpTimestamp(kTimestampGroupLengthUs),
        kRtpTimestampToMs,
        true));
    inter_arrival_ast_.reset(new InterArrival(
        MakeAbsSendTime(kTimestampGroupLengthUs),
        kAstToMs,
        true));
  }

  // Test that neither inter_arrival instance complete the timestamp group from
  // the given data.
  void ExpectFalse(int64_t timestamp_us, int64_t arrival_time_ms,
                   size_t packet_size) {
    InternalExpectFalse(inter_arrival_rtp_.get(),
                        MakeRtpTimestamp(timestamp_us), arrival_time_ms,
                        packet_size);
    InternalExpectFalse(inter_arrival_ast_.get(), MakeAbsSendTime(timestamp_us),
                        arrival_time_ms, packet_size);
  }

  // Test that both inter_arrival instances complete the timestamp group from
  // the given data and that all returned deltas are as expected (except
  // timestamp delta, which is rounded from us to different ranges and must
  // match within an interval, given in |timestamp_near].
  void ExpectTrue(int64_t timestamp_us, int64_t arrival_time_ms,
                  size_t packet_size, int64_t expected_timestamp_delta_us,
                  int64_t expected_arrival_time_delta_ms,
                  int expected_packet_size_delta,
                  uint32_t timestamp_near) {
    InternalExpectTrue(inter_arrival_rtp_.get(), MakeRtpTimestamp(timestamp_us),
                       arrival_time_ms, packet_size,
                       MakeRtpTimestamp(expected_timestamp_delta_us),
                       expected_arrival_time_delta_ms,
                       expected_packet_size_delta, timestamp_near);
    InternalExpectTrue(inter_arrival_ast_.get(), MakeAbsSendTime(timestamp_us),
                       arrival_time_ms, packet_size,
                       MakeAbsSendTime(expected_timestamp_delta_us),
                       expected_arrival_time_delta_ms,
                       expected_packet_size_delta, timestamp_near << 8);
  }

  void WrapTestHelper(int64_t wrap_start_us, uint32_t timestamp_near,
                      bool unorderly_within_group) {
    // Step through the range of a 32 bit int, 1/4 at a time to not cause
    // packets close to wraparound to be judged as out of order.

    // G1
    int64_t arrival_time = 17;
    ExpectFalse(0, arrival_time, 1);

    // G2
    arrival_time += kBurstThresholdMs + 1;
    ExpectFalse(wrap_start_us / 4, arrival_time, 1);

    // G3
    arrival_time += kBurstThresholdMs + 1;
    ExpectTrue(wrap_start_us / 2, arrival_time, 1,
               wrap_start_us / 4, 6, 0,   // Delta G2-G1
               0);

    // G4
    arrival_time += kBurstThresholdMs + 1;
    int64_t g4_arrival_time = arrival_time;
    ExpectTrue(wrap_start_us / 2 + wrap_start_us / 4, arrival_time, 1,
               wrap_start_us / 4, 6, 0,   // Delta G3-G2
               timestamp_near);

    // G5
    arrival_time += kBurstThresholdMs + 1;
    ExpectTrue(wrap_start_us, arrival_time, 2,
               wrap_start_us / 4, 6, 0,   // Delta G4-G3
               timestamp_near);
    for (int i = 0; i < 10; ++i) {
      // Slowly step across the wrap point.
      arrival_time += kBurstThresholdMs + 1;
      if (unorderly_within_group) {
        // These packets arrive with timestamps in decreasing order but are
        // nevertheless accumulated to group because their timestamps are higher
        // than the initial timestamp of the group.
        ExpectFalse(wrap_start_us + kMinStep * (9 - i), arrival_time, 1);
      } else {
        ExpectFalse(wrap_start_us + kMinStep * i, arrival_time, 1);
      }
    }
    int64_t g5_arrival_time = arrival_time;

    // This packet is out of order and should be dropped.
    arrival_time += kBurstThresholdMs + 1;
    ExpectFalse(wrap_start_us - 100, arrival_time, 100);

    // G6
    arrival_time += kBurstThresholdMs + 1;
    int64_t g6_arrival_time = arrival_time;
    ExpectTrue(wrap_start_us + kTriggerNewGroupUs, arrival_time, 10,
               wrap_start_us / 4 + 9 * kMinStep,
               g5_arrival_time - g4_arrival_time,
               (2 + 10) - 1,  // Delta G5-G4
               timestamp_near);

    // This packet is out of order and should be dropped.
    arrival_time += kBurstThresholdMs + 1;
    ExpectFalse(wrap_start_us + kTimestampGroupLengthUs, arrival_time, 100);

    // G7
    arrival_time += kBurstThresholdMs + 1;
    ExpectTrue(wrap_start_us + 2 * kTriggerNewGroupUs,
               arrival_time, 100,
               // Delta G6-G5
               kTriggerNewGroupUs - 9 * kMinStep,
               g6_arrival_time - g5_arrival_time,
               10 - (2 + 10),
               timestamp_near);
  }

 private:
  static uint32_t MakeRtpTimestamp(int64_t us) {
    return static_cast<uint32_t>(static_cast<uint64_t>(us * 90 + 500) / 1000);
  }

  static uint32_t MakeAbsSendTime(int64_t us) {
    uint32_t absolute_send_time = static_cast<uint32_t>(
        ((static_cast<uint64_t>(us) << 18) + 500000) / 1000000) & 0x00FFFFFFul;
    return absolute_send_time << 8;
  }

  static void InternalExpectFalse(InterArrival* inter_arrival,
                                  uint32_t timestamp, int64_t arrival_time_ms,
                                  size_t packet_size) {
    uint32_t dummy_timestamp = 101;
    int64_t dummy_arrival_time_ms = 303;
    int dummy_packet_size = 909;
    bool computed = inter_arrival->ComputeDeltas(timestamp,
                                                 arrival_time_ms,
                                                 packet_size,
                                                 &dummy_timestamp,
                                                 &dummy_arrival_time_ms,
                                                 &dummy_packet_size);
    EXPECT_EQ(computed, false);
    EXPECT_EQ(101ul, dummy_timestamp);
    EXPECT_EQ(303, dummy_arrival_time_ms);
    EXPECT_EQ(909, dummy_packet_size);
  }

  static void InternalExpectTrue(InterArrival* inter_arrival,
                                 uint32_t timestamp, int64_t arrival_time_ms,
                                 size_t packet_size,
                                 uint32_t expected_timestamp_delta,
                                 int64_t expected_arrival_time_delta_ms,
                                 int expected_packet_size_delta,
                                 uint32_t timestamp_near) {
    uint32_t delta_timestamp = 101;
    int64_t delta_arrival_time_ms = 303;
    int delta_packet_size = 909;
    bool computed = inter_arrival->ComputeDeltas(timestamp,
                                                 arrival_time_ms,
                                                 packet_size,
                                                 &delta_timestamp,
                                                 &delta_arrival_time_ms,
                                                 &delta_packet_size);
    EXPECT_EQ(true, computed);
    EXPECT_NEAR(expected_timestamp_delta, delta_timestamp, timestamp_near);
    EXPECT_EQ(expected_arrival_time_delta_ms, delta_arrival_time_ms);
    EXPECT_EQ(expected_packet_size_delta, delta_packet_size);
  }

  scoped_ptr<InterArrival> inter_arrival_rtp_;
  scoped_ptr<InterArrival> inter_arrival_ast_;
};

TEST_F(InterArrivalTest, FirstPacket) {
  ExpectFalse(0, 17, 1);
}

TEST_F(InterArrivalTest, FirstGroup) {
  // G1
  int64_t arrival_time = 17;
  int64_t g1_arrival_time = arrival_time;
  ExpectFalse(0, arrival_time, 1);

  // G2
  arrival_time += kBurstThresholdMs + 1;
  int64_t g2_arrival_time = arrival_time;
  ExpectFalse(kTriggerNewGroupUs, arrival_time, 2);

  // G3
  // Only once the first packet of the third group arrives, do we see the deltas
  // between the first two.
  arrival_time += kBurstThresholdMs + 1;
  ExpectTrue(2 * kTriggerNewGroupUs, arrival_time, 1,
             // Delta G2-G1
             kTriggerNewGroupUs, g2_arrival_time - g1_arrival_time, 1,
             0);
}

TEST_F(InterArrivalTest, SecondGroup) {
  // G1
  int64_t arrival_time = 17;
  int64_t g1_arrival_time = arrival_time;
  ExpectFalse(0, arrival_time, 1);

  // G2
  arrival_time += kBurstThresholdMs + 1;
  int64_t g2_arrival_time = arrival_time;
  ExpectFalse(kTriggerNewGroupUs, arrival_time, 2);

  // G3
  arrival_time += kBurstThresholdMs + 1;
  int64_t g3_arrival_time = arrival_time;
  ExpectTrue(2 * kTriggerNewGroupUs, arrival_time, 1,
             // Delta G2-G1
             kTriggerNewGroupUs, g2_arrival_time - g1_arrival_time, 1,
             0);

  // G4
  // First packet of 4th group yields deltas between group 2 and 3.
  arrival_time += kBurstThresholdMs + 1;
  ExpectTrue(3 * kTriggerNewGroupUs, arrival_time, 2,
             // Delta G3-G2
             kTriggerNewGroupUs, g3_arrival_time - g2_arrival_time, -1,
             0);
}

TEST_F(InterArrivalTest, AccumulatedGroup) {
  // G1
  int64_t arrival_time = 17;
  int64_t g1_arrival_time = arrival_time;
  ExpectFalse(0, arrival_time, 1);

  // G2
  arrival_time += kBurstThresholdMs + 1;
  ExpectFalse(kTriggerNewGroupUs, 28, 2);
  int64_t timestamp = kTriggerNewGroupUs;
  for (int i = 0; i < 10; ++i) {
    // A bunch of packets arriving within the same group.
    arrival_time += kBurstThresholdMs + 1;
    timestamp += kMinStep;
    ExpectFalse(timestamp, arrival_time, 1);
  }
  int64_t g2_arrival_time = arrival_time;
  int64_t g2_timestamp = timestamp;

  // G3
  arrival_time = 500;
  ExpectTrue(2 * kTriggerNewGroupUs, arrival_time, 100,
             g2_timestamp, g2_arrival_time - g1_arrival_time,
             (2 + 10) - 1,   // Delta G2-G1
             0);
}

TEST_F(InterArrivalTest, OutOfOrderPacket) {
  // G1
  int64_t arrival_time = 17;
  int64_t timestamp = 0;
  ExpectFalse(timestamp, arrival_time, 1);
  int64_t g1_timestamp = timestamp;
  int64_t g1_arrival_time = arrival_time;

  // G2
  arrival_time += 11;
  timestamp += kTriggerNewGroupUs;
  ExpectFalse(timestamp, 28, 2);
  for (int i = 0; i < 10; ++i) {
    arrival_time += kBurstThresholdMs + 1;
    timestamp += kMinStep;
    ExpectFalse(timestamp, arrival_time, 1);
  }
  int64_t g2_timestamp = timestamp;
  int64_t g2_arrival_time = arrival_time;

  // This packet is out of order and should be dropped.
  arrival_time = 281;
  ExpectFalse(g1_timestamp, arrival_time, 100);

  // G3
  arrival_time = 500;
  timestamp = 2 * kTriggerNewGroupUs;
  ExpectTrue(timestamp, arrival_time, 100,
             // Delta G2-G1
             g2_timestamp - g1_timestamp, g2_arrival_time - g1_arrival_time,
             (2 + 10) - 1,
             0);
}

TEST_F(InterArrivalTest, OutOfOrderWithinGroup) {
  // G1
  int64_t arrival_time = 17;
  int64_t timestamp = 0;
  ExpectFalse(timestamp, arrival_time, 1);
  int64_t g1_timestamp = timestamp;
  int64_t g1_arrival_time = arrival_time;

  // G2
  timestamp += kTriggerNewGroupUs;
  arrival_time += 11;
  ExpectFalse(kTriggerNewGroupUs, 28, 2);
  timestamp += 10 * kMinStep;
  int64_t g2_timestamp = timestamp;
  for (int i = 0; i < 10; ++i) {
    // These packets arrive with timestamps in decreasing order but are
    // nevertheless accumulated to group because their timestamps are higher
    // than the initial timestamp of the group.
    arrival_time += kBurstThresholdMs + 1;
    ExpectFalse(timestamp, arrival_time, 1);
    timestamp -= kMinStep;
  }
  int64_t g2_arrival_time = arrival_time;

  // However, this packet is deemed out of order and should be dropped.
  arrival_time = 281;
  timestamp = g1_timestamp;
  ExpectFalse(timestamp, arrival_time, 100);

  // G3
  timestamp = 2 * kTriggerNewGroupUs;
  arrival_time = 500;
  ExpectTrue(timestamp, arrival_time, 100,
             g2_timestamp - g1_timestamp, g2_arrival_time - g1_arrival_time,
             (2 + 10) - 1,
             0);
}

TEST_F(InterArrivalTest, TwoBursts) {
  // G1
  int64_t g1_arrival_time = 17;
  ExpectFalse(0, g1_arrival_time, 1);

  // G2
  int64_t timestamp = kTriggerNewGroupUs;
  int64_t arrival_time = 100;  // Simulate no packets arriving for 100 ms.
  for (int i = 0; i < 10; ++i) {
    // A bunch of packets arriving in one burst (within 5 ms apart).
    timestamp += 30000;
    arrival_time += kBurstThresholdMs;
    ExpectFalse(timestamp, arrival_time, 1);
  }
  int64_t g2_arrival_time = arrival_time;
  int64_t g2_timestamp = timestamp;

  // G3
  timestamp += 30000;
  arrival_time += kBurstThresholdMs + 1;
  ExpectTrue(timestamp, arrival_time, 100,
             g2_timestamp, g2_arrival_time - g1_arrival_time,
             10 - 1,  // Delta G2-G1
             0);
}


TEST_F(InterArrivalTest, NoBursts) {
  // G1
  ExpectFalse(0, 17, 1);

  // G2
  int64_t timestamp = kTriggerNewGroupUs;
  int64_t arrival_time = 28;
  ExpectFalse(timestamp, arrival_time, 2);

  // G3
  ExpectTrue(kTriggerNewGroupUs + 30000, arrival_time + kBurstThresholdMs + 1,
             100, timestamp - 0, arrival_time - 17,
             2 - 1,  // Delta G2-G1
             0);
}

// Yields 0xfffffffe when converted to internal representation in
// inter_arrival_rtp_ and inter_arrival_ast_ respectively.
static const int64_t kStartRtpTimestampWrapUs = 47721858827;
static const int64_t kStartAbsSendTimeWrapUs = 63999995;

TEST_F(InterArrivalTest, RtpTimestampWrap) {
  WrapTestHelper(kStartRtpTimestampWrapUs, 1, false);
}

TEST_F(InterArrivalTest, AbsSendTimeWrap) {
  WrapTestHelper(kStartAbsSendTimeWrapUs, 1, false);
}

TEST_F(InterArrivalTest, RtpTimestampWrapOutOfOrderWithinGroup) {
  WrapTestHelper(kStartRtpTimestampWrapUs, 1, true);
}

TEST_F(InterArrivalTest, AbsSendTimeWrapOutOfOrderWithinGroup) {
  WrapTestHelper(kStartAbsSendTimeWrapUs, 1, true);
}
}  // namespace testing
}  // namespace webrtc
