%% -------------------------------------------------------------------
%%
%% Copyright (c) 2018 Christopher S. Meiklejohn. All Rights Reserved.
%% Copyright (c) 2022 Alejandro M. Ramallo. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License.  You may obtain
%% a copy of the License at
%%
%%   http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied.  See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
-module(partisan_rpc).
-include("partisan.hrl").
-type error_reason()    ::  timeout | any().
%% API
-export([call/4]).
-export([call/5]).
-export([prepare_opts/1]).
-dialyzer([{nowarn_function, call/4}, no_return]).
-dialyzer([{nowarn_function, call/5}, no_return]).
%% =============================================================================
%% API
%% =============================================================================
%% -----------------------------------------------------------------------------
%% @doc
%% @end
%% -----------------------------------------------------------------------------
-spec call(
    Node :: node(),
    Module :: module(),
    Function :: atom(),
    Arguments :: [any()]) -> Reply :: any() | {badrpc, error_reason()}.
call(Node, Module, Function, Arguments) ->
    call(Node, Module, Function, Arguments, infinity).
%% -----------------------------------------------------------------------------
%% @doc
%% @end
%% -----------------------------------------------------------------------------
-spec call(
    Node :: node(),
    Module :: module(),
    Function :: atom(),
    Arguments :: [any()],
    Timeout :: timeout() | partisan_peer_service_manager:forward_opts()) ->
    Reply :: any() | {badrpc, error_reason()}.
call(Node, Module, Function, Arguments, Timeout) when ?IS_VALID_TMO(Timeout) ->
    call(Node, Module, Function, Arguments, #{timeout => Timeout});
call(Node, Module, Function, Arguments, Opts0) ->
    Self = partisan:self(),
    Timeout = maps:get(timeout, Opts0, ?DEFAULT_TIMEOUT),
    ?IS_VALID_TMO(Timeout) orelse error({?MODULE, badarg}),
    Opts = prepare_opts(
        partisan_config:get(forward_options, maps:without([timeout], Opts0))
    ),
    Msg = {call, Module, Function, Arguments, Timeout, {origin, Self}},
    case partisan:forward_message(Node, partisan_rpc_backend, Msg, Opts) of
        ok ->
            receive
                {rpc_response, Response} ->
                    Response
            after
                Timeout ->
                    {badrpc, timeout}
            end;
        {error, Reason} ->
            {badrpc, Reason}
    end.
%% -----------------------------------------------------------------------------
%% @doc
%% @end
%% -----------------------------------------------------------------------------
-spec prepare_opts(list() | map()) -> map().
prepare_opts(L) when is_list(L) ->
    prepare_opts(maps:from_list(L));
prepare_opts(#{channel := _} = Opts) ->
    Opts;
prepare_opts(Opts) when is_map(Opts) ->
    Opts#{channel => ?DEFAULT_CHANNEL}.
%% =============================================================================
%% PRIVATE
%% =============================================================================