// Copyright 2018 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 "device/gamepad/switch_pro_controller_base.h"

#include <limits>

#include "device/gamepad/gamepad_data_fetcher.h"
#include "device/gamepad/gamepad_standard_mappings.h"

namespace {
const uint32_t kVendorNintendo = 0x057e;
const uint32_t kProductSwitchProController = 0x2009;

const uint8_t kRumbleMagnitudeMax = 0xff;

// Switch Pro Controller USB packet types.
static const uint8_t kPacketTypeStatus = 0x81;
static const uint8_t kPacketTypeControllerData = 0x30;

// Status packet subtypes.
static const uint8_t kStatusTypeSerial = 0x01;
static const uint8_t kStatusTypeInit = 0x02;

// Axis extents, used for normalization.
static const int8_t kAxisMin = std::numeric_limits<int8_t>::min();
static const int8_t kAxisMax = std::numeric_limits<int8_t>::max();

enum ControllerType { UNKNOWN_CONTROLLER, SWITCH_PRO_CONTROLLER };

#pragma pack(push, 1)
struct ControllerDataReport {
  uint8_t type;  // must be kPacketTypeControllerData
  uint8_t timestamp;
  uint8_t dummy1;
  bool button_y : 1;
  bool button_x : 1;
  bool button_b : 1;
  bool button_a : 1;
  bool dummy2 : 2;
  bool button_r : 1;
  bool button_zr : 1;
  bool button_minus : 1;
  bool button_plus : 1;
  bool button_thumb_r : 1;
  bool button_thumb_l : 1;
  bool button_home : 1;
  bool button_capture : 1;
  bool dummy3 : 2;
  bool dpad_down : 1;
  bool dpad_up : 1;
  bool dpad_right : 1;
  bool dpad_left : 1;
  bool dummy4 : 2;
  bool button_l : 1;
  bool button_zl : 1;
  uint8_t analog[6];
};
#pragma pack(pop)

ControllerType ControllerTypeFromDeviceIds(int vendor_id, int product_id) {
  if (vendor_id == kVendorNintendo) {
    switch (product_id) {
      case kProductSwitchProController:
        return SWITCH_PRO_CONTROLLER;
      default:
        break;
    }
  }
  return UNKNOWN_CONTROLLER;
}

double NormalizeAxis(int value, int min, int max) {
  return (2.0 * (value - min) / static_cast<double>(max - min)) - 1.0;
}

void UpdatePadStateFromControllerData(const ControllerDataReport& report,
                                      device::Gamepad* pad) {
  pad->buttons[device::BUTTON_INDEX_PRIMARY].pressed = report.button_b;
  pad->buttons[device::BUTTON_INDEX_PRIMARY].value =
      report.button_b ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_SECONDARY].pressed = report.button_a;
  pad->buttons[device::BUTTON_INDEX_SECONDARY].value =
      report.button_a ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_TERTIARY].pressed = report.button_y;
  pad->buttons[device::BUTTON_INDEX_TERTIARY].value =
      report.button_y ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_QUATERNARY].pressed = report.button_x;
  pad->buttons[device::BUTTON_INDEX_QUATERNARY].value =
      report.button_x ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_LEFT_SHOULDER].pressed = report.button_l;
  pad->buttons[device::BUTTON_INDEX_LEFT_SHOULDER].value =
      report.button_l ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_RIGHT_SHOULDER].pressed = report.button_r;
  pad->buttons[device::BUTTON_INDEX_RIGHT_SHOULDER].value =
      report.button_r ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_LEFT_TRIGGER].pressed = report.button_zl;
  pad->buttons[device::BUTTON_INDEX_LEFT_TRIGGER].value =
      report.button_zl ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_RIGHT_TRIGGER].pressed = report.button_zr;
  pad->buttons[device::BUTTON_INDEX_RIGHT_TRIGGER].value =
      report.button_zr ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_BACK_SELECT].pressed = report.button_minus;
  pad->buttons[device::BUTTON_INDEX_BACK_SELECT].value =
      report.button_minus ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_START].pressed = report.button_plus;
  pad->buttons[device::BUTTON_INDEX_START].value =
      report.button_plus ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_LEFT_THUMBSTICK].pressed =
      report.button_thumb_l;
  pad->buttons[device::BUTTON_INDEX_LEFT_THUMBSTICK].value =
      report.button_thumb_l ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_RIGHT_THUMBSTICK].pressed =
      report.button_thumb_r;
  pad->buttons[device::BUTTON_INDEX_RIGHT_THUMBSTICK].value =
      report.button_thumb_r ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_DPAD_UP].pressed = report.dpad_up;
  pad->buttons[device::BUTTON_INDEX_DPAD_UP].value = report.dpad_up ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_DPAD_DOWN].pressed = report.dpad_down;
  pad->buttons[device::BUTTON_INDEX_DPAD_DOWN].value =
      report.dpad_down ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_DPAD_LEFT].pressed = report.dpad_left;
  pad->buttons[device::BUTTON_INDEX_DPAD_LEFT].value =
      report.dpad_left ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_DPAD_RIGHT].pressed = report.dpad_right;
  pad->buttons[device::BUTTON_INDEX_DPAD_RIGHT].value =
      report.dpad_right ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_META].pressed = report.button_home;
  pad->buttons[device::BUTTON_INDEX_META].value =
      report.button_home ? 1.0 : 0.0;

  pad->buttons[device::BUTTON_INDEX_META + 1].pressed = report.button_capture;
  pad->buttons[device::BUTTON_INDEX_META + 1].value =
      report.button_capture ? 1.0 : 0.0;

  int8_t axis_lx =
      (((report.analog[1] & 0x0F) << 4) | ((report.analog[0] & 0xF0) >> 4)) +
      127;
  int8_t axis_ly = report.analog[2] + 127;
  int8_t axis_rx =
      (((report.analog[4] & 0x0F) << 4) | ((report.analog[3] & 0xF0) >> 4)) +
      127;
  int8_t axis_ry = report.analog[5] + 127;
  pad->axes[device::AXIS_INDEX_LEFT_STICK_X] =
      NormalizeAxis(axis_lx, kAxisMin, kAxisMax);
  pad->axes[device::AXIS_INDEX_LEFT_STICK_Y] =
      NormalizeAxis(-axis_ly, kAxisMin, kAxisMax);
  pad->axes[device::AXIS_INDEX_RIGHT_STICK_X] =
      NormalizeAxis(axis_rx, kAxisMin, kAxisMax);
  pad->axes[device::AXIS_INDEX_RIGHT_STICK_Y] =
      NormalizeAxis(-axis_ry, kAxisMin, kAxisMax);

  pad->buttons_length = device::BUTTON_INDEX_COUNT + 1;
  pad->axes_length = device::AXIS_INDEX_COUNT;
}

}  // namespace

namespace device {

SwitchProControllerBase::~SwitchProControllerBase() = default;

// static
bool SwitchProControllerBase::IsSwitchPro(int vendor_id, int product_id) {
  return ControllerTypeFromDeviceIds(vendor_id, product_id) !=
         UNKNOWN_CONTROLLER;
}

void SwitchProControllerBase::DoShutdown() {
  if (force_usb_hid_)
    SendForceUsbHid(false);
  force_usb_hid_ = false;
}

void SwitchProControllerBase::ReadUsbPadState(Gamepad* pad) {
  DCHECK(pad);

  // Consume reports until the input pipe is empty.
  uint8_t report_bytes[kReportSize];
  while (true) {
    size_t report_length = ReadInputReport(report_bytes);
    if (report_length == 0)
      break;
    HandleInputReport(report_bytes, report_length, pad);
  }
}

void SwitchProControllerBase::HandleInputReport(void* report,
                                                size_t report_length,
                                                Gamepad* pad) {
  DCHECK(report);
  DCHECK_GE(report_length, 1U);
  DCHECK(pad);

  const uint8_t* report_bytes = static_cast<uint8_t*>(report);
  const uint8_t type = report_bytes[0];
  switch (type) {
    case kPacketTypeStatus:
      if (report_length >= 2) {
        const uint8_t status_type = report_bytes[1];
        switch (status_type) {
          case kStatusTypeSerial:
            if (!sent_handshake_) {
              sent_handshake_ = true;
              SendHandshake();
            }
            break;
          case kStatusTypeInit:
            force_usb_hid_ = true;
            SendForceUsbHid(true);
            break;
          default:
            break;
        }
      }
      break;
    case kPacketTypeControllerData: {
      ControllerDataReport* controller_data =
          reinterpret_cast<ControllerDataReport*>(report);
      UpdatePadStateFromControllerData(*controller_data, pad);
      pad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
      break;
    }
    default:
      break;
  }
}

void SwitchProControllerBase::SendConnectionStatusQuery() {
  // Requests the current connection status and info about the connected
  // controller. The controller will respond with a status packet.
  uint8_t report[kReportSize];
  memset(report, 0, kReportSize);
  report[0] = 0x80;
  report[1] = 0x01;

  WriteOutputReport(report, kReportSize);
}

void SwitchProControllerBase::SendHandshake() {
  // Sends handshaking packets over UART. This command can only be called once
  // per session. The controller will respond with a status packet.
  uint8_t report[kReportSize];
  memset(report, 0, kReportSize);
  report[0] = 0x80;
  report[1] = 0x02;

  WriteOutputReport(report, kReportSize);
}

void SwitchProControllerBase::SendForceUsbHid(bool enable) {
  // By default, the controller will revert to Bluetooth mode if it does not
  // receive any USB HID commands within a timeout window. Enabling the
  // ForceUsbHid mode forces all communication to go through USB HID and
  // disables the timeout.
  uint8_t report[kReportSize];
  memset(report, 0, kReportSize);
  report[0] = 0x80;
  report[1] = (enable ? 0x04 : 0x05);

  WriteOutputReport(report, kReportSize);
}

void SwitchProControllerBase::SetVibration(double strong_magnitude,
                                           double weak_magnitude) {
  uint8_t strong_magnitude_scaled =
      static_cast<uint8_t>(strong_magnitude * kRumbleMagnitudeMax);
  uint8_t weak_magnitude_scaled =
      static_cast<uint8_t>(weak_magnitude * kRumbleMagnitudeMax);

  uint8_t report[kReportSize];
  memset(report, 0, kReportSize);
  report[0] = 0x10;
  report[1] = static_cast<uint8_t>(counter_++ & 0x0F);
  report[2] = 0x80;
  report[6] = 0x80;
  if (strong_magnitude_scaled > 0) {
    report[2] = 0x80;
    report[3] = 0x20;
    report[4] = 0x62;
    report[5] = strong_magnitude_scaled >> 2;
  }
  if (weak_magnitude_scaled > 0) {
    report[6] = 0x98;
    report[7] = 0x20;
    report[8] = 0x62;
    report[9] = weak_magnitude_scaled >> 2;
  }

  WriteOutputReport(report, kReportSize);
}

size_t SwitchProControllerBase::ReadInputReport(void* report) {
  return 0;
}

size_t SwitchProControllerBase::WriteOutputReport(void* report,
                                                  size_t report_length) {
  return 0;
}

}  // namespace device
