// Copyright (c) 2011 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 <string>

#include "base/callback.h"
#include "base/stl_util.h"
#include "base/threading/simple_thread.h"
#include "media/base/pipeline_impl.h"
#include "media/base/filters.h"
#include "media/base/filter_host.h"
#include "media/base/mock_callback.h"
#include "media/base/mock_filters.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::DeleteArg;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::StrictMock;

namespace media {

// Total bytes of the data source.
static const int kTotalBytes = 1024;

// Buffered bytes of the data source.
static const int kBufferedBytes = 1024;

// Test url for raw video pipeline.
static const char kUrlRawVideo[] = "://raw_video_stream";

// Used for setting expectations on pipeline callbacks.  Using a StrictMock
// also lets us test for missing callbacks.
class CallbackHelper {
 public:
  CallbackHelper() {}
  virtual ~CallbackHelper() {}

  MOCK_METHOD1(OnStart, void(PipelineStatus));
  MOCK_METHOD1(OnSeek, void(PipelineStatus));
  MOCK_METHOD1(OnStop, void(PipelineStatus));
  MOCK_METHOD1(OnEnded, void(PipelineStatus));
  MOCK_METHOD1(OnError, void(PipelineStatus));

 private:
  DISALLOW_COPY_AND_ASSIGN(CallbackHelper);
};

// TODO(scherkus): even though some filters are initialized on separate
// threads these test aren't flaky... why?  It's because filters' Initialize()
// is executed on |message_loop_| and the mock filters instantly call
// InitializationComplete(), which keeps the pipeline humming along.  If
// either filters don't call InitializationComplete() immediately or filter
// initialization is moved to a separate thread this test will become flaky.
class PipelineImplTest : public ::testing::Test {
 public:
  PipelineImplTest()
      : pipeline_(new PipelineImpl(&message_loop_)) {
    pipeline_->Init(
        NewCallback(reinterpret_cast<CallbackHelper*>(&callbacks_),
                    &CallbackHelper::OnEnded),
        NewCallback(reinterpret_cast<CallbackHelper*>(&callbacks_),
                    &CallbackHelper::OnError),
        static_cast<PipelineStatusCallback*>(NULL));
    mocks_.reset(new MockFilterCollection());

    // InitializeDemuxer adds overriding expectations for expected non-NULL
    // streams.
    DemuxerStream* null_pointer = NULL;
    EXPECT_CALL(*mocks_->demuxer(), GetStream(_))
        .WillRepeatedly(Return(null_pointer));

    EXPECT_CALL(*mocks_->demuxer(), GetStartTime())
        .WillRepeatedly(Return(base::TimeDelta()));
  }

  virtual ~PipelineImplTest() {
    if (!pipeline_->IsRunning()) {
      return;
    }

    // Expect a stop callback if we were started.
    EXPECT_CALL(callbacks_, OnStop(PIPELINE_OK));
    pipeline_->Stop(NewCallback(reinterpret_cast<CallbackHelper*>(&callbacks_),
                                &CallbackHelper::OnStop));
    message_loop_.RunAllPending();

    mocks_.reset();
  }

 protected:
  // Sets up expectations to allow the demuxer to initialize.
  typedef std::vector<MockDemuxerStream*> MockDemuxerStreamVector;
  void InitializeDemuxer(MockDemuxerStreamVector* streams,
                         const base::TimeDelta& duration) {
    mocks_->demuxer()->SetTotalAndBufferedBytesAndDuration(
        kTotalBytes, kBufferedBytes, duration);
    EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(0.0f));
    EXPECT_CALL(*mocks_->demuxer(), SetPreload(AUTO));
    EXPECT_CALL(*mocks_->demuxer(), Seek(mocks_->demuxer()->GetStartTime(), _))
        .WillOnce(Invoke(&RunFilterStatusCB));
    EXPECT_CALL(*mocks_->demuxer(), Stop(NotNull()))
        .WillOnce(Invoke(&RunStopFilterCallback));

    // Configure the demuxer to return the streams.
    for (size_t i = 0; i < streams->size(); ++i) {
      scoped_refptr<DemuxerStream> stream((*streams)[i]);
      EXPECT_CALL(*mocks_->demuxer(), GetStream(stream->type()))
          .WillRepeatedly(Return(stream));
    }
  }

  StrictMock<MockDemuxerStream>* CreateStream(DemuxerStream::Type type) {
    StrictMock<MockDemuxerStream>* stream =
        new StrictMock<MockDemuxerStream>();
    EXPECT_CALL(*stream, type())
        .WillRepeatedly(Return(type));
    return stream;
  }

  // Sets up expectations to allow the video decoder to initialize.
  void InitializeVideoDecoder(MockDemuxerStream* stream) {
    EXPECT_CALL(*mocks_->video_decoder(),
                Initialize(stream, NotNull(), NotNull()))
        .WillOnce(DoAll(Invoke(&RunFilterCallback3), DeleteArg<2>()));
    EXPECT_CALL(*mocks_->video_decoder(), SetPlaybackRate(0.0f));
    EXPECT_CALL(*mocks_->video_decoder(),
                Seek(mocks_->demuxer()->GetStartTime(), _))
        .WillOnce(Invoke(&RunFilterStatusCB));
    EXPECT_CALL(*mocks_->video_decoder(), Stop(NotNull()))
        .WillOnce(Invoke(&RunStopFilterCallback));
  }

  // Sets up expectations to allow the audio decoder to initialize.
  void InitializeAudioDecoder(MockDemuxerStream* stream) {
    EXPECT_CALL(*mocks_->audio_decoder(),
                Initialize(stream, NotNull(), NotNull()))
        .WillOnce(DoAll(Invoke(&RunFilterCallback3), DeleteArg<2>()));
    EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(0.0f));
    EXPECT_CALL(*mocks_->audio_decoder(), Seek(base::TimeDelta(), _))
        .WillOnce(Invoke(&RunFilterStatusCB));
    EXPECT_CALL(*mocks_->audio_decoder(), Stop(NotNull()))
        .WillOnce(Invoke(&RunStopFilterCallback));
  }

  // Sets up expectations to allow the video renderer to initialize.
  void InitializeVideoRenderer() {
    EXPECT_CALL(*mocks_->video_renderer(),
                Initialize(mocks_->video_decoder(), NotNull(), NotNull()))
        .WillOnce(DoAll(Invoke(&RunFilterCallback3), DeleteArg<2>()));
    EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(0.0f));
    EXPECT_CALL(*mocks_->video_renderer(),
                Seek(mocks_->demuxer()->GetStartTime(), _))
        .WillOnce(Invoke(&RunFilterStatusCB));
    EXPECT_CALL(*mocks_->video_renderer(), Stop(NotNull()))
        .WillOnce(Invoke(&RunStopFilterCallback));
  }

  // Sets up expectations to allow the audio renderer to initialize.
  void InitializeAudioRenderer(bool disable_after_init_callback = false) {
    if (disable_after_init_callback) {
      EXPECT_CALL(*mocks_->audio_renderer(),
                  Initialize(mocks_->audio_decoder(), NotNull()))
          .WillOnce(DoAll(Invoke(&RunFilterCallback),
                          DisableAudioRenderer(mocks_->audio_renderer())));
    } else {
      EXPECT_CALL(*mocks_->audio_renderer(),
                  Initialize(mocks_->audio_decoder(), NotNull()))
          .WillOnce(Invoke(&RunFilterCallback));
    }
    EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(0.0f));
    EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(1.0f));
    EXPECT_CALL(*mocks_->audio_renderer(), Seek(base::TimeDelta(), _))
        .WillOnce(Invoke(&RunFilterStatusCB));
    EXPECT_CALL(*mocks_->audio_renderer(), Stop(NotNull()))
        .WillOnce(Invoke(&RunStopFilterCallback));
  }

  // Sets up expectations on the callback and initializes the pipeline.  Called
  // after tests have set expectations any filters they wish to use.
  void InitializePipeline() {
    InitializePipeline(PIPELINE_OK);
  }
  // Most tests can expect the |filter_collection|'s |build_status| to get
  // reflected in |Start()|'s argument.
  void InitializePipeline(PipelineStatus start_status) {
    InitializePipeline(start_status, start_status);
  }
  // But some tests require different statuses in build & Start.
  void InitializePipeline(PipelineStatus build_status,
                          PipelineStatus start_status) {
    InitializePipeline(build_status, start_status, "");
  }

  void InitializePipeline(PipelineStatus build_status,
                          PipelineStatus start_status,
                          const std::string& url) {
    // Expect an initialization callback.
    EXPECT_CALL(callbacks_, OnStart(start_status));

    bool run_build = true;
    if (url.find(kRawMediaScheme) == 0)
      run_build = false;

    pipeline_->Start(mocks_->filter_collection(true,
                                               true,
                                               run_build,
                                               build_status),
                     url,
                     NewCallback(reinterpret_cast<CallbackHelper*>(&callbacks_),
                                 &CallbackHelper::OnStart));

    message_loop_.RunAllPending();
  }

  void CreateAudioStream() {
    audio_stream_ = CreateStream(DemuxerStream::AUDIO);
  }

  void CreateVideoStream() {
    video_stream_ = CreateStream(DemuxerStream::VIDEO);
  }

  MockDemuxerStream* audio_stream() {
    return audio_stream_;
  }

  MockDemuxerStream* video_stream() {
    return video_stream_;
  }

  void ExpectSeek(const base::TimeDelta& seek_time) {
    // Every filter should receive a call to Seek().
    EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _))
        .WillOnce(Invoke(&RunFilterStatusCB));

    if (audio_stream_) {
      EXPECT_CALL(*mocks_->audio_decoder(), Seek(seek_time, _))
          .WillOnce(Invoke(&RunFilterStatusCB));
      EXPECT_CALL(*mocks_->audio_renderer(), Seek(seek_time, _))
          .WillOnce(Invoke(&RunFilterStatusCB));
    }

    if (video_stream_) {
      EXPECT_CALL(*mocks_->video_decoder(), Seek(seek_time, _))
          .WillOnce(Invoke(&RunFilterStatusCB));
      EXPECT_CALL(*mocks_->video_renderer(), Seek(seek_time, _))
          .WillOnce(Invoke(&RunFilterStatusCB));
    }

    // We expect a successful seek callback.
    EXPECT_CALL(callbacks_, OnSeek(PIPELINE_OK));
  }

  void DoSeek(const base::TimeDelta& seek_time) {
    pipeline_->Seek(seek_time,
                    NewCallback(reinterpret_cast<CallbackHelper*>(&callbacks_),
                                &CallbackHelper::OnSeek));

    // We expect the time to be updated only after the seek has completed.
    EXPECT_NE(seek_time, pipeline_->GetCurrentTime());
    message_loop_.RunAllPending();
    EXPECT_EQ(seek_time, pipeline_->GetCurrentTime());
  }

  // Fixture members.
  StrictMock<CallbackHelper> callbacks_;
  MessageLoop message_loop_;
  scoped_refptr<PipelineImpl> pipeline_;
  scoped_ptr<media::MockFilterCollection> mocks_;
  scoped_refptr<StrictMock<MockDemuxerStream> > audio_stream_;
  scoped_refptr<StrictMock<MockDemuxerStream> > video_stream_;

 private:
  DISALLOW_COPY_AND_ASSIGN(PipelineImplTest);
};

// Test that playback controls methods no-op when the pipeline hasn't been
// started.
TEST_F(PipelineImplTest, NotStarted) {
  const base::TimeDelta kZero;

  // StrictMock<> will ensure these never get called, and valgrind/purify will
  // make sure the callbacks are instantly deleted.
  pipeline_->Stop(NewCallback(reinterpret_cast<CallbackHelper*>(&callbacks_),
                              &CallbackHelper::OnStop));
  pipeline_->Seek(kZero,
                  NewCallback(reinterpret_cast<CallbackHelper*>(&callbacks_),
                              &CallbackHelper::OnSeek));

  EXPECT_FALSE(pipeline_->IsRunning());
  EXPECT_FALSE(pipeline_->IsInitialized());
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_FALSE(pipeline_->HasVideo());

  // Setting should still work.
  EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate());
  pipeline_->SetPlaybackRate(-1.0f);
  EXPECT_EQ(0.0f, pipeline_->GetPlaybackRate());
  pipeline_->SetPlaybackRate(1.0f);
  EXPECT_EQ(1.0f, pipeline_->GetPlaybackRate());

  // Setting should still work.
  EXPECT_EQ(1.0f, pipeline_->GetVolume());
  pipeline_->SetVolume(-1.0f);
  EXPECT_EQ(1.0f, pipeline_->GetVolume());
  pipeline_->SetVolume(0.0f);
  EXPECT_EQ(0.0f, pipeline_->GetVolume());

  EXPECT_TRUE(kZero == pipeline_->GetCurrentTime());
  EXPECT_TRUE(kZero == pipeline_->GetBufferedTime());
  EXPECT_TRUE(kZero == pipeline_->GetMediaDuration());

  EXPECT_EQ(0, pipeline_->GetBufferedBytes());
  EXPECT_EQ(0, pipeline_->GetTotalBytes());

  // Should always get set to zero.
  size_t width = 1u;
  size_t height = 1u;
  pipeline_->GetVideoSize(&width, &height);
  EXPECT_EQ(0u, width);
  EXPECT_EQ(0u, height);
}

TEST_F(PipelineImplTest, NeverInitializes) {
  // This test hangs during initialization by never calling
  // InitializationComplete().  StrictMock<> will ensure that the callback is
  // never executed.
  pipeline_->Start(mocks_->filter_collection(false,
                                             false,
                                             true,
                                             PIPELINE_OK),
                   "",
                   NewCallback(reinterpret_cast<CallbackHelper*>(&callbacks_),
                               &CallbackHelper::OnStart));
  message_loop_.RunAllPending();

  EXPECT_FALSE(pipeline_->IsInitialized());

  // Because our callback will get executed when the test tears down, we'll
  // verify that nothing has been called, then set our expectation for the call
  // made during tear down.
  Mock::VerifyAndClear(&callbacks_);
  EXPECT_CALL(callbacks_, OnStart(PIPELINE_OK));
}

TEST_F(PipelineImplTest, RequiredFilterMissing) {
  EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_REQUIRED_FILTER_MISSING));

  // Sets up expectations on the callback and initializes the pipeline.  Called
  // after tests have set expectations any filters they wish to use.
  // Expect an initialization callback.
  EXPECT_CALL(callbacks_, OnStart(PIPELINE_ERROR_REQUIRED_FILTER_MISSING));

  // Create a filter collection with missing filter.
  FilterCollection* collection =
      mocks_->filter_collection(false,
                                true,
                                true,
                                PIPELINE_ERROR_REQUIRED_FILTER_MISSING);
  pipeline_->Start(collection, "",
                   NewCallback(reinterpret_cast<CallbackHelper*>(&callbacks_),
                               &CallbackHelper::OnStart));
  message_loop_.RunAllPending();

  EXPECT_FALSE(pipeline_->IsInitialized());
}

TEST_F(PipelineImplTest, URLNotFound) {
  // TODO(acolwell,fischman): Since OnStart() is getting called with an error
  // code already, OnError() doesn't also need to get called.  Fix the pipeline
  // (and it's consumers!) so that OnError doesn't need to be called after
  // another callback has already reported the error.  Same applies to NoStreams
  // below.
  EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_URL_NOT_FOUND));
  InitializePipeline(PIPELINE_ERROR_URL_NOT_FOUND);
  EXPECT_FALSE(pipeline_->IsInitialized());
}

TEST_F(PipelineImplTest, NoStreams) {
  // Manually set these expectations because SetPlaybackRate() is not called if
  // we cannot fully initialize the pipeline.
  EXPECT_CALL(*mocks_->demuxer(), Stop(NotNull()))
      .WillOnce(Invoke(&RunStopFilterCallback));
  // TODO(acolwell,fischman): see TODO in URLNotFound above.
  EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_COULD_NOT_RENDER));

  InitializePipeline(PIPELINE_OK, PIPELINE_ERROR_COULD_NOT_RENDER);
  EXPECT_FALSE(pipeline_->IsInitialized());
}

TEST_F(PipelineImplTest, AudioStream) {
  CreateAudioStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());

  InitializeDemuxer(&streams, base::TimeDelta());
  InitializeAudioDecoder(audio_stream());
  InitializeAudioRenderer();

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->IsInitialized());
  EXPECT_TRUE(pipeline_->HasAudio());
  EXPECT_FALSE(pipeline_->HasVideo());
}

TEST_F(PipelineImplTest, VideoStream) {
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, base::TimeDelta());
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->IsInitialized());
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());
}

TEST_F(PipelineImplTest, RawVideoStream) {
  InitializeVideoDecoder(NULL);
  InitializeVideoRenderer();

  InitializePipeline(PIPELINE_OK, PIPELINE_OK,
      std::string(kRawMediaScheme).append(kUrlRawVideo));
  EXPECT_TRUE(pipeline_->IsInitialized());
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());
}

TEST_F(PipelineImplTest, AudioVideoStream) {
  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, base::TimeDelta());
  InitializeAudioDecoder(audio_stream());
  InitializeAudioRenderer();
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->IsInitialized());
  EXPECT_TRUE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());
}

TEST_F(PipelineImplTest, Seek) {
  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(3000));
  InitializeAudioDecoder(audio_stream());
  InitializeAudioRenderer();
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();

  // Every filter should receive a call to Seek().
  base::TimeDelta expected = base::TimeDelta::FromSeconds(2000);
  ExpectSeek(expected);

  // Initialize then seek!
  InitializePipeline(PIPELINE_OK);
  DoSeek(expected);
}

TEST_F(PipelineImplTest, SetVolume) {
  CreateAudioStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());

  InitializeDemuxer(&streams, base::TimeDelta());
  InitializeAudioDecoder(audio_stream());
  InitializeAudioRenderer();

  // The audio renderer should receive a call to SetVolume().
  float expected = 0.5f;
  EXPECT_CALL(*mocks_->audio_renderer(), SetVolume(expected));

  // Initialize then set volume!
  InitializePipeline(PIPELINE_OK);
  pipeline_->SetVolume(expected);
}

TEST_F(PipelineImplTest, Properties) {
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
  InitializeDemuxer(&streams, kDuration);
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->IsInitialized());
  EXPECT_EQ(kDuration.ToInternalValue(),
            pipeline_->GetMediaDuration().ToInternalValue());
  EXPECT_EQ(kTotalBytes, pipeline_->GetTotalBytes());
  EXPECT_EQ(kBufferedBytes, pipeline_->GetBufferedBytes());

  // Because kTotalBytes and kBufferedBytes are equal to each other,
  // the entire video should be buffered.
  EXPECT_EQ(kDuration.ToInternalValue(),
            pipeline_->GetBufferedTime().ToInternalValue());
}

TEST_F(PipelineImplTest, GetBufferedTime) {
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
  InitializeDemuxer(&streams, kDuration);
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->IsInitialized());

  // TODO(vrk): The following mini-test cases are order-dependent, and should
  // probably be separated into independent test cases.

  // Buffered time is 0 if no bytes are buffered.
  pipeline_->SetBufferedBytes(0);
  EXPECT_EQ(0, pipeline_->GetBufferedTime().ToInternalValue());

  // We should return buffered_time_ if it is set, valid and less than
  // the current time.
  const base::TimeDelta buffered = base::TimeDelta::FromSeconds(10);
  pipeline_->SetBufferedTime(buffered);
  EXPECT_EQ(buffered.ToInternalValue(),
            pipeline_->GetBufferedTime().ToInternalValue());

  // Test the case where the current time is beyond the buffered time.
  base::TimeDelta kSeekTime = buffered + base::TimeDelta::FromSeconds(5);
  ExpectSeek(kSeekTime);
  DoSeek(kSeekTime);

  // Verify that buffered time is equal to the current time.
  EXPECT_EQ(kSeekTime, pipeline_->GetCurrentTime());
  EXPECT_EQ(kSeekTime, pipeline_->GetBufferedTime());

  // Clear buffered time.
  pipeline_->SetBufferedTime(base::TimeDelta());

  double time_percent =
      static_cast<double>(pipeline_->GetCurrentTime().ToInternalValue()) /
      kDuration.ToInternalValue();

  int estimated_bytes = static_cast<int>(time_percent * kTotalBytes);

  // Test VBR case where bytes have been consumed slower than the average rate.
  pipeline_->SetBufferedBytes(estimated_bytes - 10);
  EXPECT_EQ(pipeline_->GetCurrentTime(), pipeline_->GetBufferedTime());

  // Test VBR case where the bytes have been consumed faster than the average
  // rate.
  pipeline_->SetBufferedBytes(estimated_bytes + 10);
  EXPECT_LT(pipeline_->GetCurrentTime(), pipeline_->GetBufferedTime());

  // If media has been fully received, we should return the duration
  // of the media.
  pipeline_->SetBufferedBytes(kTotalBytes);
  EXPECT_EQ(kDuration.ToInternalValue(),
            pipeline_->GetBufferedTime().ToInternalValue());

  // If media is loaded, we should return duration of media.
  pipeline_->SetLoaded(true);
  EXPECT_EQ(kDuration.ToInternalValue(),
            pipeline_->GetBufferedTime().ToInternalValue());
}

TEST_F(PipelineImplTest, DisableAudioRenderer) {
  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, base::TimeDelta());
  InitializeAudioDecoder(audio_stream());
  InitializeAudioRenderer();
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->IsInitialized());
  EXPECT_TRUE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(1.0f))
      .WillOnce(DisableAudioRenderer(mocks_->audio_renderer()));
  EXPECT_CALL(*mocks_->demuxer(),
              OnAudioRendererDisabled());
  EXPECT_CALL(*mocks_->audio_decoder(),
              OnAudioRendererDisabled());
  EXPECT_CALL(*mocks_->audio_renderer(),
              OnAudioRendererDisabled());
  EXPECT_CALL(*mocks_->video_decoder(),
              OnAudioRendererDisabled());
  EXPECT_CALL(*mocks_->video_renderer(),
              OnAudioRendererDisabled());

  mocks_->audio_renderer()->SetPlaybackRate(1.0f);

  // Verify that ended event is fired when video ends.
  EXPECT_CALL(*mocks_->video_renderer(), HasEnded())
      .WillOnce(Return(true));
  EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK));
  FilterHost* host = pipeline_;
  host->NotifyEnded();
}

TEST_F(PipelineImplTest, DisableAudioRendererDuringInit) {
  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, base::TimeDelta());
  InitializeAudioDecoder(audio_stream());
  InitializeAudioRenderer(true);
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();

  EXPECT_CALL(*mocks_->demuxer(),
              OnAudioRendererDisabled());
  EXPECT_CALL(*mocks_->audio_decoder(),
              OnAudioRendererDisabled());
  EXPECT_CALL(*mocks_->audio_renderer(),
              OnAudioRendererDisabled());
  EXPECT_CALL(*mocks_->video_decoder(),
              OnAudioRendererDisabled());
  EXPECT_CALL(*mocks_->video_renderer(),
              OnAudioRendererDisabled());

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->IsInitialized());
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  // Verify that ended event is fired when video ends.
  EXPECT_CALL(*mocks_->video_renderer(), HasEnded())
      .WillOnce(Return(true));
  EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK));
  FilterHost* host = pipeline_;
  host->NotifyEnded();
}

TEST_F(PipelineImplTest, EndedCallback) {
  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, base::TimeDelta());
  InitializeAudioDecoder(audio_stream());
  InitializeAudioRenderer();
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();
  InitializePipeline(PIPELINE_OK);

  // For convenience to simulate filters calling the methods.
  FilterHost* host = pipeline_;

  // Due to short circuit evaluation we only need to test a subset of cases.
  InSequence s;
  EXPECT_CALL(*mocks_->audio_renderer(), HasEnded())
      .WillOnce(Return(false));
  host->NotifyEnded();

  EXPECT_CALL(*mocks_->audio_renderer(), HasEnded())
      .WillOnce(Return(true));
  EXPECT_CALL(*mocks_->video_renderer(), HasEnded())
      .WillOnce(Return(false));
  host->NotifyEnded();

  EXPECT_CALL(*mocks_->audio_renderer(), HasEnded())
      .WillOnce(Return(true));
  EXPECT_CALL(*mocks_->video_renderer(), HasEnded())
      .WillOnce(Return(true));
  EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK));
  host->NotifyEnded();
}

// Static function & time variable used to simulate changes in wallclock time.
static int64 g_static_clock_time;
static base::Time StaticClockFunction() {
  return base::Time::FromInternalValue(g_static_clock_time);
}

TEST_F(PipelineImplTest, AudioStreamShorterThanVideo) {
  base::TimeDelta duration = base::TimeDelta::FromSeconds(10);

  CreateAudioStream();
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, duration);
  InitializeAudioDecoder(audio_stream());
  InitializeAudioRenderer();
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();
  InitializePipeline(PIPELINE_OK);

  // For convenience to simulate filters calling the methods.
  FilterHost* host = pipeline_;

  // Replace the clock so we can simulate wallclock time advancing w/o using
  // Sleep().
  pipeline_->SetClockForTesting(new Clock(&StaticClockFunction));

  EXPECT_EQ(0, host->GetTime().ToInternalValue());

  float playback_rate = 1.0f;
  EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate));
  EXPECT_CALL(*mocks_->video_decoder(), SetPlaybackRate(playback_rate));
  EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(playback_rate));
  EXPECT_CALL(*mocks_->video_renderer(), SetPlaybackRate(playback_rate));
  EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate));
  pipeline_->SetPlaybackRate(playback_rate);
  message_loop_.RunAllPending();

  InSequence s;

  // Verify that the clock doesn't advance since it hasn't been started by
  // a time update from the audio stream.
  int64 start_time = host->GetTime().ToInternalValue();
  g_static_clock_time +=
      base::TimeDelta::FromMilliseconds(100).ToInternalValue();
  EXPECT_EQ(host->GetTime().ToInternalValue(), start_time);

  // Signal end of audio stream.
  EXPECT_CALL(*mocks_->audio_renderer(), HasEnded())
      .WillOnce(Return(true));
  EXPECT_CALL(*mocks_->video_renderer(), HasEnded())
      .WillOnce(Return(false));
  host->NotifyEnded();
  message_loop_.RunAllPending();

  // Verify that the clock advances.
  start_time = host->GetTime().ToInternalValue();
  g_static_clock_time +=
      base::TimeDelta::FromMilliseconds(100).ToInternalValue();
  EXPECT_GT(host->GetTime().ToInternalValue(), start_time);

  // Signal end of video stream and make sure OnEnded() callback occurs.
  EXPECT_CALL(*mocks_->audio_renderer(), HasEnded())
      .WillOnce(Return(true));
  EXPECT_CALL(*mocks_->video_renderer(), HasEnded())
      .WillOnce(Return(true));
  EXPECT_CALL(callbacks_, OnEnded(PIPELINE_OK));
  host->NotifyEnded();
}

void SendReadErrorToCB(::testing::Unused, const FilterStatusCB& cb) {
  cb.Run(PIPELINE_ERROR_READ);
}

TEST_F(PipelineImplTest, ErrorDuringSeek) {
  CreateAudioStream();
  MockDemuxerStreamVector streams;
  streams.push_back(audio_stream());

  InitializeDemuxer(&streams, base::TimeDelta::FromSeconds(10));
  InitializeAudioDecoder(audio_stream());
  InitializeAudioRenderer();
  InitializePipeline(PIPELINE_OK);

  float playback_rate = 1.0f;
  EXPECT_CALL(*mocks_->demuxer(), SetPlaybackRate(playback_rate));
  EXPECT_CALL(*mocks_->audio_decoder(), SetPlaybackRate(playback_rate));
  EXPECT_CALL(*mocks_->audio_renderer(), SetPlaybackRate(playback_rate));
  pipeline_->SetPlaybackRate(playback_rate);
  message_loop_.RunAllPending();

  InSequence s;

  base::TimeDelta seek_time = base::TimeDelta::FromSeconds(5);

  EXPECT_CALL(*mocks_->demuxer(), Seek(seek_time, _))
      .WillOnce(Invoke(&SendReadErrorToCB));

  pipeline_->Seek(seek_time, NewCallback(
      reinterpret_cast<CallbackHelper*>(&callbacks_), &CallbackHelper::OnSeek));
  EXPECT_CALL(callbacks_, OnSeek(PIPELINE_ERROR_READ));
  EXPECT_CALL(callbacks_, OnError(PIPELINE_ERROR_READ));
  message_loop_.RunAllPending();
}

TEST_F(PipelineImplTest, StartTimeIsZero) {
  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);
  InitializeDemuxer(&streams, kDuration);
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->IsInitialized());
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  EXPECT_EQ(base::TimeDelta(), pipeline_->GetCurrentTime());
}

TEST_F(PipelineImplTest, StartTimeIsNonZero) {
  const base::TimeDelta kStartTime = base::TimeDelta::FromSeconds(4);
  const base::TimeDelta kDuration = base::TimeDelta::FromSeconds(100);

  EXPECT_CALL(*mocks_->demuxer(), GetStartTime())
      .WillRepeatedly(Return(kStartTime));

  CreateVideoStream();
  MockDemuxerStreamVector streams;
  streams.push_back(video_stream());

  InitializeDemuxer(&streams, kDuration);
  InitializeVideoDecoder(video_stream());
  InitializeVideoRenderer();

  InitializePipeline(PIPELINE_OK);
  EXPECT_TRUE(pipeline_->IsInitialized());
  EXPECT_FALSE(pipeline_->HasAudio());
  EXPECT_TRUE(pipeline_->HasVideo());

  EXPECT_EQ(kStartTime, pipeline_->GetCurrentTime());
}

class FlexibleCallbackRunner : public base::DelegateSimpleThread::Delegate {
 public:
  FlexibleCallbackRunner(int delayInMs, PipelineStatus status,
                         PipelineStatusCallback* callback)
      : delayInMs_(delayInMs), status_(status), callback_(callback) {
    if (delayInMs_ < 0) {
      callback_->Run(status_);
      return;
    }
  }
  virtual void Run() {
    if (delayInMs_ < 0) return;
    base::PlatformThread::Sleep(delayInMs_);
    callback_->Run(status_);
  }

 private:
  int delayInMs_;
  PipelineStatus status_;
  scoped_ptr<PipelineStatusCallback> callback_;
};

void TestPipelineStatusNotification(int delayInMs) {
  PipelineStatusNotification note;
  // Arbitrary error value we expect to fish out of the notification after the
  // callback is fired.
  const PipelineStatus expected_error = PIPELINE_ERROR_URL_NOT_FOUND;
  FlexibleCallbackRunner runner(delayInMs, expected_error, note.Callback());
  base::DelegateSimpleThread thread(&runner, "FlexibleCallbackRunner");
  thread.Start();
  note.Wait();
  EXPECT_EQ(note.status(), expected_error);
  thread.Join();
}

// Test that in-line callback (same thread, no yield) works correctly.
TEST(PipelineStatusNotificationTest, InlineCallback) {
  TestPipelineStatusNotification(-1);
}

// Test that different-thread, no-delay callback works correctly.
TEST(PipelineStatusNotificationTest, ImmediateCallback) {
  TestPipelineStatusNotification(0);
}

// Test that different-thread, some-delay callback (the expected common case)
// works correctly.
TEST(PipelineStatusNotificationTest, DelayedCallback) {
  TestPipelineStatusNotification(20);
}

}  // namespace media
