%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1998-2023. 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%
%%

-module(seq_trace).

%% Don't forget to update seq_trace_SUITE after changing these.
-define(SEQ_TRACE_SEND, 1).                     %(1 << 0)
-define(SEQ_TRACE_RECEIVE, 2).                  %(1 << 1)
-define(SEQ_TRACE_PRINT, 4).                    %(1 << 2)
-define(SEQ_TRACE_NOW_TIMESTAMP, 8).            %(1 << 3)
-define(SEQ_TRACE_STRICT_MON_TIMESTAMP, 16).    %(1 << 4)
-define(SEQ_TRACE_MON_TIMESTAMP, 32).           %(1 << 5)

-export([set_token/1,
	 set_token/2,
	 get_token/0,
	 get_token/1,
	 print/1,
	 print/2,
	 reset_trace/0,
	 set_system_tracer/1,
	 get_system_tracer/0]).

%%---------------------------------------------------------------------------

-type flag()       :: 'send' | 'receive' | 'print' | 'timestamp' |
                      'monotonic_timestamp' | 'strict_monotonic_timestamp'.
-type component()  :: 'label' | 'serial' | flag().
-type value()      :: (Label :: term())
                    | {Previous :: non_neg_integer(),
                       Current :: non_neg_integer()}
                    | (Bool :: boolean()).

%%---------------------------------------------------------------------------

-type token() :: {integer(), boolean(), _, _, _}.
-spec set_token(Token) -> PreviousToken | 'ok' when
      Token :: [] | token(),
      PreviousToken :: [] | token().

set_token([]) ->
    erlang:seq_trace(sequential_trace_token,[]);
set_token({Flags,Label,Serial,_From,Lastcnt}) ->
    F = decode_flags(Flags),
    set_token2([{label,Label},{serial,{Lastcnt, Serial}} | F]).

-spec set_token(Component, Val) -> OldVal when
      Component :: component(),
      Val :: value(),
      OldVal :: value().

set_token(Type, Val) ->
    erlang:seq_trace(Type, Val).

-spec get_token() -> [] | token().

get_token() ->
    element(2,process_info(self(),sequential_trace_token)).

-spec get_token(Component) -> [] | {Component, Val} when
      Component :: component(),
      Val :: value().
get_token(Type) ->
    erlang:seq_trace_info(Type).

-spec print(TraceInfo) -> 'ok' when
      TraceInfo :: term().

print(Term) ->
    erlang:seq_trace_print(Term),
    ok.

-spec print(Label, TraceInfo) -> 'ok' when
      Label :: integer(),
      TraceInfo :: term().

print(Label, Term) when is_atom(Label) ->
    erlang:error(badarg, [Label, Term]);
print(Label, Term) ->
    erlang:seq_trace_print(Label, Term),
    ok.

-spec reset_trace() -> 'true'.

reset_trace() ->
    erlang:system_flag(reset_seq_trace, true).

%% reset_trace(Pid) -> % this might be a useful function too

-type tracer() :: (Pid :: pid()) | port() |
                  (TracerModule :: {module(), term()}) |
                  'false'.

-spec set_system_tracer(Tracer) -> OldTracer when
      Tracer :: tracer(),
      OldTracer :: tracer().

set_system_tracer({Module, State} = Tracer) ->
    case erlang:module_loaded(Module) of
        false ->
            Module:enabled(trace_status, erlang:self(), State);
        true ->
            ok
    end,
    erlang:system_flag(sequential_tracer, Tracer);
set_system_tracer(Tracer) ->
    erlang:system_flag(sequential_tracer, Tracer).

-spec get_system_tracer() -> Tracer when
      Tracer :: tracer().

get_system_tracer() ->
    element(2, erlang:system_info(sequential_tracer)).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% internal help functions 
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

set_token2([{Type,Val}|T]) ->
    _ = erlang:seq_trace(Type, Val),
    set_token2(T);
set_token2([]) ->
    ok.

decode_flags(Flags) ->
    Print = (Flags band ?SEQ_TRACE_PRINT) > 0,
    Send = (Flags band ?SEQ_TRACE_SEND) > 0,
    Rec = (Flags band ?SEQ_TRACE_RECEIVE) > 0,
    NowTs = (Flags band ?SEQ_TRACE_NOW_TIMESTAMP) > 0,
    StrictMonTs = (Flags band ?SEQ_TRACE_STRICT_MON_TIMESTAMP) > 0,
    MonTs = (Flags band ?SEQ_TRACE_MON_TIMESTAMP) > 0,
    [{print,Print},{send,Send},{'receive',Rec},{timestamp,NowTs},
     {strict_monotonic_timestamp, StrictMonTs},
     {monotonic_timestamp, MonTs}].
