// Copyright 2015 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.

#include "base/basictypes.h"
#include "base/bind.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "chromecast/media/cma/base/balanced_media_task_runner_factory.h"
#include "chromecast/media/cma/base/decoder_buffer_base.h"
#include "chromecast/public/media/cast_decoder_buffer.h"
#include "chromecast/renderer/media/demuxer_stream_adapter.h"
#include "chromecast/renderer/media/demuxer_stream_for_test.h"
#include "media/base/audio_decoder_config.h"
#include "media/base/decoder_buffer.h"
#include "media/base/demuxer_stream.h"
#include "media/base/video_decoder_config.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace chromecast {
namespace media {

namespace {
// Maximum pts diff between frames
const int kMaxPtsDiffMs = 2000;
}  // namespace

// Test for multiple streams
class MultiDemuxerStreamAdaptersTest : public testing::Test {
 public:
  MultiDemuxerStreamAdaptersTest();
  ~MultiDemuxerStreamAdaptersTest() override;

  void Start();

 protected:
  void OnTestTimeout();
  void OnNewFrame(CodedFrameProvider* frame_provider,
                  const scoped_refptr<DecoderBufferBase>& buffer,
                  const ::media::AudioDecoderConfig& audio_config,
                  const ::media::VideoDecoderConfig& video_config);

  // Number of expected read frames.
  int total_expected_frames_;

  // Number of frames actually read so far.
  int frame_received_count_;

  // List of expected frame indices with decoder config changes.
  std::list<int> config_idx_;

  ScopedVector<DemuxerStreamForTest> demuxer_streams_;

  ScopedVector<CodedFrameProvider> coded_frame_providers_;

 private:
  // exit if all of the streams end
  void OnEos();

  // Number of reading-streams
  int running_stream_count_;

  scoped_refptr<BalancedMediaTaskRunnerFactory> media_task_runner_factory_;
  DISALLOW_COPY_AND_ASSIGN(MultiDemuxerStreamAdaptersTest);
};

MultiDemuxerStreamAdaptersTest::MultiDemuxerStreamAdaptersTest() {
}

MultiDemuxerStreamAdaptersTest::~MultiDemuxerStreamAdaptersTest() {
}

void MultiDemuxerStreamAdaptersTest::Start() {
  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE,
      base::Bind(&MultiDemuxerStreamAdaptersTest::OnTestTimeout,
                 base::Unretained(this)),
      base::TimeDelta::FromSeconds(5));

  media_task_runner_factory_ = new BalancedMediaTaskRunnerFactory(
      base::TimeDelta::FromMilliseconds(kMaxPtsDiffMs));

  coded_frame_providers_.clear();
  frame_received_count_ = 0;

  for (auto& stream : demuxer_streams_) {
    coded_frame_providers_.push_back(make_scoped_ptr(
        new DemuxerStreamAdapter(base::ThreadTaskRunnerHandle::Get(),
                                 media_task_runner_factory_,
                                 stream)));
  }
  running_stream_count_ = coded_frame_providers_.size();

  // read each stream
  for (auto& code_frame_provider : coded_frame_providers_) {
    auto read_cb = base::Bind(&MultiDemuxerStreamAdaptersTest::OnNewFrame,
                              base::Unretained(this),
                              code_frame_provider);

    base::Closure task = base::Bind(&CodedFrameProvider::Read,
                                    base::Unretained(code_frame_provider),
                                    read_cb);

    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, task);
  }
}

void MultiDemuxerStreamAdaptersTest::OnTestTimeout() {
  if (running_stream_count_ != 0) {
    ADD_FAILURE() << "Test timed out";
  }
}

void MultiDemuxerStreamAdaptersTest::OnNewFrame(
    CodedFrameProvider* frame_provider,
    const scoped_refptr<DecoderBufferBase>& buffer,
    const ::media::AudioDecoderConfig& audio_config,
    const ::media::VideoDecoderConfig& video_config) {
  if (buffer->end_of_stream()) {
    OnEos();
    return;
  }

  frame_received_count_++;
  auto read_cb = base::Bind(&MultiDemuxerStreamAdaptersTest::OnNewFrame,
                            base::Unretained(this),
                            frame_provider);
  frame_provider->Read(read_cb);
}

void MultiDemuxerStreamAdaptersTest::OnEos() {
  running_stream_count_--;
  ASSERT_GE(running_stream_count_, 0);
  if (running_stream_count_ == 0) {
    ASSERT_EQ(frame_received_count_, total_expected_frames_);
    base::MessageLoop::current()->QuitWhenIdle();
  }
}

TEST_F(MultiDemuxerStreamAdaptersTest, EarlyEos) {
  // We have more than one streams here. One of them is much shorter than the
  // others. When the shortest stream reaches EOS, other streams should still
  // run as usually. BalancedTaskRunner should not be blocked.
  int frame_count_short = 2;
  int frame_count_long =
      frame_count_short +
      kMaxPtsDiffMs / DemuxerStreamForTest::kDemuxerStreamForTestFrameDuration +
      100;
  demuxer_streams_.push_back(scoped_ptr<DemuxerStreamForTest>(
      new DemuxerStreamForTest(frame_count_short, 2, 0, config_idx_)));
  demuxer_streams_.push_back(scoped_ptr<DemuxerStreamForTest>(
      new DemuxerStreamForTest(frame_count_long, 10, 0, config_idx_)));

  total_expected_frames_ = frame_count_short + frame_count_long;

  scoped_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
  message_loop->PostTask(FROM_HERE,
                         base::Bind(&MultiDemuxerStreamAdaptersTest::Start,
                                    base::Unretained(this)));
  message_loop->Run();
}
}  // namespace media
}  // namespace chromecast