// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#ifndef WINVER
#define WINVER 0x0601 // for touch support, 0x0601 means target Windows 7 or later
#endif

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // for touch support, 0x0601 means target Windows 7 or later
#endif

#include "vtkHardwareWindow.h"
#include "vtkRenderWindow.h"
#include "vtkStringArray.h"
#include "vtkWin32RenderWindowInteractor.h"
#include "vtkWindows.h"
#include "vtksys/Encoding.hxx"

#include <shellapi.h> // for drag and drop
#include <winuser.h>  // for touch support

// Mouse wheel support
// In an ideal world we would just have to include <zmouse.h>, but it is not
// always available with all compilers/headers
#ifndef WM_MOUSEWHEEL
#define WM_MOUSEWHEEL 0x020A
#endif // WM_MOUSEWHEEL
#ifndef GET_WHEEL_DELTA_WPARAM
#define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam))
#endif // GET_WHEEL_DELTA_WPARAM

// MSVC does the right thing without the forward declaration when it
// sees it in the friend decl in vtkWin32RenderWindowInteractor, but
// GCC needs to see the declaration beforehand. It has to do with the
// CALLBACK attribute.
VTK_ABI_NAMESPACE_BEGIN
VTKRENDERINGUI_EXPORT LRESULT CALLBACK vtkHandleMessage(HWND, UINT, WPARAM, LPARAM);
VTKRENDERINGUI_EXPORT LRESULT CALLBACK vtkHandleMessage2(
  HWND, UINT, WPARAM, LPARAM, class vtkWin32RenderWindowInteractor*);

VTK_ABI_NAMESPACE_END
#include "vtkActor.h"
#include "vtkCommand.h"
#include "vtkObjectFactory.h"

#ifdef VTK_USE_TDX
#include "vtkTDxWinDevice.h"
#endif

// we hard define the touch structures we use since we cannot take them
// from the header without requiring windows 7 (stupid stupid stupid!)
// so we define them and then do a runtime checks and function pointers
// to avoid a link requirement on Windows 7
#define MOUSEEVENTF_FROMTOUCH 0xFF515700
#define WM_TOUCH 0x0240
#define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)

typedef TOUCHINPUT* PTOUCHINPUT;

//#define HTOUCHINPUT ULONG
#define TOUCHEVENTF_MOVE 0x0001
#define TOUCHEVENTF_DOWN 0x0002
#define TOUCHEVENTF_UP 0x0004
typedef bool(WINAPI* RegisterTouchWindowType)(HWND, ULONG);
typedef bool(WINAPI* GetTouchInputInfoType)(HTOUCHINPUT, UINT, PTOUCHINPUT, int);
typedef bool(WINAPI* CloseTouchInputHandleType)(HTOUCHINPUT);

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkWin32RenderWindowInteractor);

void (*vtkWin32RenderWindowInteractor::ClassExitMethod)(void*) = (void (*)(void*)) nullptr;
void* vtkWin32RenderWindowInteractor::ClassExitMethodArg = nullptr;
void (*vtkWin32RenderWindowInteractor::ClassExitMethodArgDelete)(void*) = (void (*)(void*)) nullptr;

//------------------------------------------------------------------------------
// Construct object so that light follows camera motion.
vtkWin32RenderWindowInteractor::vtkWin32RenderWindowInteractor()
{
  this->WindowId = 0;
  this->InstallMessageProc = 1;
  this->MouseInWindow = 0;
  this->StartedMessageLoop = 0;

#ifdef VTK_USE_TDX
  this->Device = vtkTDxWinDevice::New();
#endif
}

//------------------------------------------------------------------------------
vtkWin32RenderWindowInteractor::~vtkWin32RenderWindowInteractor()
{
  vtkRenderWindow* tmp;

  // we need to release any hold we have on a windows event loop
  if (this->WindowId && this->Enabled && this->InstallMessageProc)
  {
    vtkRenderWindow* ren = this->RenderWindow;
    tmp = (vtkRenderWindow*)(vtkGetWindowLong(this->WindowId, sizeof(vtkLONG)));
    // watch for odd conditions
    if ((tmp != ren) && (ren != nullptr))
    {
      // OK someone else has a hold on our event handler
      // so lets have them handle this stuff
      // well send a USER message to the other
      // event handler so that it can properly
      // call this event handler if required
      CallWindowProc(this->OldProc, this->WindowId, WM_USER + 14, 28, (intptr_t)this->OldProc);
    }
    else
    {
      vtkSetWindowLong(this->WindowId, vtkGWL_WNDPROC, (intptr_t)this->OldProc);
    }
    this->Enabled = 0;
  }
#ifdef VTK_USE_TDX
  this->Device->Delete();
#endif
}

//------------------------------------------------------------------------------
void vtkWin32RenderWindowInteractor::ProcessEvents()
{
  // No need to do anything if this is a 'mapped' interactor
  if (!this->Enabled || !this->InstallMessageProc)
  {
    return;
  }

  MSG msg;
  while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
}

//------------------------------------------------------------------------------
void vtkWin32RenderWindowInteractor::StartEventLoop()
{
  // No need to do anything if this is a 'mapped' interactor
  if (!this->Enabled || !this->InstallMessageProc)
  {
    return;
  }

  this->StartedMessageLoop = 1;

  MSG msg;
  while (GetMessage(&msg, nullptr, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
}

//------------------------------------------------------------------------------
// Begin processing keyboard strokes.
void vtkWin32RenderWindowInteractor::Initialize()
{
  vtkRenderWindow* ren;
  int* size;

  // make sure we have a RenderWindow and camera
  if (!this->RenderWindow)
  {
    vtkErrorMacro(<< "No renderer defined!");
    return;
  }
  if (this->Initialized)
  {
    return;
  }
  this->Initialized = 1;
  // get the info we need from the RenderingWindow
  ren = this->RenderWindow;
  ren->Start();
  ren->End();
  size = ren->GetSize();
  ren->GetPosition();

  this->WindowId = (HWND)(ren->GetGenericWindowId());
  if (this->HardwareWindow)
  {
    this->WindowId = (HWND)(this->HardwareWindow->GetGenericWindowId());
    size = this->HardwareWindow->GetSize();
    vtkSetWindowLong(this->WindowId, sizeof(vtkLONG), (intptr_t)ren);
  }
  else
  {
    this->WindowId = (HWND)(ren->GetGenericWindowId());
  }

  this->Enable();
  this->Size[0] = size[0];
  this->Size[1] = size[1];
}

//------------------------------------------------------------------------------
void vtkWin32RenderWindowInteractor::Enable()
{
  vtkRenderWindow* ren;
  vtkRenderWindow* tmp;
  if (this->Enabled)
  {
    return;
  }
  if (this->InstallMessageProc)
  {
    // add our callback
    ren = this->RenderWindow;
    this->OldProc = (WNDPROC)vtkGetWindowLong(this->WindowId, vtkGWL_WNDPROC);
    tmp = (vtkRenderWindow*)vtkGetWindowLong(this->WindowId, sizeof(vtkLONG));
    // watch for odd conditions
    if (tmp != ren)
    {
      // OK someone else has a hold on our event handler
      // so lets have them handle this stuff
      // well send a USER message to the other
      // event handler so that it can properly
      // call this event handler if required
      CallWindowProc(this->OldProc, this->WindowId, WM_USER + 12, 24, (intptr_t)vtkHandleMessage);
    }
    else
    {
      vtkSetWindowLong(this->WindowId, vtkGWL_WNDPROC, (intptr_t)vtkHandleMessage);
    }

    // Check for windows multitouch support at runtime
    RegisterTouchWindowType RTW = (RegisterTouchWindowType)GetProcAddress(
      GetModuleHandle(TEXT("user32")), "RegisterTouchWindow");
    if (RTW != nullptr)
    {
      RTW(this->WindowId, 0);
    }

#ifdef VTK_USE_TDX
    if (this->UseTDx)
    {
      this->Device->SetInteractor(this);
      this->Device->Initialize();
      this->Device->StartListening();
    }
#endif

    // enable drag and drop events
    DragAcceptFiles(this->WindowId, TRUE);

    // in case the size of the window has changed while we were away
    int* size;
    size = ren->GetSize();
    this->Size[0] = size[0];
    this->Size[1] = size[1];
  }
  this->Enabled = 1;
  this->Modified();
}

//------------------------------------------------------------------------------
void vtkWin32RenderWindowInteractor::Disable()
{
  vtkRenderWindow* tmp;
  if (!this->Enabled)
  {
    return;
  }

  if (this->InstallMessageProc && this->Enabled && this->WindowId)
  {
    // we need to release any hold we have on a windows event loop
    vtkRenderWindow* ren = this->RenderWindow;
    tmp = (vtkRenderWindow*)vtkGetWindowLong(this->WindowId, sizeof(vtkLONG));
    // watch for odd conditions
    if ((tmp != ren) && (ren != nullptr))
    {
      // OK someone else has a hold on our event handler
      // so lets have them handle this stuff
      // well send a USER message to the other
      // event handler so that it can properly
      // call this event handler if required
      CallWindowProc(this->OldProc, this->WindowId, WM_USER + 14, 28, (intptr_t)this->OldProc);
    }
    else
    {
      vtkSetWindowLong(this->WindowId, vtkGWL_WNDPROC, (intptr_t)this->OldProc);
    }
#ifdef VTK_USE_TDX
    if (this->Device->GetInitialized())
    {
      this->Device->Close();
    }
#endif
  }
  this->Enabled = 0;
  this->Modified();
}

//------------------------------------------------------------------------------
void vtkWin32RenderWindowInteractor::TerminateApp(void)
{
  if (this->Done)
  {
    return;
  }

  this->Done = true;

  // Only post a quit message if Start was called...
  //
  if (this->StartedMessageLoop)
  {
    PostQuitMessage(0);
  }
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::InternalCreateTimer(
  int timerId, int vtkNotUsed(timerType), unsigned long duration)
{
  // Win32 always creates repeating timers
  SetTimer(this->WindowId, timerId, duration, nullptr);
  return timerId;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::InternalDestroyTimer(int platformTimerId)
{
  return KillTimer(this->WindowId, platformTimerId);
}

//-------------------------------------------------------------
// Virtual Key Code to Unix KeySym Conversion
//-------------------------------------------------------------

// this ascii code to keysym table is meant to mimic Tk

static const char* AsciiToKeySymTable[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "space", "exclam", "quotedbl", "numbersign", "dollar",
  "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma",
  "minus", "period", "slash", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "colon",
  "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H",
  "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
  "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b",
  "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
  "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "Delete", 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

// this virtual key code to keysym table is meant to mimic Tk

static const char* VKeyCodeToKeySymTable[] = { 0, 0, 0, "Cancel", 0, 0, 0, 0, "BackSpace", "Tab", 0,
  0, "Clear", "Return", 0, 0, "Shift_L", "Control_L", "Alt_L", "Pause", "Caps_Lock", 0, 0, 0, 0, 0,
  0, "Escape", 0, 0, 0, 0, "space", "Prior", "Next", "End", "Home", "Left", "Up", "Right", "Down",
  "Select", 0, "Execute", "Snapshot", "Insert", "Delete", "Help", "0", "1", "2", "3", "4", "5", "6",
  "7", "8", "9", 0, 0, 0, 0, 0, 0, 0, "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l",
  "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "Win_L", "Win_R", "App", 0,
  0, "KP_0", "KP_1", "KP_2", "KP_3", "KP_4", "KP_5", "KP_6", "KP_7", "KP_8", "KP_9", "asterisk",
  "plus", "bar", "minus", "period", "slash", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9",
  "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23",
  "F24", 0, 0, 0, 0, 0, 0, 0, 0, "Num_Lock", "Scroll_Lock", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

//-------------------------------------------------------------
// Event loop handlers
//-------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnMouseMove(HWND hWnd, UINT nFlags, int X, int Y)
{
  if (!this->Enabled)
  {
    return 0;
  }

  // touch events handled by WM_TOUCH
  if ((GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH)
  {
    return 0;
  }

  this->SetEventInformationFlipY(X, Y, nFlags & MK_CONTROL, nFlags & MK_SHIFT);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  if (!this->MouseInWindow && (X >= 0 && X < this->Size[0] && Y >= 0 && Y < this->Size[1]))
  {
    this->InvokeEvent(vtkCommand::EnterEvent, nullptr);
    this->MouseInWindow = 1;
    // request WM_MOUSELEAVE generation
    TRACKMOUSEEVENT tme;
    tme.cbSize = sizeof(TRACKMOUSEEVENT);
    tme.dwFlags = TME_LEAVE;
    tme.hwndTrack = hWnd;
    TrackMouseEvent(&tme);
  }

  return this->InvokeEvent(vtkCommand::MouseMoveEvent, nullptr);
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnNCMouseMove(HWND, UINT nFlags, int X, int Y)
{
  if (!this->Enabled || !this->MouseInWindow)
  {
    return 0;
  }

  const int* pos = this->RenderWindow->GetPosition();
  this->SetEventInformationFlipY(X - pos[0], Y - pos[1], nFlags & MK_CONTROL, nFlags & MK_SHIFT);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  const int ret = this->InvokeEvent(vtkCommand::LeaveEvent, nullptr);
  this->MouseInWindow = 0;
  return ret;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnMouseWheelForward(HWND, UINT nFlags, int X, int Y)
{
  if (!this->Enabled)
  {
    return 0;
  }
  this->SetEventInformationFlipY(X, Y, nFlags & MK_CONTROL, nFlags & MK_SHIFT);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  return this->InvokeEvent(vtkCommand::MouseWheelForwardEvent, nullptr);
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnMouseWheelBackward(HWND, UINT nFlags, int X, int Y)
{
  if (!this->Enabled)
  {
    return 0;
  }
  this->SetEventInformationFlipY(X, Y, nFlags & MK_CONTROL, nFlags & MK_SHIFT);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  return this->InvokeEvent(vtkCommand::MouseWheelBackwardEvent, nullptr);
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnLButtonDown(HWND wnd, UINT nFlags, int X, int Y, int repeat)
{
  if (!this->Enabled)
  {
    return 0;
  }

  // touch events handled by WM_TOUCH
  if ((GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH)
  {
    return 0;
  }

  SetFocus(wnd);
  SetCapture(wnd);
  this->SetEventInformationFlipY(X, Y, nFlags & MK_CONTROL, nFlags & MK_SHIFT, 0, repeat);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  return this->InvokeEvent(vtkCommand::LeftButtonPressEvent, nullptr);
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnLButtonUp(HWND, UINT nFlags, int X, int Y)
{
  if (!this->Enabled)
  {
    return 0;
  }
  // touch events handled by WM_TOUCH
  if ((GetMessageExtraInfo() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH)
  {
    return 0;
  }
  this->SetEventInformationFlipY(X, Y, nFlags & MK_CONTROL, nFlags & MK_SHIFT);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  const int ret = this->InvokeEvent(vtkCommand::LeftButtonReleaseEvent, nullptr);
  ReleaseCapture();
  return ret;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnMButtonDown(HWND wnd, UINT nFlags, int X, int Y, int repeat)
{
  if (!this->Enabled)
  {
    return 0;
  }
  SetFocus(wnd);
  SetCapture(wnd);
  this->SetEventInformationFlipY(X, Y, nFlags & MK_CONTROL, nFlags & MK_SHIFT, 0, repeat);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  return this->InvokeEvent(vtkCommand::MiddleButtonPressEvent, nullptr);
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnMButtonUp(HWND, UINT nFlags, int X, int Y)
{
  if (!this->Enabled)
  {
    return 0;
  }
  this->SetEventInformationFlipY(X, Y, nFlags & MK_CONTROL, nFlags & MK_SHIFT);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  const int ret = this->InvokeEvent(vtkCommand::MiddleButtonReleaseEvent, nullptr);
  ReleaseCapture();
  return ret;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnRButtonDown(HWND wnd, UINT nFlags, int X, int Y, int repeat)
{
  if (!this->Enabled)
  {
    return 0;
  }
  SetFocus(wnd);
  SetCapture(wnd);
  this->SetEventInformationFlipY(X, Y, nFlags & MK_CONTROL, nFlags & MK_SHIFT, 0, repeat);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  return this->InvokeEvent(vtkCommand::RightButtonPressEvent, nullptr);
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnRButtonUp(HWND, UINT nFlags, int X, int Y)
{
  if (!this->Enabled)
  {
    return 0;
  }
  this->SetEventInformationFlipY(X, Y, nFlags & MK_CONTROL, nFlags & MK_SHIFT);
  this->SetAltKey(GetKeyState(VK_MENU) & (~1));
  const int ret = this->InvokeEvent(vtkCommand::RightButtonReleaseEvent, nullptr);
  ReleaseCapture();
  return ret;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnSize(HWND, UINT, int X, int Y)
{
  this->UpdateSize(X, Y);
  if (this->Enabled)
  {
    return this->InvokeEvent(vtkCommand::ConfigureEvent, nullptr);
  }
  return 0;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnTimer(HWND, UINT timerId)
{
  if (!this->Enabled)
  {
    return 0;
  }
  int tid = static_cast<int>(timerId);
  const int ret = this->InvokeEvent(vtkCommand::TimerEvent, (void*)&tid);

  // Here we deal with one-shot versus repeating timers
  if (this->IsOneShotTimer(tid))
  {
    KillTimer(this->WindowId, tid); //'cause windows timers are always repeating
  }

  return ret;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnKeyDown(HWND, UINT vCode, UINT nRepCnt, UINT nFlags)
{
  if (!this->Enabled)
  {
    return 0;
  }
  int ctrl = GetKeyState(VK_CONTROL) & (~1);
  int shift = GetKeyState(VK_SHIFT) & (~1);
  int alt = GetKeyState(VK_MENU) & (~1);
  WORD nChar = 0;
  {
#ifndef _WIN32_WCE
    BYTE keyState[256];
    GetKeyboardState(keyState);
    if (ToAscii(vCode, nFlags & 0xff, keyState, &nChar, 0) == 0)
    {
      nChar = 0;
    }
#endif
  }
  const char* keysym = AsciiToKeySymTable[(unsigned char)nChar];
  if (keysym == 0)
  {
    keysym = VKeyCodeToKeySymTable[(unsigned char)vCode];
  }
  if (keysym == 0)
  {
    keysym = "None";
  }
  this->SetKeyEventInformation(ctrl, shift, nChar, nRepCnt, keysym);
  this->SetAltKey(alt);
  return this->InvokeEvent(vtkCommand::KeyPressEvent, nullptr);
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnKeyUp(HWND, UINT vCode, UINT nRepCnt, UINT nFlags)
{
  if (!this->Enabled)
  {
    return 0;
  }
  int ctrl = GetKeyState(VK_CONTROL) & (~1);
  int shift = GetKeyState(VK_SHIFT) & (~1);
  int alt = GetKeyState(VK_MENU) & (~1);
  WORD nChar = 0;
  {
    BYTE keyState[256];
#ifndef _WIN32_WCE
    GetKeyboardState(keyState);
    if (ToAscii(vCode, nFlags & 0xff, keyState, &nChar, 0) == 0)
    {
      nChar = 0;
    }
#endif
  }
  const char* keysym = AsciiToKeySymTable[(unsigned char)nChar];
  if (keysym == 0)
  {
    keysym = VKeyCodeToKeySymTable[(unsigned char)vCode];
  }
  if (keysym == 0)
  {
    keysym = "None";
  }
  this->SetKeyEventInformation(ctrl, shift, nChar, nRepCnt, keysym);
  this->SetAltKey(alt);
  return this->InvokeEvent(vtkCommand::KeyReleaseEvent, nullptr);
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnChar(HWND, UINT nChar, UINT nRepCnt, UINT)
{
  if (!this->Enabled)
  {
    return 0;
  }
  int ctrl = GetKeyState(VK_CONTROL) & (~1);
  int shift = GetKeyState(VK_SHIFT) & (~1);
  int alt = GetKeyState(VK_MENU) & (~1);
  this->SetKeyEventInformation(ctrl, shift, nChar, nRepCnt);
  this->SetAltKey(alt);
  return this->InvokeEvent(vtkCommand::CharEvent, nullptr);
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnFocus(HWND, UINT)
{
  if (!this->Enabled)
  {
    return 0;
  }

#ifdef VTK_USE_TDX
  if (this->Device->GetInitialized() && !this->Device->GetIsListening())
  {
    this->Device->StartListening();
    return 1;
  }
#endif

  return 0;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnKillFocus(HWND, UINT)
{
  if (!this->Enabled)
  {
    return 0;
  }
#ifdef VTK_USE_TDX
  if (this->Device->GetInitialized() && this->Device->GetIsListening())
  {
    this->Device->StopListening();
    return 1;
  }
#endif

  return 0;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnTouch(HWND hWnd, UINT wParam, UINT lParam)
{
  if (!this->Enabled)
  {
    return 0;
  }

  int ret(0);
  UINT cInputs = LOWORD(wParam);
  PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs];
  if (pInputs)
  {
    int ctrl = GetKeyState(VK_CONTROL) & (~1);
    int shift = GetKeyState(VK_SHIFT) & (~1);
    this->SetAltKey(GetKeyState(VK_MENU) & (~1));
    GetTouchInputInfoType GTII =
      (GetTouchInputInfoType)GetProcAddress(GetModuleHandle(TEXT("user32")), "GetTouchInputInfo");
    if (GTII((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT)))
    {
      POINT ptInput;
      for (UINT i = 0; i < cInputs; i++)
      {
        TOUCHINPUT ti = pInputs[i];
        int index = this->GetPointerIndexForContact(ti.dwID);
        if (ti.dwID != 0 && index < VTKI_MAX_POINTERS)
        {
          // Do something with your touch input handle
          ptInput.x = TOUCH_COORD_TO_PIXEL(ti.x);
          ptInput.y = TOUCH_COORD_TO_PIXEL(ti.y);
          ScreenToClient(hWnd, &ptInput);
          this->SetEventInformationFlipY(ptInput.x, ptInput.y, ctrl, shift, 0, 0, 0, index);
        }
      }
      bool didUpOrDown = false;
      for (UINT i = 0; i < cInputs; i++)
      {
        TOUCHINPUT ti = pInputs[i];
        int index = this->GetPointerIndexForContact(ti.dwID);
        if (ti.dwID != 0 && index < VTKI_MAX_POINTERS)
        {
          if (ti.dwFlags & TOUCHEVENTF_UP)
          {
            this->SetPointerIndex(index);
            didUpOrDown = true;
            this->InvokeEvent(vtkCommand::LeftButtonReleaseEvent, nullptr);
            this->ClearPointerIndex(index);
          }
          if (ti.dwFlags & TOUCHEVENTF_DOWN)
          {
            this->SetPointerIndex(index);
            didUpOrDown = true;
            this->InvokeEvent(vtkCommand::LeftButtonPressEvent, nullptr);
          }
          this->SetPointerIndex(index);
        }
      }
      if (!didUpOrDown)
      {
        ret = this->InvokeEvent(vtkCommand::MouseMoveEvent, nullptr);
      }
      else
      {
        ret = 1;
      }
    }
    CloseTouchInputHandleType CTIH = (CloseTouchInputHandleType)GetProcAddress(
      GetModuleHandle(TEXT("user32")), "CloseTouchInputHandle");
    CTIH((HTOUCHINPUT)lParam);
    delete[] pInputs;
  }

  return ret;
}

//------------------------------------------------------------------------------
int vtkWin32RenderWindowInteractor::OnDropFiles(HWND, WPARAM wParam)
{
  if (!this->Enabled)
  {
    return 0;
  }

  int ret = 0;

  HDROP hdrop = reinterpret_cast<HDROP>(wParam);

  POINT pt;
  if (DragQueryPoint(hdrop, &pt))
  {
    double location[2];
    location[0] = static_cast<double>(pt.x);
    location[1] = static_cast<double>(this->Size[1] - pt.y - 1); // flip Y

    this->InvokeEvent(vtkCommand::UpdateDropLocationEvent, location);
  }

  UINT cFiles = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);

  if (cFiles > 0)
  {
    vtkNew<vtkStringArray> filePaths;
    filePaths->Allocate(cFiles);

    for (UINT i = 0; i < cFiles; i++)
    {
      wchar_t file[MAX_PATH];
      UINT cch = DragQueryFileW(hdrop, i, file, MAX_PATH);
      if (cch > 0 && cch < MAX_PATH)
      {
        filePaths->InsertNextValue(vtksys::Encoding::ToNarrow(file));
        ret = 1;
      }
    }
    this->InvokeEvent(vtkCommand::DropFilesEvent, filePaths);
  }

  return ret;
}

//------------------------------------------------------------------------------
// This is only called when InstallMessageProc is true
LRESULT CALLBACK vtkHandleMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  LRESULT res = 0;
  vtkRenderWindow* ren;
  vtkWin32RenderWindowInteractor* me = 0;

  ren = (vtkRenderWindow*)vtkGetWindowLong(hWnd, sizeof(vtkLONG));

  if (ren)
  {
    me = (vtkWin32RenderWindowInteractor*)ren->GetInteractor();
  }

  if (me && me->GetReferenceCount() > 0)
  {
    me->Register(me);
    res = vtkHandleMessage2(hWnd, uMsg, wParam, lParam, me);
    me->UnRegister(me);
  }

  return res;
}

#ifndef MAKEPOINTS
#define MAKEPOINTS(l) (*((POINTS FAR*)&(l)))
#endif

//------------------------------------------------------------------------------
LRESULT CALLBACK vtkHandleMessage2(
  HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, vtkWin32RenderWindowInteractor* me)
{
  if ((uMsg == WM_USER + 13) && (wParam == 26))
  {
    // someone is telling us to set our OldProc
    me->OldProc = (WNDPROC)lParam;
    return 1;
  }

  int handled(0);

  switch (uMsg)
  {
    case WM_PAINT:
    {
      const LRESULT ret(CallWindowProc(me->OldProc, hWnd, uMsg, wParam, lParam));
      me->InvokeEvent(vtkCommand::RenderEvent, nullptr);
      return ret;
    }

    case WM_SIZE:
      handled = me->OnSize(hWnd, wParam, LOWORD(lParam), HIWORD(lParam));
      break;

    case WM_LBUTTONDBLCLK:
      handled = me->OnLButtonDown(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y, 1);
      break;

    case WM_LBUTTONDOWN:
      handled = me->OnLButtonDown(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y, 0);
      break;

    case WM_LBUTTONUP:
      handled = me->OnLButtonUp(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y);
      break;

    case WM_MBUTTONDBLCLK:
      handled = me->OnMButtonDown(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y, 1);
      break;

    case WM_MBUTTONDOWN:
      handled = me->OnMButtonDown(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y, 0);
      break;

    case WM_MBUTTONUP:
      handled = me->OnMButtonUp(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y);
      break;

    case WM_RBUTTONDBLCLK:
      handled = me->OnRButtonDown(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y, 1);
      break;

    case WM_RBUTTONDOWN:
      handled = me->OnRButtonDown(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y, 0);
      break;

    case WM_RBUTTONUP:
      handled = me->OnRButtonUp(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y);
      break;

    case WM_MOUSELEAVE:
      me->InvokeEvent(vtkCommand::LeaveEvent, nullptr);
      me->MouseInWindow = 0;
      break;

    case WM_MOUSEMOVE:
      handled = me->OnMouseMove(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y);
      break;

    case WM_MOUSEWHEEL:
    {
      POINT pt;
      pt.x = MAKEPOINTS(lParam).x;
      pt.y = MAKEPOINTS(lParam).y;
      ::ScreenToClient(hWnd, &pt);
      if (GET_WHEEL_DELTA_WPARAM(wParam) > 0)
        handled = me->OnMouseWheelForward(hWnd, wParam, pt.x, pt.y);
      else
        handled = me->OnMouseWheelBackward(hWnd, wParam, pt.x, pt.y);
    }
    break;

#ifdef WM_MCVMOUSEMOVE
    case WM_NCMOUSEMOVE:
      handled = me->OnNCMouseMove(hWnd, wParam, MAKEPOINTS(lParam).x, MAKEPOINTS(lParam).y);
      break;
#endif

    case WM_CLOSE:
      me->ExitCallback();
      break;

    case WM_CHAR:
      handled = me->OnChar(hWnd, wParam, LOWORD(lParam), HIWORD(lParam));
      break;

    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
      handled = me->OnKeyDown(hWnd, wParam, LOWORD(lParam), HIWORD(lParam));
      break;

    case WM_KEYUP:
    case WM_SYSKEYUP:
      handled = me->OnKeyUp(hWnd, wParam, LOWORD(lParam), HIWORD(lParam));
      break;

    case WM_TIMER:
      handled = me->OnTimer(hWnd, wParam);
      break;

    case WM_ACTIVATE:
      if (wParam == WA_INACTIVE)
      {
        handled = me->OnKillFocus(hWnd, wParam);
      }
      else
      {
        handled = me->OnFocus(hWnd, wParam);
      }
      break;

    case WM_SETFOCUS:
      // occurs when SetFocus() is called on the current window
      handled = me->OnFocus(hWnd, wParam);
      break;

    case WM_KILLFOCUS:
      // occurs when the focus was on the current window and SetFocus() is
      // called on another window.
      handled = me->OnKillFocus(hWnd, wParam);
      break;

    case WM_TOUCH:
      handled = me->OnTouch(hWnd, wParam, lParam);
      break;

    case WM_DROPFILES:
      handled = me->OnDropFiles(hWnd, wParam);
      break;

    default:
      break;
  };

  if (0 == handled)
  {
    return CallWindowProc(me->OldProc, hWnd, uMsg, wParam, lParam);
  }

  return 0;
}

//------------------------------------------------------------------------------
// Specify the default function to be called when an interactor needs to exit.
// This callback is overridden by an instance ExitMethod that is defined.
void vtkWin32RenderWindowInteractor::SetClassExitMethod(void (*f)(void*), void* arg)
{
  if (f != vtkWin32RenderWindowInteractor::ClassExitMethod ||
    arg != vtkWin32RenderWindowInteractor::ClassExitMethodArg)
  {
    // delete the current arg if there is a delete method
    if ((vtkWin32RenderWindowInteractor::ClassExitMethodArg) &&
      (vtkWin32RenderWindowInteractor::ClassExitMethodArgDelete))
    {
      (*vtkWin32RenderWindowInteractor::ClassExitMethodArgDelete)(
        vtkWin32RenderWindowInteractor::ClassExitMethodArg);
    }
    vtkWin32RenderWindowInteractor::ClassExitMethod = f;
    vtkWin32RenderWindowInteractor::ClassExitMethodArg = arg;

    // no call to this->Modified() since this is a class member function
  }
}

//------------------------------------------------------------------------------
// Set the arg delete method.  This is used to free user memory.
void vtkWin32RenderWindowInteractor::SetClassExitMethodArgDelete(void (*f)(void*))
{
  if (f != vtkWin32RenderWindowInteractor::ClassExitMethodArgDelete)
  {
    vtkWin32RenderWindowInteractor::ClassExitMethodArgDelete = f;

    // no call to this->Modified() since this is a class member function
  }
}

//------------------------------------------------------------------------------
void vtkWin32RenderWindowInteractor::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "InstallMessageProc: " << this->InstallMessageProc << endl;
  os << indent << "StartedMessageLoop: " << this->StartedMessageLoop << endl;
}

//------------------------------------------------------------------------------
void vtkWin32RenderWindowInteractor::ExitCallback()
{
  if (this->HasObserver(vtkCommand::ExitEvent))
  {
    this->InvokeEvent(vtkCommand::ExitEvent, nullptr);
  }
  else if (this->ClassExitMethod)
  {
    (*this->ClassExitMethod)(this->ClassExitMethodArg);
  }

  this->TerminateApp();
}
VTK_ABI_NAMESPACE_END
