// 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 <string.h>

#include <iostream>
#include <sstream>

#include "ppapi/c/pp_errors.h"
#include "ppapi/c/ppb_opengles2.h"
#include "ppapi/cpp/core.h"
#include "ppapi/cpp/fullscreen.h"
#include "ppapi/cpp/graphics_3d.h"
#include "ppapi/cpp/graphics_3d_client.h"
#include "ppapi/cpp/input_event.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/rect.h"
#include "ppapi/cpp/var.h"
#include "ppapi/lib/gl/include/GLES2/gl2.h"
#include "ppapi/utility/completion_callback_factory.h"

// Use assert as a poor-man's CHECK, even in non-debug mode.
// Since <assert.h> redefines assert on every inclusion (it doesn't use
// include-guards), make sure this is the last file #include'd in this file.
#undef NDEBUG
#include <assert.h>

// Assert |context_| isn't holding any GL Errors.  Done as a macro instead of a
// function to preserve line number information in the failure message.
#define assertNoGLError() \
  assert(!gles2_if_->GetError(context_->pp_resource()));

namespace {

class GLES2DemoInstance : public pp::Instance,
                          public pp::Graphics3DClient {
 public:
  GLES2DemoInstance(PP_Instance instance, pp::Module* module);
  virtual ~GLES2DemoInstance();

  // pp::Instance implementation (see PPP_Instance).
  virtual void DidChangeView(const pp::Rect& position,
                             const pp::Rect& clip_ignored);

  // pp::Graphics3DClient implementation.
  virtual void Graphics3DContextLost() {
    // TODO(jamesr): What's the state of context_? Should we delete the old one
    // or try to revive it somehow?
    // For now, just delete it and construct+bind a new context.
    delete context_;
    context_ = NULL;
    pp::CompletionCallback cb = callback_factory_.NewCallback(
        &GLES2DemoInstance::InitGL);
    module_->core()->CallOnMainThread(0, cb, 0);
  }

  virtual bool HandleInputEvent(const pp::InputEvent& event) {
    if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP) {
      fullscreen_ = !fullscreen_;
      pp::Fullscreen(this).SetFullscreen(fullscreen_);
    }
    return true;
  }

 private:

  // GL-related functions.
  void InitGL(int32_t result);
  void FlickerAndPaint(int32_t result, bool paint_blue);

  pp::Size plugin_size_;
  pp::CompletionCallbackFactory<GLES2DemoInstance> callback_factory_;

  // Unowned pointers.
  const PPB_OpenGLES2* gles2_if_;
  pp::Module* module_;

  // Owned data.
  pp::Graphics3D* context_;
  bool fullscreen_;
};

GLES2DemoInstance::GLES2DemoInstance(PP_Instance instance, pp::Module* module)
    : pp::Instance(instance), pp::Graphics3DClient(this),
      callback_factory_(this),
      module_(module),
      context_(NULL),
      fullscreen_(false) {
  assert((gles2_if_ = static_cast<const PPB_OpenGLES2*>(
      module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE))));
  RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
}

GLES2DemoInstance::~GLES2DemoInstance() {
  delete context_;
}

void GLES2DemoInstance::DidChangeView(
    const pp::Rect& position, const pp::Rect& clip_ignored) {
  if (position.width() == 0 || position.height() == 0)
    return;
  plugin_size_ = position.size();

  // Initialize graphics.
  InitGL(0);
}

// This object is the global object representing this plugin library as long
// as it is loaded.
class GLES2DemoModule : public pp::Module {
 public:
  GLES2DemoModule() : pp::Module() {}
  virtual ~GLES2DemoModule() {}

  virtual pp::Instance* CreateInstance(PP_Instance instance) {
    return new GLES2DemoInstance(instance, this);
  }
};

void GLES2DemoInstance::InitGL(int32_t result) {
  assert(plugin_size_.width() && plugin_size_.height());

  if (context_) {
    context_->ResizeBuffers(plugin_size_.width(), plugin_size_.height());
    return;
  }
  int32_t context_attributes[] = {
    PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
    PP_GRAPHICS3DATTRIB_BLUE_SIZE, 8,
    PP_GRAPHICS3DATTRIB_GREEN_SIZE, 8,
    PP_GRAPHICS3DATTRIB_RED_SIZE, 8,
    PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 0,
    PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 0,
    PP_GRAPHICS3DATTRIB_SAMPLES, 0,
    PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0,
    PP_GRAPHICS3DATTRIB_WIDTH, plugin_size_.width(),
    PP_GRAPHICS3DATTRIB_HEIGHT, plugin_size_.height(),
    PP_GRAPHICS3DATTRIB_NONE,
  };
  context_ = new pp::Graphics3D(this, context_attributes);
  assert(!context_->is_null());
  assert(BindGraphics(*context_));

  // Clear color bit.
  gles2_if_->ClearColor(context_->pp_resource(), 0, 1, 0, 1);
  gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT);

  assertNoGLError();

  FlickerAndPaint(0, true);
}

void GLES2DemoInstance::FlickerAndPaint(int32_t result, bool paint_blue) {
  if (result != 0 || !context_)
    return;
  float r = paint_blue ? 0 : 1;
  float g = 0;
  float b = paint_blue ? 1 : 0;
  float a = 0.75;
  gles2_if_->ClearColor(context_->pp_resource(), r, g, b, a);
  gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT);
  assertNoGLError();

  pp::CompletionCallback cb = callback_factory_.NewCallback(
      &GLES2DemoInstance::FlickerAndPaint, !paint_blue);
  context_->SwapBuffers(cb);
  assertNoGLError();
}

}  // anonymous namespace

namespace pp {
// Factory function for your specialization of the Module object.
Module* CreateModule() {
  return new GLES2DemoModule();
}
}  // namespace pp