// 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/ipc_streamer/av_streamer_proxy.h"

#include "base/bind.h"
#include "base/location.h"
#include "base/message_loop/message_loop_proxy.h"
#include "chromecast/media/cma/base/coded_frame_provider.h"
#include "chromecast/media/cma/base/decoder_buffer_base.h"
#include "chromecast/media/cma/ipc/media_memory_chunk.h"
#include "chromecast/media/cma/ipc/media_message.h"
#include "chromecast/media/cma/ipc/media_message_fifo.h"
#include "chromecast/media/cma/ipc/media_message_type.h"
#include "chromecast/media/cma/ipc_streamer/audio_decoder_config_marshaller.h"
#include "chromecast/media/cma/ipc_streamer/decoder_buffer_base_marshaller.h"
#include "chromecast/media/cma/ipc_streamer/video_decoder_config_marshaller.h"

namespace chromecast {
namespace media {

AvStreamerProxy::AvStreamerProxy()
    : is_running_(false),
      pending_read_(false),
      pending_av_data_(false),
      weak_factory_(this) {
  weak_this_ = weak_factory_.GetWeakPtr();
  thread_checker_.DetachFromThread();
}

AvStreamerProxy::~AvStreamerProxy() {
  DCHECK(thread_checker_.CalledOnValidThread());
}

void AvStreamerProxy::SetCodedFrameProvider(
    scoped_ptr<CodedFrameProvider> frame_provider) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!frame_provider_);
  frame_provider_.reset(frame_provider.release());
}

void AvStreamerProxy::SetMediaMessageFifo(
    scoped_ptr<MediaMessageFifo> fifo) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!fifo_);
  fifo_.reset(fifo.release());
}

void AvStreamerProxy::Start() {
  DCHECK(!is_running_);

  is_running_ = true;
  RequestBufferIfNeeded();
}

void AvStreamerProxy::StopAndFlush(const base::Closure& done_cb) {
  is_running_ = false;

  pending_av_data_ = false;
  pending_audio_config_ = ::media::AudioDecoderConfig();
  pending_video_config_ = ::media::VideoDecoderConfig();
  pending_buffer_ = scoped_refptr<DecoderBufferBase>();

  pending_read_ = false;
  frame_provider_->Flush(done_cb);
}

void AvStreamerProxy::OnFifoReadEvent() {
  DCHECK(thread_checker_.CalledOnValidThread());

  // Some enough space might have been released
  // to accommodate the pending data.
  if (pending_av_data_)
    ProcessPendingData();
}

void AvStreamerProxy::RequestBufferIfNeeded() {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (!is_running_ || pending_read_ || pending_av_data_)
    return;

  // |frame_provider_| is assumed to run on the same message loop.
  // Add a BindToCurrentLoop if that's not the case in the future.
  pending_read_ = true;
  frame_provider_->Read(base::Bind(&AvStreamerProxy::OnNewBuffer, weak_this_));
}

void AvStreamerProxy::OnNewBuffer(
    const scoped_refptr<DecoderBufferBase>& buffer,
    const ::media::AudioDecoderConfig& audio_config,
    const ::media::VideoDecoderConfig& video_config) {
  DCHECK(thread_checker_.CalledOnValidThread());

  pending_read_ = false;

  if (buffer->end_of_stream())
    is_running_ = false;

  DCHECK(!pending_av_data_);
  pending_av_data_ = true;

  pending_buffer_ = buffer;
  pending_audio_config_ = audio_config;
  pending_video_config_ = video_config;

  ProcessPendingData();
}

void AvStreamerProxy::ProcessPendingData() {
  if (pending_audio_config_.IsValidConfig()) {
    if (!SendAudioDecoderConfig(pending_audio_config_))
      return;
    pending_audio_config_ = ::media::AudioDecoderConfig();
  }

  if (pending_video_config_.IsValidConfig()) {
    if (!SendVideoDecoderConfig(pending_video_config_))
      return;
    pending_video_config_ = ::media::VideoDecoderConfig();
  }

  if (pending_buffer_.get()) {
    if (!SendBuffer(pending_buffer_))
      return;
    pending_buffer_ = scoped_refptr<DecoderBufferBase>();
  }

  pending_av_data_ = false;
  base::MessageLoopProxy::current()->PostTask(
      FROM_HERE,
      base::Bind(&AvStreamerProxy::RequestBufferIfNeeded, weak_this_));
}

bool AvStreamerProxy::SendAudioDecoderConfig(
    const ::media::AudioDecoderConfig& config) {
  // Create a dummy message to calculate first the message size.
  scoped_ptr<MediaMessage> dummy_msg(
      MediaMessage::CreateDummyMessage(AudioConfigMediaMsg));
  AudioDecoderConfigMarshaller::Write(config, dummy_msg.get());

  // Create the real message and write the actual content.
  scoped_ptr<MediaMessage> msg(
      MediaMessage::CreateMessage(
          AudioConfigMediaMsg,
          base::Bind(&MediaMessageFifo::ReserveMemory,
                     base::Unretained(fifo_.get())),
          dummy_msg->content_size()));
  if (!msg)
    return false;

  AudioDecoderConfigMarshaller::Write(config, msg.get());
  return true;
}

bool AvStreamerProxy::SendVideoDecoderConfig(
    const ::media::VideoDecoderConfig& config) {
  // Create a dummy message to calculate first the message size.
  scoped_ptr<MediaMessage> dummy_msg(
      MediaMessage::CreateDummyMessage(VideoConfigMediaMsg));
  VideoDecoderConfigMarshaller::Write(config, dummy_msg.get());

  // Create the real message and write the actual content.
  scoped_ptr<MediaMessage> msg(
      MediaMessage::CreateMessage(
          VideoConfigMediaMsg,
          base::Bind(&MediaMessageFifo::ReserveMemory,
                     base::Unretained(fifo_.get())),
          dummy_msg->content_size()));
  if (!msg)
    return false;

  VideoDecoderConfigMarshaller::Write(config, msg.get());
  return true;
}

bool AvStreamerProxy::SendBuffer(
    const scoped_refptr<DecoderBufferBase>& buffer) {
  // Create a dummy message to calculate first the message size.
  scoped_ptr<MediaMessage> dummy_msg(
      MediaMessage::CreateDummyMessage(FrameMediaMsg));
  DecoderBufferBaseMarshaller::Write(buffer, dummy_msg.get());

  // Create the real message and write the actual content.
  scoped_ptr<MediaMessage> msg(
      MediaMessage::CreateMessage(
          FrameMediaMsg,
          base::Bind(&MediaMessageFifo::ReserveMemory,
                     base::Unretained(fifo_.get())),
          dummy_msg->content_size()));
  if (!msg)
    return false;

  DecoderBufferBaseMarshaller::Write(buffer, msg.get());
  return true;
}

}  // namespace media
}  // namespace chromecast
