#include <cstdio>
#include <string>

#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/graphics_2d.h"
#include "ppapi/cpp/image_data.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"

#include "SampleApp.h"
#include "SkApplication.h"
#include "SkCanvas.h"
#include "SkBitmap.h"
#include "SkEvent.h"
#include "SkWindow.h"

class SkiaInstance;

namespace {
void FlushCallback(void* data, int32_t result);
}

SkiaInstance* gPluginInstance;
extern int main(int, char**);

class SkiaInstance : public pp::Instance {
 public:
    explicit SkiaInstance(PP_Instance instance) : pp::Instance(instance),
                                                  fFlushPending(false),
                                                  fGraphics2dContext(NULL),
                                                  fPixelBuffer(NULL)
    {
        gPluginInstance = this;
        application_init();
        char* commandName = "SampleApp";
        fWindow = new SampleWindow(NULL, 0, &commandName, NULL);
    }

    virtual ~SkiaInstance() {
        gPluginInstance = NULL;
        delete fWindow;
        application_term();
    }

    virtual void HandleMessage(const pp::Var& var_message) {
        // Receive a message from javascript.  Right now this just signals us to
        // get started.
        uint32_t width = 500;
        uint32_t height = 500;
        char buffer[2048];
        sprintf(buffer, "SetSize:%d,%d", width, height);
        PostMessage(buffer);
    }

    virtual void DidChangeView(const pp::Rect& position,
                               const pp::Rect& clip) {
        if (position.size().width() == width() &&
            position.size().height() == height()) {
            return;  // Size didn't change, no need to update anything.
        }
        // Create a new device context with the new size.
        DestroyContext();
        CreateContext(position.size());
        // Delete the old pixel buffer and create a new one.
        delete fPixelBuffer;
        fPixelBuffer = NULL;
        if (fGraphics2dContext != NULL) {
            fPixelBuffer = new pp::ImageData(this,
                                              PP_IMAGEDATAFORMAT_BGRA_PREMUL,
                                              fGraphics2dContext->size(),
                                              false);
            fWindow->resize(position.size().width(), position.size().height());
            fWindow->update(NULL);
            paint();
        }
    }

    // Indicate whether a flush is pending.  This can only be called from the
    // main thread; it is not thread safe.
    bool flush_pending() const {
        return fFlushPending;
    }
    void set_flush_pending(bool flag) {
        fFlushPending = flag;
    }

  void paint() {
    if (fPixelBuffer) {
      // Draw some stuff.  TODO(borenet): Actually have SampleApp draw into
      // the plugin area.
      uint32_t w = fPixelBuffer->size().width();
      uint32_t h = fPixelBuffer->size().height();
      uint32_t* data = (uint32_t*) fPixelBuffer->data();
      // Create a bitmap using the fPixelBuffer pixels
      SkBitmap bitmap;
      bitmap.setConfig(SkBitmap::kARGB_8888_Config, w, h);
      bitmap.setPixels(data);
      // Create a canvas with the bitmap as the backend
      SkCanvas canvas(bitmap);

      canvas.drawColor(SK_ColorBLUE);
      SkRect rect = SkRect::MakeXYWH(10, 10, 80, 80);
      SkPaint rect_paint;
      rect_paint.setStyle(SkPaint::kFill_Style);
      rect_paint.setColor(SK_ColorRED);
      canvas.drawRect(rect, rect_paint);

      FlushPixelBuffer();
    }
  }

private:
    int width() const {
        return fPixelBuffer ? fPixelBuffer->size().width() : 0;
    }

    int height() const {
        return fPixelBuffer ? fPixelBuffer->size().height() : 0;
    }

    bool IsContextValid() const {
        return fGraphics2dContext != NULL;
    }

    void CreateContext(const pp::Size& size) {
        if (IsContextValid())
            return;
        fGraphics2dContext = new pp::Graphics2D(this, size, false);
        if (!BindGraphics(*fGraphics2dContext)) {
            SkDebugf("Couldn't bind the device context");
        }
    }

    void DestroyContext() {
        if (!IsContextValid())
            return;
        delete fGraphics2dContext;
        fGraphics2dContext = NULL;
    }

    void FlushPixelBuffer() {
        if (!IsContextValid())
            return;
        // Note that the pixel lock is held while the buffer is copied into the
        // device context and then flushed.
        fGraphics2dContext->PaintImageData(*fPixelBuffer, pp::Point());
        if (flush_pending())
            return;
        set_flush_pending(true);
        fGraphics2dContext->Flush(pp::CompletionCallback(&FlushCallback, this));
    }

    bool fFlushPending;
    pp::Graphics2D* fGraphics2dContext;
    pp::ImageData* fPixelBuffer;
    SampleWindow* fWindow;
};

class SkiaModule : public pp::Module {
public:
    SkiaModule() : pp::Module() {}
    virtual ~SkiaModule() {}

    virtual pp::Instance* CreateInstance(PP_Instance instance) {
        gPluginInstance = new SkiaInstance(instance);
        return gPluginInstance;
    }
};

namespace {
void FlushCallback(void* data, int32_t result) {
    static_cast<SkiaInstance*>(data)->set_flush_pending(false);
}
}

namespace pp {
Module* CreateModule() {
    return new SkiaModule();
}
}  // namespace pp


///////////////////////////////////////////
///////////// SkOSWindow impl /////////////
///////////////////////////////////////////

void SkOSWindow::onSetTitle(const char title[])
{
    char buffer[2048];
    sprintf(buffer, "SetTitle:%s", title);
    gPluginInstance->PostMessage(buffer);
}

void SkOSWindow::onHandleInval(const SkIRect& rect)
{
    gPluginInstance->paint();
}

void SkOSWindow::onPDFSaved(const char title[], const char desc[],
                            const char path[]) {
}

///////////////////////////////////////////
/////////////// SkEvent impl //////////////
///////////////////////////////////////////

void SkEvent::SignalQueueTimer(SkMSec ms) {
}

void SkEvent::SignalNonEmptyQueue() {
}