// 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/dualshock4_controller_win.h"

#include <Unknwn.h>
#include <WinDef.h>
#include <stdint.h>
#include <windows.h>

namespace device {

Dualshock4ControllerWin::Dualshock4ControllerWin(HANDLE device_handle) {
  UINT size;
  UINT result =
      ::GetRawInputDeviceInfo(device_handle, RIDI_DEVICENAME, nullptr, &size);
  if (result == 0U) {
    std::unique_ptr<wchar_t[]> name_buffer(new wchar_t[size]);
    result = ::GetRawInputDeviceInfo(device_handle, RIDI_DEVICENAME,
                                     name_buffer.get(), &size);
    if (result == size) {
      // Open the device handle for asynchronous I/O.
      hid_handle_.Set(
          ::CreateFile(name_buffer.get(), GENERIC_READ | GENERIC_WRITE,
                       FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
                       OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr));
    }
  }
}

Dualshock4ControllerWin::~Dualshock4ControllerWin() = default;

void Dualshock4ControllerWin::DoShutdown() {
  hid_handle_.Close();
}

size_t Dualshock4ControllerWin::WriteOutputReport(void* report,
                                                  size_t report_length) {
  DCHECK(report);
  DCHECK_GE(report_length, 1U);
  if (!hid_handle_.IsValid())
    return 0;

  base::win::ScopedHandle event_handle(
      ::CreateEvent(nullptr, false, false, L""));
  OVERLAPPED overlapped = {0};
  overlapped.hEvent = event_handle.Get();

  // Set up an asynchronous write.
  DWORD bytes_written = 0;
  BOOL write_success = ::WriteFile(hid_handle_.Get(), report, report_length,
                                   &bytes_written, &overlapped);
  if (!write_success) {
    DWORD error = ::GetLastError();
    if (error == ERROR_IO_PENDING) {
      // Wait for the write to complete. This causes WriteOutputReport to behave
      // synchronously.
      DWORD wait_object = ::WaitForSingleObject(overlapped.hEvent, 100);
      if (wait_object == WAIT_OBJECT_0) {
        ::GetOverlappedResult(hid_handle_.Get(), &overlapped, &bytes_written,
                              true);
      } else {
        // Wait failed, or the timeout was exceeded before the write completed.
        // Cancel the write request.
        if (::CancelIo(hid_handle_.Get())) {
          HANDLE handles[2];
          handles[0] = hid_handle_.Get();
          handles[1] = overlapped.hEvent;
          ::WaitForMultipleObjects(2, handles, false, INFINITE);
        }
      }
    }
  }
  return write_success ? bytes_written : 0;
}

}  // namespace device
