// Copyright 2014 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 "chromecast/media/cma/base/demuxer_stream_adapter.h"

#include <list>
#include <memory>

#include "base/bind.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.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/media/cma/base/demuxer_stream_for_test.h"
#include "chromecast/public/media/cast_decoder_buffer.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 {

class DemuxerStreamAdapterTest : public testing::Test {
 public:
  DemuxerStreamAdapterTest();
  ~DemuxerStreamAdapterTest() override;

  void Initialize(::media::DemuxerStream* demuxer_stream);
  void Start();

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

  // Total number of frames to request.
  int total_frames_;

  // Number of demuxer read before issuing an early flush.
  int early_flush_idx_;
  bool use_post_task_for_flush_;

  // 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_;

  std::unique_ptr<DemuxerStreamForTest> demuxer_stream_;

  std::unique_ptr<CodedFrameProvider> coded_frame_provider_;

  DISALLOW_COPY_AND_ASSIGN(DemuxerStreamAdapterTest);
};

DemuxerStreamAdapterTest::DemuxerStreamAdapterTest()
    : use_post_task_for_flush_(false) {
}

DemuxerStreamAdapterTest::~DemuxerStreamAdapterTest() {
}

void DemuxerStreamAdapterTest::Initialize(
    ::media::DemuxerStream* demuxer_stream) {
  coded_frame_provider_.reset(
      new DemuxerStreamAdapter(base::ThreadTaskRunnerHandle::Get(),
                               scoped_refptr<BalancedMediaTaskRunnerFactory>(),
                               demuxer_stream));
}

void DemuxerStreamAdapterTest::Start() {
  frame_received_count_ = 0;

  // TODO(damienv): currently, test assertions which fail do not trigger the
  // exit of the unit test, the message loop is still running. Find a different
  // way to exit the unit test.
  base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE, base::Bind(&DemuxerStreamAdapterTest::OnTestTimeout,
                            base::Unretained(this)),
      base::TimeDelta::FromSeconds(5));

  coded_frame_provider_->Read(base::Bind(&DemuxerStreamAdapterTest::OnNewFrame,
                                         base::Unretained(this)));
}

void DemuxerStreamAdapterTest::OnTestTimeout() {
  ADD_FAILURE() << "Test timed out";
  if (base::MessageLoop::current())
    base::MessageLoop::current()->QuitWhenIdle();
}

void DemuxerStreamAdapterTest::OnNewFrame(
    const scoped_refptr<DecoderBufferBase>& buffer,
    const ::media::AudioDecoderConfig& audio_config,
    const ::media::VideoDecoderConfig& video_config) {
  if (video_config.IsValidConfig()) {
    ASSERT_GT(config_idx_.size(), 0u);
    ASSERT_EQ(frame_received_count_, config_idx_.front());
    config_idx_.pop_front();
  }

  ASSERT_TRUE(buffer.get() != NULL);
  ASSERT_EQ(base::TimeDelta::FromMicroseconds(buffer->timestamp()),
            base::TimeDelta::FromMilliseconds(40 * frame_received_count_));
  frame_received_count_++;

  if (frame_received_count_ >= total_frames_) {
    coded_frame_provider_->Flush(base::Bind(
        &DemuxerStreamAdapterTest::OnFlushCompleted, base::Unretained(this)));
    return;
  }

  coded_frame_provider_->Read(base::Bind(&DemuxerStreamAdapterTest::OnNewFrame,
                                         base::Unretained(this)));

  ASSERT_LE(frame_received_count_, early_flush_idx_);
  if (frame_received_count_ == early_flush_idx_) {
    base::Closure flush_cb = base::Bind(
        &DemuxerStreamAdapterTest::OnFlushCompleted, base::Unretained(this));
    if (use_post_task_for_flush_) {
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE,
          base::Bind(&CodedFrameProvider::Flush,
                     base::Unretained(coded_frame_provider_.get()), flush_cb));
    } else {
      coded_frame_provider_->Flush(flush_cb);
    }
    return;
  }
}

void DemuxerStreamAdapterTest::OnFlushCompleted() {
  ASSERT_EQ(frame_received_count_, total_expected_frames_);
  ASSERT_FALSE(demuxer_stream_->has_pending_read());
  base::MessageLoop::current()->QuitWhenIdle();
}

TEST_F(DemuxerStreamAdapterTest, NoDelay) {
  total_frames_ = 10;
  early_flush_idx_ = total_frames_;  // No early flush.
  total_expected_frames_ = 10;
  config_idx_.push_back(0);
  config_idx_.push_back(5);

  int cycle_count = 1;
  int delayed_frame_count = 0;
  demuxer_stream_.reset(new DemuxerStreamForTest(
      -1, cycle_count, delayed_frame_count, config_idx_));

  std::unique_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
  Initialize(demuxer_stream_.get());
  message_loop->PostTask(
      FROM_HERE,
      base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this)));
  message_loop->Run();
}

TEST_F(DemuxerStreamAdapterTest, AllDelayed) {
  total_frames_ = 10;
  early_flush_idx_ = total_frames_;  // No early flush.
  total_expected_frames_ = 10;
  config_idx_.push_back(0);
  config_idx_.push_back(5);

  int cycle_count = 1;
  int delayed_frame_count = 1;
  demuxer_stream_.reset(new DemuxerStreamForTest(
      -1, cycle_count, delayed_frame_count, config_idx_));

  std::unique_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
  Initialize(demuxer_stream_.get());
  message_loop->PostTask(
      FROM_HERE,
      base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this)));
  message_loop->Run();
}

TEST_F(DemuxerStreamAdapterTest, AllDelayedEarlyFlush) {
  total_frames_ = 10;
  early_flush_idx_ = 5;
  use_post_task_for_flush_ = true;
  total_expected_frames_ = 5;
  config_idx_.push_back(0);
  config_idx_.push_back(3);

  int cycle_count = 1;
  int delayed_frame_count = 1;
  demuxer_stream_.reset(new DemuxerStreamForTest(
      -1, cycle_count, delayed_frame_count, config_idx_));

  std::unique_ptr<base::MessageLoop> message_loop(new base::MessageLoop());
  Initialize(demuxer_stream_.get());
  message_loop->PostTask(
      FROM_HERE,
      base::Bind(&DemuxerStreamAdapterTest::Start, base::Unretained(this)));
  message_loop->Run();
}

}  // namespace media
}  // namespace chromecast
