// 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/browser/media/cma_message_filter_host.h"

#include <utility>

#include "base/memory/scoped_ptr.h"
#include "base/memory/shared_memory.h"
#include "base/sync_socket.h"
#include "chromecast/browser/media/cma_message_loop.h"
#include "chromecast/browser/media/media_pipeline_host.h"
#include "chromecast/common/media/cma_messages.h"
#include "chromecast/media/cma/backend/video_plane.h"
#include "chromecast/media/cma/pipeline/av_pipeline_client.h"
#include "chromecast/media/cma/pipeline/media_pipeline_client.h"
#include "chromecast/media/cma/pipeline/video_pipeline_client.h"
#include "content/public/browser/browser_thread.h"
#include "media/base/bind_to_current_loop.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/quad_f.h"
#include "ui/gfx/geometry/size.h"

namespace chromecast {
namespace media {

#define FORWARD_CALL(arg_pipeline, arg_fn, ...) \
  task_runner_->PostTask( \
      FROM_HERE, \
      base::Bind(&MediaPipelineHost::arg_fn, \
                 base::Unretained(arg_pipeline), __VA_ARGS__))

namespace {

const size_t kMaxSharedMem = 8 * 1024 * 1024;

void DestroyMediaPipeline(scoped_ptr<MediaPipelineHost> media_pipeline) {
}

void UpdateVideoSurfaceHost(int surface_id, const gfx::QuadF& quad) {
  // Currently supports only one video plane.
  CHECK_EQ(surface_id, 0);

  VideoPlane* video_plane = GetVideoPlane();
  video_plane->SetGeometry(
      quad,
      VideoPlane::COORDINATE_TYPE_GRAPHICS_PLANE);
}

}  // namespace

CmaMessageFilterHost::CmaMessageFilterHost(int render_process_id)
    : content::BrowserMessageFilter(CastMediaMsgStart),
      process_id_(render_process_id),
      task_runner_(CmaMessageLoop::GetMessageLoopProxy()),
      weak_factory_(this) {
  weak_this_ = weak_factory_.GetWeakPtr();
}

CmaMessageFilterHost::~CmaMessageFilterHost() {
  DCHECK(media_pipelines_.empty());
}

void CmaMessageFilterHost::OnChannelClosing() {
  content::BrowserMessageFilter::OnChannelClosing();
  DeleteEntries();
}

void CmaMessageFilterHost::OnDestruct() const {
  content::BrowserThread::DeleteOnIOThread::Destruct(this);
}

bool CmaMessageFilterHost::OnMessageReceived(const IPC::Message& message) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(CmaMessageFilterHost, message)
    IPC_MESSAGE_HANDLER(CmaHostMsg_CreateMedia, CreateMedia)
    IPC_MESSAGE_HANDLER(CmaHostMsg_DestroyMedia, DestroyMedia)
    IPC_MESSAGE_HANDLER(CmaHostMsg_SetCdm, SetCdm)
    IPC_MESSAGE_HANDLER(CmaHostMsg_CreateAvPipe, CreateAvPipe)
    IPC_MESSAGE_HANDLER(CmaHostMsg_AudioInitialize, AudioInitialize)
    IPC_MESSAGE_HANDLER(CmaHostMsg_VideoInitialize, VideoInitialize)
    IPC_MESSAGE_HANDLER(CmaHostMsg_StartPlayingFrom, StartPlayingFrom)
    IPC_MESSAGE_HANDLER(CmaHostMsg_Flush, Flush)
    IPC_MESSAGE_HANDLER(CmaHostMsg_Stop, Stop)
    IPC_MESSAGE_HANDLER(CmaHostMsg_SetPlaybackRate, SetPlaybackRate)
    IPC_MESSAGE_HANDLER(CmaHostMsg_SetVolume, SetVolume)
    IPC_MESSAGE_HANDLER(CmaHostMsg_NotifyPipeWrite, NotifyPipeWrite)
    IPC_MESSAGE_HANDLER(CmaHostMsg_NotifyExternalSurface,
                        NotifyExternalSurface)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()

  return handled;
}

void CmaMessageFilterHost::DeleteEntries() {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));

  for (MediaPipelineMap::iterator it = media_pipelines_.begin();
       it != media_pipelines_.end(); ) {
    scoped_ptr<MediaPipelineHost> media_pipeline(it->second);
    media_pipelines_.erase(it++);
    task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&DestroyMediaPipeline, base::Passed(&media_pipeline)));
  }
}

MediaPipelineHost* CmaMessageFilterHost::LookupById(int media_id) {
  MediaPipelineMap::iterator it = media_pipelines_.find(media_id);
  if (it == media_pipelines_.end())
    return NULL;
  return it->second;
}


// *** Handle incoming messages ***

void CmaMessageFilterHost::CreateMedia(int media_id, LoadType load_type) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));

  scoped_ptr<MediaPipelineHost> media_pipeline_host(new MediaPipelineHost());
  MediaPipelineClient client;
  client.time_update_cb = ::media::BindToCurrentLoop(base::Bind(
      &CmaMessageFilterHost::OnTimeUpdate, weak_this_, media_id));
  client.buffering_state_cb = ::media::BindToCurrentLoop(base::Bind(
      &CmaMessageFilterHost::OnBufferingNotification, weak_this_, media_id));
  client.error_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnPlaybackError,
                 weak_this_, media_id, media::kNoTrackId));
  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&MediaPipelineHost::Initialize,
                 base::Unretained(media_pipeline_host.get()),
                 load_type, client));
  std::pair<MediaPipelineMap::iterator, bool> ret =
    media_pipelines_.insert(
        std::make_pair(media_id, media_pipeline_host.release()));

  // Check there is no other entry with the same ID.
  DCHECK(ret.second != false);
}

void CmaMessageFilterHost::DestroyMedia(int media_id) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));

  MediaPipelineMap::iterator it = media_pipelines_.find(media_id);
  if (it == media_pipelines_.end())
    return;

  scoped_ptr<MediaPipelineHost> media_pipeline(it->second);
  media_pipelines_.erase(it);
  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&DestroyMediaPipeline, base::Passed(&media_pipeline)));
}

void CmaMessageFilterHost::SetCdm(int media_id,
                                  int render_frame_id,
                                  int cdm_id) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline)
    return;

  FORWARD_CALL(media_pipeline, SetCdm, process_id_, render_frame_id, cdm_id);
}

void CmaMessageFilterHost::CreateAvPipe(
    int media_id, TrackId track_id, size_t shared_mem_size) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));

  base::FileDescriptor foreign_socket_handle;
  base::SharedMemoryHandle foreign_memory_handle;

  // A few sanity checks before allocating resources.
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline || !PeerHandle() || shared_mem_size > kMaxSharedMem) {
    Send(new CmaMsg_AvPipeCreated(
       media_id, track_id, false,
       foreign_memory_handle, foreign_socket_handle));
    return;
  }

  // Create the local/foreign sockets to signal media message
  // consune/feed events.
  // Use CancelableSyncSocket so that write is always non-blocking.
  scoped_ptr<base::CancelableSyncSocket> local_socket(
      new base::CancelableSyncSocket());
  scoped_ptr<base::CancelableSyncSocket> foreign_socket(
      new base::CancelableSyncSocket());
  if (!base::CancelableSyncSocket::CreatePair(local_socket.get(),
                                              foreign_socket.get()) ||
      foreign_socket->handle() == -1) {
    Send(new CmaMsg_AvPipeCreated(
       media_id, track_id, false,
       foreign_memory_handle, foreign_socket_handle));
    return;
  }

  // Shared memory used to convey media messages.
  scoped_ptr<base::SharedMemory> shared_memory(new base::SharedMemory());
  if (!shared_memory->CreateAndMapAnonymous(shared_mem_size) ||
      !shared_memory->ShareToProcess(PeerHandle(), &foreign_memory_handle)) {
    Send(new CmaMsg_AvPipeCreated(
       media_id, track_id, false,
       foreign_memory_handle, foreign_socket_handle));
    return;
  }

  // Note: the IPC message can be sent only once the pipe has been fully
  // configured. Part of this configuration is done in
  // |MediaPipelineHost::SetAvPipe|.
  // TODO(erickung): investigate possible memory leak here.
  // If the weak pointer in |av_pipe_set_cb| gets invalidated,
  // then |foreign_memory_handle| leaks.
  base::Closure pipe_read_activity_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnPipeReadActivity, weak_this_,
                 media_id, track_id));
  base::Closure av_pipe_set_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnAvPipeSet, weak_this_,
                 media_id, track_id,
                 foreign_memory_handle, base::Passed(&foreign_socket)));
  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&MediaPipelineHost::SetAvPipe,
                 base::Unretained(media_pipeline),
                 track_id,
                 base::Passed(&shared_memory),
                 pipe_read_activity_cb,
                 av_pipe_set_cb));
}

void CmaMessageFilterHost::OnAvPipeSet(
    int media_id,
    TrackId track_id,
    base::SharedMemoryHandle foreign_memory_handle,
    scoped_ptr<base::CancelableSyncSocket> foreign_socket) {
  base::FileDescriptor foreign_socket_handle;
  foreign_socket_handle.fd = foreign_socket->handle();
  foreign_socket_handle.auto_close = false;

  // This message can only be set once the pipe has fully been configured
  // by |MediaPipelineHost|.
  Send(new CmaMsg_AvPipeCreated(
      media_id, track_id, true, foreign_memory_handle, foreign_socket_handle));
}

void CmaMessageFilterHost::AudioInitialize(
    int media_id, TrackId track_id, const ::media::AudioDecoderConfig& config) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline) {
    Send(new CmaMsg_TrackStateChanged(
        media_id, track_id, ::media::PIPELINE_ERROR_ABORT));
    return;
  }

  AvPipelineClient client;
  client.eos_cb = ::media::BindToCurrentLoop(base::Bind(
      &CmaMessageFilterHost::OnEos, weak_this_, media_id, track_id));
  client.playback_error_cb = ::media::BindToCurrentLoop(base::Bind(
      &CmaMessageFilterHost::OnPlaybackError, weak_this_, media_id, track_id));
  client.statistics_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnStatisticsUpdated, weak_this_,
                 media_id, track_id));

  ::media::PipelineStatusCB pipeline_status_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnTrackStateChanged, weak_this_,
                 media_id, track_id));
  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&MediaPipelineHost::AudioInitialize,
                 base::Unretained(media_pipeline),
                 track_id, client, config, pipeline_status_cb));
}

void CmaMessageFilterHost::VideoInitialize(
    int media_id, TrackId track_id, const ::media::VideoDecoderConfig& config) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline) {
    Send(new CmaMsg_TrackStateChanged(
        media_id, track_id, ::media::PIPELINE_ERROR_ABORT));
    return;
  }

  VideoPipelineClient client;
  client.av_pipeline_client.eos_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnEos, weak_this_,
                 media_id, track_id));
  client.av_pipeline_client.playback_error_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnPlaybackError, weak_this_,
                 media_id, track_id));
  client.av_pipeline_client.statistics_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnStatisticsUpdated, weak_this_,
                 media_id, track_id));
  client.natural_size_changed_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnNaturalSizeChanged, weak_this_,
                 media_id, track_id));

  ::media::PipelineStatusCB pipeline_status_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnTrackStateChanged, weak_this_,
                 media_id, track_id));
  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&MediaPipelineHost::VideoInitialize,
                 base::Unretained(media_pipeline),
                 track_id, client, config, pipeline_status_cb));
}

void CmaMessageFilterHost::StartPlayingFrom(
    int media_id, base::TimeDelta time) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline)
    return;
  FORWARD_CALL(media_pipeline, StartPlayingFrom, time);
}

void CmaMessageFilterHost::Flush(int media_id) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline) {
    Send(new CmaMsg_MediaStateChanged(
        media_id, ::media::PIPELINE_ERROR_ABORT));
    return;
  }
  ::media::PipelineStatusCB pipeline_status_cb = ::media::BindToCurrentLoop(
      base::Bind(&CmaMessageFilterHost::OnMediaStateChanged, weak_this_,
                 media_id));
  FORWARD_CALL(media_pipeline, Flush, pipeline_status_cb);
}

void CmaMessageFilterHost::Stop(int media_id) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline)
    return;
  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&MediaPipelineHost::Stop,
                 base::Unretained(media_pipeline)));
}

void CmaMessageFilterHost::SetPlaybackRate(
    int media_id, float playback_rate) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline)
    return;
  FORWARD_CALL(media_pipeline, SetPlaybackRate, playback_rate);
}

void CmaMessageFilterHost::SetVolume(
    int media_id, TrackId track_id, float volume) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline)
    return;
  FORWARD_CALL(media_pipeline, SetVolume, track_id, volume);
}

void CmaMessageFilterHost::NotifyPipeWrite(int media_id, TrackId track_id) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  MediaPipelineHost* media_pipeline = LookupById(media_id);
  if (!media_pipeline)
    return;
  FORWARD_CALL(media_pipeline, NotifyPipeWrite, track_id);
}

void CmaMessageFilterHost::NotifyExternalSurface(
    int surface_id,
    const gfx::PointF& p0, const gfx::PointF& p1,
    const gfx::PointF& p2, const gfx::PointF& p3) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&UpdateVideoSurfaceHost, surface_id,
                 gfx::QuadF(p0, p1, p2, p3)));
}

// *** Browser to renderer messages ***

void CmaMessageFilterHost::OnMediaStateChanged(
    int media_id, ::media::PipelineStatus status) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  Send(new CmaMsg_MediaStateChanged(media_id, status));
}

void CmaMessageFilterHost::OnTrackStateChanged(
    int media_id, TrackId track_id, ::media::PipelineStatus status) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  Send(new CmaMsg_TrackStateChanged(media_id, track_id, status));
}

void CmaMessageFilterHost::OnPipeReadActivity(int media_id, TrackId track_id) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  Send(new CmaMsg_NotifyPipeRead(media_id, track_id));
}

void CmaMessageFilterHost::OnTimeUpdate(
    int media_id,
    base::TimeDelta media_time,
    base::TimeDelta max_media_time,
    base::TimeTicks stc) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  Send(new CmaMsg_TimeUpdate(media_id,
                             media_time, max_media_time, stc));
}

void CmaMessageFilterHost::OnBufferingNotification(
    int media_id, ::media::BufferingState state) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  Send(new CmaMsg_BufferingNotification(media_id, state));
}

void CmaMessageFilterHost::OnEos(int media_id, TrackId track_id) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  Send(new CmaMsg_Eos(media_id, track_id));
}

void CmaMessageFilterHost::OnPlaybackError(
    int media_id, TrackId track_id, ::media::PipelineStatus status) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  Send(new CmaMsg_PlaybackError(media_id, track_id, status));
}

void CmaMessageFilterHost::OnStatisticsUpdated(
    int media_id, TrackId track_id, const ::media::PipelineStatistics& stats) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  Send(new CmaMsg_PlaybackStatistics(media_id, track_id, stats));
}

void CmaMessageFilterHost::OnNaturalSizeChanged(
    int media_id, TrackId track_id, const gfx::Size& size) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
  Send(new CmaMsg_NaturalSizeChanged(media_id, track_id, size));
}

}  // namespace media
}  // namespace chromecast
