// Copyright (c) 2010 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/pepper_devices.h"

#include "chrome/common/render_messages_params.h"
#include "chrome/renderer/render_thread.h"
#include "chrome/renderer/webplugin_delegate_pepper.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "webkit/glue/plugins/plugin_instance.h"
#include "webkit/glue/plugins/webplugin.h"

namespace {

const uint32 kBytesPerPixel = 4;  // Only 8888 RGBA for now.

}  // namespace

int Graphics2DDeviceContext::next_buffer_id_ = 0;

struct Graphics2DDeviceContext::FlushCallbackData {
  FlushCallbackData(NPDeviceFlushContextCallbackPtr f,
                    NPP n,
                    NPDeviceContext2D* c,
                    NPUserData* u)
      : function(f),
        npp(n),
        context(c),
        user_data(u) {
  }

  NPDeviceFlushContextCallbackPtr function;
  NPP npp;
  NPDeviceContext2D* context;
  NPUserData* user_data;
};

Graphics2DDeviceContext::Graphics2DDeviceContext(
    WebPluginDelegatePepper* plugin_delegate)
    : plugin_delegate_(plugin_delegate) {
}

Graphics2DDeviceContext::~Graphics2DDeviceContext() {}

NPError Graphics2DDeviceContext::Initialize(
    gfx::Rect window_rect, const NPDeviceContext2DConfig* config,
    NPDeviceContext2D* context) {
  int width = window_rect.width();
  int height = window_rect.height();
  uint32 buffer_size = width * height * kBytesPerPixel;

  // Allocate the transport DIB and the PlatformCanvas pointing to it.
#if defined(OS_MACOSX)
  // On the Mac, shared memory has to be created in the browser in order to
  // work in the sandbox.  Do this by sending a message to the browser
  // requesting a TransportDIB (see also
  // chrome/renderer/webplugin_delegate_proxy.cc, method
  // WebPluginDelegateProxy::CreateBitmap() for similar code).  Note that the
  // TransportDIB is _not_ cached in the browser; this is because this memory
  // gets flushed by the renderer into another TransportDIB that represents the
  // page, which is then in turn flushed to the screen by the browser process.
  // When |transport_dib_| goes out of scope in the dtor, all of its shared
  // memory gets reclaimed.
  TransportDIB::Handle dib_handle;
  IPC::Message* msg = new ViewHostMsg_AllocTransportDIB(buffer_size,
                                                        false,
                                                        &dib_handle);
  if (!RenderThread::current()->Send(msg))
    return NPERR_GENERIC_ERROR;
  if (!TransportDIB::is_valid(dib_handle))
    return NPERR_OUT_OF_MEMORY_ERROR;
  transport_dib_.reset(TransportDIB::Map(dib_handle));
#else
  transport_dib_.reset(TransportDIB::Create(buffer_size, ++next_buffer_id_));
  if (!transport_dib_.get())
    return NPERR_OUT_OF_MEMORY_ERROR;
#endif  // defined(OS_MACOSX)
  canvas_.reset(transport_dib_->GetPlatformCanvas(width, height));
  if (!canvas_.get())
    return NPERR_OUT_OF_MEMORY_ERROR;

  // Note that we need to get the address out of the bitmap rather than
  // using plugin_buffer_->memory(). The memory() is when the bitmap data
  // has had "Map" called on it. For Windows, this is separate than making a
  // bitmap using the shared section.
  const SkBitmap& plugin_bitmap =
      canvas_->getTopPlatformDevice().accessBitmap(true);
  SkAutoLockPixels locker(plugin_bitmap);

  // TODO(brettw) this theoretically shouldn't be necessary. But the
  // platform device on Windows will fill itself with green to help you
  // catch areas you didn't paint.
  plugin_bitmap.eraseARGB(0, 0, 0, 0);

  // Save the canvas to the output context structure and save the
  // OpenPaintContext for future reference.
  context->region = plugin_bitmap.getAddr32(0, 0);
  context->stride = width * kBytesPerPixel;
  context->dirty.left = 0;
  context->dirty.top = 0;
  context->dirty.right = width;
  context->dirty.bottom = height;
  return NPERR_NO_ERROR;
}

NPError Graphics2DDeviceContext::Flush(SkBitmap* committed_bitmap,
                                       NPDeviceContext2D* context,
                                       NPDeviceFlushContextCallbackPtr callback,
                                       NPP id, void* user_data) {
  // Draw the bitmap to the backing store.
  //
  // TODO(brettw) we can optimize this in the case where the entire canvas is
  // updated by actually taking ownership of the buffer and not telling the
  // plugin we're done using it. This wat we can avoid the copy when the entire
  // canvas has been updated.
  SkIRect src_rect = { context->dirty.left,
                       context->dirty.top,
                       context->dirty.right,
                       context->dirty.bottom };
  SkRect dest_rect = { SkIntToScalar(context->dirty.left),
                       SkIntToScalar(context->dirty.top),
                       SkIntToScalar(context->dirty.right),
                       SkIntToScalar(context->dirty.bottom) };
  SkCanvas committed_canvas(*committed_bitmap);

  // We want to replace the contents of the bitmap rather than blend.
  SkPaint paint;
  paint.setXfermodeMode(SkXfermode::kSrc_Mode);
  committed_canvas.drawBitmapRect(
      canvas_->getTopPlatformDevice().accessBitmap(false),
      &src_rect, dest_rect, &paint);

  committed_bitmap->setIsOpaque(false);

  // Cause the updated part of the screen to be repainted. This will happen
  // asynchronously.
  // TODO(brettw) is this the coorect coordinate system?
  gfx::Rect dest_gfx_rect(context->dirty.left, context->dirty.top,
                          context->dirty.right - context->dirty.left,
                          context->dirty.bottom - context->dirty.top);

  plugin_delegate_->instance()->webplugin()->InvalidateRect(dest_gfx_rect);

  // Save the callback to execute later. See |unpainted_flush_callbacks_| in
  // the header file.
  if (callback) {
    unpainted_flush_callbacks_.push_back(
        FlushCallbackData(callback, id, context, user_data));
  }

  return NPERR_NO_ERROR;
}

void Graphics2DDeviceContext::RenderViewInitiatedPaint() {
  // Move all "unpainted" callbacks to the painted state. See
  // |unpainted_flush_callbacks_| in the header for more.
  std::copy(unpainted_flush_callbacks_.begin(),
            unpainted_flush_callbacks_.end(),
            std::back_inserter(painted_flush_callbacks_));
  unpainted_flush_callbacks_.clear();
}

void Graphics2DDeviceContext::RenderViewFlushedPaint() {
  // Notify all "painted" callbacks. See |unpainted_flush_callbacks_| in the
  // header for more.
  for (size_t i = 0; i < painted_flush_callbacks_.size(); i++) {
    const FlushCallbackData& data = painted_flush_callbacks_[i];
    data.function(data.npp, data.context, NPERR_NO_ERROR, data.user_data);
  }
  painted_flush_callbacks_.clear();
}

AudioDeviceContext::AudioDeviceContext()
    : context_(NULL),
      stream_id_(0),
      shared_memory_size_(0) {
}

AudioDeviceContext::~AudioDeviceContext() {
  if (stream_id_) {
    OnDestroy();
  }
}

NPError AudioDeviceContext::Initialize(AudioMessageFilter* filter,
                                       const NPDeviceContextAudioConfig* config,
                                       NPDeviceContextAudio* context) {
  DCHECK(filter);
  // Make sure we don't call init more than once.
  DCHECK_EQ(0, stream_id_);

  if (!config || !context) {
    return NPERR_INVALID_PARAM;
  }
  filter_ = filter;
  context_= context;

  ViewHostMsg_Audio_CreateStream_Params params;
  params.params.format = AudioParameters::AUDIO_PCM_LINEAR;
  params.params.channels = config->outputChannelMap;
  params.params.sample_rate = config->sampleRate;
  switch (config->sampleType) {
    case NPAudioSampleTypeInt16:
      params.params.bits_per_sample = 16;
      break;
    case NPAudioSampleTypeFloat32:
      params.params.bits_per_sample = 32;
      break;
    default:
      return NPERR_INVALID_PARAM;
  }

  context->config = *config;
  params.params.samples_per_packet = config->sampleFrameCount;

  stream_id_ = filter_->AddDelegate(this);
  filter->Send(new ViewHostMsg_CreateAudioStream(0, stream_id_, params, true));
  return NPERR_NO_ERROR;
}

void AudioDeviceContext::OnDestroy() {
  // Make sure we don't call destroy more than once.
  DCHECK_NE(0, stream_id_);
  filter_->RemoveDelegate(stream_id_);
  filter_->Send(new ViewHostMsg_CloseAudioStream(0, stream_id_));
  stream_id_ = 0;
  if (audio_thread_.get()) {
    socket_->Close();
    audio_thread_->Join();
  }
}

void AudioDeviceContext::OnRequestPacket(AudioBuffersState buffers_state) {
  FireAudioCallback();
  filter_->Send(new ViewHostMsg_NotifyAudioPacketReady(0, stream_id_,
                                                       shared_memory_size_));
}

void AudioDeviceContext::OnStateChanged(
    const ViewMsg_AudioStreamState_Params& state) {
}

void AudioDeviceContext::OnCreated(
    base::SharedMemoryHandle handle, uint32 length) {
#if defined(OS_WIN)
  DCHECK(handle);
#else
  DCHECK_NE(-1, handle.fd);
#endif
  DCHECK(length);
  DCHECK(context_);

  shared_memory_.reset(new base::SharedMemory(handle, false));
  shared_memory_->Map(length);
  shared_memory_size_ = length;

  context_->outBuffer = shared_memory_->memory();
  FireAudioCallback();
  filter_->Send(new ViewHostMsg_PlayAudioStream(0, stream_id_));
}

void AudioDeviceContext::OnLowLatencyCreated(
    base::SharedMemoryHandle handle, base::SyncSocket::Handle socket_handle,
    uint32 length) {
#if defined(OS_WIN)
  DCHECK(handle);
  DCHECK(socket_handle);
#else
  DCHECK_NE(-1, handle.fd);
  DCHECK_NE(-1, socket_handle);
#endif
  DCHECK(length);
  DCHECK(context_);
  DCHECK(!audio_thread_.get());
  shared_memory_.reset(new base::SharedMemory(handle, false));
  shared_memory_->Map(length);
  shared_memory_size_ = length;

  context_->outBuffer = shared_memory_->memory();
  socket_.reset(new base::SyncSocket(socket_handle));
  // Allow the client to pre-populate the buffer.
  FireAudioCallback();
  if (context_->config.startThread) {
    audio_thread_.reset(
        new base::DelegateSimpleThread(this, "plugin_audio_thread"));
    audio_thread_->Start();
  }
  filter_->Send(new ViewHostMsg_PlayAudioStream(0, stream_id_));
}

void AudioDeviceContext::OnVolume(double volume) {
}

void AudioDeviceContext::Run() {
  int pending_data;
  while (sizeof(pending_data) == socket_->Receive(&pending_data,
                                                  sizeof(pending_data)) &&
      pending_data >= 0) {
    FireAudioCallback();
  }
}