//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the MIT License.
//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
//    ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//    TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
//    PARTICULAR PURPOSE AND NONINFRINGEMENT.
//
//*********************************************************
//! @file
//! Helpers for invoking RPC functions and translating structured exceptions to HRESULTs or C++ exceptions
#ifndef __WIL_RPC_HELPERS_INCLUDED
#define __WIL_RPC_HELPERS_INCLUDED

#include "result.h"
#include "resource.h"
#include "wistd_functional.h"
#include "wistd_type_traits.h"

namespace wil
{

/// @cond
namespace details
{
    // This call-adapter template converts a void-returning 'wistd::invoke' into
    // an HRESULT-returning 'wistd::invoke' that emits S_OK. It can be eliminated
    // with 'if constexpr' when C++17 is in wide use.
    template <typename TReturnType>
    struct call_adapter
    {
        template <typename... TArgs>
        static HRESULT call(TArgs&&... args)
        {
            return wistd::invoke(wistd::forward<TArgs>(args)...);
        }
    };

    template <>
    struct call_adapter<void>
    {
        template <typename... TArgs>
        static HRESULT call(TArgs&&... args)
        {
            wistd::invoke(wistd::forward<TArgs>(args)...);
            return S_OK;
        }
    };

    // Some RPC exceptions are already HRESULTs. Others are in the regular Win32
    // error space. If the incoming exception code isn't an HRESULT, wrap it.
    constexpr HRESULT map_rpc_exception(DWORD code)
    {
        return IS_ERROR(code) ? code : __HRESULT_FROM_WIN32(code);
    }
} // namespace details
/// @endcond

/** Invokes an RPC method, mapping structured exceptions to HRESULTs
Failures encountered by the RPC infrastructure (such as server crashes, authentication
errors, client parameter issues, etc.) are emitted by raising a structured exception from
within the RPC machinery. This method wraps the requested call in the usual RpcTryExcept,
RpcTryCatch, and RpcEndExcept sequence then maps the exceptions to HRESULTs for the usual
flow control machinery to use.

Many RPC methods are defined as returning HRESULT themselves, where the HRESULT indicates
the result of the _work_. HRESULTs returned by a successful completion of the _call_ are
returned as-is.

RPC methods that have a return type of 'void' are mapped to returning S_OK when the _call_
completes successfully.

For example, consider an RPC interface method defined in idl as:
~~~
HRESULT GetKittenState([in, ref, string] const wchar_t* name, [out, retval] KittenState** state);
~~~
To call this method, use:
~~~
wil::unique_rpc_binding binding = // typically gotten elsewhere;
wil::unique_midl_ptr<KittenState> state;
HRESULT hr = wil::invoke_rpc_nothrow(GetKittenState, binding.get(), L"fluffy", state.put());
RETURN_IF_FAILED(hr);
~~~
*/
template <typename... TCall>
HRESULT invoke_rpc_nothrow(TCall&&... args) WI_NOEXCEPT
{
    RpcTryExcept
    {
        // Note: this helper type can be removed with C++17 enabled via
        // 'if constexpr(wistd::is_same_v<void, result_t>)'
        using result_t = typename wistd::__invoke_of<TCall...>::type;
        RETURN_IF_FAILED(details::call_adapter<result_t>::call(wistd::forward<TCall>(args)...));
        return S_OK;
    }
    RpcExcept(RpcExceptionFilter(RpcExceptionCode()))
    {
        RETURN_HR(details::map_rpc_exception(RpcExceptionCode()));
    }
    RpcEndExcept
}

/** Invokes an RPC method, mapping structured exceptions to HRESULTs
Failures encountered by the RPC infrastructure (such as server crashes, authentication
errors, client parameter issues, etc.) are emitted by raising a structured exception from
within the RPC machinery. This method wraps the requested call in the usual RpcTryExcept,
RpcTryCatch, and RpcEndExcept sequence then maps the exceptions to HRESULTs for the usual
flow control machinery to use.

Some RPC methods return results (such as a state enumeration or other value) directly in
their signature. This adapter writes that result into a caller-provided object then
returns S_OK.

For example, consider an RPC interface method defined in idl as:
~~~
GUID GetKittenId([in, ref, string] const wchar_t* name);
~~~
To call this method, use:
~~~
wil::unique_rpc_binding binding = // typically gotten elsewhere;
GUID id;
HRESULT hr = wil::invoke_rpc_result_nothrow(id, GetKittenId, binding.get(), L"fluffy");
RETURN_IF_FAILED(hr);
~~~
*/
template <typename TResult, typename... TCall>
HRESULT invoke_rpc_result_nothrow(TResult& result, TCall&&... args) WI_NOEXCEPT
{
    RpcTryExcept
    {
        result = wistd::invoke(wistd::forward<TCall>(args)...);
        return S_OK;
    }
    RpcExcept(RpcExceptionFilter(RpcExceptionCode()))
    {
        RETURN_HR(details::map_rpc_exception(RpcExceptionCode()));
    }
    RpcEndExcept
}

/// @cond
namespace details
{
    // Provides an adapter around calling the context-handle-close method on an
    // RPC interface, which itself is an RPC call.
    template <typename TStorage, typename close_fn_t, close_fn_t close_fn>
    struct rpc_closer_t
    {
        static void Close(TStorage arg) WI_NOEXCEPT
        {
            LOG_IF_FAILED(invoke_rpc_nothrow(close_fn, &arg));
        }
    };
} // namespace details
/// @endcond

/** Manages explicit RPC context handles
Explicit RPC context handles are used in many RPC interfaces. Most interfaces with
context handles have an explicit `FooClose([in, out] CONTEXT*)` method that lets
the server close out the context handle. As the close method itself is an RPC call,
it can fail and raise a structured exception.

This type routes the context-handle-specific `Close` call through the `invoke_rpc_nothrow`
helper, ensuring correct cleanup and lifecycle management.
@code
// Assume the interface has two methods:
// HRESULT OpenFoo([in] handle_t binding, [out] FOO_CONTEXT*);
// HRESULT UseFoo([in] FOO_CONTEXT context;
// void CloseFoo([in, out] PFOO_CONTEXT);
using unique_foo_context = wil::unique_rpc_context_handle<FOO_CONTEXT, decltype(&CloseFoo), CloseFoo>;
unique_foo_context context;
RETURN_IF_FAILED(wil::invoke_rpc_nothrow(OpenFoo, m_binding.get(), context.put()));
RETURN_IF_FAILED(wil::invoke_rpc_nothrow(UseFoo, context.get()));
context.reset();
@endcode
*/
template <typename TContext, typename close_fn_t, close_fn_t close_fn>
using unique_rpc_context_handle =
    unique_any<TContext, decltype(&details::rpc_closer_t<TContext, close_fn_t, close_fn>::Close), details::rpc_closer_t<TContext, close_fn_t, close_fn>::Close>;

#ifdef WIL_ENABLE_EXCEPTIONS
/** Invokes an RPC method, mapping structured exceptions to C++ exceptions
See `wil::invoke_rpc_nothrow` for additional information.  Failures during the _call_
and those returned by the _method_ are mapped to HRESULTs and thrown inside a
wil::ResultException. Using the example RPC method provided above:
@code
wil::unique_midl_ptr<KittenState> state;
wil::invoke_rpc(GetKittenState, binding.get(), L"fluffy", state.put());
// use 'state'
@endcode
*/
template <typename... TCall>
void invoke_rpc(TCall&&... args)
{
    THROW_IF_FAILED(invoke_rpc_nothrow(wistd::forward<TCall>(args)...));
}

/** Invokes an RPC method, mapping structured exceptions to C++ exceptions
See `wil::invoke_rpc_result_nothrow` for additional information. Failures during the
_call_ are mapped to HRESULTs and thrown inside a `wil::ResultException`. Using the
example RPC method provided above:
@code
GUID id = wil::invoke_rpc_result(GetKittenId, binding.get());
// use 'id'
@endcode
*/
template <typename... TCall>
auto invoke_rpc_result(TCall&&... args)
{
    using result_t = typename wistd::__invoke_of<TCall...>::type;
    result_t result{};
    THROW_IF_FAILED(invoke_rpc_result_nothrow(result, wistd::forward<TCall>(args)...));
    return result;
}
#endif
} // namespace wil

#endif
