/*
 *  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 "gtest/gtest.h"
#include "vpx/vpx_encoder.h"
#include "vpx/vp8cx.h"
#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
#include "webrtc/modules/video_coding/codecs/vp8/screenshare_layers.h"
#include "webrtc/modules/video_coding/utility/include/mock/mock_frame_dropper.h"
#include "webrtc/system_wrappers/interface/scoped_ptr.h"

using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;

namespace webrtc {

enum { kTimestampDelta5Fps = 90000 / 5 };  // 5 frames per second at 90 kHz.
enum { kTimestampDelta30Fps = 90000 / 30 };  // 30 frames per second at 90 kHz.
enum { kFrameSize = 2500 };

const int kFlagsTL0 = VP8_EFLAG_NO_UPD_GF | VP8_EFLAG_NO_UPD_ARF |
    VP8_EFLAG_NO_REF_GF | VP8_EFLAG_NO_REF_ARF;
const int kFlagsTL1 = VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_UPD_ARF |
    VP8_EFLAG_NO_UPD_LAST;
const int kFlagsTL1Sync = VP8_EFLAG_NO_REF_ARF | VP8_EFLAG_NO_REF_GF |
    VP8_EFLAG_NO_UPD_ARF | VP8_EFLAG_NO_UPD_LAST;

class ScreenshareLayersFT : public ScreenshareLayers {
 public:
  ScreenshareLayersFT(int num_temporal_layers,
                      uint8_t initial_tl0_pic_idx,
                      FrameDropper* tl0_frame_dropper,
                      FrameDropper* tl1_frame_dropper)
      : ScreenshareLayers(num_temporal_layers,
                          initial_tl0_pic_idx,
                          tl0_frame_dropper,
                          tl1_frame_dropper) {}
  virtual ~ScreenshareLayersFT() {}

 protected:
  virtual bool TargetBitrateExperimentEnabled() OVERRIDE { return true; }
};

class ScreenshareLayerTest : public ::testing::Test {
 protected:
  void SetEncodeExpectations(bool drop_tl0, bool drop_tl1, int framerate) {
    EXPECT_CALL(tl0_frame_dropper_, DropFrame())
        .Times(1)
        .WillRepeatedly(Return(drop_tl0));
    if (drop_tl0) {
      EXPECT_CALL(tl1_frame_dropper_, DropFrame())
          .Times(1)
          .WillRepeatedly(Return(drop_tl1));
    }
    EXPECT_CALL(tl0_frame_dropper_, Leak(framerate))
        .Times(1);
    EXPECT_CALL(tl1_frame_dropper_, Leak(framerate))
        .Times(1);
    if (drop_tl0) {
      EXPECT_CALL(tl0_frame_dropper_, Fill(_, _))
          .Times(0);
      if (drop_tl1) {
        EXPECT_CALL(tl1_frame_dropper_, Fill(_, _))
              .Times(0);
      } else {
        EXPECT_CALL(tl1_frame_dropper_, Fill(kFrameSize, true))
            .Times(1);
      }
    } else {
      EXPECT_CALL(tl0_frame_dropper_, Fill(kFrameSize, true))
          .Times(1);
      EXPECT_CALL(tl1_frame_dropper_, Fill(kFrameSize, true))
          .Times(1);
    }
  }

  void EncodeFrame(uint32_t timestamp,
                   bool base_sync,
                   CodecSpecificInfoVP8* vp8_info,
                   int* flags) {
    *flags = layers_->EncodeFlags(timestamp);
    layers_->PopulateCodecSpecific(base_sync, vp8_info, timestamp);
    layers_->FrameEncoded(kFrameSize, timestamp);
  }

  NiceMock<MockFrameDropper> tl0_frame_dropper_;
  NiceMock<MockFrameDropper> tl1_frame_dropper_;
  scoped_ptr<ScreenshareLayersFT> layers_;
};

TEST_F(ScreenshareLayerTest, 1Layer) {
  layers_.reset(
      new ScreenshareLayersFT(1, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
  EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
  int flags = 0;
  uint32_t timestamp = 0;
  CodecSpecificInfoVP8 vp8_info;
  // One layer screenshare should not use the frame dropper as all frames will
  // belong to the base layer.
  EXPECT_CALL(tl0_frame_dropper_, DropFrame())
      .Times(0);
  EXPECT_CALL(tl1_frame_dropper_, DropFrame())
      .Times(0);
  flags = layers_->EncodeFlags(timestamp);
  EXPECT_EQ(0, flags);
  layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
  EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info.temporalIdx);
  EXPECT_FALSE(vp8_info.layerSync);
  EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx);
  layers_->FrameEncoded(kFrameSize, timestamp);

  EXPECT_CALL(tl0_frame_dropper_, DropFrame())
      .Times(0);
  EXPECT_CALL(tl1_frame_dropper_, DropFrame())
      .Times(0);
  flags = layers_->EncodeFlags(timestamp);
  EXPECT_EQ(0, flags);
  timestamp += kTimestampDelta5Fps;
  layers_->PopulateCodecSpecific(false, &vp8_info, timestamp);
  EXPECT_EQ(static_cast<uint8_t>(kNoTemporalIdx), vp8_info.temporalIdx);
  EXPECT_FALSE(vp8_info.layerSync);
  EXPECT_EQ(kNoTl0PicIdx, vp8_info.tl0PicIdx);
  layers_->FrameEncoded(kFrameSize, timestamp);
}

TEST_F(ScreenshareLayerTest, 2Layer) {
  layers_.reset(
      new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
  EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
  int flags = 0;
  uint32_t timestamp = 0;
  uint8_t expected_tl0_idx = 0;
  CodecSpecificInfoVP8 vp8_info;
  SetEncodeExpectations(false, false, 1);
  EncodeFrame(timestamp, false, &vp8_info, &flags);
  EXPECT_EQ(kFlagsTL0, flags);
  EXPECT_EQ(0, vp8_info.temporalIdx);
  EXPECT_FALSE(vp8_info.layerSync);
  ++expected_tl0_idx;
  EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);

  EXPECT_CALL(tl1_frame_dropper_, SetRates(1000, 1))
      .Times(1);
  EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, -1, NULL));
  // Insert 5 frames at 30 fps. All should belong to TL0.
  for (int i = 0; i < 5; ++i) {
    timestamp += kTimestampDelta30Fps;
    // First iteration has a framerate based on a single frame, thus 1.
    SetEncodeExpectations(false, false, 30);
    EncodeFrame(timestamp, false, &vp8_info, &flags);
    EXPECT_EQ(0, vp8_info.temporalIdx);
    EXPECT_FALSE(vp8_info.layerSync);
    ++expected_tl0_idx;
    EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
  }
  // Drop two frames from TL0, thus being coded in TL1.
  timestamp += kTimestampDelta30Fps;
  SetEncodeExpectations(true, false, 30);
  EncodeFrame(timestamp, false, &vp8_info, &flags);
  EXPECT_EQ(kFlagsTL1Sync, flags);
  EXPECT_EQ(1, vp8_info.temporalIdx);
  EXPECT_TRUE(vp8_info.layerSync);
  EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);

  timestamp += kTimestampDelta30Fps;
  SetEncodeExpectations(true, false, 30);
  EncodeFrame(timestamp, false, &vp8_info, &flags);
  EXPECT_EQ(kFlagsTL1, flags);
  EXPECT_EQ(1, vp8_info.temporalIdx);
  EXPECT_FALSE(vp8_info.layerSync);
  EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);
}

TEST_F(ScreenshareLayerTest, 2LayersPeriodicSync) {
  layers_.reset(
      new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
  EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
  int flags = 0;
  uint32_t timestamp = 0;
  CodecSpecificInfoVP8 vp8_info;
  const int kNumFrames = 10;
  const bool kDrops[kNumFrames] = {false, true, true, true, true,
      true, true, true, true, true};
  const int kExpectedFramerates[kNumFrames] = {1, 5, 5, 5, 5, 5, 5, 5, 5, 5};
  const bool kExpectedSyncs[kNumFrames] = {false, true, false, false, false,
      false, false, true, false, false};
  const int kExpectedTemporalIdx[kNumFrames] = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1};
  for (int i = 0; i < kNumFrames; ++i) {
    timestamp += kTimestampDelta5Fps;
    SetEncodeExpectations(kDrops[i], false, kExpectedFramerates[i]);
    EncodeFrame(timestamp, false, &vp8_info, &flags);
    EXPECT_EQ(kExpectedTemporalIdx[i], vp8_info.temporalIdx);
    EXPECT_EQ(kExpectedSyncs[i], vp8_info.layerSync) << "Iteration: " << i;
    EXPECT_EQ(1, vp8_info.tl0PicIdx);
  }
}

TEST_F(ScreenshareLayerTest, 2LayersToggling) {
  layers_.reset(
      new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
  EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
  int flags = 0;
  uint32_t timestamp = 0;
  CodecSpecificInfoVP8 vp8_info;
  const int kNumFrames = 10;
  const bool kDrops[kNumFrames] = {false, true, false, true, false,
      true, false, true, false, true};
  const int kExpectedFramerates[kNumFrames] = {1, 5, 5, 5, 5, 5, 5, 5, 5, 5};
  const bool kExpectedSyncs[kNumFrames] = {false, true, false, false, false,
      false, false, true, false, false};
  const int kExpectedTemporalIdx[kNumFrames] = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
  const int kExpectedTl0Idx[kNumFrames] = {1, 1, 2, 2, 3, 3, 4, 4, 5, 5};
  for (int i = 0; i < kNumFrames; ++i) {
    timestamp += kTimestampDelta5Fps;
    SetEncodeExpectations(kDrops[i], false, kExpectedFramerates[i]);
    EncodeFrame(timestamp, false, &vp8_info, &flags);
    EXPECT_EQ(kExpectedTemporalIdx[i], vp8_info.temporalIdx);
    EXPECT_EQ(kExpectedSyncs[i], vp8_info.layerSync) << "Iteration: " << i;
    EXPECT_EQ(kExpectedTl0Idx[i], vp8_info.tl0PicIdx);
  }
}

TEST_F(ScreenshareLayerTest, 2LayersBothDrops) {
  layers_.reset(
      new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
  EXPECT_TRUE(layers_->ConfigureBitrates(100, 1000, 5, NULL));
  int flags = 0;
  uint32_t timestamp = 0;
  uint8_t expected_tl0_idx = 0;
  CodecSpecificInfoVP8 vp8_info;
  SetEncodeExpectations(false, false, 1);
  EncodeFrame(timestamp, false, &vp8_info, &flags);
  EXPECT_EQ(kFlagsTL0, flags);
  EXPECT_EQ(0, vp8_info.temporalIdx);
  EXPECT_FALSE(vp8_info.layerSync);
  ++expected_tl0_idx;
  EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);

  timestamp += kTimestampDelta5Fps;
  SetEncodeExpectations(true, false, 5);
  EncodeFrame(timestamp, false, &vp8_info, &flags);
  EXPECT_EQ(kFlagsTL1Sync, flags);
  EXPECT_EQ(1, vp8_info.temporalIdx);
  EXPECT_TRUE(vp8_info.layerSync);
  EXPECT_EQ(expected_tl0_idx, vp8_info.tl0PicIdx);

  timestamp += kTimestampDelta5Fps;
  SetEncodeExpectations(true, true, 5);
  flags = layers_->EncodeFlags(timestamp);
  EXPECT_EQ(-1, flags);
}

TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL0) {
  layers_.reset(
      new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));

  vpx_codec_enc_cfg_t cfg;
  layers_->ConfigureBitrates(100, 1000, 5, &cfg);

  EXPECT_EQ(static_cast<unsigned int>(
                ScreenshareLayers::kMaxTL0FpsReduction * 100 + 0.5),
            cfg.rc_target_bitrate);
}

TEST_F(ScreenshareLayerTest, TargetBitrateCappedByTL1) {
  layers_.reset(
      new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
  vpx_codec_enc_cfg_t cfg;
  layers_->ConfigureBitrates(100, 450, 5, &cfg);

  EXPECT_EQ(static_cast<unsigned int>(
                450 / ScreenshareLayers::kAcceptableTargetOvershoot),
            cfg.rc_target_bitrate);
}

TEST_F(ScreenshareLayerTest, TargetBitrateBelowTL0) {
  layers_.reset(
      new ScreenshareLayersFT(2, 0, &tl0_frame_dropper_, &tl1_frame_dropper_));
  vpx_codec_enc_cfg_t cfg;
  layers_->ConfigureBitrates(100, 100, 5, &cfg);

  EXPECT_EQ(100U, cfg.rc_target_bitrate);
}

}  // namespace webrtc
