/*
 *  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/modules/audio_coding/neteq/timestamp_scaler.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "webrtc/modules/audio_coding/neteq/mock/mock_decoder_database.h"
#include "webrtc/modules/audio_coding/neteq/packet.h"

using ::testing::Return;
using ::testing::ReturnNull;
using ::testing::_;

namespace webrtc {

TEST(TimestampScaler, TestNoScaling) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info;
  info.codec_type = kDecoderPCMu;  // Does not use scaled timestamps.
  static const uint8_t kRtpPayloadType = 0;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillRepeatedly(Return(&info));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  for (uint32_t timestamp = 0xFFFFFFFF - 5; timestamp != 5; ++timestamp) {
    // Scale to internal timestamp.
    EXPECT_EQ(timestamp, scaler.ToInternal(timestamp, kRtpPayloadType));
    // Scale back.
    EXPECT_EQ(timestamp, scaler.ToExternal(timestamp));
  }

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

TEST(TimestampScaler, TestNoScalingLargeStep) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info;
  info.codec_type = kDecoderPCMu;  // Does not use scaled timestamps.
  static const uint8_t kRtpPayloadType = 0;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillRepeatedly(Return(&info));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  static const uint32_t kStep = 160;
  uint32_t start_timestamp = 0;
  // |external_timestamp| will be a large positive value.
  start_timestamp = start_timestamp - 5 * kStep;
  for (uint32_t timestamp = start_timestamp; timestamp != 5 * kStep;
      timestamp += kStep) {
    // Scale to internal timestamp.
    EXPECT_EQ(timestamp, scaler.ToInternal(timestamp, kRtpPayloadType));
    // Scale back.
    EXPECT_EQ(timestamp, scaler.ToExternal(timestamp));
  }

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

TEST(TimestampScaler, TestG722) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info;
  info.codec_type = kDecoderG722;  // Uses a factor 2 scaling.
  static const uint8_t kRtpPayloadType = 17;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillRepeatedly(Return(&info));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  uint32_t external_timestamp = 0xFFFFFFFF - 5;
  uint32_t internal_timestamp = external_timestamp;
  for (; external_timestamp != 5; ++external_timestamp) {
    // Scale to internal timestamp.
    EXPECT_EQ(internal_timestamp,
              scaler.ToInternal(external_timestamp, kRtpPayloadType));
    // Scale back.
    EXPECT_EQ(external_timestamp, scaler.ToExternal(internal_timestamp));
    internal_timestamp += 2;
  }

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

TEST(TimestampScaler, TestG722LargeStep) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info;
  info.codec_type = kDecoderG722;  // Uses a factor 2 scaling.
  static const uint8_t kRtpPayloadType = 17;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillRepeatedly(Return(&info));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  static const uint32_t kStep = 320;
  uint32_t external_timestamp = 0;
  // |external_timestamp| will be a large positive value.
  external_timestamp = external_timestamp - 5 * kStep;
  uint32_t internal_timestamp = external_timestamp;
  for (; external_timestamp != 5 * kStep; external_timestamp += kStep) {
    // Scale to internal timestamp.
    EXPECT_EQ(internal_timestamp,
              scaler.ToInternal(external_timestamp, kRtpPayloadType));
    // Scale back.
    EXPECT_EQ(external_timestamp, scaler.ToExternal(internal_timestamp));
    // Internal timestamp should be incremented with twice the step.
    internal_timestamp += 2 * kStep;
  }

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

TEST(TimestampScaler, TestG722WithCng) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info_g722, info_cng;
  info_g722.codec_type = kDecoderG722;  // Uses a factor 2 scaling.
  info_cng.codec_type = kDecoderCNGwb;
  static const uint8_t kRtpPayloadTypeG722 = 17;
  static const uint8_t kRtpPayloadTypeCng = 13;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadTypeG722))
      .WillRepeatedly(Return(&info_g722));
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadTypeCng))
      .WillRepeatedly(Return(&info_cng));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  uint32_t external_timestamp = 0xFFFFFFFF - 5;
  uint32_t internal_timestamp = external_timestamp;
  bool next_is_cng = false;
  for (; external_timestamp != 5; ++external_timestamp) {
    // Alternate between G.722 and CNG every other packet.
    if (next_is_cng) {
      // Scale to internal timestamp.
      EXPECT_EQ(internal_timestamp,
                scaler.ToInternal(external_timestamp, kRtpPayloadTypeCng));
      next_is_cng = false;
    } else {
      // Scale to internal timestamp.
      EXPECT_EQ(internal_timestamp,
                scaler.ToInternal(external_timestamp, kRtpPayloadTypeG722));
      next_is_cng = true;
    }
    // Scale back.
    EXPECT_EQ(external_timestamp, scaler.ToExternal(internal_timestamp));
    internal_timestamp += 2;
  }

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

// Make sure that the method ToInternal(Packet* packet) is wired up correctly.
// Since it is simply calling the other ToInternal method, we are not doing
// as many tests here.
TEST(TimestampScaler, TestG722Packet) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info;
  info.codec_type = kDecoderG722;  // Does uses a factor 2 scaling.
  static const uint8_t kRtpPayloadType = 17;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillRepeatedly(Return(&info));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  uint32_t external_timestamp = 0xFFFFFFFF - 5;
  uint32_t internal_timestamp = external_timestamp;
  Packet packet;
  packet.header.payloadType = kRtpPayloadType;
  for (; external_timestamp != 5; ++external_timestamp) {
    packet.header.timestamp = external_timestamp;
    // Scale to internal timestamp.
    scaler.ToInternal(&packet);
    EXPECT_EQ(internal_timestamp, packet.header.timestamp);
    internal_timestamp += 2;
  }

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

// Make sure that the method ToInternal(PacketList* packet_list) is wired up
// correctly. Since it is simply calling the ToInternal(Packet* packet) method,
// we are not doing as many tests here.
TEST(TimestampScaler, TestG722PacketList) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info;
  info.codec_type = kDecoderG722;  // Uses a factor 2 scaling.
  static const uint8_t kRtpPayloadType = 17;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillRepeatedly(Return(&info));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  uint32_t external_timestamp = 0xFFFFFFFF - 5;
  uint32_t internal_timestamp = external_timestamp;
  Packet packet1;
  packet1.header.payloadType = kRtpPayloadType;
  packet1.header.timestamp = external_timestamp;
  Packet packet2;
  packet2.header.payloadType = kRtpPayloadType;
  packet2.header.timestamp = external_timestamp + 10;
  PacketList packet_list;
  packet_list.push_back(&packet1);
  packet_list.push_back(&packet2);

  scaler.ToInternal(&packet_list);
  EXPECT_EQ(internal_timestamp, packet1.header.timestamp);
  EXPECT_EQ(internal_timestamp + 20, packet2.header.timestamp);

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

TEST(TimestampScaler, TestG722Reset) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info;
  info.codec_type = kDecoderG722;  // Uses a factor 2 scaling.
  static const uint8_t kRtpPayloadType = 17;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillRepeatedly(Return(&info));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  uint32_t external_timestamp = 0xFFFFFFFF - 5;
  uint32_t internal_timestamp = external_timestamp;
  for (; external_timestamp != 5; ++external_timestamp) {
    // Scale to internal timestamp.
    EXPECT_EQ(internal_timestamp,
              scaler.ToInternal(external_timestamp, kRtpPayloadType));
    // Scale back.
    EXPECT_EQ(external_timestamp, scaler.ToExternal(internal_timestamp));
    internal_timestamp += 2;
  }
  // Reset the scaler. After this, we expect the internal and external to start
  // over at the same value again.
  scaler.Reset();
  internal_timestamp = external_timestamp;
  for (; external_timestamp != 15; ++external_timestamp) {
    // Scale to internal timestamp.
    EXPECT_EQ(internal_timestamp,
              scaler.ToInternal(external_timestamp, kRtpPayloadType));
    // Scale back.
    EXPECT_EQ(external_timestamp, scaler.ToExternal(internal_timestamp));
    internal_timestamp += 2;
  }

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

// TODO(minyue): This test becomes trivial since Opus does not need a timestamp
// scaler. Therefore, this test may be removed in future. There is no harm to
// keep it, since it can be taken as a test case for the situation of a trivial
// timestamp scaler.
TEST(TimestampScaler, TestOpusLargeStep) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info;
  info.codec_type = kDecoderOpus;
  static const uint8_t kRtpPayloadType = 17;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillRepeatedly(Return(&info));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  static const uint32_t kStep = 960;
  uint32_t external_timestamp = 0;
  // |external_timestamp| will be a large positive value.
  external_timestamp = external_timestamp - 5 * kStep;
  uint32_t internal_timestamp = external_timestamp;
  for (; external_timestamp != 5 * kStep; external_timestamp += kStep) {
    // Scale to internal timestamp.
    EXPECT_EQ(internal_timestamp,
              scaler.ToInternal(external_timestamp, kRtpPayloadType));
    // Scale back.
    EXPECT_EQ(external_timestamp, scaler.ToExternal(internal_timestamp));
    internal_timestamp += kStep;
  }

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

TEST(TimestampScaler, TestIsacFbLargeStep) {
  MockDecoderDatabase db;
  DecoderDatabase::DecoderInfo info;
  info.codec_type = kDecoderISACfb;
  static const uint8_t kRtpPayloadType = 17;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillRepeatedly(Return(&info));

  TimestampScaler scaler(db);
  // Test both sides of the timestamp wrap-around.
  static const uint32_t kStep = 960;
  uint32_t external_timestamp = 0;
  // |external_timestamp| will be a large positive value.
  external_timestamp = external_timestamp - 5 * kStep;
  uint32_t internal_timestamp = external_timestamp;
  for (; external_timestamp != 5 * kStep; external_timestamp += kStep) {
    // Scale to internal timestamp.
    EXPECT_EQ(internal_timestamp,
              scaler.ToInternal(external_timestamp, kRtpPayloadType));
    // Scale back.
    EXPECT_EQ(external_timestamp, scaler.ToExternal(internal_timestamp));
    // Internal timestamp should be incremented with two-thirds the step.
    internal_timestamp += 2 * kStep / 3;
  }

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

TEST(TimestampScaler, Failures) {
  static const uint8_t kRtpPayloadType = 17;
  MockDecoderDatabase db;
  EXPECT_CALL(db, GetDecoderInfo(kRtpPayloadType))
      .WillOnce(ReturnNull());  // Return NULL to indicate unknown payload type.

  TimestampScaler scaler(db);
  uint32_t timestamp = 4711;  // Some number.
  EXPECT_EQ(timestamp, scaler.ToInternal(timestamp, kRtpPayloadType));

  Packet* packet = NULL;
  scaler.ToInternal(packet);  // Should not crash. That's all we can test.

  EXPECT_CALL(db, Die());  // Called when database object is deleted.
}

}  // namespace webrtc
