// 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 "webkit/glue/plugins/pepper_graphics_3d.h"

#include "gpu/command_buffer/common/command_buffer.h"
#include "base/singleton.h"
#include "base/thread_local.h"
#include "ppapi/c/dev/ppb_graphics_3d_dev.h"
#include "webkit/glue/plugins/pepper_common.h"
#include "webkit/glue/plugins/pepper_plugin_instance.h"

namespace pepper {

namespace {

struct CurrentContextTag {};
typedef Singleton<base::ThreadLocalPointer<Graphics3D>,
    DefaultSingletonTraits<base::ThreadLocalPointer<Graphics3D> >,
    CurrentContextTag> CurrentContextKey;

// Size of the transfer buffer.
enum { kTransferBufferSize = 512 * 1024 };

PP_Bool IsGraphics3D(PP_Resource resource) {
  return BoolToPPBool(!!Resource::GetAs<Graphics3D>(resource));
}

PP_Bool GetConfigs(int32_t* configs, int32_t config_size, int32_t* num_config) {
  // TODO(neb): Implement me!
  return PP_FALSE;
}

PP_Bool ChooseConfig(const int32_t* attrib_list, int32_t* configs,
                     int32_t config_size, int32_t* num_config) {
  // TODO(neb): Implement me!
  return PP_FALSE;
}

PP_Bool GetConfigAttrib(int32_t config, int32_t attribute, int32_t* value) {
  // TODO(neb): Implement me!
  return PP_FALSE;
}

const char* QueryString(int32_t name) {
  switch (name) {
    case EGL_CLIENT_APIS:
      return "OpenGL_ES";
    case EGL_EXTENSIONS:
      return "";
    case EGL_VENDOR:
      return "Google";
    case EGL_VERSION:
      return "1.0 Google";
    default:
      return NULL;
  }
}

PP_Resource CreateContext(PP_Instance instance_id, int32_t config,
                          int32_t share_context,
                          const int32_t* attrib_list) {
  DCHECK_EQ(0, share_context);

  PluginInstance* instance = ResourceTracker::Get()->GetInstance(instance_id);
  if (!instance) {
    return 0;
  }

  scoped_refptr<Graphics3D> context(new Graphics3D(instance->module()));
  if (!context->Init(instance_id, config, attrib_list)) {
    return 0;
  }

  return context->GetReference();
}

void* GetProcAddress(const char* name) {
  // TODO(neb): Implement me!
  return NULL;
}

PP_Bool MakeCurrent(PP_Resource graphics3d) {
  if (!graphics3d) {
    Graphics3D::ResetCurrent();
    return PP_TRUE;
  } else {
    scoped_refptr<Graphics3D> context(Resource::GetAs<Graphics3D>(graphics3d));
    return BoolToPPBool(context.get() && context->MakeCurrent());
  }
}

PP_Resource GetCurrentContext() {
  Graphics3D* current_context = Graphics3D::GetCurrent();
  return current_context ? current_context->GetReference() : 0;
}

PP_Bool SwapBuffers(PP_Resource graphics3d) {
  scoped_refptr<Graphics3D> context(Resource::GetAs<Graphics3D>(graphics3d));
  return BoolToPPBool(context && context->SwapBuffers());
}

uint32_t GetError() {
  // Technically, this should return the last error that occurred on the current
  // thread, rather than an error associated with a particular context.
  // TODO(apatrick): Fix this.
  Graphics3D* current_context = Graphics3D::GetCurrent();
  if (!current_context)
    return 0;

  return current_context->GetError();
}

const PPB_Graphics3D_Dev ppb_graphics3d = {
  &IsGraphics3D,
  &GetConfigs,
  &ChooseConfig,
  &GetConfigAttrib,
  &QueryString,
  &CreateContext,
  &GetProcAddress,
  &MakeCurrent,
  &GetCurrentContext,
  &SwapBuffers,
  &GetError
};

}  // namespace

Graphics3D::Graphics3D(PluginModule* module)
  : Resource(module),
    bound_instance_(NULL) {
}

const PPB_Graphics3D_Dev* Graphics3D::GetInterface() {
  return &ppb_graphics3d;
}

Graphics3D* Graphics3D::GetCurrent() {
  return CurrentContextKey::get()->Get();
}

void Graphics3D::ResetCurrent() {
  CurrentContextKey::get()->Set(NULL);
}

Graphics3D* Graphics3D::AsGraphics3D() {
  return this;
}

Graphics3D::~Graphics3D() {
  Destroy();
}

bool Graphics3D::Init(PP_Instance instance_id, int32_t config,
                      const int32_t* attrib_list) {
  PluginInstance* instance = ResourceTracker::Get()->GetInstance(instance_id);
  if (!instance) {
    return false;
  }

  // Create and initialize the objects required to issue GLES2 calls.
  platform_context_.reset(instance->delegate()->CreateContext3D());
  if (!platform_context_.get()) {
    Destroy();
    return false;
  }

  if (!platform_context_->Init()) {
    Destroy();
    return false;
  }

  gles2_implementation_ = platform_context_->GetGLES2Implementation();
  DCHECK(gles2_implementation_);

  return true;
}

bool Graphics3D::BindToInstance(PluginInstance* new_instance) {
  if (bound_instance_ == new_instance)
    return true;  // Rebinding the same device, nothing to do.
  if (bound_instance_ && new_instance)
    return false;  // Can't change a bound device.

  if (new_instance) {
    // Resize the backing texture to the size of the instance when it is bound.
    platform_context_->ResizeBackingTexture(new_instance->position().size());

    // This is a temporary hack. The SwapBuffers is issued to force the resize
    // to take place before any subsequent rendering. This might lead to a
    // partially rendered frame being displayed. It is also not thread safe
    // since the SwapBuffers is written to the command buffer and that command
    // buffer might be written to by another thread.
    // TODO(apatrick): Figure out the semantics of binding and resizing.
    platform_context_->SwapBuffers();
  }

  bound_instance_ = new_instance;
  return true;
}

bool Graphics3D::MakeCurrent() {
  if (!platform_context_.get())
    return false;

  CurrentContextKey::get()->Set(this);

  // TODO(apatrick): Return false on context lost.
  return true;
}

bool Graphics3D::SwapBuffers() {
  if (!platform_context_.get())
    return false;

  return platform_context_->SwapBuffers();
}

unsigned Graphics3D::GetError() {
  if (!platform_context_.get())
    return 0;

  return platform_context_->GetError();
}

void Graphics3D::ResizeBackingTexture(const gfx::Size& size) {
  if (!platform_context_.get())
    return;

  platform_context_->ResizeBackingTexture(size);
}

void Graphics3D::SetSwapBuffersCallback(Callback0::Type* callback) {
  if (!platform_context_.get())
    return;

  platform_context_->SetSwapBuffersCallback(callback);
}

unsigned Graphics3D::GetBackingTextureId() {
  if (!platform_context_.get())
    return 0;

  return platform_context_->GetBackingTextureId();
}

void Graphics3D::Destroy() {
  if (GetCurrent() == this) {
    ResetCurrent();
  }

  gles2_implementation_ = NULL;

  platform_context_.reset();
}

}  // namespace pepper