/** @file

AppleEventDxe

Copyright (c) 2018, vit9696

All rights reserved.

This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include <AppleMacEfi.h>

#include <Guid/AppleVariable.h>

#include <Protocol/GraphicsOutput.h>
#include <Protocol/UgaDraw.h>
#include <Protocol/SimplePointer.h>

#include <Library/AppleEventLib.h>
#include <Library/OcAppleEventLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/TimerLib.h>

#include "AppleEventInternal.h"

#include <Library/BaseLib.h>

//
// CHANGE: Apple polls with a frequency of 2 ms, however this is infeasible on
//         most machines. Poll with 10 ms, which matches the keyboard behaviour,
//         and also is the minimum for QEMU.
//
#define MIN_POINTER_POLL_PERIOD  10
#define MAX_POINTER_POLL_PERIOD  80

#define MAX_CLICK_DURATION      148  // 74 for 2 ms
#define MAX_DOUBLE_CLICK_SPEED  748  // 374 for 2 ms

#define MAX_POLL_DURATION  ((MAX_UINT32 / 10000) / MAX_DOUBLE_CLICK_SPEED)

GLOBAL_REMOVE_IF_UNREFERENCED UINT32  mPointerSpeedDiv = 0;
GLOBAL_REMOVE_IF_UNREFERENCED UINT32  mPointerSpeedMul = 0;

STATIC UINT16  mMaximumDoubleClickSpeed = 0;
STATIC UINT16  mMaximumClickDuration    = 0;

// MINIMAL_MOVEMENT
#define MINIMAL_MOVEMENT  5

// POINTER_BUTTON_INFORMATION
typedef struct {
  APPLE_EVENT_TYPE    EventType;
  UINTN               ButtonTicksHold;
  UINTN               ButtonTicksSinceClick;
  UINTN               PreviousClickEventType;
  BOOLEAN             PreviousButton;
  BOOLEAN             CurrentButton;
  DIMENSION           MouseDownPosition;
  DIMENSION           ClickPosition;
} POINTER_BUTTON_INFORMATION;

// SIMPLE_POINTER_INSTANCE
typedef struct {
  EFI_HANDLE                     Handle;     ///<
  EFI_SIMPLE_POINTER_PROTOCOL    *Interface; ///<
  BOOLEAN                        Installed;  ///<
} SIMPLE_POINTER_INSTANCE;

// mSimplePointerInstallNotifyEvent
STATIC EFI_EVENT  mSimplePointerInstallNotifyEvent = NULL;

// mSimplePointerInstallNotifyRegistration
STATIC VOID  *mSimplePointerInstallNotifyRegistration = NULL;

// mSimplePointerInstances
STATIC SIMPLE_POINTER_INSTANCE  *mPointerProtocols = NULL;

// mNoSimplePointerInstances
STATIC UINTN  mNumberOfPointerProtocols = 0;

// mSimplePointerPollEvent
STATIC EFI_EVENT  mSimplePointerPollEvent = NULL;

// mSimplePointerPollTime
STATIC UINT32  mSimplePointerPollTime    = 0;
STATIC UINT32  mSimplePointerMinPollTime = 0;
STATIC UINT32  mSimplePointerMaxPollTime = 0;

STATIC UINT32  mSimplePointerPollMask = POINTER_POLL_ALL_MASK;

// mUiScale
STATIC UINT8  mUiScale = 1;

// mLeftButtonInfo
STATIC POINTER_BUTTON_INFORMATION  mLeftButtonInfo = {
  APPLE_EVENT_TYPE_LEFT_BUTTON,
  0,
  0,
  0,
  FALSE,
  FALSE,
  { 0,                         0  },
  { 0,                         0  }
};

// mRightButtonInfo
STATIC POINTER_BUTTON_INFORMATION  mRightButtonInfo = {
  APPLE_EVENT_TYPE_RIGHT_BUTTON,
  0,
  0,
  0,
  FALSE,
  FALSE,
  { 0,                          0  },
  { 0,                          0  }
};

// mCursorPosition
STATIC DIMENSION  mCursorPosition;

// mMouseMoved
STATIC BOOLEAN  mMouseMoved;

// mScreenResolution
STATIC DIMENSION  mResolution = { 800, 600 };

STATIC UINT64  mMaxPointerResolutionX = 1;
STATIC UINT64  mMaxPointerResolutionY = 1;

STATIC UINT64  mPointerRawX;
STATIC UINT64  mPointerRawY;

STATIC UINT32     mDwellClickTimeout;
STATIC UINT32     mDwellDoubleClickTimeout;
STATIC UINT32     mDwellClickRadiusSqr;
STATIC DIMENSION  mDwellPosition;
STATIC UINT32     mDwellClickTime;

VOID
InternalInitializePointerUiScale (
  VOID
  )
{
  EFI_STATUS  Status;
  UINTN       DataSize;

  DataSize = sizeof (mUiScale);
  Status   = gRT->GetVariable (
                    APPLE_UI_SCALE_VARIABLE_NAME,
                    &gAppleVendorVariableGuid,
                    NULL,
                    &DataSize,
                    (VOID *)&mUiScale
                    );
  if (EFI_ERROR (Status) || (mUiScale != 2)) {
    mUiScale = 1;
  }
}

VOID
InternalSetPointerPolling (
  IN UINT32  PointerPollMin,
  IN UINT32  PointerPollMax,
  IN UINT32  PointerPollMask
  )
{
  if (PointerPollMin == POINTER_POLL_DEFAULT) {
    PointerPollMin = MIN_POINTER_POLL_PERIOD;
  } else if (PointerPollMin > MAX_POLL_DURATION) {
    PointerPollMin = MAX_POLL_DURATION;
  }

  mSimplePointerMinPollTime = PointerPollMin * 10000;

  mMaximumClickDuration    = (UINT16)(MAX_CLICK_DURATION / PointerPollMin);
  mMaximumDoubleClickSpeed = (UINT16)(MAX_DOUBLE_CLICK_SPEED / PointerPollMin);

  if (PointerPollMax == POINTER_POLL_DEFAULT) {
    PointerPollMax = MAX_POINTER_POLL_PERIOD;
  } else if (PointerPollMax > MAX_POLL_DURATION) {
    PointerPollMax = MAX_POLL_DURATION;
  }

  mSimplePointerMaxPollTime = PointerPollMax * 10000;

  mSimplePointerPollMask = PointerPollMask;
}

VOID
InternalSetPointerSpeed (
  IN UINT16  PointerSpeedDiv,
  IN UINT16  PointerSpeedMul
  )
{
  if (PointerSpeedDiv != 0) {
    mPointerSpeedDiv = PointerSpeedDiv;
  } else {
    DEBUG ((
      DEBUG_WARN,
      "OCAE: Illegal PointerSpeedDiv value 0, using 1\n",
      mPointerSpeedDiv
      ));
    mPointerSpeedDiv = 1;
  }

  mPointerSpeedMul = PointerSpeedMul;
}

VOID
InternalSetDwellClicking (
  IN UINT16  ClickTimeout,
  IN UINT16  DoubleClickTimeout,
  IN UINT16  Radius
  )
{
  mDwellClickTimeout       = (UINT32)ClickTimeout * 10000;
  mDwellDoubleClickTimeout = (UINT32)DoubleClickTimeout * 10000;
  mDwellClickRadiusSqr     = ((UINT32)Radius * Radius) * (mUiScale * mUiScale);
}

// InternalRegisterSimplePointerInterface
STATIC
VOID
InternalRegisterSimplePointerInterface (
  IN EFI_HANDLE                   Handle,
  IN EFI_SIMPLE_POINTER_PROTOCOL  *SimplePointer
  )
{
  SIMPLE_POINTER_INSTANCE  *Instance;
  UINTN                    Index;

  DEBUG ((DEBUG_VERBOSE, "InternalRegisterSimplePointerInterface\n"));

  Instance = AllocateZeroPool (
               (mNumberOfPointerProtocols + 1) * sizeof (*Instance)
               );

  if (Instance != NULL) {
    //
    // Apple did not check against NULL here and below as of boot.efi
    //
    if (mPointerProtocols != NULL) {
      CopyMem (
        Instance,
        mPointerProtocols,
        (mNumberOfPointerProtocols * sizeof (*Instance))
        );
    }

    Index = mNumberOfPointerProtocols;

    ++mNumberOfPointerProtocols;

    Instance[Index].Handle    = Handle;
    Instance[Index].Interface = SimplePointer;
    Instance[Index].Installed = TRUE;

    if (mPointerProtocols != NULL) {
      FreePool ((VOID *)mPointerProtocols);
    }

    mPointerProtocols = Instance;

    if (SimplePointer->Mode->ResolutionX > mMaxPointerResolutionX) {
      mPointerRawX = MultU64x64 (mPointerRawX, mMaxPointerResolutionX);
      mPointerRawX = DivU64x64Remainder (
                       mPointerRawX,
                       SimplePointer->Mode->ResolutionX,
                       NULL
                       );
      mMaxPointerResolutionX = SimplePointer->Mode->ResolutionX;
    }

    if (SimplePointer->Mode->ResolutionY > mMaxPointerResolutionY) {
      mPointerRawY = MultU64x64 (mPointerRawY, mMaxPointerResolutionY);
      mPointerRawY = DivU64x64Remainder (
                       mPointerRawY,
                       SimplePointer->Mode->ResolutionY,
                       NULL
                       );
      mMaxPointerResolutionY = SimplePointer->Mode->ResolutionY;
    }
  }
}

// EventSimplePointerDesctructor
VOID
EventSimplePointerDesctructor (
  VOID
  )
{
  DEBUG ((DEBUG_VERBOSE, "EventSimplePointerDesctructor\n"));

  if (mPointerProtocols != NULL) {
    FreePool ((VOID *)mPointerProtocols);
    //
    // Apple did not null the pointer here (not an error)
    //
    mPointerProtocols = NULL;
  }
}

// InternalRemoveUninstalledInstances
STATIC
VOID
InternalRemoveUninstalledInstances (
  IN OUT SIMPLE_POINTER_INSTANCE  **InstancesPtr,
  IN     UINTN                    *NumberOfInstances,
  IN     EFI_GUID                 *Protocol
  )
{
  EFI_STATUS               Status;
  UINTN                    NumberHandles;
  EFI_HANDLE               *Buffer;
  SIMPLE_POINTER_INSTANCE  *Instance;
  UINTN                    Index;
  UINTN                    Index2;
  SIMPLE_POINTER_INSTANCE  *OrgInstances;
  SIMPLE_POINTER_INSTANCE  *NewInstances;
  UINTN                    NumberOfMatches;

  DEBUG ((DEBUG_VERBOSE, "InternalRemoveUninstalledInstances\n"));

  OrgInstances = *InstancesPtr;

  Status = gBS->LocateHandleBuffer (
                  ByProtocol,
                  Protocol,
                  NULL,
                  &NumberHandles,
                  &Buffer
                  );

  if (!EFI_ERROR (Status)) {
    if (*NumberOfInstances > 0) {
      NumberOfMatches = 0;

      for (Index = 0; Index < *NumberOfInstances; ++Index) {
        Instance = &OrgInstances[Index];

        if (Instance->Installed) {
          for (Index2 = 0; Index2 < NumberHandles; ++Index2) {
            if (Instance->Handle == Buffer[Index2]) {
              ++NumberOfMatches;
              break;
            }
          }

          if (NumberHandles == Index2) {
            Instance->Installed = FALSE;
          }
        }
      }

      if (NumberOfMatches != *NumberOfInstances) {
        //
        // Apple did not check for NumberOfMatches being > 0.
        //
        NewInstances = NULL;
        if (NumberOfMatches > 0) {
          NewInstances = AllocateZeroPool (
                           NumberOfMatches * sizeof (*NewInstances)
                           );
        }

        if (NewInstances != NULL) {
          Index2 = 0;
          for (Index = 0; Index < *NumberOfInstances; ++Index) {
            Instance = &OrgInstances[Index];
            if (Instance->Installed) {
              CopyMem (
                (VOID *)&NewInstances[Index2],
                (VOID *)Instance,
                sizeof (*Instance)
                );

              ++Index2;
            }
          }

          FreePool (OrgInstances);

          *InstancesPtr      = NewInstances;
          *NumberOfInstances = NumberOfMatches;
        }
      }
    }

    FreePool (Buffer);
  } else {
    if (OrgInstances != NULL) {
      FreePool (OrgInstances);
    }

    *InstancesPtr      = NULL;
    *NumberOfInstances = 0;
  }
}

// InternalSimplePointerInstallNotifyFunction
STATIC
VOID
EFIAPI
InternalSimplePointerInstallNotifyFunction (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  EFI_STATUS                   Status;
  UINTN                        NumberHandles;
  EFI_HANDLE                   *Buffer;
  UINTN                        Index;
  EFI_SIMPLE_POINTER_PROTOCOL  *SimplePointer;

  DEBUG ((DEBUG_VERBOSE, "InternalSimplePointerInstallNotifyFunction\n"));

  if (Event != NULL) {
    Status = gBS->LocateHandleBuffer (
                    ByRegisterNotify,
                    NULL,
                    mSimplePointerInstallNotifyRegistration,
                    &NumberHandles,
                    &Buffer
                    );
  } else {
    Status = gBS->LocateHandleBuffer (
                    ByProtocol,
                    &gEfiSimplePointerProtocolGuid,
                    NULL,
                    &NumberHandles,
                    &Buffer
                    );
  }

  if (!EFI_ERROR (Status)) {
    for (Index = 0; Index < NumberHandles; ++Index) {
      Status = gBS->HandleProtocol (
                      Buffer[Index],
                      &gEfiSimplePointerProtocolGuid,
                      (VOID **)&SimplePointer
                      );

      if (!EFI_ERROR (Status)) {
        InternalRegisterSimplePointerInterface (Buffer[Index], SimplePointer);
      }
    }

    FreePool ((VOID *)Buffer);
  }
}

// EventCreateSimplePointerInstallNotifyEvent
EFI_STATUS
EventCreateSimplePointerInstallNotifyEvent (
  VOID
  )
{
  EFI_STATUS  Status;

  DEBUG ((DEBUG_VERBOSE, "EventCreateSimplePointerInstallNotifyEvent\n"));

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  InternalSimplePointerInstallNotifyFunction,
                  NULL,
                  &mSimplePointerInstallNotifyEvent
                  );

  if (!EFI_ERROR (Status)) {
    Status = gBS->RegisterProtocolNotify (
                    &gEfiSimplePointerProtocolGuid,
                    mSimplePointerInstallNotifyEvent,
                    &mSimplePointerInstallNotifyRegistration
                    );

    if (EFI_ERROR (Status)) {
      gBS->CloseEvent (mSimplePointerInstallNotifyEvent);

      mSimplePointerInstallNotifyEvent = NULL;
    } else {
      InternalSimplePointerInstallNotifyFunction (NULL, NULL);
    }
  }

  return Status;
}

// EventCreateSimplePointerInstallNotifyEvent
VOID
EventCloseSimplePointerInstallNotifyEvent (
  VOID
  )
{
  DEBUG ((DEBUG_VERBOSE, "EventCloseSimplePointerInstallNotifyEvent\n"));

  if (mSimplePointerInstallNotifyEvent != NULL) {
    gBS->CloseEvent (mSimplePointerInstallNotifyEvent);
  }
}

// InternalGetScreenResolution
STATIC
VOID
InternalGetScreenResolution (
  VOID
  )
{
  EFI_STATUS                            Status;
  EFI_GRAPHICS_OUTPUT_PROTOCOL          *GraphicsOutput;
  EFI_UGA_DRAW_PROTOCOL                 *UgaDraw;
  EFI_GRAPHICS_OUTPUT_MODE_INFORMATION  *Info;
  UINT32                                HorizontalResolution;
  UINT32                                VerticalResolution;
  UINT32                                ColorDepth;
  UINT32                                RefreshRate;

  DEBUG ((DEBUG_VERBOSE, "InternalGetScreenResolution\n"));

  //
  // CHANGE: Do not cache screen resolution to account for changes.
  //

  HorizontalResolution = 0;
  VerticalResolution   = 0;

  Status = gBS->HandleProtocol (
                  gST->ConsoleOutHandle,
                  &gEfiGraphicsOutputProtocolGuid,
                  (VOID **)&GraphicsOutput
                  );
  if (!EFI_ERROR (Status)) {
    Info                 = GraphicsOutput->Mode->Info;
    HorizontalResolution = Info->HorizontalResolution;
    VerticalResolution   = Info->VerticalResolution;
  } else if (Status == EFI_UNSUPPORTED) {
    Status = gBS->HandleProtocol (
                    gST->ConsoleOutHandle,
                    &gEfiUgaDrawProtocolGuid,
                    (VOID **)&UgaDraw
                    );
    DEBUG ((
      DEBUG_INFO,
      "OCAE: Failed to handle GOP, discovering UGA - %r\n",
      Status
      ));

    if (!EFI_ERROR (Status)) {
      Status = UgaDraw->GetMode (
                          UgaDraw,
                          &HorizontalResolution,
                          &VerticalResolution,
                          &ColorDepth,
                          &RefreshRate
                          );
    }
  }

  if (!EFI_ERROR (Status)) {
    if ((HorizontalResolution > 0) && (VerticalResolution > 0)) {
      mResolution.Horizontal = (INT32)HorizontalResolution;
      mResolution.Vertical   = (INT32)VerticalResolution;

      if (mCursorPosition.Horizontal >= mResolution.Horizontal) {
        mCursorPosition.Horizontal = mResolution.Horizontal - 1;
      }

      if (mCursorPosition.Vertical >= mResolution.Vertical) {
        mCursorPosition.Vertical = mResolution.Vertical - 1;
      }

      DEBUG ((
        DEBUG_INFO,
        "OCAE: Set screen resolution to %dx%d - %r\n",
        mResolution.Horizontal,
        mResolution.Vertical,
        Status
        ));
    } else {
      DEBUG ((DEBUG_INFO, "OCAE: Screen resolution has 0-dimension\n"));
    }
  } else {
    DEBUG ((
      DEBUG_INFO,
      "OCAE: Failed to get screen resolution - %r\n",
      Status
      ));
  }
}

// InternalGetUiScaleData
STATIC
INT64
InternalGetUiScaleData (
  IN INT64  Movement
  )
{
  INT64  AbsoluteValue;
  INTN   Value;
  INT64  Factor;

  DEBUG ((DEBUG_VERBOSE, "InternalGetUiScaleData\n"));

  AbsoluteValue = ABS (Movement);
  Value         = HighBitSet64 ((UINT64)(AbsoluteValue));
  Factor        = 5;

  if (Value <= 3) {
    Factor = (HighBitSet64 ((UINT64)(AbsoluteValue) + 1));
  }

  return (INT64)(MultS64x64 (
                   (INT64)(INT32)mUiScale,
                   MultS64x64 (Movement, Factor)
                   ));
}

// InternalCreatePointerEventQueueInformation
STATIC
APPLE_EVENT_INFORMATION *
InternalCreatePointerEventQueueInformation (
  IN APPLE_EVENT_TYPE    EventType,
  IN APPLE_MODIFIER_MAP  Modifiers
  )
{
  UINT32            FinalEventType;
  APPLE_EVENT_DATA  EventData;

  DEBUG ((DEBUG_VERBOSE, "InternalCreatePointerEventQueueInformation\n"));

  FinalEventType = APPLE_EVENT_TYPE_MOUSE_MOVED;

  if ((EventType & APPLE_EVENT_TYPE_MOUSE_MOVED) == 0) {
    FinalEventType = APPLE_ALL_MOUSE_EVENTS;

    if ((EventType & APPLE_CLICK_MOUSE_EVENTS) != 0) {
      FinalEventType = APPLE_CLICK_MOUSE_EVENTS;
    }
  }

  EventData.PointerEventType = EventType;

  return EventCreateAppleEventQueueInfo (
           EventData,
           FinalEventType,
           &mCursorPosition,
           Modifiers
           );
}

// InternalHandleButtonInteraction
STATIC
VOID
InternalHandleButtonInteraction (
  IN     EFI_STATUS                  PointerStatus,
  IN OUT POINTER_BUTTON_INFORMATION  *Pointer,
  IN     APPLE_MODIFIER_MAP          Modifiers
  )
{
  APPLE_EVENT_INFORMATION  *Information;
  INT32                    HorizontalMovement;
  INT32                    VerticalMovement;
  APPLE_EVENT_TYPE         EventType;

  DEBUG ((DEBUG_VERBOSE, "InternalHandleButtonInteraction\n"));

  if (!EFI_ERROR (PointerStatus)) {
    if (!Pointer->PreviousButton) {
      if (Pointer->CurrentButton) {
        Pointer->ButtonTicksHold   = 0;
        Pointer->MouseDownPosition = mCursorPosition;

        Information = InternalCreatePointerEventQueueInformation (
                        (Pointer->EventType | APPLE_EVENT_TYPE_MOUSE_DOWN),
                        Modifiers
                        );

        if (Information != NULL) {
          EventAddEventToQueue (Information);
        }
      }
    } else if (!Pointer->CurrentButton) {
      Information = InternalCreatePointerEventQueueInformation (
                      (Pointer->EventType | APPLE_EVENT_TYPE_MOUSE_UP),
                      Modifiers
                      );

      if (Information != NULL) {
        EventAddEventToQueue (Information);
      }

      if (Pointer->ButtonTicksHold <= mMaximumClickDuration) {
        HorizontalMovement = ABS (Pointer->MouseDownPosition.Horizontal - mCursorPosition.Horizontal);
        VerticalMovement   = ABS (Pointer->MouseDownPosition.Vertical - mCursorPosition.Vertical);
        //
        // CHANGE: Apple did not scale by UIScale.
        //
        if (  (HorizontalMovement <= mUiScale * MINIMAL_MOVEMENT)
           && (VerticalMovement <= mUiScale * MINIMAL_MOVEMENT))
        {
          EventType = APPLE_EVENT_TYPE_MOUSE_CLICK;

          if (  (Pointer->PreviousClickEventType == APPLE_EVENT_TYPE_MOUSE_CLICK)
             && (Pointer->ButtonTicksSinceClick <= mMaximumDoubleClickSpeed))
          {
            HorizontalMovement = ABS (Pointer->ClickPosition.Horizontal - mCursorPosition.Horizontal);
            VerticalMovement   = ABS (Pointer->ClickPosition.Vertical - mCursorPosition.Vertical);

            if (  (HorizontalMovement <= mUiScale * MINIMAL_MOVEMENT)
               && (VerticalMovement <= mUiScale * MINIMAL_MOVEMENT))
            {
              EventType = APPLE_EVENT_TYPE_MOUSE_DOUBLE_CLICK;
            }
          }

          Information = InternalCreatePointerEventQueueInformation (
                          (Pointer->EventType | EventType),
                          Modifiers
                          );

          if (Information != NULL) {
            EventAddEventToQueue (Information);
          }

          if (Pointer->PreviousClickEventType == APPLE_EVENT_TYPE_MOUSE_DOUBLE_CLICK) {
            EventType = ((Pointer->ButtonTicksSinceClick <= mMaximumDoubleClickSpeed)
                            ? APPLE_EVENT_TYPE_MOUSE_DOUBLE_CLICK
                            : APPLE_EVENT_TYPE_MOUSE_CLICK);
          }

          Pointer->PreviousClickEventType = EventType;
          Pointer->ClickPosition          = mCursorPosition;
          Pointer->ButtonTicksSinceClick  = 0;
        }
      }
    }

    Pointer->PreviousButton = Pointer->CurrentButton;
  }

  if (Pointer->PreviousButton && Pointer->CurrentButton) {
    ++Pointer->ButtonTicksHold;
  }

  ++Pointer->ButtonTicksSinceClick;
}

STATIC
VOID
InternalResetDwellClicking (
  VOID
  )
{
  mDwellClickTime = 0;
  CopyMem (
    &mDwellPosition,
    &mCursorPosition,
    sizeof (mDwellPosition)
    );
}

STATIC
VOID
InternalQueueDwellClick (
  IN APPLE_EVENT_TYPE    EventType,
  IN APPLE_MODIFIER_MAP  Modifiers
  )
{
  APPLE_EVENT_INFORMATION  *Information;

  Information = InternalCreatePointerEventQueueInformation (
                  APPLE_EVENT_TYPE_LEFT_BUTTON | APPLE_EVENT_TYPE_MOUSE_DOWN,
                  Modifiers
                  );
  if (Information != NULL) {
    EventAddEventToQueue (Information);
  }

  Information = InternalCreatePointerEventQueueInformation (
                  APPLE_EVENT_TYPE_LEFT_BUTTON | APPLE_EVENT_TYPE_MOUSE_UP,
                  Modifiers
                  );
  if (Information != NULL) {
    EventAddEventToQueue (Information);
  }

  Information = InternalCreatePointerEventQueueInformation (
                  APPLE_EVENT_TYPE_LEFT_BUTTON | EventType,
                  Modifiers
                  );
  if (Information != NULL) {
    EventAddEventToQueue (Information);
  }
}

STATIC
VOID
InternalHandleDwellClicking (
  IN APPLE_MODIFIER_MAP  Modifiers
  )
{
  BOOLEAN  ClickDisabled;
  BOOLEAN  DoubleClickDisabled;
  INT32    DistX;
  INT32    DistY;

  ClickDisabled       = mDwellClickTimeout == 0;
  DoubleClickDisabled = mDwellDoubleClickTimeout == 0;
  if (ClickDisabled && DoubleClickDisabled) {
    return;
  }

  DistX = mCursorPosition.Horizontal - mDwellPosition.Horizontal;
  DistY = mCursorPosition.Vertical - mDwellPosition.Vertical;
  if ((UINT32)(DistX * DistX + DistY * DistY) <= mDwellClickRadiusSqr) {
    mDwellClickTime += mSimplePointerPollTime;

    if (!DoubleClickDisabled && (mDwellClickTime >= mDwellDoubleClickTimeout)) {
      InternalQueueDwellClick (APPLE_EVENT_TYPE_MOUSE_DOUBLE_CLICK, Modifiers);
      InternalResetDwellClicking ();
    } else if (!ClickDisabled && (mDwellClickTime >= mDwellClickTimeout)) {
      InternalQueueDwellClick (APPLE_EVENT_TYPE_MOUSE_CLICK, Modifiers);
      if (DoubleClickDisabled) {
        InternalResetDwellClicking ();
      }
    }
  } else {
    InternalResetDwellClicking ();
  }
}

// InternalSimplePointerPollNotifyFunction
STATIC
VOID
EFIAPI
InternalSimplePointerPollNotifyFunction (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  APPLE_MODIFIER_MAP           Modifiers;
  UINTN                        Index;
  SIMPLE_POINTER_INSTANCE      *Instance;
  EFI_SIMPLE_POINTER_PROTOCOL  *SimplePointer;
  EFI_STATUS                   Status;
  EFI_STATUS                   CommonStatus;
  EFI_SIMPLE_POINTER_STATE     State;
  INT64                        UiScaleX;
  INT64                        UiScaleY;
  INT64                        ScaledY;
  INT64                        ScaledX;
  DIMENSION                    NewPosition;
  APPLE_EVENT_INFORMATION      *Information;
  APPLE_EVENT_DATA             EventData;
  UINT64                       StartTime;
  UINT64                       EndTime;
  INT64                        MaxRawPointerX;
  INT64                        MaxRawPointerY;
  UINT32                       ClickTemp;
  UINT32                       DoubleClickTemp;
  UINT64                       SimplePointerPollTime;

  StartTime = GetPerformanceCounter ();

  DEBUG ((DEBUG_VERBOSE, "InternalSimplePointerPollNotifyFunction\n"));

  Modifiers = InternalGetModifierStrokes ();

  InternalRemoveUninstalledInstances (
    &mPointerProtocols,
    &mNumberOfPointerProtocols,
    &gEfiSimplePointerProtocolGuid
    );

  CommonStatus = EFI_UNSUPPORTED;

  if (mNumberOfPointerProtocols > 0) {
    CommonStatus = EFI_NOT_READY;

    for (Index = 0; Index < mNumberOfPointerProtocols; ++Index) {
      if (  (mSimplePointerPollMask != POINTER_POLL_ALL_MASK)
         && ((Index >= 32) || (((1U << Index) & mSimplePointerPollMask) == 0)))
      {
        continue;
      }

      Instance      = &mPointerProtocols[Index];
      SimplePointer = Instance->Interface;
      Status        = SimplePointer->GetState (SimplePointer, &State);

      if (!EFI_ERROR (Status)) {
        //
        // CHANGE: Apple scaled the deltas and due to rounding errors, this is
        //         unacceptable. Changed to scale all pointer deltas to the
        //         maximum pointer resolution and scaling down to display
        //         coordinates always based on the accurate raw input value.
        //

        UiScaleX = InternalGetUiScaleData ((INT64)State.RelativeMovementX);
        UiScaleX = MultS64x64 (UiScaleX, (INT64)mMaxPointerResolutionX);
        UiScaleX = MultS64x64 (UiScaleX, mPointerSpeedMul);
        UiScaleX = DivS64x64Remainder (UiScaleX, mPointerSpeedDiv, NULL);

        UiScaleY = InternalGetUiScaleData ((INT64)State.RelativeMovementY);
        UiScaleY = MultS64x64 (UiScaleY, (INT64)mMaxPointerResolutionY);
        UiScaleY = MultS64x64 (UiScaleY, mPointerSpeedMul);
        UiScaleY = DivS64x64Remainder (UiScaleY, mPointerSpeedDiv, NULL);

        if (SimplePointer->Mode->ResolutionX > 0) {
          UiScaleX = DivS64x64Remainder (
                       UiScaleX,
                       (INT64)SimplePointer->Mode->ResolutionX,
                       NULL
                       );
        }

        if (SimplePointer->Mode->ResolutionY > 0) {
          UiScaleY = DivS64x64Remainder (
                       UiScaleY,
                       (INT64)SimplePointer->Mode->ResolutionY,
                       NULL
                       );
        }

        //
        // CHANGE: Fix maximum coordinates.
        //
        ScaledX        = mPointerRawX + UiScaleX;
        MaxRawPointerX = MultU64x64 (
                           mResolution.Horizontal - 1,
                           mMaxPointerResolutionX
                           );
        if (ScaledX > MaxRawPointerX) {
          ScaledX = MaxRawPointerX;
        } else if (ScaledX < 0) {
          ScaledX = 0;
        }

        mPointerRawX = (UINT64)ScaledX;

        ScaledY        = mPointerRawY + UiScaleY;
        MaxRawPointerY = MultS64x64 (
                           mResolution.Vertical - 1,
                           (INT64)mMaxPointerResolutionY
                           );
        if (ScaledY > MaxRawPointerY) {
          ScaledY = MaxRawPointerY;
        } else if (ScaledY < 0) {
          ScaledY = 0;
        }

        mPointerRawY = (UINT64)ScaledY;

        ScaledX = DivU64x64Remainder (
                    ScaledX,
                    mMaxPointerResolutionX,
                    NULL
                    );

        ScaledY = DivS64x64Remainder (
                    ScaledY,
                    (INT64)mMaxPointerResolutionY,
                    NULL
                    );

        NewPosition.Horizontal = (INT32)ScaledX;
        NewPosition.Vertical   = (INT32)ScaledY;

        if (  (mCursorPosition.Horizontal != NewPosition.Horizontal)
           || (mCursorPosition.Vertical != NewPosition.Vertical))
        {
          CopyMem (
            &mCursorPosition,
            &NewPosition,
            sizeof (mCursorPosition)
            );

          mMouseMoved = TRUE;
        }

        mLeftButtonInfo.PreviousButton  = mLeftButtonInfo.CurrentButton;
        mLeftButtonInfo.CurrentButton   = State.LeftButton;
        mRightButtonInfo.PreviousButton = mRightButtonInfo.CurrentButton;
        mRightButtonInfo.CurrentButton  = State.RightButton;
        CommonStatus                    = Status;
      }
    }

    InternalHandleButtonInteraction (CommonStatus, &mLeftButtonInfo, Modifiers);
    InternalHandleButtonInteraction (CommonStatus, &mRightButtonInfo, Modifiers);

    InternalHandleDwellClicking (Modifiers);
  }

  if (EFI_ERROR (CommonStatus)) {
    if (CommonStatus == EFI_UNSUPPORTED) {
      EventLibCancelEvent (mSimplePointerPollEvent);

      mSimplePointerPollEvent = NULL;
    }
  } else if (mMouseMoved != FALSE) {
    mMouseMoved = FALSE;

    EventData.PointerEventType = APPLE_EVENT_TYPE_MOUSE_MOVED;
    Information                = EventCreateAppleEventQueueInfo (
                                   EventData,
                                   APPLE_EVENT_TYPE_MOUSE_MOVED,
                                   &mCursorPosition,
                                   Modifiers
                                   );

    if (Information != NULL) {
      EventAddEventToQueue (Information);
    }
  }

  //
  // This code is here to workaround very slow mouse polling performance on some computers,
  // like most of Dell laptops (one of the worst examples is Dell Latitude 3330 with ~50 ms).
  // Even if we try all the hacks we could make this code approximately only twice faster,
  // which is still far from enough. The event system on these laptops is pretty broken,
  // and even adding gBS->CheckEvent prior to GetState almost does not reduce the time spent.
  //
  if ((mSimplePointerPollEvent != NULL) && (mSimplePointerPollTime < mSimplePointerMaxPollTime)) {
    EndTime = GetPerformanceCounter ();
    if (StartTime > EndTime) {
      EndTime = StartTime;
    }

    EndTime = GetTimeInNanoSecond (EndTime - StartTime);
    // Maximum time allowed in this function is half the interval plus some margin (0.55 * 100ns)
    if (EndTime > mSimplePointerPollTime * 55) {
      ClickTemp       = mSimplePointerPollTime * mMaximumClickDuration;
      DoubleClickTemp = mSimplePointerPollTime * mMaximumDoubleClickSpeed;

      SimplePointerPollTime = DivU64x32 (EndTime, 50);
      if (SimplePointerPollTime <= mSimplePointerMaxPollTime) {
        mSimplePointerPollTime = (UINT32)SimplePointerPollTime;
      } else {
        mSimplePointerPollTime = mSimplePointerMaxPollTime;
      }

      mMaximumClickDuration    = (UINT16)(ClickTemp / mSimplePointerPollTime);
      mMaximumDoubleClickSpeed = (UINT16)(DoubleClickTemp / mSimplePointerPollTime);

      gBS->SetTimer (mSimplePointerPollEvent, TimerPeriodic, mSimplePointerPollTime);
    }
  }
}

// EventCreateSimplePointerPollEvent
EFI_STATUS
EventCreateSimplePointerPollEvent (
  VOID
  )
{
  EFI_STATUS  Status;
  UINTN       Index;

  DEBUG ((DEBUG_VERBOSE, "EventCreateSimplePointerPollEvent\n"));

  InternalRemoveUninstalledInstances (
    &mPointerProtocols,
    &mNumberOfPointerProtocols,
    &gEfiSimplePointerProtocolGuid
    );

  for (Index = 0; Index < mNumberOfPointerProtocols; ++Index) {
    mPointerProtocols[Index].Interface->Reset (
                                          mPointerProtocols[Index].Interface,
                                          FALSE
                                          );
  }

  InternalGetScreenResolution ();
  ZeroMem (&mCursorPosition, sizeof (mCursorPosition));

  mSimplePointerPollTime  = mSimplePointerMinPollTime;
  mSimplePointerPollEvent = EventLibCreateNotifyTimerEvent (
                              InternalSimplePointerPollNotifyFunction,
                              NULL,
                              mSimplePointerPollTime,
                              TRUE
                              );

  Status = EFI_OUT_OF_RESOURCES;

  if (mSimplePointerPollEvent != NULL) {
    mRightButtonInfo.CurrentButton  = FALSE;
    mLeftButtonInfo.CurrentButton   = FALSE;
    mRightButtonInfo.PreviousButton = FALSE;
    mLeftButtonInfo.PreviousButton  = FALSE;

    Status = EFI_SUCCESS;
  }

  return Status;
}

// EventCancelSimplePointerPollEvent
VOID
EventCancelSimplePointerPollEvent (
  VOID
  )
{
  DEBUG ((DEBUG_VERBOSE, "EventCancelSimplePointerPollEvent\n"));

  EventLibCancelEvent (mSimplePointerPollEvent);

  mSimplePointerPollEvent = NULL;
}

// EventSetCursorPositionImpl
EFI_STATUS
EventSetCursorPositionImpl (
  IN DIMENSION  *Position
  )
{
  EFI_STATUS  Status;

  DEBUG ((DEBUG_VERBOSE, "EventSetCursorPositionImpl\n"));

  InternalGetScreenResolution ();

  Status = EFI_INVALID_PARAMETER;

  //
  // Apple did not check for negatives here.
  //

  if (  (Position->Horizontal >= 0) && (Position->Vertical >= 0)
     && (Position->Horizontal < mResolution.Horizontal)
     && (Position->Vertical < mResolution.Vertical))
  {
    mCursorPosition.Horizontal = Position->Horizontal;
    mCursorPosition.Vertical   = Position->Vertical;
    mPointerRawX               = MultS64x64 (
                                   mCursorPosition.Horizontal,
                                   (INT64)mMaxPointerResolutionX
                                   );
    mPointerRawY = MultS64x64 (
                     mCursorPosition.Vertical,
                     (INT64)mMaxPointerResolutionY
                     );
    Status = EFI_SUCCESS;
  }

  return Status;
}
