%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2018-2021. All Rights Reserved.
%%
%% Licensed 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.
%%
%% %CopyrightEnd%
%%

%% Purpose : Main atomics API module.

-module(atomics).

-export([new/2,
         put/3, get/2,
         add/3, add_get/3,
         sub/3, sub_get/3,
         exchange/3, compare_exchange/4,
         info/1]).

-export_type([atomics_ref/0]).

-opaque atomics_ref() :: reference().

%% We must inline this function so that the stacktrace points to
%% the correct function.
-compile({inline, [error_with_info/2]}).

-define(OPT_SIGNED, (1 bsl 0)).
-define(OPT_DEFAULT, ?OPT_SIGNED).

-spec new(Arity, Opts) -> atomics_ref() when
      Arity :: pos_integer(),
      Opts :: [Opt],
      Opt :: {signed, boolean()}.
new(Arity, Opts) ->
    try
        EncodedOpts = encode_opts(Opts, ?OPT_DEFAULT),
        erts_internal:atomics_new(Arity, EncodedOpts)
    catch
        throw:badopt ->
            ExtraInfo = [{error_info, #{module => erl_erts_errors,
                                        cause => badopt}}],
            error(badarg, [Arity, Opts], ExtraInfo);
        error:Error ->
            error_with_info(Error, [Arity, Opts])
    end.

encode_opts([{signed, true}|T], Acc) ->
    encode_opts(T, Acc bor ?OPT_SIGNED);
encode_opts([{signed, false}|T], Acc) ->
    encode_opts(T, Acc band (bnot ?OPT_SIGNED));
encode_opts([], Acc) ->
    Acc;
encode_opts(_, _) ->
    throw(badopt).

-spec put(Ref, Ix, Value) -> ok when
      Ref  :: atomics_ref(),
      Ix :: integer(),
      Value :: integer().
put(_Ref, _Ix, _Value) ->
    erlang:nif_error(undef).

-spec get(Ref, Ix) -> integer() when
      Ref  :: atomics_ref(),
      Ix :: integer().
get(_Ref, _Ix) ->
    erlang:nif_error(undef).

-spec add(Ref, Ix, Incr) -> ok when
      Ref  :: atomics_ref(),
      Ix :: integer(),
      Incr :: integer().
add(_Ref, _Ix, _Incr) ->
    erlang:nif_error(undef).

-spec add_get(Ref, Ix, Incr) -> integer() when
      Ref  :: atomics_ref(),
      Ix :: integer(),
      Incr :: integer().
add_get(_Ref, _Ix, _Incr) ->
    erlang:nif_error(undef).

-spec sub(Ref, Ix, Decr) -> ok when
      Ref  :: atomics_ref(),
      Ix :: integer(),
      Decr :: integer().
sub(Ref, Ix, Decr) ->
    try
        ?MODULE:add(Ref, Ix, -Decr)
    catch
        error:Error ->
            error_with_info(Error, [Ref, Ix, Decr])
    end.

-spec sub_get(Ref, Ix, Decr) -> integer() when
      Ref  :: atomics_ref(),
      Ix :: integer(),
      Decr :: integer().
sub_get(Ref, Ix, Decr) ->
    try
        ?MODULE:add_get(Ref, Ix, -Decr)
    catch
        error:Error ->
            error_with_info(Error, [Ref, Ix, Decr])
    end.

-spec exchange(Ref, Ix, Desired) -> integer() when
      Ref  :: atomics_ref(),
      Ix :: integer(),
      Desired :: integer().
exchange(_Ref, _Ix, _Desired) ->
    erlang:nif_error(undef).

-spec compare_exchange(Ref, Ix, Expected, Desired) -> ok | integer() when
      Ref  :: atomics_ref(),
      Ix :: integer(),
      Expected :: integer(),
      Desired :: integer().
compare_exchange(_Ref, _Ix, _Expected, _Desired) ->
    erlang:nif_error(undef).

-spec info(Ref) -> Info when
      Ref  :: atomics_ref(),
      Info :: #{'size':=Size,'max':=Max,'min':=Min,'memory':=Memory},
      Size :: non_neg_integer(),
      Max :: integer(),
      Min :: integer(),
      Memory :: non_neg_integer().
info(_Ref) ->
    erlang:nif_error(undef).

error_with_info(Reason, Args) ->
    error(Reason, Args, [{error_info, #{module => erl_erts_errors}}]).
