/* 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/. */

// Based on
// https://cs.chromium.org/chromium/src/device/gamepad/gamepad_standard_mappings.h

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "mozilla/dom/GamepadRemapping.h"

#include <unordered_map>
#include <vector>

#include "mozilla/dom/GamepadPlatformService.h"

namespace mozilla::dom {

float NormalizeTouch(long aValue, long aMin, long aMax) {
  return (2.f * (aValue - aMin) / static_cast<float>(aMax - aMin)) - 1.f;
}

void FetchDpadFromAxis(GamepadHandle aHandle, double dir) {
  bool up = false;
  bool right = false;
  bool down = false;
  bool left = false;

  // Dpad is mapped as a direction on one axis, where -1 is up and it
  // increases clockwise to 1, which is up + left. It's set to a large (> 1.f)
  // number when nothing is depressed, except on start up, sometimes it's 0.0
  // for no data, rather than the large number.
  if (dir != 0.0f) {
    up = (dir >= -1.f && dir < -0.7f) || (dir >= .95f && dir <= 1.f);
    right = dir >= -.75f && dir < -.1f;
    down = dir >= -.2f && dir < .45f;
    left = dir >= .4f && dir <= 1.f;
  }

  RefPtr<GamepadPlatformService> service =
      GamepadPlatformService::GetParentService();
  if (!service) {
    return;
  }

  service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_UP, up);
  service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_RIGHT, right);
  service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_DOWN, down);
  service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_LEFT, left);
}

class DefaultRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return numAxes; }

  virtual uint32_t GetButtonCount() const override { return numButtons; }

  virtual void SetAxisCount(uint32_t aAxisCount) override {
    numAxes = aAxisCount;
  }

  virtual void SetButtonCount(uint32_t aButtonCount) override {
    numButtons = aButtonCount;
  }

  virtual GamepadMappingType GetMappingType() const override {
    return GamepadMappingType::_empty;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    if (GetAxisCount() <= aAxis) {
      NS_WARNING(
          nsPrintfCString("Axis idx '%d' doesn't support in DefaultRemapper().",
                          aAxis)
              .get());
      return;
    }
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }
    service->NewAxisMoveEvent(aHandle, aAxis, aValue);
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in DefaultRemapper().", aButton)
              .get());
      return;
    }
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }
    service->NewButtonEvent(aHandle, aButton, aPressed);
  }

 private:
  uint32_t numAxes;
  uint32_t numButtons;
};

class ADT1Remapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString("Axis idx '%d' doesn't support in ADT1Remapper().",
                            aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString("Button idx '%d' doesn't support in ADT1Remapper().",
                          aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {3, BUTTON_INDEX_TERTIARY},
        {4, BUTTON_INDEX_QUATERNARY},
        {6, BUTTON_INDEX_LEFT_SHOULDER},
        {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {12, BUTTON_INDEX_META},
        {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class TwoAxesEightKeysRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return 0; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT - 1;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_LEFT,
                                AxisNegativeAsButton(aValue));
        service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_RIGHT,
                                AxisPositiveAsButton(aValue));
        break;
      case 1:
        service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_UP,
                                AxisNegativeAsButton(aValue));
        service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_DOWN,
                                AxisPositiveAsButton(aValue));
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in TwoAxesEightKeysRemapper().",
                aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in TwoAxesEightKeysRemapper().",
              aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {0, BUTTON_INDEX_QUATERNARY},
        {2, BUTTON_INDEX_PRIMARY},
        {3, BUTTON_INDEX_TERTIARY}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class StadiaControllerRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return STADIA_BUTTON_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in StadiaControllerRemapper().",
                aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (STADIA_BUTTON_COUNT <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in StadiaControllerRemapper().",
              aButton)
              .get());
      return;
    }

    service->NewButtonEvent(aHandle, aButton, aPressed);
  }

 private:
  enum STADIAButtons {
    STADIA_BUTTON_EXTRA1 = BUTTON_INDEX_COUNT,
    STADIA_BUTTON_EXTRA2,
    STADIA_BUTTON_COUNT
  };
};

class Playstation3Remapper final : public GamepadRemapper {
 public:
  Playstation3Remapper() = default;

  uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  uint32_t GetButtonCount() const override { return BUTTON_INDEX_COUNT; }

  void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                          double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in Playstation3Remapper().",
                aAxis)
                .get());
        break;
    }
  }

  void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                        bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    constexpr std::array buttonMapping = {BUTTON_INDEX_BACK_SELECT,
                                          BUTTON_INDEX_LEFT_THUMBSTICK,
                                          BUTTON_INDEX_RIGHT_THUMBSTICK,
                                          BUTTON_INDEX_START,
                                          BUTTON_INDEX_DPAD_UP,
                                          BUTTON_INDEX_DPAD_RIGHT,
                                          BUTTON_INDEX_DPAD_DOWN,
                                          BUTTON_INDEX_DPAD_LEFT,
                                          BUTTON_INDEX_LEFT_TRIGGER,
                                          BUTTON_INDEX_RIGHT_TRIGGER,
                                          BUTTON_INDEX_LEFT_SHOULDER,
                                          BUTTON_INDEX_RIGHT_SHOULDER,
                                          BUTTON_INDEX_QUATERNARY,
                                          BUTTON_INDEX_SECONDARY,
                                          BUTTON_INDEX_PRIMARY,
                                          BUTTON_INDEX_TERTIARY,
                                          BUTTON_INDEX_META};

    if (buttonMapping.size() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in Playstation3Remapper().",
              aButton)
              .get());
      return;
    }
    service->NewButtonEvent(aHandle, buttonMapping[aButton], aPressed);
  }
};

class Dualshock4Remapper final : public GamepadRemapper {
 public:
  Dualshock4Remapper() {
    mLastTouches.SetLength(TOUCH_EVENT_COUNT);
    mLastTouchId.SetLength(TOUCH_EVENT_COUNT);
  }

  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return DUALSHOCK_BUTTON_COUNT;
  }

  virtual uint32_t GetLightIndicatorCount() const override {
    return LIGHT_INDICATOR_COUNT;
  }

  virtual void GetLightIndicators(
      nsTArray<GamepadLightIndicatorType>& aTypes) const override {
    const uint32_t len = GetLightIndicatorCount();
    aTypes.SetLength(len);
    for (uint32_t i = 0; i < len; ++i) {
      aTypes[i] = GamepadLightIndicatorType::Rgb;
    }
  }

  virtual uint32_t GetTouchEventCount() const override {
    return TOUCH_EVENT_COUNT;
  }

  virtual void GetLightColorReport(
      uint8_t aRed, uint8_t aGreen, uint8_t aBlue,
      std::vector<uint8_t>& aReport) const override {
    const size_t report_length = 32;
    aReport.resize(report_length);
    aReport.assign(report_length, 0);

    aReport[0] = 0x05;  // report ID USB only
    aReport[1] = 0x02;  // LED only
    aReport[6] = aRed;
    aReport[7] = aGreen;
    aReport[8] = aBlue;
  }

  virtual uint32_t GetMaxInputReportLength() const override {
    return MAX_INPUT_LEN;
  }

  static constexpr size_t kMinTouchReportLen = 43;

  virtual void ProcessTouchData(GamepadHandle aHandle, const uint8_t* aInput,
                                size_t aInputLen) override {
    if (aInputLen < kMinTouchReportLen) {
      return;
    }
    nsTArray<GamepadTouchState> touches(TOUCH_EVENT_COUNT);
    touches.SetLength(TOUCH_EVENT_COUNT);
    const uint8_t* rawData = aInput;

    const uint32_t kTouchDimensionX = 1920;
    const uint32_t kTouchDimensionY = 942;
    bool touch0Pressed = (((rawData[35] & 0xff) >> 7) == 0);
    bool touch1Pressed = (((rawData[39] & 0xff) >> 7) == 0);

    if ((touch0Pressed && (rawData[35] & 0xff) < mLastTouchId[0]) ||
        (touch1Pressed && (rawData[39] & 0xff) < mLastTouchId[1])) {
      mTouchIdBase += 128;
    }

    if (touch0Pressed) {
      touches[0].touchId = mTouchIdBase + (rawData[35] & 0x7f);
      touches[0].surfaceId = 0;
      touches[0].position[0] = NormalizeTouch(
          ((rawData[37] & 0xf) << 8) | rawData[36], 0, (kTouchDimensionX - 1));
      touches[0].position[1] =
          NormalizeTouch(rawData[38] << 4 | ((rawData[37] & 0xf0) >> 4), 0,
                         (kTouchDimensionY - 1));
      touches[0].surfaceDimensions[0] = kTouchDimensionX;
      touches[0].surfaceDimensions[1] = kTouchDimensionY;
      touches[0].isSurfaceDimensionsValid = true;
      mLastTouchId[0] = rawData[35] & 0x7f;
    }
    if (touch1Pressed) {
      touches[1].touchId = mTouchIdBase + (rawData[39] & 0x7f);
      touches[1].surfaceId = 0;
      touches[1].position[0] =
          NormalizeTouch((((rawData[41] & 0xf) << 8) | rawData[40]) + 1, 0,
                         (kTouchDimensionX - 1));
      touches[1].position[1] =
          NormalizeTouch(rawData[42] << 4 | ((rawData[41] & 0xf0) >> 4), 0,
                         (kTouchDimensionY - 1));
      touches[1].surfaceDimensions[0] = kTouchDimensionX;
      touches[1].surfaceDimensions[1] = kTouchDimensionY;
      touches[1].isSurfaceDimensionsValid = true;
      mLastTouchId[1] = rawData[39] & 0x7f;
    }

    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    // Avoid to send duplicate untouched events to the gamepad service.
    if ((mLastTouches[0] != touch0Pressed) || touch0Pressed) {
      service->NewMultiTouchEvent(aHandle, 0, touches[0]);
    }
    if ((mLastTouches[1] != touch1Pressed) || touch1Pressed) {
      service->NewMultiTouchEvent(aHandle, 1, touches[1]);
    }
    mLastTouches[0] = touch0Pressed;
    mLastTouches[1] = touch1Pressed;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in Dualshock4Remapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    constexpr std::array<uint32_t, 14> buttonMapping = {
        BUTTON_INDEX_TERTIARY,
        BUTTON_INDEX_PRIMARY,
        BUTTON_INDEX_SECONDARY,
        BUTTON_INDEX_QUATERNARY,
        BUTTON_INDEX_LEFT_SHOULDER,
        BUTTON_INDEX_RIGHT_SHOULDER,
        BUTTON_INDEX_LEFT_TRIGGER,
        BUTTON_INDEX_RIGHT_TRIGGER,
        BUTTON_INDEX_BACK_SELECT,
        BUTTON_INDEX_START,
        BUTTON_INDEX_LEFT_THUMBSTICK,
        BUTTON_INDEX_RIGHT_THUMBSTICK,
        BUTTON_INDEX_META,
        DUALSHOCK_BUTTON_TOUCHPAD};

    if (buttonMapping.size() <= aButton) {
      NS_WARNING(nsPrintfCString(
                     "Button idx '%d' doesn't support in Dualshock4Remapper().",
                     aButton)
                     .get());
      return;
    }

    service->NewButtonEvent(aHandle, buttonMapping[aButton], aPressed);
  }

 private:
  enum Dualshock4Buttons {
    DUALSHOCK_BUTTON_TOUCHPAD = BUTTON_INDEX_COUNT,
    DUALSHOCK_BUTTON_COUNT
  };

  static const uint32_t LIGHT_INDICATOR_COUNT = 1;
  static const uint32_t TOUCH_EVENT_COUNT = 2;
  static const uint32_t MAX_INPUT_LEN = 68;

  nsTArray<unsigned long> mLastTouchId;
  nsTArray<bool> mLastTouches;
  unsigned long mTouchIdBase = 0;
};

class DualSenseRemapper final : public GamepadRemapper {
 public:
  DualSenseRemapper() {
    mLastTouches.SetLength(TOUCH_EVENT_COUNT);
    mLastTouchId.SetLength(TOUCH_EVENT_COUNT);
  }

  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return DUALSENSE_BUTTON_COUNT;
  }

  virtual uint32_t GetLightIndicatorCount() const override {
    return LIGHT_INDICATOR_COUNT;
  }

  virtual void GetLightIndicators(
      nsTArray<GamepadLightIndicatorType>& aTypes) const override {
    const uint32_t len = GetLightIndicatorCount();
    aTypes.SetLength(len);
    for (uint32_t i = 0; i < len; ++i) {
      aTypes[i] = GamepadLightIndicatorType::Rgb;
    }
  }

  virtual uint32_t GetTouchEventCount() const override {
    return TOUCH_EVENT_COUNT;
  }

  virtual void GetLightColorReport(
      uint8_t aRed, uint8_t aGreen, uint8_t aBlue,
      std::vector<uint8_t>& aReport) const override {
    const size_t report_length = 48;
    aReport.resize(report_length);
    aReport.assign(report_length, 0);

    aReport[0] = 0x02;  // Report ID (set controller state)
    aReport[2] = 0x04;  // Enable RGB LED section
    aReport[45] = aRed;
    aReport[46] = aGreen;
    aReport[47] = aBlue;
  }

  virtual uint32_t GetMaxInputReportLength() const override {
    return MAX_INPUT_LEN;
  }

  static constexpr size_t kMinTouchReportLen = 41;

  virtual void ProcessTouchData(GamepadHandle aHandle, const uint8_t* aInput,
                                size_t aInputLen) override {
    if (aInputLen < kMinTouchReportLen) {
      return;
    }

    nsTArray<GamepadTouchState> touches(TOUCH_EVENT_COUNT);
    touches.SetLength(TOUCH_EVENT_COUNT);
    const uint8_t* rawData = aInput;

    const uint32_t kTouchDimensionX = 1920;
    const uint32_t kTouchDimensionY = 1080;
    bool touch0Pressed = (rawData[33] & 0x80) == 0;
    bool touch1Pressed = (rawData[37] & 0x80) == 0;
    uint8_t touch0RawId = rawData[33] & 0x7f;
    uint8_t touch1RawId = rawData[37] & 0x7f;

    if ((touch0Pressed && touch0RawId < mLastTouchId[0]) ||
        (touch1Pressed && touch1RawId < mLastTouchId[1])) {
      mTouchIdBase += 128;
    }

    if (touch0Pressed) {
      touches[0].touchId = mTouchIdBase + touch0RawId;
      touches[0].surfaceId = 0;
      touches[0].position[0] = ((rawData[35] & 0x0f) << 8) | rawData[34];
      touches[0].position[1] = (rawData[36] << 4) | ((rawData[35] & 0xf0) >> 4);
      touches[0].surfaceDimensions[0] = kTouchDimensionX;
      touches[0].surfaceDimensions[1] = kTouchDimensionY;
      touches[0].isSurfaceDimensionsValid = true;
      mLastTouchId[0] = touch0RawId;
    }
    if (touch1Pressed) {
      touches[1].touchId = mTouchIdBase + touch1RawId;
      touches[1].surfaceId = 0;
      touches[1].position[0] = ((rawData[39] & 0x0f) << 8) | rawData[38];
      touches[1].position[1] = (rawData[40] << 4) | ((rawData[39] & 0xf0) >> 4);
      touches[1].surfaceDimensions[0] = kTouchDimensionX;
      touches[1].surfaceDimensions[1] = kTouchDimensionY;
      touches[1].isSurfaceDimensionsValid = true;
      mLastTouchId[1] = touch1RawId;
    }

    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    // Avoid to send duplicate untouched events to the gamepad service.
    if ((mLastTouches[0] != touch0Pressed) || touch0Pressed) {
      service->NewMultiTouchEvent(aHandle, 0, touches[0]);
    }
    if ((mLastTouches[1] != touch1Pressed) || touch1Pressed) {
      service->NewMultiTouchEvent(aHandle, 1, touches[1]);
    }
    mLastTouches[0] = touch0Pressed;
    mLastTouches[1] = touch1Pressed;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in DualSenseRemapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    const std::vector<uint32_t> buttonMapping = {BUTTON_INDEX_TERTIARY,
                                                 BUTTON_INDEX_PRIMARY,
                                                 BUTTON_INDEX_SECONDARY,
                                                 BUTTON_INDEX_QUATERNARY,
                                                 BUTTON_INDEX_LEFT_SHOULDER,
                                                 BUTTON_INDEX_RIGHT_SHOULDER,
                                                 BUTTON_INDEX_LEFT_TRIGGER,
                                                 BUTTON_INDEX_RIGHT_TRIGGER,
                                                 BUTTON_INDEX_BACK_SELECT,
                                                 BUTTON_INDEX_START,
                                                 BUTTON_INDEX_LEFT_THUMBSTICK,
                                                 BUTTON_INDEX_RIGHT_THUMBSTICK,
                                                 BUTTON_INDEX_META,
                                                 DUALSENSE_BUTTON_TOUCHPAD};

    if (buttonMapping.size() <= aButton) {
      NS_WARNING(nsPrintfCString(
                     "Button idx '%d' doesn't support in DualSenseRemapper().",
                     aButton)
                     .get());
      return;
    }

    service->NewButtonEvent(aHandle, buttonMapping[aButton], aPressed);
  }

 private:
  enum DualSenseButtons {
    DUALSENSE_BUTTON_TOUCHPAD = BUTTON_INDEX_COUNT,
    DUALSENSE_BUTTON_COUNT
  };

  static const uint32_t LIGHT_INDICATOR_COUNT = 1;
  static const uint32_t TOUCH_EVENT_COUNT = 2;
  static const uint32_t MAX_INPUT_LEN = 64;

  nsTArray<unsigned long> mLastTouchId;
  nsTArray<bool> mLastTouches;
  unsigned long mTouchIdBase = 0;
};

class Xbox360Remapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 3:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 4:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 5: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in Xbox360Remapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in Xbox360Remapper().", aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {6, BUTTON_INDEX_LEFT_THUMBSTICK}, {7, BUTTON_INDEX_RIGHT_THUMBSTICK},
        {8, BUTTON_INDEX_START},           {9, BUTTON_INDEX_BACK_SELECT},
        {10, BUTTON_INDEX_META},           {11, BUTTON_INDEX_DPAD_UP},
        {12, BUTTON_INDEX_DPAD_DOWN},      {13, BUTTON_INDEX_DPAD_LEFT},
        {14, BUTTON_INDEX_DPAD_RIGHT}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class XboxOneSRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 3:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 4:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 5: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in XboxOneSRemapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in XboxOneSRemapper().", aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {6, BUTTON_INDEX_BACK_SELECT},
        {7, BUTTON_INDEX_START},
        {8, BUTTON_INDEX_LEFT_THUMBSTICK},
        {9, BUTTON_INDEX_RIGHT_THUMBSTICK},
        {10, BUTTON_INDEX_META}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class XboxOneS2016FirmwareRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(nsPrintfCString("Axis idx '%d' doesn't support in "
                                   "XboxOneS2016FirmwareRemapper().",
                                   aAxis)
                       .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(nsPrintfCString("Button idx '%d' doesn't support in "
                                 "XboxOneS2016FirmwareRemapper().",
                                 aButton)
                     .get());
      return;
    }

    // kMicrosoftProductXboxOneSWireless2016 controller received a firmware
    // update in 2019 that changed which field is populated with the meta button
    // state. In order to cover the old and new cases, we have to check both
    // fields of {12, 15} buttons.
    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {0, BUTTON_INDEX_PRIMARY},
        {1, BUTTON_INDEX_SECONDARY},
        {3, BUTTON_INDEX_TERTIARY},
        {4, BUTTON_INDEX_QUATERNARY},
        {6, BUTTON_INDEX_LEFT_SHOULDER},
        {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {11, BUTTON_INDEX_START},
        {12, BUTTON_INDEX_META},
        {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK},
        {15, BUTTON_INDEX_META},
        {16, BUTTON_INDEX_BACK_SELECT}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class XboxOneRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      case 10: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 11: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in XboxOneRemapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in XboxOneRemapper().", aButton)
              .get());
      return;
    }

    // Accessing {30, 31} buttons looks strange to me
    // and without an avilable device to help verify it.
    // It is according to `MapperXboxOneBluetooth()` in
    // https://cs.chromium.org/chromium/src/device/gamepad/gamepad_standard_mappings_mac.mm
    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {0, BUTTON_INDEX_PRIMARY},
        {1, BUTTON_INDEX_SECONDARY},
        {3, BUTTON_INDEX_TERTIARY},
        {4, BUTTON_INDEX_QUATERNARY},
        {6, BUTTON_INDEX_LEFT_SHOULDER},
        {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {11, BUTTON_INDEX_START},
        {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK},
        {30, BUTTON_INDEX_META},
        {31, BUTTON_INDEX_BACK_SELECT}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class XboxSeriesXRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      case 148: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 149: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in XboxSeriesXRemapper().",
                aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in XboxSeriesXRemapper().",
              aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {0, BUTTON_INDEX_PRIMARY},
        {1, BUTTON_INDEX_SECONDARY},
        {3, BUTTON_INDEX_TERTIARY},
        {4, BUTTON_INDEX_QUATERNARY},
        {6, BUTTON_INDEX_LEFT_SHOULDER},
        {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {10, BUTTON_INDEX_BACK_SELECT},
        {11, BUTTON_INDEX_START},
        {12, BUTTON_INDEX_META},
        {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class LogitechDInputRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    // The Logitech button (BUTTON_INDEX_META) is not accessible through the
    // device's D-mode.
    return BUTTON_INDEX_COUNT - 1;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in LogitechDInputRemapper().",
                aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in LogitechDInputRemapper().",
              aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {0, BUTTON_INDEX_TERTIARY},
        {1, BUTTON_INDEX_PRIMARY},
        {2, BUTTON_INDEX_SECONDARY}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class SwitchJoyConRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return 2; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    if (GetAxisCount() <= aAxis) {
      NS_WARNING(
          nsPrintfCString(
              "Axis idx '%d' doesn't support in SwitchJoyConRemapper().", aAxis)
              .get());
      return;
    }
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    service->NewButtonEvent(aHandle, aButton, aPressed);
  }
};

class SwitchProRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    // The Switch Pro controller has a Capture button that has no equivalent in
    // the Standard Gamepad.
    return SWITCHPRO_BUTTON_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    if (GetAxisCount() <= aAxis) {
      NS_WARNING(
          nsPrintfCString(
              "Axis idx '%d' doesn't support in SwitchProRemapper().", aAxis)
              .get());
      return;
    }
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    service->NewButtonEvent(aHandle, aButton, aPressed);
  }

 private:
  enum SwitchProButtons {
    SWITCHPRO_BUTTON_EXTRA = BUTTON_INDEX_COUNT,
    SWITCHPRO_BUTTON_COUNT
  };
};

class NvShieldRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return SHIELD_BUTTON_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in NvShieldRemapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in NvShieldRemapper().", aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {2, BUTTON_INDEX_META},
        {3, BUTTON_INDEX_TERTIARY},
        {4, BUTTON_INDEX_QUATERNARY},
        {5, SHIELD_BUTTON_CIRCLE},
        {6, BUTTON_INDEX_LEFT_SHOULDER},
        {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {9, BUTTON_INDEX_BACK_SELECT},
        {11, BUTTON_INDEX_START},
        {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }

 private:
  enum ShieldButtons {
    SHIELD_BUTTON_CIRCLE = BUTTON_INDEX_COUNT,
    SHIELD_BUTTON_COUNT
  };
};

class NvShield2017Remapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return SHIELD2017_BUTTON_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in NvShield2017Remapper().",
                aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in NvShield2017Remapper().",
              aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {2, BUTTON_INDEX_META},
        {3, BUTTON_INDEX_TERTIARY},
        {4, BUTTON_INDEX_QUATERNARY},
        {5, BUTTON_INDEX_START},
        {6, BUTTON_INDEX_LEFT_SHOULDER},
        {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {8, BUTTON_INDEX_BACK_SELECT},
        {11, SHIELD2017_BUTTON_PLAYPAUSE},
        {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }

 private:
  enum Shield2017Buttons {
    SHIELD2017_BUTTON_PLAYPAUSE = BUTTON_INDEX_COUNT,
    SHIELD2017_BUTTON_COUNT
  };
};

class IBuffaloRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return 2; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT - 1; /* no meta */
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_LEFT,
                                AxisNegativeAsButton(aValue));
        service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_RIGHT,
                                AxisPositiveAsButton(aValue));
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_UP,
                                AxisNegativeAsButton(aValue));
        service->NewButtonEvent(aHandle, BUTTON_INDEX_DPAD_DOWN,
                                AxisPositiveAsButton(aValue));
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in IBuffaloRemapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in IBuffaloRemapper().", aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {0, BUTTON_INDEX_SECONDARY},     {1, BUTTON_INDEX_PRIMARY},
        {2, BUTTON_INDEX_QUATERNARY},    {3, BUTTON_INDEX_TERTIARY},
        {5, BUTTON_INDEX_RIGHT_TRIGGER}, {6, BUTTON_INDEX_BACK_SELECT},
        {7, BUTTON_INDEX_START}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class XSkillsRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return GAMECUBE_BUTTON_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in XSkillsRemapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in XSkillsRemapper().", aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {0, BUTTON_INDEX_PRIMARY},     // A
        {1, BUTTON_INDEX_TERTIARY},    // B
        {2, BUTTON_INDEX_SECONDARY},   // X
        {3, BUTTON_INDEX_QUATERNARY},  // Y
        {4, GAMECUBE_BUTTON_LEFT_TRIGGER_CLICK},
        {5, GAMECUBE_BUTTON_RIGHT_TRIGGER_CLICK},
        {6, BUTTON_INDEX_RIGHT_SHOULDER},
        {7, BUTTON_INDEX_START},
        {8, BUTTON_INDEX_DPAD_LEFT},
        {9, BUTTON_INDEX_DPAD_RIGHT},
        {10, BUTTON_INDEX_DPAD_DOWN},
        {11, BUTTON_INDEX_DPAD_UP}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }

 private:
  enum GamecubeButtons {
    GAMECUBE_BUTTON_LEFT_TRIGGER_CLICK = BUTTON_INDEX_COUNT,
    GAMECUBE_BUTTON_RIGHT_TRIGGER_CLICK,
    GAMECUBE_BUTTON_COUNT
  };
};

class BoomN64PsxRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT - 1;  // no meta
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in BoomN64PsxRemapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    static constexpr std::array buttonMapping = {
        BUTTON_INDEX_QUATERNARY,       BUTTON_INDEX_SECONDARY,
        BUTTON_INDEX_PRIMARY,          BUTTON_INDEX_TERTIARY,
        BUTTON_INDEX_LEFT_TRIGGER,     BUTTON_INDEX_RIGHT_TRIGGER,
        BUTTON_INDEX_LEFT_SHOULDER,    BUTTON_INDEX_RIGHT_SHOULDER,
        BUTTON_INDEX_BACK_SELECT,      BUTTON_INDEX_LEFT_THUMBSTICK,
        BUTTON_INDEX_RIGHT_THUMBSTICK, BUTTON_INDEX_START,
        BUTTON_INDEX_DPAD_UP,          BUTTON_INDEX_DPAD_RIGHT,
        BUTTON_INDEX_DPAD_DOWN,        BUTTON_INDEX_DPAD_LEFT};

    if (buttonMapping.size() <= aButton) {
      NS_WARNING(nsPrintfCString(
                     "Button idx '%d' doesn't support in BoomN64PsxRemapper().",
                     aButton)
                     .get());
      return;
    }

    service->NewButtonEvent(aHandle, buttonMapping[aButton], aPressed);
  }

 private:
  enum GamecubeButtons {
    GAMECUBE_BUTTON_LEFT_TRIGGER_CLICK = BUTTON_INDEX_COUNT,
    GAMECUBE_BUTTON_RIGHT_TRIGGER_CLICK,
    GAMECUBE_BUTTON_COUNT
  };
};

class StadiaControllerOldFirmwareRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return ANALOG_GAMEPAD_BUTTON_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in AnalogGamepadRemapper().",
                aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in AnalogGamepadRemapper().",
              aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {3, BUTTON_INDEX_TERTIARY},
        {4, BUTTON_INDEX_QUATERNARY},
        {6, BUTTON_INDEX_LEFT_SHOULDER},
        {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {10, BUTTON_INDEX_BACK_SELECT},
        {11, BUTTON_INDEX_META},
        {12, BUTTON_INDEX_START},
        {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK},
        {16, ANALOG_GAMEPAD_BUTTON_EXTRA},
        {17, ANALOG_GAMEPAD_BUTTON_EXTRA2}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }

 private:
  enum AnalogGamepadButtons {
    ANALOG_GAMEPAD_BUTTON_EXTRA = BUTTON_INDEX_COUNT,
    ANALOG_GAMEPAD_BUTTON_EXTRA2,
    ANALOG_GAMEPAD_BUTTON_COUNT
  };
};

class RazerServalRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT - 1; /* no meta */
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in RazerServalRemapper().",
                aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in RazerServalRemapper().",
              aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {3, BUTTON_INDEX_TERTIARY},         {4, BUTTON_INDEX_QUATERNARY},
        {6, BUTTON_INDEX_LEFT_SHOULDER},    {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {10, BUTTON_INDEX_BACK_SELECT},     {11, BUTTON_INDEX_START},
        {12, BUTTON_INDEX_START},           {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class MogaProRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT - 1; /* no meta */
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 3: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 4: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 5:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in MogaProRemapper().", aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in MogaProRemapper().", aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {3, BUTTON_INDEX_TERTIARY},         {4, BUTTON_INDEX_QUATERNARY},
        {6, BUTTON_INDEX_LEFT_SHOULDER},    {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {11, BUTTON_INDEX_START},           {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class OnLiveWirelessRemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 3:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 4:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 5: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 9:
        FetchDpadFromAxis(aHandle, aValue);
        break;
      default:
        NS_WARNING(
            nsPrintfCString(
                "Axis idx '%d' doesn't support in OnLiveWirelessRemapper().",
                aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString(
              "Button idx '%d' doesn't support in OnLiveWirelessRemapper().",
              aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {3, BUTTON_INDEX_TERTIARY},
        {4, BUTTON_INDEX_QUATERNARY},
        {6, BUTTON_INDEX_LEFT_SHOULDER},
        {7, BUTTON_INDEX_RIGHT_SHOULDER},
        {12, BUTTON_INDEX_META},
        {13, BUTTON_INDEX_LEFT_THUMBSTICK},
        {14, BUTTON_INDEX_RIGHT_THUMBSTICK}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

class OUYARemapper final : public GamepadRemapper {
 public:
  virtual uint32_t GetAxisCount() const override { return AXIS_INDEX_COUNT; }

  virtual uint32_t GetButtonCount() const override {
    return BUTTON_INDEX_COUNT;
  }

  virtual void RemapAxisMoveEvent(GamepadHandle aHandle, uint32_t aAxis,
                                  double aValue) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    switch (aAxis) {
      case 0:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_X, aValue);
        break;
      case 1:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_LEFT_STICK_Y, aValue);
        break;
      case 2: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_LEFT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      case 3:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_X, aValue);
        break;
      case 4:
        service->NewAxisMoveEvent(aHandle, AXIS_INDEX_RIGHT_STICK_Y, aValue);
        break;
      case 5: {
        const double value = AxisToButtonValue(aValue);
        service->NewButtonEvent(aHandle, BUTTON_INDEX_RIGHT_TRIGGER,
                                value > BUTTON_THRESHOLD_VALUE, value);
        break;
      }
      default:
        NS_WARNING(
            nsPrintfCString("Axis idx '%d' doesn't support in OUYARemapper().",
                            aAxis)
                .get());
        break;
    }
  }

  virtual void RemapButtonEvent(GamepadHandle aHandle, uint32_t aButton,
                                bool aPressed) const override {
    RefPtr<GamepadPlatformService> service =
        GamepadPlatformService::GetParentService();
    if (!service) {
      return;
    }

    if (GetButtonCount() <= aButton) {
      NS_WARNING(
          nsPrintfCString("Button idx '%d' doesn't support in OUYARemapper().",
                          aButton)
              .get());
      return;
    }

    const std::unordered_map<uint32_t, uint32_t> buttonMapping = {
        {1, BUTTON_INDEX_TERTIARY},         {2, BUTTON_INDEX_QUATERNARY},
        {3, BUTTON_INDEX_SECONDARY},        {6, BUTTON_INDEX_LEFT_THUMBSTICK},
        {7, BUTTON_INDEX_RIGHT_THUMBSTICK}, {8, BUTTON_INDEX_DPAD_UP},
        {9, BUTTON_INDEX_DPAD_DOWN},        {10, BUTTON_INDEX_DPAD_LEFT},
        {11, BUTTON_INDEX_DPAD_RIGHT},      {15, BUTTON_INDEX_META}};

    auto find = buttonMapping.find(aButton);
    if (find != buttonMapping.end()) {
      service->NewButtonEvent(aHandle, find->second, aPressed);
    } else {
      service->NewButtonEvent(aHandle, aButton, aPressed);
    }
  }
};

already_AddRefed<GamepadRemapper> GetGamepadRemapper(const uint16_t aVendorId,
                                                     const uint16_t aProductId,
                                                     bool& aUsingDefault) {
  const std::vector<GamepadRemappingData> remappingRules = {
      {GamepadId::kAsusTekProduct4500, new ADT1Remapper()},
      {GamepadId::kDragonRiseProduct0011, new TwoAxesEightKeysRemapper()},
      {GamepadId::kGoogleProduct2c40, new ADT1Remapper()},
      {GamepadId::kGoogleProduct9400, new StadiaControllerRemapper()},
      {GamepadId::kLogitechProductc216, new LogitechDInputRemapper()},
      {GamepadId::kLogitechProductc218, new LogitechDInputRemapper()},
      {GamepadId::kLogitechProductc219, new LogitechDInputRemapper()},
      {GamepadId::kMicrosoftProductXbox360Wireless, new Xbox360Remapper()},
      {GamepadId::kMicrosoftProductXbox360Wireless2, new Xbox360Remapper()},
      {GamepadId::kMicrosoftProductXboxOneElite2Wireless,
       new XboxOneRemapper()},
      {GamepadId::kMicrosoftProductXboxOneSWireless, new XboxOneSRemapper()},
      {GamepadId::kMicrosoftProductXboxOneSWireless2016,
       new XboxOneS2016FirmwareRemapper()},
      {GamepadId::kMicrosoftProductXboxAdaptiveWireless, new XboxOneRemapper()},
      {GamepadId::kMicrosoftProductXboxSeriesXWireless,
       new XboxSeriesXRemapper()},
      {GamepadId::kNintendoProduct2006, new SwitchJoyConRemapper()},
      {GamepadId::kNintendoProduct2007, new SwitchJoyConRemapper()},
      {GamepadId::kNintendoProduct2009, new SwitchProRemapper()},
      {GamepadId::kNintendoProduct200e, new SwitchProRemapper()},
      {GamepadId::kNvidiaProduct7210, new NvShieldRemapper()},
      {GamepadId::kNvidiaProduct7214, new NvShield2017Remapper()},
      {GamepadId::kPadixProduct2060, new IBuffaloRemapper()},
      {GamepadId::kPlayComProduct0005, new XSkillsRemapper()},
      {GamepadId::kPrototypeVendorProduct0667, new BoomN64PsxRemapper()},
      {GamepadId::kPrototypeVendorProduct9401,
       new StadiaControllerOldFirmwareRemapper()},
      {GamepadId::kRazer1532Product0900, new RazerServalRemapper()},
      {GamepadId::kSonyProduct0268, new Playstation3Remapper()},
      {GamepadId::kSonyProduct05c4, new Dualshock4Remapper()},
      {GamepadId::kSonyProduct09cc, new Dualshock4Remapper()},
      {GamepadId::kSonyProduct0ba0, new Dualshock4Remapper()},
      {GamepadId::kSonyProduct0ce6, new DualSenseRemapper()},
      {GamepadId::kSonyProduct0df2, new DualSenseRemapper()},
      {GamepadId::kVendor20d6Product6271, new MogaProRemapper()},
      {GamepadId::kVendor2378Product1008, new OnLiveWirelessRemapper()},
      {GamepadId::kVendor2378Product100a, new OnLiveWirelessRemapper()},
      {GamepadId::kVendor2836Product0001, new OUYARemapper()}};
  const GamepadId id = static_cast<GamepadId>((aVendorId << 16) | aProductId);

  for (uint32_t i = 0; i < remappingRules.size(); ++i) {
    if (id == remappingRules[i].id) {
      aUsingDefault = false;
      return do_AddRef(remappingRules[i].remapping.get());
    }
  }

  RefPtr<GamepadRemapper> defaultRemapper = new DefaultRemapper();
  aUsingDefault = true;
  return do_AddRef(defaultRemapper.get());
}

}  // namespace mozilla::dom
