// Copyright (c) 2012 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 "content/renderer/media/audio_message_filter.h"

#include "base/bind.h"
#include "base/message_loop/message_loop_proxy.h"
#include "content/common/media/audio_messages.h"
#include "content/renderer/render_thread_impl.h"
#include "ipc/ipc_logging.h"

namespace content {

namespace {
const int kStreamIDNotSet = -1;
}

class AudioMessageFilter::AudioOutputIPCImpl
    : public NON_EXPORTED_BASE(media::AudioOutputIPC) {
 public:
  AudioOutputIPCImpl(const scoped_refptr<AudioMessageFilter>& filter,
                     int render_view_id);
  virtual ~AudioOutputIPCImpl();

  // media::AudioOutputIPC implementation.
  virtual void CreateStream(media::AudioOutputIPCDelegate* delegate,
                            const media::AudioParameters& params,
                            int session_id) OVERRIDE;
  virtual void PlayStream() OVERRIDE;
  virtual void PauseStream() OVERRIDE;
  virtual void CloseStream() OVERRIDE;
  virtual void SetVolume(double volume) OVERRIDE;

 private:
  const scoped_refptr<AudioMessageFilter> filter_;
  const int render_view_id_;
  int stream_id_;
};

AudioMessageFilter* AudioMessageFilter::g_filter = NULL;

AudioMessageFilter::AudioMessageFilter(
    const scoped_refptr<base::MessageLoopProxy>& io_message_loop)
    : channel_(NULL),
      audio_hardware_config_(NULL),
      io_message_loop_(io_message_loop) {
  DCHECK(!g_filter);
  g_filter = this;
}

AudioMessageFilter::~AudioMessageFilter() {
  DCHECK_EQ(g_filter, this);
  g_filter = NULL;
}

// static
AudioMessageFilter* AudioMessageFilter::Get() {
  return g_filter;
}

AudioMessageFilter::AudioOutputIPCImpl::AudioOutputIPCImpl(
    const scoped_refptr<AudioMessageFilter>& filter, int render_view_id)
    : filter_(filter),
      render_view_id_(render_view_id),
      stream_id_(kStreamIDNotSet) {}

AudioMessageFilter::AudioOutputIPCImpl::~AudioOutputIPCImpl() {}

scoped_ptr<media::AudioOutputIPC> AudioMessageFilter::CreateAudioOutputIPC(
    int render_view_id) {
  DCHECK_GT(render_view_id, 0);
  return scoped_ptr<media::AudioOutputIPC>(
      new AudioOutputIPCImpl(this, render_view_id));
}

void AudioMessageFilter::AudioOutputIPCImpl::CreateStream(
    media::AudioOutputIPCDelegate* delegate,
    const media::AudioParameters& params,
    int session_id) {
  DCHECK(filter_->io_message_loop_->BelongsToCurrentThread());
  DCHECK(delegate);
  DCHECK_EQ(stream_id_, kStreamIDNotSet);
  stream_id_ = filter_->delegates_.Add(delegate);
  filter_->Send(new AudioHostMsg_CreateStream(
      stream_id_, render_view_id_, session_id, params));
}

void AudioMessageFilter::AudioOutputIPCImpl::PlayStream() {
  DCHECK_NE(stream_id_, kStreamIDNotSet);
  filter_->Send(new AudioHostMsg_PlayStream(stream_id_));
}

void AudioMessageFilter::AudioOutputIPCImpl::PauseStream() {
  DCHECK_NE(stream_id_, kStreamIDNotSet);
  filter_->Send(new AudioHostMsg_PauseStream(stream_id_));
}

void AudioMessageFilter::AudioOutputIPCImpl::CloseStream() {
  DCHECK(filter_->io_message_loop_->BelongsToCurrentThread());
  DCHECK_NE(stream_id_, kStreamIDNotSet);
  filter_->Send(new AudioHostMsg_CloseStream(stream_id_));
  filter_->delegates_.Remove(stream_id_);
  stream_id_ = kStreamIDNotSet;
}

void AudioMessageFilter::AudioOutputIPCImpl::SetVolume(double volume) {
  DCHECK_NE(stream_id_, kStreamIDNotSet);
  filter_->Send(new AudioHostMsg_SetVolume(stream_id_, volume));
}

void AudioMessageFilter::Send(IPC::Message* message) {
  DCHECK(io_message_loop_->BelongsToCurrentThread());
  if (!channel_) {
    delete message;
  } else {
    channel_->Send(message);
  }
}

bool AudioMessageFilter::OnMessageReceived(const IPC::Message& message) {
  DCHECK(io_message_loop_->BelongsToCurrentThread());
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(AudioMessageFilter, message)
    IPC_MESSAGE_HANDLER(AudioMsg_NotifyStreamCreated, OnStreamCreated)
    IPC_MESSAGE_HANDLER(AudioMsg_NotifyStreamStateChanged, OnStreamStateChanged)
    IPC_MESSAGE_HANDLER(AudioMsg_NotifyDeviceChanged, OnOutputDeviceChanged)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void AudioMessageFilter::OnFilterAdded(IPC::Channel* channel) {
  DCHECK(io_message_loop_->BelongsToCurrentThread());
  channel_ = channel;
}

void AudioMessageFilter::OnFilterRemoved() {
  DCHECK(io_message_loop_->BelongsToCurrentThread());

  // Once removed, a filter will not be used again.  At this time all
  // delegates must be notified so they release their reference.
  OnChannelClosing();
}

void AudioMessageFilter::OnChannelClosing() {
  DCHECK(io_message_loop_->BelongsToCurrentThread());
  channel_ = NULL;

  DLOG_IF(WARNING, !delegates_.IsEmpty())
      << "Not all audio devices have been closed.";

  IDMap<media::AudioOutputIPCDelegate>::iterator it(&delegates_);
  while (!it.IsAtEnd()) {
    it.GetCurrentValue()->OnIPCClosed();
    delegates_.Remove(it.GetCurrentKey());
    it.Advance();
  }
}

void AudioMessageFilter::OnStreamCreated(
    int stream_id,
    base::SharedMemoryHandle handle,
#if defined(OS_WIN)
    base::SyncSocket::Handle socket_handle,
#else
    base::FileDescriptor socket_descriptor,
#endif
    uint32 length) {
  DCHECK(io_message_loop_->BelongsToCurrentThread());

#if !defined(OS_WIN)
  base::SyncSocket::Handle socket_handle = socket_descriptor.fd;
#endif

  media::AudioOutputIPCDelegate* delegate = delegates_.Lookup(stream_id);
  if (!delegate) {
    DLOG(WARNING) << "Got OnStreamCreated() event for a non-existent or removed"
                  << " audio renderer. (stream_id=" << stream_id << ").";
    base::SharedMemory::CloseHandle(handle);
    base::SyncSocket socket(socket_handle);
    return;
  }
  delegate->OnStreamCreated(handle, socket_handle, length);
}

void AudioMessageFilter::OnStreamStateChanged(
    int stream_id, media::AudioOutputIPCDelegate::State state) {
  DCHECK(io_message_loop_->BelongsToCurrentThread());
  media::AudioOutputIPCDelegate* delegate = delegates_.Lookup(stream_id);
  if (!delegate) {
    DLOG(WARNING) << "Got OnStreamStateChanged() event for a non-existent or"
                  << " removed audio renderer.  State: " << state;
    return;
  }
  delegate->OnStateChanged(state);
}

void AudioMessageFilter::OnOutputDeviceChanged(int stream_id,
                                               int new_buffer_size,
                                               int new_sample_rate) {
  DCHECK(io_message_loop_->BelongsToCurrentThread());
  base::AutoLock auto_lock(lock_);

  // Ignore the message if an audio hardware config hasn't been created; this
  // can occur if the renderer is using the high latency audio path.
  // TODO(dalecurtis): After http://crbug.com/173435 is fixed, convert to CHECK.
  if (!audio_hardware_config_)
    return;

  // TODO(crogers): fix OnOutputDeviceChanged() to pass AudioParameters.
  media::ChannelLayout channel_layout =
      audio_hardware_config_->GetOutputChannelLayout();
  int channels = audio_hardware_config_->GetOutputChannels();

  media::AudioParameters output_params;
  output_params.Reset(
      media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
      channel_layout,
      channels,
      0,
      new_sample_rate,
      16,
      new_buffer_size);

  audio_hardware_config_->UpdateOutputConfig(output_params);
}

void AudioMessageFilter::SetAudioHardwareConfig(
    media::AudioHardwareConfig* config) {
  base::AutoLock auto_lock(lock_);
  audio_hardware_config_ = config;
}

}  // namespace content
