/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * WinPointerEvents - Helper functions to retrieve PointerEvent's attributes
 */

#include "nscore.h"
#include "WinPointerEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/WindowsVersion.h"

using namespace mozilla;
using namespace mozilla::widget;

const wchar_t WinPointerEvents::kPointerLibraryName[] =  L"user32.dll";
HMODULE WinPointerEvents::sLibraryHandle = nullptr;
WinPointerEvents::GetPointerTypePtr WinPointerEvents::getPointerType = nullptr;
WinPointerEvents::GetPointerInfoPtr WinPointerEvents::getPointerInfo = nullptr;
WinPointerEvents::GetPointerPenInfoPtr WinPointerEvents::getPointerPenInfo = nullptr;
bool WinPointerEvents::sPointerEventEnabled = true;
bool WinPointerEvents::sFirePointerEventsByWinPointerMessages = false;

WinPointerEvents::WinPointerEvents()
{
  InitLibrary();
  static bool addedPointerEventEnabled = false;
  if (!addedPointerEventEnabled) {
    Preferences::AddBoolVarCache(&sPointerEventEnabled,
                                 "dom.w3c_pointer_events.enabled", true);
    Preferences::AddBoolVarCache(
      &sFirePointerEventsByWinPointerMessages,
      "dom.w3c_pointer_events.dispatch_by_pointer_messages", false);
    addedPointerEventEnabled = true;
  }
}

/* Load and shutdown */
void
WinPointerEvents::InitLibrary()
{
  MOZ_ASSERT(XRE_IsParentProcess());
  if (!IsWin8OrLater()) {
    // Only Win8 or later supports WM_POINTER*
    return;
  }
  if (getPointerType) {
    // Return if we already initialized the PointerEvent related interfaces
    return;
  }
  sLibraryHandle = ::LoadLibraryW(kPointerLibraryName);
  MOZ_ASSERT(sLibraryHandle, "cannot load pointer library");
  if (sLibraryHandle) {
    getPointerType =
      (GetPointerTypePtr)GetProcAddress(sLibraryHandle, "GetPointerType");
    getPointerInfo =
      (GetPointerInfoPtr)GetProcAddress(sLibraryHandle, "GetPointerInfo");
    getPointerPenInfo =
      (GetPointerPenInfoPtr)GetProcAddress(sLibraryHandle, "GetPointerPenInfo");
  }

  if (!getPointerType || !getPointerInfo || !getPointerPenInfo) {
    MOZ_ASSERT(false, "get PointerEvent interfaces failed");
    getPointerType = nullptr;
    getPointerInfo = nullptr;
    getPointerPenInfo = nullptr;
    return;
  }
}

bool
WinPointerEvents::ShouldHandleWinPointerMessages(UINT aMsg, WPARAM aWParam)
{
  MOZ_ASSERT(aMsg == WM_POINTERDOWN || aMsg == WM_POINTERUP ||
             aMsg == WM_POINTERUPDATE || aMsg == WM_POINTERLEAVE);
  if (!sLibraryHandle || !sPointerEventEnabled) {
    return false;
  }

  // We only handle WM_POINTER* when the input source is pen. This is because
  // we need some information (e.g. tiltX, tiltY) which can't be retrieved by
  // WM_*BUTTONDOWN.
  uint32_t pointerId = GetPointerId(aWParam);
  POINTER_INPUT_TYPE pointerType = PT_POINTER;
  if (!GetPointerType(pointerId, &pointerType)) {
    MOZ_ASSERT(false, "cannot find PointerType");
    return false;
  }
  return (pointerType == PT_PEN);
}

bool
WinPointerEvents::GetPointerType(uint32_t aPointerId,
                                 POINTER_INPUT_TYPE *aPointerType)
{
  if (!getPointerType) {
    return false;
  }
  return getPointerType(aPointerId, aPointerType);
}

POINTER_INPUT_TYPE
WinPointerEvents::GetPointerType(uint32_t aPointerId)
{
  POINTER_INPUT_TYPE pointerType = PT_POINTER;
  Unused << GetPointerType(aPointerId, &pointerType);
  return pointerType;
}

bool
WinPointerEvents::GetPointerInfo(uint32_t aPointerId,
                                 POINTER_INFO *aPointerInfo)
{
  if (!getPointerInfo) {
    return false;
  }
  return getPointerInfo(aPointerId, aPointerInfo);
}

bool
WinPointerEvents::GetPointerPenInfo(uint32_t aPointerId,
                                    POINTER_PEN_INFO *aPenInfo)
{
  if (!getPointerPenInfo) {
    return false;
  }
  return getPointerPenInfo(aPointerId, aPenInfo);
}

bool
WinPointerEvents::ShouldEnableInkCollector()
{
  // We need InkCollector on Win7. For Win8 or later, we handle WM_POINTER* for
  // pen.
  return !IsWin8OrLater();
}

bool
WinPointerEvents::ShouldRollupOnPointerEvent(UINT aMsg, WPARAM aWParam)
{
  MOZ_ASSERT(aMsg == WM_POINTERDOWN);
  // Only roll up popups when we handling WM_POINTER* to fire Gecko
  // WidgetMouseEvent and suppress Windows WM_*BUTTONDOWN.
  return ShouldHandleWinPointerMessages(aMsg, aWParam) &&
         ShouldFirePointerEventByWinPointerMessages();
}

bool
WinPointerEvents::ShouldFirePointerEventByWinPointerMessages()
{
  MOZ_ASSERT(sLibraryHandle && sPointerEventEnabled);
  return sFirePointerEventsByWinPointerMessages;
}

WinPointerInfo*
WinPointerEvents::GetCachedPointerInfo(UINT aMsg, WPARAM aWParam)
{
  if (!sLibraryHandle || !sPointerEventEnabled ||
      MOUSE_INPUT_SOURCE() != nsIDOMMouseEvent::MOZ_SOURCE_PEN ||
      ShouldFirePointerEventByWinPointerMessages()) {
    return nullptr;
  }
  switch (aMsg) {
  case WM_LBUTTONDOWN:
  case WM_MBUTTONDOWN:
  case WM_RBUTTONDOWN:
    return &mPenPointerDownInfo;
  case WM_LBUTTONUP:
  case WM_MBUTTONUP:
  case WM_RBUTTONUP:
    return &mPenPointerDownInfo;
  case WM_MOUSEMOVE:
    return &mPenPointerUpdateInfo;
  default:
    MOZ_ASSERT(false);
  }
  return nullptr;
}

void
WinPointerEvents::ConvertAndCachePointerInfo(UINT aMsg, WPARAM aWParam)
{
  MOZ_ASSERT(!sFirePointerEventsByWinPointerMessages);
  // Windows doesn't support chorded buttons for pen, so we can simply keep the
  // latest information from pen generated pointer messages and use them when
  // handling mouse messages. Used different pointer info for pointerdown,
  // pointerupdate, and pointerup because Windows doesn't always interleave
  // pointer messages and mouse messages.
  switch (aMsg) {
  case WM_POINTERDOWN:
    ConvertAndCachePointerInfo(aWParam, &mPenPointerDownInfo);
    break;
  case WM_POINTERUP:
    ConvertAndCachePointerInfo(aWParam, &mPenPointerUpInfo);
    break;
  case WM_POINTERUPDATE:
    ConvertAndCachePointerInfo(aWParam, &mPenPointerUpdateInfo);
    break;
  default:
    break;
  }
}

void
WinPointerEvents::ConvertAndCachePointerInfo(WPARAM aWParam,
                                             WinPointerInfo* aInfo)
{
  MOZ_ASSERT(!sFirePointerEventsByWinPointerMessages);
  aInfo->pointerId = GetPointerId(aWParam);
  MOZ_ASSERT(GetPointerType(aInfo->pointerId) == PT_PEN);
  POINTER_PEN_INFO penInfo;
  GetPointerPenInfo(aInfo->pointerId, &penInfo);
  aInfo->tiltX = penInfo.tiltX;
  aInfo->tiltY = penInfo.tiltY;
  // Windows defines the pen pressure is normalized to a range between 0 and
  // 1024. Convert it to float.
  aInfo->mPressure = penInfo.pressure ? (float)penInfo.pressure / 1024 : 0;
}
