// Copyright 2013 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 "chrome/renderer/media/cast_session_delegate.h"

#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
#include "content/public/renderer/p2p_socket_client.h"
#include "content/public/renderer/render_thread.h"
#include "media/cast/cast_config.h"
#include "media/cast/cast_environment.h"
#include "media/cast/cast_sender.h"
#include "media/cast/logging/logging_defines.h"

using media::cast::AudioSenderConfig;
using media::cast::CastEnvironment;
using media::cast::CastSender;
using media::cast::VideoSenderConfig;

CastSessionDelegate::CastSessionDelegate()
    : audio_encode_thread_("CastAudioEncodeThread"),
      video_encode_thread_("CastVideoEncodeThread"),
      audio_configured_(false),
      video_configured_(false),
      io_message_loop_proxy_(
          content::RenderThread::Get()->GetIOMessageLoopProxy()) {
}

CastSessionDelegate::~CastSessionDelegate() {
  DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());
}

void CastSessionDelegate::SetSocketFactory(
    scoped_ptr<CastSession::P2PSocketFactory> socket_factory,
    const net::IPEndPoint& remote_address) {
  socket_factory_ = socket_factory.Pass();
  remote_address_ = remote_address;
}

void CastSessionDelegate::StartAudio(
    const AudioSenderConfig& config,
    const FrameInputAvailableCallback& callback) {
  DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());

  audio_configured_ = true;
  audio_config_ = config;
  frame_input_available_callbacks_.push_back(callback);
  MaybeStartSending();
}

void CastSessionDelegate::StartVideo(
    const VideoSenderConfig& config,
    const FrameInputAvailableCallback& callback) {
  DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());

  video_configured_ = true;
  video_config_ = config;
  frame_input_available_callbacks_.push_back(callback);
  MaybeStartSending();
}

void CastSessionDelegate::StartSending() {
  DCHECK(io_message_loop_proxy_->BelongsToCurrentThread());

  if (cast_environment_)
    return;

  if (!socket_factory_) {
    // TODO(hubbe): Post an error back to the user
    return;
  }

  socket_ = socket_factory_->Create();
  socket_->SetDelegate(this);

  audio_encode_thread_.Start();
  video_encode_thread_.Start();

  // CastSender uses the renderer's IO thread as the main thread. This reduces
  // thread hopping for incoming video frames and outgoing network packets.
  // There's no need to decode so no thread assigned for decoding.
  // Get default logging: All disabled.
  cast_environment_ = new CastEnvironment(
      &clock_,
      base::MessageLoopProxy::current(),
      audio_encode_thread_.message_loop_proxy(),
      NULL,
      video_encode_thread_.message_loop_proxy(),
      NULL,
      media::cast::GetDefaultCastLoggingConfig());

  // TODO(hclam): Implement VideoEncoderController to configure hardware
  // encoder.
  cast_sender_.reset(CastSender::CreateCastSender(
      cast_environment_,
      audio_config_,
      video_config_,
      NULL,
      this));

  for (size_t i = 0; i < frame_input_available_callbacks_.size(); ++i) {
    frame_input_available_callbacks_[i].Run(
        cast_sender_->frame_input());
  }
  frame_input_available_callbacks_.clear();
}

  // media::cast::PacketSender Implementation
bool CastSessionDelegate::SendPacket(
    const media::cast::Packet& packet) {
  // TODO(hubbe): Make sure audio and video packets gets the right DSCP.
  socket_->SendWithDscp(
      remote_address_,
      *reinterpret_cast<const std::vector<char> *>(&packet),
      net::DSCP_AF41);
  return true;
}

bool CastSessionDelegate::SendPackets(
    const media::cast::PacketList& packets) {
  // TODO(hubbe): Add ability to send multiple packets in one IPC message.
  for (size_t i = 0; i < packets.size(); i++) {
    SendPacket(packets[i]);
  }
  return true;
}

// content::P2PSocketClient::Delegate Implementation
void CastSessionDelegate::OnOpen(
    const net::IPEndPoint& address) {
  // Called once Init completes. Ignored.
}

void CastSessionDelegate::OnIncomingTcpConnection(
    const net::IPEndPoint& address,
    content::P2PSocketClient* client) {
  // We only support UDP sockets. This function should not be called
  // for UDP sockets.
  NOTREACHED();
}

void CastSessionDelegate::OnSendComplete() {
  // Ignored for now.
}

void CastSessionDelegate::OnError() {
  // TODO(hubbe): Report this back to the user.
}

void CastSessionDelegate::OnDataReceived(const net::IPEndPoint& address,
                                         const std::vector<char>& data,
                                         const base::TimeTicks& timestamp) {
  uint8 *packet_copy = new uint8[data.size()];
  memcpy(packet_copy, &data[0], data.size());
  cast_sender_->packet_receiver()->ReceivedPacket(
      packet_copy,
      data.size(),
      base::Bind(&media::cast::PacketReceiver::DeletePacket,
                 packet_copy));
}

void CastSessionDelegate::MaybeStartSending() {
  if (!audio_configured_ || !video_configured_)
    return;
  StartSending();
}