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

#ifndef DEVICE_BLUETOOTH_EVENT_UTILS_WINRT_H_
#define DEVICE_BLUETOOTH_EVENT_UTILS_WINRT_H_

#include <windows.foundation.h>
#include <wrl/client.h>
#include <wrl/event.h>

#include <type_traits>
#include <utility>

#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/threading/thread_task_runner_handle.h"

namespace device {

namespace internal {

// Utility function to pretty print enum values.
constexpr const char* ToCString(AsyncStatus async_status) {
  switch (async_status) {
    case AsyncStatus::Started:
      return "AsyncStatus::Started";
    case AsyncStatus::Completed:
      return "AsyncStatus::Completed";
    case AsyncStatus::Canceled:
      return "AsyncStatus::Canceled";
    case AsyncStatus::Error:
      return "AsyncStatus::Error";
  }

  NOTREACHED();
  return "";
}

template <typename Interface, typename... Args>
using IMemberFunction = HRESULT (__stdcall Interface::*)(Args...);

template <typename T>
using AsyncAbiT = typename ABI::Windows::Foundation::Internal::GetAbiType<
    typename ABI::Windows::Foundation::IAsyncOperation<T>::TResult_complex>::
    type;

// Compile time switch to decide what container to use for the async results for
// |T|. Depends on whether the underlying Abi type is a pointer to IUnknown or
// not. It queries the internals of Windows::Foundation to obtain this
// information.
template <typename T>
using AsyncResultsT = std::conditional_t<
    std::is_convertible<AsyncAbiT<T>, IUnknown*>::value,
    Microsoft::WRL::ComPtr<std::remove_pointer_t<AsyncAbiT<T>>>,
    AsyncAbiT<T>>;

// Obtains the results of the provided async operation.
template <typename T>
AsyncResultsT<T> GetAsyncResults(
    ABI::Windows::Foundation::IAsyncOperation<T>* async_op) {
  AsyncResultsT<T> results;
  HRESULT hr = async_op->GetResults(&results);
  if (FAILED(hr)) {
    VLOG(2) << "GetAsyncResults failed: "
            << logging::SystemErrorCodeToString(hr);
  }

  return results;
}

}  // namespace internal

// This method registers a completion handler for |async_op| and will post the
// results to |callback|. The |callback| will be run on the same thread that
// invoked this method. Callers need to ensure that this method is invoked in
// the correct COM apartment, i.e. the one that created |async_op|. While a WRL
// Callback can be constructed from callable types such as a lambda or
// std::function objects, it cannot be directly constructed from a
// base::OnceCallback. Thus the callback is moved into a capturing lambda, which
// then posts the callback once it is run. Posting the results to the TaskRunner
// is required, since the completion callback might be invoked on an arbitrary
// thread. Lastly, the lambda takes ownership of |async_op|, as this needs to be
// kept alive until GetAsyncResults can be invoked.
template <typename T>
HRESULT PostAsyncResults(
    Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<T>>
        async_op,
    base::OnceCallback<void(internal::AsyncResultsT<T>)> callback) {
  auto completion_cb = base::BindOnce(
      [](Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<T>>
             async_op,
         base::OnceCallback<void(internal::AsyncResultsT<T>)> callback) {
        std::move(callback).Run(internal::GetAsyncResults(async_op.Get()));
      },
      async_op, std::move(callback));

  return async_op->put_Completed(
      Microsoft::WRL::Callback<
          ABI::Windows::Foundation::IAsyncOperationCompletedHandler<T>>([
        task_runner(base::ThreadTaskRunnerHandle::Get()),
        completion_cb(std::move(completion_cb))
      ](auto&&, AsyncStatus async_status) mutable {
        if (async_status != AsyncStatus::Completed) {
          VLOG(2) << "Got unexpected AsyncStatus: "
                  << internal::ToCString(async_status);
        }

        // Note: We are ignoring the passed in pointer to async_op, as the
        // completion callback has access to the initially provided |async_op|.
        // Since the code within the lambda could be executed on any thread, it
        // is vital that the completion callback gets posted to the original
        // |task_runner|, as this is guaranteed to be in the correct COM
        // apartment.
        task_runner->PostTask(FROM_HERE, std::move(completion_cb));
        return S_OK;
      })
          .Get());
}

// Convenience template function to construct a TypedEventHandler from a
// base::RepeatingCallback of a matching signature. In case of success, the
// EventRegistrationToken is returned to the caller. A return value of
// base::nullopt indicates a failure.
template <typename Interface,
          typename Sender,
          typename Args,
          typename SenderAbi,
          typename ArgsAbi>
base::Optional<EventRegistrationToken> AddTypedEventHandler(
    Interface* i,
    internal::IMemberFunction<
        Interface,
        ABI::Windows::Foundation::ITypedEventHandler<Sender, Args>*,
        EventRegistrationToken*> function,
    base::RepeatingCallback<void(SenderAbi, ArgsAbi)> callback) {
  EventRegistrationToken token;
  HRESULT hr = ((*i).*function)(
      Microsoft::WRL::Callback<
          ABI::Windows::Foundation::ITypedEventHandler<Sender, Args>>(
          [callback](SenderAbi sender, ArgsAbi args) {
            callback.Run(std::move(sender), std::move(args));
            return S_OK;
          })
          .Get(),
      &token);

  if (FAILED(hr)) {
    VLOG(2) << "Adding EventHandler failed: "
            << logging::SystemErrorCodeToString(hr);
    return base::nullopt;
  }

  return token;
}

}  // namespace device

#endif  // DEVICE_BLUETOOTH_EVENT_UTILS_WINRT_H_
