/* ***** BEGIN LICENSE BLOCK *****
 * 
 * Copyright (c) 2008, Mozilla Corporation
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the Mozilla Corporation nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Contributor(s):
 *   Josh Aas <josh@mozilla.com>
 *   Jim Mathies <jmathies@mozilla.com>
 * 
 * ***** END LICENSE BLOCK ***** */

#include "nptest_platform.h"

#include <windows.h>
#include <windowsx.h>
#include <stdio.h>

#include <d3d10_1.h>
#include <d2d1.h>

using namespace std;

void SetSubclass(HWND hWnd, InstanceData* instanceData);
void ClearSubclass(HWND hWnd);
LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

struct _PlatformData {
  HWND childWindow;
  IDXGIAdapter1 *adapter;
  ID3D10Device1 *device;
  ID3D10Texture2D *frontBuffer;
  ID3D10Texture2D *backBuffer;
  ID2D1Factory *d2d1Factory;
};

bool
pluginSupportsWindowMode()
{
  return true;
}

bool
pluginSupportsWindowlessMode()
{
  return true;
}

NPError
pluginInstanceInit(InstanceData* instanceData)
{
  NPP npp = instanceData->npp;

  instanceData->platformData = static_cast<PlatformData*>
    (NPN_MemAlloc(sizeof(PlatformData)));
  if (!instanceData->platformData)
    return NPERR_OUT_OF_MEMORY_ERROR;
  
  instanceData->platformData->childWindow = nullptr;
  instanceData->platformData->device = nullptr;
  instanceData->platformData->frontBuffer = nullptr;
  instanceData->platformData->backBuffer = nullptr;
  instanceData->platformData->adapter = nullptr;
  instanceData->platformData->d2d1Factory = nullptr;
  return NPERR_NO_ERROR;
}

static inline bool
openSharedTex2D(ID3D10Device* device, HANDLE handle, ID3D10Texture2D** out)
{
  HRESULT hr = device->OpenSharedResource(handle, __uuidof(ID3D10Texture2D), (void**)out);
  if (FAILED(hr) || !*out) {
    return false;
  }
  return true;
}

// This is overloaded in d2d1.h so we can't use decltype().
typedef HRESULT (WINAPI*D2D1CreateFactoryFunc)(
    D2D1_FACTORY_TYPE factoryType,
    REFIID iid,
    CONST D2D1_FACTORY_OPTIONS *pFactoryOptions,
    void **factory
);

static IDXGIAdapter1*
FindDXGIAdapter(NPP npp, IDXGIFactory1* factory)
{
  DXGI_ADAPTER_DESC preferred;
  if (NPN_GetValue(npp, NPNVpreferredDXGIAdapter, &preferred) != NPERR_NO_ERROR) {
    return nullptr;
  }

  UINT index = 0;
  for (;;) {
    IDXGIAdapter1* adapter = nullptr;
    if (FAILED(factory->EnumAdapters1(index, &adapter)) || !adapter) {
      return nullptr;
    }

    DXGI_ADAPTER_DESC desc;
    if (SUCCEEDED(adapter->GetDesc(&desc)) &&
        desc.AdapterLuid.LowPart == preferred.AdapterLuid.LowPart &&
        desc.AdapterLuid.HighPart == preferred.AdapterLuid.HighPart &&
        desc.VendorId == preferred.VendorId &&
        desc.DeviceId == preferred.DeviceId)
    {
      return adapter;
    }

    adapter->Release();
    index++;
  }
}

// Note: we leak modules since we need them anyway.
bool
setupDxgiSurfaces(NPP npp, InstanceData* instanceData)
{
  HMODULE dxgi = LoadLibraryA("dxgi.dll");
  if (!dxgi) {
    return false;
  }
  decltype(CreateDXGIFactory1)* createDXGIFactory1 =
    (decltype(CreateDXGIFactory1)*)GetProcAddress(dxgi, "CreateDXGIFactory1");
  if (!createDXGIFactory1) {
    return false;
  }

  IDXGIFactory1* factory1 = nullptr;
  HRESULT hr = createDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&factory1);
  if (FAILED(hr) || !factory1) {
    return false;
  }

  instanceData->platformData->adapter = FindDXGIAdapter(npp, factory1);
  if (!instanceData->platformData->adapter) {
    return false;
  }

  HMODULE d3d10 = LoadLibraryA("d3d10_1.dll");
  if (!d3d10) {
    return false;
  }

  decltype(D3D10CreateDevice1)* createDevice =
    (decltype(D3D10CreateDevice1)*)GetProcAddress(d3d10, "D3D10CreateDevice1");
  if (!createDevice) {
    return false;
  }


  hr = createDevice(
    instanceData->platformData->adapter,
    D3D10_DRIVER_TYPE_HARDWARE, nullptr,
    D3D10_CREATE_DEVICE_BGRA_SUPPORT |
      D3D10_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS,
    D3D10_FEATURE_LEVEL_10_1,
    D3D10_1_SDK_VERSION, &instanceData->platformData->device);
  if (FAILED(hr) || !instanceData->platformData->device) {
    return false;
  }

  if (!openSharedTex2D(instanceData->platformData->device,
                       instanceData->frontBuffer->sharedHandle,
                       &instanceData->platformData->frontBuffer))
  {
    return false;
  }
  if (!openSharedTex2D(instanceData->platformData->device,
                       instanceData->backBuffer->sharedHandle,
                       &instanceData->platformData->backBuffer))
  {
    return false;
  }

  HMODULE d2d1 = LoadLibraryA("D2d1.dll");
  if (!d2d1) {
    return false;
  }
  auto d2d1CreateFactory = (D2D1CreateFactoryFunc)GetProcAddress(d2d1, "D2D1CreateFactory");
  if (!d2d1CreateFactory) {
    return false;
  }

  D2D1_FACTORY_OPTIONS options;
  options.debugLevel = D2D1_DEBUG_LEVEL_NONE;

  hr = d2d1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED,
                         __uuidof(ID2D1Factory),
                         &options,
                         (void**)&instanceData->platformData->d2d1Factory);
  if (FAILED(hr) || !instanceData->platformData->d2d1Factory) {
    return false;
  }

  return true;
}

void
drawDxgiBitmapColor(InstanceData* instanceData)
{
  NPP npp = instanceData->npp;

  HRESULT hr;

  IDXGISurface* surface = nullptr;
  hr = instanceData->platformData->backBuffer->QueryInterface(
    __uuidof(IDXGISurface), (void **)&surface);
  if (FAILED(hr) || !surface) {
    return;
  }

  D2D1_RENDER_TARGET_PROPERTIES props =
    D2D1::RenderTargetProperties(
      D2D1_RENDER_TARGET_TYPE_DEFAULT,
      D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED));

  ID2D1RenderTarget* target = nullptr;
  hr = instanceData->platformData->d2d1Factory->CreateDxgiSurfaceRenderTarget(
    surface,
    &props,
    &target);
  if (FAILED(hr) || !target) {
    surface->Release();
    return;
  }

  IDXGIKeyedMutex* mutex = nullptr;
  hr = instanceData->platformData->backBuffer->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&mutex);
  if (mutex) {
    mutex->AcquireSync(0, 0);
  }

  target->BeginDraw();

  unsigned char subpixels[4];
  memcpy(subpixels,
         &instanceData->scriptableObject->drawColor,
         sizeof(subpixels));

  auto rect = D2D1::RectF(
    0, 0,
    instanceData->backBuffer->size.width,
    instanceData->backBuffer->size.height);
  auto color = D2D1::ColorF(
    float(subpixels[3] * subpixels[2]) / 0xFF,
    float(subpixels[3] * subpixels[1]) / 0xFF,
    float(subpixels[3] * subpixels[0]) / 0xFF,
    float(subpixels[3]) / 0xff);

  ID2D1SolidColorBrush* brush = nullptr;
  hr = target->CreateSolidColorBrush(color, &brush);
  if (SUCCEEDED(hr) && brush) {
    target->FillRectangle(rect, brush);
    brush->Release();
    brush = nullptr;
  }
  hr = target->EndDraw();

  if (mutex) {
    mutex->ReleaseSync(0);
    mutex->Release();
    mutex = nullptr;
  }

  target->Release();
  surface->Release();
  target = nullptr;
  surface = nullptr;

  NPN_SetCurrentAsyncSurface(npp, instanceData->backBuffer, NULL);
  std::swap(instanceData->backBuffer, instanceData->frontBuffer);
  std::swap(instanceData->platformData->backBuffer,
            instanceData->platformData->frontBuffer);
}

void
pluginInstanceShutdown(InstanceData* instanceData)
{
  PlatformData *pd = instanceData->platformData;
  if (pd->frontBuffer) {
    pd->frontBuffer->Release();
  }
  if (pd->backBuffer) {
    pd->backBuffer->Release();
  }
  if (pd->d2d1Factory) {
    pd->d2d1Factory->Release();
  }
  if (pd->device) {
    pd->device->Release();
  }
  if (pd->adapter) {
    pd->adapter->Release();
  }
  NPN_MemFree(instanceData->platformData);
  instanceData->platformData = 0;
}

void
pluginDoSetWindow(InstanceData* instanceData, NPWindow* newWindow)
{
  instanceData->window = *newWindow;
}

#define CHILD_WIDGET_SIZE 10

void
pluginWidgetInit(InstanceData* instanceData, void* oldWindow)
{
  HWND hWnd = (HWND)instanceData->window.window;
  if (oldWindow) {
    // chrashtests/539897-1.html excercises this code
    HWND hWndOld = (HWND)oldWindow;
    ClearSubclass(hWndOld);
    if (instanceData->platformData->childWindow) {
      ::DestroyWindow(instanceData->platformData->childWindow);
    }
  }

  SetSubclass(hWnd, instanceData);

  instanceData->platformData->childWindow =
    ::CreateWindowW(L"SCROLLBAR", L"Dummy child window", 
                    WS_CHILD, 0, 0, CHILD_WIDGET_SIZE, CHILD_WIDGET_SIZE, hWnd, nullptr,
                    nullptr, nullptr);
}

static void
drawToDC(InstanceData* instanceData, HDC dc,
         int x, int y, int width, int height)
{
  switch (instanceData->scriptableObject->drawMode) {
    case DM_DEFAULT:
    {
      const RECT fill = { x, y, x + width, y + height };

      int oldBkMode = ::SetBkMode(dc, TRANSPARENT);
      HBRUSH brush = ::CreateSolidBrush(RGB(0, 0, 0));
      if (brush) {
        ::FillRect(dc, &fill, brush);
        ::DeleteObject(brush);
      }
      if (width > 6 && height > 6) {
        brush = ::CreateSolidBrush(RGB(192, 192, 192));
        if (brush) {
          RECT inset = { x + 3, y + 3, x + width - 3, y + height - 3 };
          ::FillRect(dc, &inset, brush);
          ::DeleteObject(brush);
        }
      }

      const char* uaString = NPN_UserAgent(instanceData->npp);
      if (uaString && width > 10 && height > 10) {
        HFONT font =
          ::CreateFontA(20, 0, 0, 0, 400, FALSE, FALSE, FALSE,
                        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
                        CLIP_DEFAULT_PRECIS, 5, // CLEARTYPE_QUALITY
                        DEFAULT_PITCH, "Arial");
        if (font) {
          HFONT oldFont = (HFONT)::SelectObject(dc, font);
          RECT inset = { x + 5, y + 5, x + width - 5, y + height - 5 };
          ::DrawTextA(dc, uaString, -1, &inset,
                      DT_LEFT | DT_TOP | DT_NOPREFIX | DT_WORDBREAK);
          ::SelectObject(dc, oldFont);
          ::DeleteObject(font);
        }
      }
      ::SetBkMode(dc, oldBkMode);
    }
    break;

    case DM_SOLID_COLOR:
    {
      HDC offscreenDC = ::CreateCompatibleDC(dc);
      if (!offscreenDC)
	return;

      const BITMAPV4HEADER bitmapheader = {
	sizeof(BITMAPV4HEADER),
	width,
	height,
	1, // planes
	32, // bits
	BI_BITFIELDS,
	0, // unused size
	0, 0, // unused metrics
	0, 0, // unused colors used/important
	0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, // ARGB masks
      };
      uint32_t *pixelData;
      HBITMAP offscreenBitmap =
	::CreateDIBSection(dc, reinterpret_cast<const BITMAPINFO*>(&bitmapheader),
			   0, reinterpret_cast<void**>(&pixelData), 0, 0);
      if (!offscreenBitmap)
	return;

      uint32_t rgba = instanceData->scriptableObject->drawColor;
      unsigned int alpha = ((rgba & 0xFF000000) >> 24);
      BYTE r = ((rgba & 0xFF0000) >> 16);
      BYTE g = ((rgba & 0xFF00) >> 8);
      BYTE b = (rgba & 0xFF);

      // Windows expects premultiplied
      r = BYTE(float(alpha * r) / 0xFF);
      g = BYTE(float(alpha * g) / 0xFF);
      b = BYTE(float(alpha * b) / 0xFF);
      uint32_t premultiplied =
	(alpha << 24) +	(r << 16) + (g << 8) + b;

      for (uint32_t* lastPixel = pixelData + width * height;
	   pixelData < lastPixel;
	   ++pixelData)
	*pixelData = premultiplied;

      ::SelectObject(offscreenDC, offscreenBitmap);
      BLENDFUNCTION blendFunc;
      blendFunc.BlendOp = AC_SRC_OVER;
      blendFunc.BlendFlags = 0;
      blendFunc.SourceConstantAlpha = 255;
      blendFunc.AlphaFormat = AC_SRC_ALPHA;
      ::AlphaBlend(dc, x, y, width, height, offscreenDC, 0, 0, width, height,
		   blendFunc);

      ::DeleteObject(offscreenDC);
      ::DeleteObject(offscreenBitmap);
    }
    break;
  }
}

void
pluginDraw(InstanceData* instanceData)
{
  NPP npp = instanceData->npp;
  if (!npp)
    return;

  HDC hdc = nullptr;
  PAINTSTRUCT ps;

  notifyDidPaint(instanceData);

  if (instanceData->hasWidget)
    hdc = ::BeginPaint((HWND)instanceData->window.window, &ps);
  else
    hdc = (HDC)instanceData->window.window;

  if (hdc == nullptr)
    return;

  // Push the browser's hdc on the resource stack. If this test plugin is windowless,
  // we share the drawing surface with the rest of the browser.
  int savedDCID = SaveDC(hdc);

  // When we have a widget, window.x/y are meaningless since our widget
  // is always positioned correctly and we just draw into it at 0,0.
  int x = instanceData->hasWidget ? 0 : instanceData->window.x;
  int y = instanceData->hasWidget ? 0 : instanceData->window.y;
  int width = instanceData->window.width;
  int height = instanceData->window.height;
  drawToDC(instanceData, hdc, x, y, width, height);

  // Pop our hdc changes off the resource stack
  RestoreDC(hdc, savedDCID);

  if (instanceData->hasWidget)
    ::EndPaint((HWND)instanceData->window.window, &ps);
}

/* script interface */

int32_t
pluginGetEdge(InstanceData* instanceData, RectEdge edge)
{
  if (!instanceData || !instanceData->hasWidget)
    return NPTEST_INT32_ERROR;

  // Get the plugin client rect in screen coordinates
  RECT rect = {0};
  if (!::GetClientRect((HWND)instanceData->window.window, &rect))
    return NPTEST_INT32_ERROR;
  ::MapWindowPoints((HWND)instanceData->window.window, nullptr,
                    (LPPOINT)&rect, 2);

  // Get the toplevel window frame rect in screen coordinates
  HWND rootWnd = ::GetAncestor((HWND)instanceData->window.window, GA_ROOT);
  if (!rootWnd)
    return NPTEST_INT32_ERROR;
  RECT rootRect;
  if (!::GetWindowRect(rootWnd, &rootRect))
    return NPTEST_INT32_ERROR;

  switch (edge) {
  case EDGE_LEFT:
    return rect.left - rootRect.left;
  case EDGE_TOP:
    return rect.top - rootRect.top;
  case EDGE_RIGHT:
    return rect.right - rootRect.left;
  case EDGE_BOTTOM:
    return rect.bottom - rootRect.top;
  }

  return NPTEST_INT32_ERROR;
}

static BOOL
getWindowRegion(HWND wnd, HRGN rgn)
{
  if (::GetWindowRgn(wnd, rgn) != ERROR)
    return TRUE;

  RECT clientRect;
  if (!::GetClientRect(wnd, &clientRect))
    return FALSE;
  return ::SetRectRgn(rgn, 0, 0, clientRect.right, clientRect.bottom);
}

static RGNDATA*
computeClipRegion(InstanceData* instanceData)
{
  HWND wnd = (HWND)instanceData->window.window;
  HRGN rgn = ::CreateRectRgn(0, 0, 0, 0);
  if (!rgn)
    return nullptr;
  HRGN ancestorRgn = ::CreateRectRgn(0, 0, 0, 0);
  if (!ancestorRgn) {
    ::DeleteObject(rgn);
    return nullptr;
  }
  if (!getWindowRegion(wnd, rgn)) {
    ::DeleteObject(ancestorRgn);
    ::DeleteObject(rgn);
    return nullptr;
  }

  HWND ancestor = wnd;
  for (;;) {
    ancestor = ::GetAncestor(ancestor, GA_PARENT);
    if (!ancestor || ancestor == ::GetDesktopWindow()) {
      ::DeleteObject(ancestorRgn);

      DWORD size = ::GetRegionData(rgn, 0, nullptr);
      if (!size) {
        ::DeleteObject(rgn);
        return nullptr;
      }

      HANDLE heap = ::GetProcessHeap();
      RGNDATA* data = static_cast<RGNDATA*>(::HeapAlloc(heap, 0, size));
      if (!data) {
        ::DeleteObject(rgn);
        return nullptr;
      }
      DWORD result = ::GetRegionData(rgn, size, data);
      ::DeleteObject(rgn);
      if (!result) {
        ::HeapFree(heap, 0, data);
        return nullptr;
      }

      return data;
    }

    if (!getWindowRegion(ancestor, ancestorRgn)) {
      ::DeleteObject(ancestorRgn);
      ::DeleteObject(rgn);
      return 0;
    }

    POINT pt = { 0, 0 };
    ::MapWindowPoints(ancestor, wnd, &pt, 1);
    if (::OffsetRgn(ancestorRgn, pt.x, pt.y) == ERROR ||
        ::CombineRgn(rgn, rgn, ancestorRgn, RGN_AND) == ERROR) {
      ::DeleteObject(ancestorRgn);
      ::DeleteObject(rgn);
      return 0;
    }
  }
}

int32_t
pluginGetClipRegionRectCount(InstanceData* instanceData)
{
  RGNDATA* data = computeClipRegion(instanceData);
  if (!data)
    return NPTEST_INT32_ERROR;

  int32_t result = data->rdh.nCount;
  ::HeapFree(::GetProcessHeap(), 0, data);
  return result;
}

static int32_t
addOffset(LONG coord, int32_t offset)
{
  if (offset == NPTEST_INT32_ERROR)
    return NPTEST_INT32_ERROR;
  return coord + offset;
}

int32_t
pluginGetClipRegionRectEdge(InstanceData* instanceData, 
    int32_t rectIndex, RectEdge edge)
{
  RGNDATA* data = computeClipRegion(instanceData);
  if (!data)
    return NPTEST_INT32_ERROR;

  HANDLE heap = ::GetProcessHeap();
  if (rectIndex >= int32_t(data->rdh.nCount)) {
    ::HeapFree(heap, 0, data);
    return NPTEST_INT32_ERROR;
  }

  RECT rect = reinterpret_cast<RECT*>(data->Buffer)[rectIndex];
  ::HeapFree(heap, 0, data);

  switch (edge) {
  case EDGE_LEFT:
    return addOffset(rect.left, pluginGetEdge(instanceData, EDGE_LEFT));
  case EDGE_TOP:
    return addOffset(rect.top, pluginGetEdge(instanceData, EDGE_TOP));
  case EDGE_RIGHT:
    return addOffset(rect.right, pluginGetEdge(instanceData, EDGE_LEFT));
  case EDGE_BOTTOM:
    return addOffset(rect.bottom, pluginGetEdge(instanceData, EDGE_TOP));
  }

  return NPTEST_INT32_ERROR;
}

static
void
createDummyWindowForIME(InstanceData* instanceData)
{
  WNDCLASSW wndClass;
  wndClass.style = 0;
  wndClass.lpfnWndProc = DefWindowProcW;
  wndClass.cbClsExtra = 0;
  wndClass.cbWndExtra = 0;
  wndClass.hInstance = GetModuleHandleW(NULL);
  wndClass.hIcon = nullptr;
  wndClass.hCursor = nullptr;
  wndClass.hbrBackground = (HBRUSH)COLOR_WINDOW;
  wndClass.lpszMenuName = NULL;
  wndClass.lpszClassName = L"SWFlash_PlaceholderX";
  RegisterClassW(&wndClass);

  instanceData->placeholderWnd =
    static_cast<void*>(CreateWindowW(L"SWFlash_PlaceholderX", L"", WS_CHILD, 0,
                                     0, 0, 0, HWND_MESSAGE, NULL,
                                     GetModuleHandleW(NULL), NULL));
}

/* windowless plugin events */

static bool
handleEventInternal(InstanceData* instanceData, NPEvent* pe, LRESULT* result)
{
  switch ((UINT)pe->event) {
    case WM_PAINT:
      pluginDraw(instanceData);
      return true;

    case WM_MOUSEACTIVATE:
      if (instanceData->hasWidget) {
        ::SetFocus((HWND)instanceData->window.window);
        *result = MA_ACTIVATEANDEAT;
        return true;
      }
      return false;

    case WM_MOUSEWHEEL:
      return true;

    case WM_WINDOWPOSCHANGED: {
      WINDOWPOS* pPos = (WINDOWPOS*)pe->lParam;
      instanceData->winX = instanceData->winY = 0;
      if (pPos) {
        instanceData->winX = pPos->x;
        instanceData->winY = pPos->y;
        return true;
      }
      return false;
    }

    case WM_MOUSEMOVE:
    case WM_LBUTTONDOWN:
    case WM_LBUTTONUP:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONUP:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONUP: {
      int x = instanceData->hasWidget ? 0 : instanceData->winX;
      int y = instanceData->hasWidget ? 0 : instanceData->winY;
      instanceData->lastMouseX = GET_X_LPARAM(pe->lParam) - x;
      instanceData->lastMouseY = GET_Y_LPARAM(pe->lParam) - y;
      if ((UINT)pe->event == WM_LBUTTONUP) {
        instanceData->mouseUpEventCount++;
      }
      return true;
    }

    case WM_KEYDOWN:
      instanceData->lastKeyText.erase();
      *result = 0;
      return true;

    case WM_CHAR: {
      *result = 0;
      wchar_t uniChar = static_cast<wchar_t>(pe->wParam);
      if (!uniChar) {
        return true;
      }
      char utf8Char[6];
      int len =
        ::WideCharToMultiByte(CP_UTF8, 0, &uniChar, 1, utf8Char, 6,
                              nullptr, nullptr);
      if (len == 0 || len > 6) {
        return true;
      }
      instanceData->lastKeyText.append(utf8Char, len);
      return true;
    }

    case WM_IME_STARTCOMPOSITION:
      instanceData->lastComposition.erase();
      if (!instanceData->placeholderWnd) {
        createDummyWindowForIME(instanceData);
      }
      return true;

    case WM_IME_ENDCOMPOSITION:
      instanceData->lastComposition.erase();
      return true;

    case WM_IME_COMPOSITION: {
      if (pe->lParam & GCS_COMPSTR) {
        HIMC hIMC = ImmGetContext((HWND)instanceData->placeholderWnd);
        if (!hIMC) {
          return false;
        }
        WCHAR compStr[256];
        LONG len = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, compStr,
                                            256 * sizeof(WCHAR));
        CHAR buffer[256];
        len = ::WideCharToMultiByte(CP_UTF8, 0, compStr, len / sizeof(WCHAR),
                                    buffer, 256, nullptr, nullptr);
        instanceData->lastComposition.append(buffer, len);
        ::ImmReleaseContext((HWND)instanceData->placeholderWnd, hIMC);
      }
      return true;
    }

    default:
      return false;
  }
}

int16_t
pluginHandleEvent(InstanceData* instanceData, void* event)
{
  NPEvent* pe = (NPEvent*)event;

  if (pe == nullptr || instanceData == nullptr ||
      instanceData->window.type != NPWindowTypeDrawable)
    return 0;   

  LRESULT result = 0;
  return handleEventInternal(instanceData, pe, &result);
}

/* windowed plugin events */

LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	WNDPROC wndProc = (WNDPROC)GetProp(hWnd, "MozillaWndProc");
  if (!wndProc)
    return 0;
  InstanceData* pInstance = (InstanceData*)GetProp(hWnd, "InstanceData");
  if (!pInstance)
    return 0;

  NPEvent event = { static_cast<uint16_t>(uMsg), wParam, lParam };

  LRESULT result = 0;
  if (handleEventInternal(pInstance, &event, &result))
    return result;

  if (uMsg == WM_CLOSE) {
    ClearSubclass((HWND)pInstance->window.window);
  }

  return CallWindowProc(wndProc, hWnd, uMsg, wParam, lParam);
}

void
ClearSubclass(HWND hWnd)
{
  if (GetProp(hWnd, "MozillaWndProc")) {
    ::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)GetProp(hWnd, "MozillaWndProc"));
    RemoveProp(hWnd, "MozillaWndProc");
    RemoveProp(hWnd, "InstanceData");
  }
}

void
SetSubclass(HWND hWnd, InstanceData* instanceData)
{
  // Subclass the plugin window so we can handle our own windows events.
  SetProp(hWnd, "InstanceData", (HANDLE)instanceData);
  WNDPROC origProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)PluginWndProc);
  SetProp(hWnd, "MozillaWndProc", (HANDLE)origProc);
}

static void checkEquals(int a, int b, const char* msg, string& error)
{
  if (a == b) {
    return;
  }

  error.append(msg);
  char buf[100];
  sprintf(buf, " (got %d, expected %d)\n", a, b);
  error.append(buf);
}

void pluginDoInternalConsistencyCheck(InstanceData* instanceData, string& error)
{
  if (instanceData->platformData->childWindow) {
    RECT childRect;
    ::GetWindowRect(instanceData->platformData->childWindow, &childRect);
    RECT ourRect;
    HWND hWnd = (HWND)instanceData->window.window;
    ::GetWindowRect(hWnd, &ourRect);
    checkEquals(childRect.left, ourRect.left, "Child widget left", error);
    checkEquals(childRect.top, ourRect.top, "Child widget top", error);
    checkEquals(childRect.right, childRect.left + CHILD_WIDGET_SIZE, "Child widget width", error);
    checkEquals(childRect.bottom, childRect.top + CHILD_WIDGET_SIZE, "Child widget height", error);
  }
}

bool pluginNativeWidgetIsVisible(InstanceData* instanceData)
{
  HWND hWnd = (HWND)instanceData->window.window;
  wchar_t className[60];
  if (::GetClassNameW(hWnd, className, sizeof(className) / sizeof(char16_t)) &&
      !wcsicmp(className, L"GeckoPluginWindow")) {
    return ::IsWindowVisible(hWnd);
  }
  // something isn't right, fail the check
  return false;
}