// Copyright 2018 The Chromium Authors
// 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/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.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 IAsyncOperationT = typename ABI::Windows::Foundation::IAsyncOperation<T>;

template <typename T>
using IAsyncOperationCompletedHandlerT =
    typename base::OnceCallback<void(IAsyncOperationT<T>*, AsyncStatus)>;

template <typename T>
using AsyncAbiT = typename ABI::Windows::Foundation::Internal::GetAbiType<
    typename IAsyncOperationT<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>>;

// Fetches the result of the provided |async_operation| and corresponding
// |async_status| and assigns that value to |result|. Returns an HRESULT
// indicating the success of the operation.
template <typename T>
HRESULT GetAsyncResultsT(IAsyncOperationT<T>* async_operation,
                         AsyncStatus async_status,
                         AsyncResultsT<T>* results) {
  if (async_status == AsyncStatus::Completed) {
    // To expose |results| to GetResults as the expected type, this call first
    // dereferences |results| from ComPtr<T>* or T* to ComPtr<T> or T
    // respectively, then requests the address, converting to T** or T*
    // respectively.
    HRESULT hr = async_operation->GetResults(&(*results));
    if (FAILED(hr))
      *results = AsyncResultsT<T>{};
    return hr;
  }

  *results = AsyncResultsT<T>{};
  Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncInfo> async_info;
  HRESULT hr = async_operation->QueryInterface(IID_PPV_ARGS(&async_info));
  if (FAILED(hr))
    return hr;

  HRESULT operation_hr;
  hr = async_info->get_ErrorCode(&operation_hr);
  if (FAILED(hr))
    return hr;

  DCHECK(FAILED(operation_hr));
  return operation_hr;
}

// Registers an internal completion handler for |async_operation| and upon
// completion, posts the results to the provided |completed_handler|. Returns an
// HRESULT indicating the success of registering the internal completion
// handler.
//
// Callers need to ensure that this method is invoked in the correct COM
// apartment, i.e. the one that created |async_operation|. The
// |completed_handler| will be run on the same sequence that invoked this
// method. This call does not ensure the lifetime of the |async_operation|,
// which must be done by the caller.
template <typename T>
HRESULT PostAsyncOperationCompletedHandler(
    IAsyncOperationT<T>* async_operation,
    IAsyncOperationCompletedHandlerT<T> completed_handler) {
  using AsyncResult =
      std::pair<Microsoft::WRL::ComPtr<IAsyncOperationT<T>>, AsyncStatus>;

  auto internal_completed_handler =
      base::BindOnce([](IAsyncOperationT<T>* async_operation,
                        AsyncStatus async_status) -> AsyncResult {
        // Posting the results to the TaskRunner is required, since this
        // CompletedHandler might be invoked on an arbitrary thread however
        // the raw |async_operation| pointer is only guaranteed to be valid
        // for the lifetime of this call, so to ensure it is still valid
        // through the lifetime of the call to the |completed_handler| we
        // capture it in an appropriate ref-counted pointer.
        return std::make_pair(async_operation, async_status);
      })
          .Then(
              base::BindPostTaskToCurrentDefault(base::BindOnce(
                  [](IAsyncOperationCompletedHandlerT<T> completed_handler,
                     AsyncResult async_result) {
                    std::move(completed_handler)
                        .Run(async_result.first.Get(), async_result.second);
                  },
                  std::move(completed_handler))));

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

  return async_operation->put_Completed(
      Microsoft::WRL::Callback<CompletedHandler>(
          [internal_completed_handler(std::move(internal_completed_handler))](
              IAsyncOperationT<T>* async_operation,
              AsyncStatus async_status) mutable {
            std::move(internal_completed_handler)
                .Run(async_operation, async_status);
            return S_OK;
          })
          .Get());
}

}  // namespace internal

// Registers an internal completion handler for |async_operation| and upon
// successful completion invokes the |success_callback| with the result. If the
// |async_operation| encounters an error no callback will be invoked. Returns
// an HRESULT indicating the success of registering the internal completion
// handler.
//
// Callers need to ensure that this method is invoked in the correct COM
// apartment, i.e. the one that created |async_operation|. The resulting
// callback (i.e. |success_callback|) will be run on the same sequence that
// invoked this method. This call does not ensure the lifetime of the
// |async_operation|, which must be done by the caller.
template <typename T>
HRESULT PostAsyncHandlers(
    internal::IAsyncOperationT<T>* async_operation,
    base::OnceCallback<void(internal::AsyncResultsT<T>)> success_callback) {
  return internal::PostAsyncOperationCompletedHandler(
      async_operation,
      base::BindOnce(
          [](base::OnceCallback<void(internal::AsyncResultsT<T>)>
                 success_callback,
             internal::IAsyncOperationT<T>* async_operation,
             AsyncStatus async_status) {
            internal::AsyncResultsT<T> results;
            HRESULT hr = internal::GetAsyncResultsT(async_operation,
                                                    async_status, &results);
            if (SUCCEEDED(hr))
              std::move(success_callback).Run(results);
          },
          std::move(success_callback)));
}

// Registers an internal completion handler for |async_operation| and upon
// successful completion invokes the |success_callback| with the result. If the
// |async_operation| encounters an error the |failure_callback| will instead be
// invoked. Returns an HRESULT indicating the success of registering the
// internal completion handler.
//
// Callers need to ensure that this method is invoked in the correct COM
// apartment, i.e. the one that created |async_operation|. The resulting
// callback (|success_callback| or |failure_callback|) will be run on the same
// sequence that invoked this method. This call does not ensure the lifetime of
// the |async_operation|, which must be done by the caller.
template <typename T>
HRESULT PostAsyncHandlers(
    internal::IAsyncOperationT<T>* async_operation,
    base::OnceCallback<void(internal::AsyncResultsT<T>)> success_callback,
    base::OnceCallback<void()> failure_callback) {
  return internal::PostAsyncOperationCompletedHandler(
      async_operation,
      base::BindOnce(
          [](base::OnceCallback<void(internal::AsyncResultsT<T>)>
                 success_callback,
             base::OnceCallback<void()> failure_callback,
             internal::IAsyncOperationT<T>* async_operation,
             AsyncStatus async_status) {
            internal::AsyncResultsT<T> results;
            HRESULT hr = internal::GetAsyncResultsT(async_operation,
                                                    async_status, &results);
            if (SUCCEEDED(hr))
              std::move(success_callback).Run(results);
            else
              std::move(failure_callback).Run();
          },
          std::move(success_callback), std::move(failure_callback)));
}

// Registers an internal completion handler for |async_operation| and upon
// successful completion invokes the |success_callback| with the result. If the
// |async_operation| encounters an error the |failure_callback| will instead be
// invoked with the failing HRESULT. Returns an HRESULT indicating the success
// of registering the internal completion handler.
//
// Callers need to ensure that this method is invoked in the correct COM
// apartment, i.e. the one that created |async_operation|. The resulting
// callback (|success_callback| or |failure_callback|) will be run on the same
// sequence that invoked this method. This call does not ensure the lifetime of
// the |async_operation|, which must be done by the caller.
template <typename T>
HRESULT PostAsyncHandlers(
    internal::IAsyncOperationT<T>* async_operation,
    base::OnceCallback<void(internal::AsyncResultsT<T>)> success_callback,
    base::OnceCallback<void(HRESULT)> failure_callback) {
  return internal::PostAsyncOperationCompletedHandler(
      async_operation,
      base::BindOnce(
          [](base::OnceCallback<void(internal::AsyncResultsT<T>)>
                 success_callback,
             base::OnceCallback<void(HRESULT)> failure_callback,
             internal::IAsyncOperationT<T>* async_operation,
             AsyncStatus async_status) {
            internal::AsyncResultsT<T> results;
            HRESULT hr = internal::GetAsyncResultsT(async_operation,
                                                    async_status, &results);
            if (SUCCEEDED(hr))
              std::move(success_callback).Run(results);
            else
              std::move(failure_callback).Run(hr);
          },
          std::move(success_callback), std::move(failure_callback)));
}

// Registers an internal completion handler for |async_operation| and upon
// successful completion invokes the |success_callback| with the result. If the
// |async_operation| encounters an error the |failure_callback| will instead be
// invoked with the result and an HRESULT indicating the success of fetching
// that result (NOT an HRESULT expressing the failure of the operation). Returns
// an HRESULT indicating the success of registering the internal completion
// handler.
//
// This overload is designed for (uncommon) operations whose results encapsulate
// success and failure information (and as a result of that are expected to be
// available under both success and failure conditions).
//
// Callers need to ensure that this method is invoked in the correct COM
// apartment, i.e. the one that created |async_operation|. The resulting
// callback (|success_callback| or |failure_callback|) will be run on the same
// sequence that invoked this method. This call does not ensure the lifetime of
// the |async_operation|, which must be done by the caller.
template <typename T>
HRESULT PostAsyncHandlers(
    internal::IAsyncOperationT<T>* async_operation,
    base::OnceCallback<void(internal::AsyncResultsT<T>)> success_callback,
    base::OnceCallback<void(HRESULT, internal::AsyncResultsT<T>)>
        failure_callback) {
  return internal::PostAsyncOperationCompletedHandler(
      async_operation,
      base::BindOnce(
          [](base::OnceCallback<void(internal::AsyncResultsT<T>)>
                 success_callback,
             base::OnceCallback<void(HRESULT, internal::AsyncResultsT<T>)>
                 failure_callback,
             internal::IAsyncOperationT<T>* async_operation,
             AsyncStatus async_status) {
            internal::AsyncResultsT<T> results;
            HRESULT hr = internal::GetAsyncResultsT(
                async_operation,
                async_status == AsyncStatus::Error ? AsyncStatus::Completed
                                                   : async_status,
                &results);
            if (SUCCEEDED(hr) && async_status == AsyncStatus::Completed)
              std::move(success_callback).Run(results);
            else
              std::move(failure_callback).Run(hr, results);
          },
          std::move(success_callback), std::move(failure_callback)));
}

// Deprecated.
//
// Registers an internal completion handler for |async_operation| and upon
// invocation, posts the results to the provided |callback|. Returns an HRESULT
// indicating the success of registering the internal completion handler.
//
// Callers need to ensure that this method is invoked in the correct COM
// apartment, i.e. the one that created |async_operation|. The |callback| will
// be run on the same sequence that invoked this method.
//
// WARNING: This call holds a reference to the provided |async_operation| until
// it completes.
template <typename T>
HRESULT PostAsyncResults(
    Microsoft::WRL::ComPtr<internal::IAsyncOperationT<T>> async_operation,
    base::OnceCallback<void(internal::AsyncResultsT<T>)> callback) {
  return internal::PostAsyncOperationCompletedHandler(
      async_operation.Get(),
      base::BindOnce(
          [](Microsoft::WRL::ComPtr<internal::IAsyncOperationT<T>>
                 original_async_operation,
             base::OnceCallback<void(internal::AsyncResultsT<T>)> callback,
             internal::IAsyncOperationT<T>* async_operation,
             AsyncStatus async_status) {
            DCHECK(original_async_operation.Get() == async_operation);
            if (async_status != AsyncStatus::Completed) {
              VLOG(2) << "Got unexpected AsyncStatus: "
                      << internal::ToCString(async_status);
            }

            internal::AsyncResultsT<T> results;
            HRESULT hr = internal::GetAsyncResultsT(async_operation,
                                                    async_status, &results);
            if (FAILED(hr)) {
              VLOG(2) << "GetAsyncResultsT failed: "
                      << logging::SystemErrorCodeToString(hr);
            }
            std::move(callback).Run(results);
          },
          async_operation, std::move(callback)));
}

}  // namespace win
}  // namespace base

#endif  // BASE_WIN_POST_ASYNC_RESULTS_H_
