// 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 BASE_WIN_POST_ASYNC_RESULTS_H_
#define BASE_WIN_POST_ASYNC_RESULTS_H_

#include <unknwn.h>
#include <windows.foundation.h>
#include <wrl/async.h>
#include <wrl/client.h>

#include <type_traits>
#include <utility>

#include "base/bind.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/threading/thread_task_runner_handle.h"

namespace base {
namespace win {

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 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 completed_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));

  auto completed_lambda = [task_runner(base::ThreadTaskRunnerHandle::Get()),
                           completed_cb(std::move(completed_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 |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
    // 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(completed_cb));
    return S_OK;
  };

  using CompletedHandler = Microsoft::WRL::Implements<
      Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
      ABI::Windows::Foundation::IAsyncOperationCompletedHandler<T>,
      Microsoft::WRL::FtmBase>;

  return async_op->put_Completed(
      Microsoft::WRL::Callback<CompletedHandler>(std::move(completed_lambda))
          .Get());
}

}  // namespace win
}  // namespace base

#endif  // BASE_WIN_POST_ASYNC_RESULTS_H_