%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2001-2024. 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: Define event transforms and trace patterns 
%%----------------------------------------------------------------------

-module(et_selector).
-moduledoc "Define event transforms and trace patterns".

-export([make_pattern/1,
         change_pattern/1,
         parse_event/2]).

-compile([{nowarn_deprecated_function,[{erlang,now,0}]}]).

-include("../include/et.hrl").

-type event() :: #event{}.

-type level() :: 0..100.

%%----------------------------------------------------------------------
%% make_pattern(RawPattern) -> TracePattern
%%
%% Makes a trace pattern suitable to feed change_pattern/1
%% See also erlang:trace_pattern/2 for more info about its match_spec()
%%
%% RawPattern = detail_level()
%% TracePattern = erlang_trace_pattern_match_spec()
%%
%% detail_level() = min | max | integer(X) when X >= 0, X =< 100
%%
%%   min       - minimum level of tracing  (ignore calls to trace_me/4,5)
%%   max       - maximum level of tracing  (all calls to trace_me/4,5)
%%   integer() - explicit detail level of tracing
%%----------------------------------------------------------------------
-doc """
make_pattern(RawPattern) -> TracePattern

Makes a trace pattern suitable to feed change_pattern/1

Min detail level deactivates tracing of calls to `et:trace_me/4,5`

Max detail level activates tracing of all calls to `et:trace_me/4,5`

integer(X) detail level activates tracing of all calls to `et:trace_me/4,5`
whose detail level argument is lesser than X.

See also `erlang:trace_pattern/2` for more info about its `match_spec()`
""".
-spec make_pattern({Mod::module(), RawPattern}) -> {Mod::module(), TracePattern} when
      RawPattern :: level(),
      TracePattern :: [{[term()] | '_' | atom(), [term()], [term()]}].
make_pattern(undefined) ->
    {undefined, undefined};
make_pattern({Mod, Pattern}) when is_atom(Mod) ->
    case Pattern of
	min ->
	    {Mod, []};
	max ->
	    Head = ['$1', '_', '_', '_', '_'],
	    Body = [],
	    Cond = [],
	    {Mod, [{Head, Cond, Body}]};
	DetailLevel when is_integer(DetailLevel) ->
	    Head = ['$1', '_', '_', '_', '_'],
	    Body = [],
	    Cond = [{ '<', '$1', DetailLevel}],
	    {Mod, [{Head, Cond, Body}]};
	undefined ->
	    {Mod, undefined};
	_ ->
	    exit({bad_pattern, Pattern})
    end.

%%----------------------------------------------------------------------
%% change_pattern(Pattern) -> ok
%%
%% Activates/deactivates tracing by changing the current trace pattern
%%
%% Pattern = detail_level() |
%%           empty_match_spec() |
%%           erlang_trace_pattern_match_spec()
%%
%% detail_level() = min | max | integer(X) when X =<0, X >= 100
%% empty_match_spec() = [] 
%%
%% Min detail level deactivates tracing of calls to trace_me/4,5
%%
%% Max detail level activates tracing of all calls to trace_me/4,5
%%
%% integer(X) detail level activates tracing of all calls to
%% trace_me/4,5 whose detail level argument is lesser than X.
%%
%% An empty match spec deactivates tracing of calls to trace_me/4,5
%%
%% Other match specs activates tracing of calls to trace_me/4,5
%% accordingly with erlang:trace_pattern/2.
%%----------------------------------------------------------------------
-doc """
change_pattern(Pattern) -> ok

Activates/deactivates tracing by changing the current trace pattern.

`min` detail level deactivates tracing of calls to `et:trace_me/4,5`

`max` detail level activates tracing of all calls to `et:trace_me/4,5`

`integer(X)` detail level activates tracing of all calls to `et:trace_me/4,5`
whose detail level argument is lesser than `X`.

An empty match spec deactivates tracing of calls to `et:trace_me/4,5`

Other match specs activates tracing of calls to `et:trace_me/4,5` accordingly
with `erlang:trace_pattern/2`.
""".
-spec change_pattern({Mod::module(), Pattern}) -> ok when
      Pattern :: DetailLevel | TracePattern | EmptyTracePattern,
      DetailLevel :: level(),
      TracePattern :: [{[term()] | '_' | atom(), [term()], [term()]}],
      EmptyTracePattern :: [].
change_pattern({Mod, Pattern}) when is_atom(Mod) ->
    MFA = {Mod, trace_me, 5},
    case Pattern of
	undefined ->
	    ignore;
        [] ->
            error_to_exit(old_ctp(MFA)),
            error_to_exit(dbg:ctp(MFA)),
            error_to_exit(dbg:p(all, clear));
        List when is_list(List) ->
            error_to_exit(old_ctp(MFA)),
            error_to_exit(old_tp(MFA, Pattern)),
            error_to_exit(dbg:ctp(MFA)),
            error_to_exit(dbg:tp(MFA, Pattern)),
            error_to_exit(dbg:p(all, [call, timestamp]));
        Other ->
            change_pattern(make_pattern({Mod, Other}))
    end,
    ok.

old_ctp({Mod, _Fun, Args}) ->
    case Mod of
	et -> {ok, ignore};
	_  -> dbg:ctp({Mod, report_event, Args})
    end.

old_tp({Mod, _Fun, Args}, Pattern) ->
    case Mod of
	et -> {ok, ignore};
	_  -> dbg:tp({Mod, report_event, Args}, Pattern)
    end.

error_to_exit({error, Reason}) ->
    exit(Reason);
error_to_exit({ok, _Res}) ->
    ok.

%%----------------------------------------------------------------------
%% parse_event(Mod, ValidTraceData) -> false | true | {true, Event}
%%
%% Transforms trace data and makes an event record out of it
%%
%% ValidTraceData = erlang_trace_data() | record(event)
%% Mod = module_name() | undefined
%% module_name() = atom()
%%
%% erlang_trace_data() =
%% 
%%     {trace, Pid, Label, Info} | 
%%     {trace, Pid, Label, Info, Extra} | 
%%     {trace_ts, Pid, Label, Info, ReportedTS} | 
%%     {trace_ts, Pid, Label, Info, Extra, ReportedTS} | 
%%     {seq_trace, Label, Info} | 
%%     {seq_trace, Label, Info, ReportedTS} | 
%%     {drop, NumberOfDroppedItems}
%%
%% See erlang:trace/3 for more info about the semantics of
%% the trace data.
%%
%% An event record consists of the following fields:
%%
%%     detail_level - Noise has a high level as opposed to essentials.
%%     trace_ts     - Time when the trace was generated.
%%                    Same as event_ts if omitted in trace data.
%%     event_ts     - Time when the event record was created.
%%     from         - From actor, such as sender of a message.
%%     to           - To actor, such as receiver of message.
%%     label        - Label intended to provide a brief event summary.
%%     contents     - All nitty gritty details of the event.
%%
%% See et:trace_me/4 and et:trace_me/5 for details.
%%     
%% Returns: 
%%
%%   {true, Event} - where Event is an #event{} record representing the
%%                   trace data
%%   true          - means that the trace data already is an event
%%                   record and that it is valid as it is.
%%                   No transformation is needed.
%%   false         - means that the trace data is uninteresting and
%%                   should be dropped
%%----------------------------------------------------------------------
-doc """
parse_event(Mod, ValidTraceData) -> false | true | {true, Event}

Transforms trace data and makes an event record out of it.

See `erlang:trace/3` for more info about the semantics of the trace data.

An event record consists of the following fields:

- **_detail_level_** - Noise has a high level as opposed to essentials.

- **_trace_ts_** - Time when the trace was generated. Same as event_ts if
  omitted in trace data.

- **_event_ts_** - Time when the event record was created.

- **_from_** - From actor, such as sender of a message.

- **_to_** - To actor, such as receiver of message.

- **_label_** - Label intended to provide a brief event summary.

- **_contents_** - All nitty gritty details of the event.

See `et:trace_me/4`and `et:trace_me/5` for details.

Returns:

- **_\{true, Event\}_** - where Event is an #event\{\} record representing the
  trace data

- **_true_** - means that the trace data already is an event record and that it
  is valid as it is. No transformation is needed.

- **_false_** - means that the trace data is uninteresting and should be dropped
""".
-spec parse_event(Mod, ValidTraceData) -> boolean | {true, event()} when
      Mod :: module(),
      ValidTraceData :: ErlangTraceData | event(),
      ErlangTraceData ::
        {trace, pid(), atom(), term()} |
        {trace, pid(), atom(), term(), term()} |
        {trace_ts, pid(), atom(), term(), TS::{integer(), integer(), integer()}} |
        {trace_ts, pid(), atom(), term(), term(), TS::{integer(), integer(), integer()}} |
        {seq_trace, atom(), term()} |
        {seq_trace, atom(), term(), TS::{integer(), integer(), integer()}} |
        {drop, integer()}.
parse_event(_Mod, E) when is_record(E, event) ->
    true;
parse_event(Mod, Trace) ->
    ParsedTS = erlang:now(),
    case Trace of
        {trace, Pid, Label, Info} ->
            parse_event(Mod, Trace, ParsedTS, ParsedTS, Pid, Label, [Info]);
        {trace, Pid, Label, Info, Extra} ->
            parse_event(Mod, Trace, ParsedTS, ParsedTS, Pid, Label, [Info, Extra]);
        {trace_ts, Pid, Label, Info, ReportedTS} ->
            parse_event(Mod, Trace, ParsedTS, ReportedTS, Pid, Label, [Info]);
        {trace_ts, Pid, Label, Info, Extra, ReportedTS} ->
            parse_event(Mod, Trace, ParsedTS, ReportedTS, Pid, Label, [Info, Extra]);
        {seq_trace, Label, Info} ->
            parse_seq_event(Trace, ParsedTS, ParsedTS, Label, Info);
        {seq_trace, Label, Info, ReportedTS} ->
            parse_seq_event(Trace, ParsedTS, ReportedTS, Label, Info);
        {drop, NumberOfDroppedItems} ->
            DetailLevel = 20,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ParsedTS,
                          event_ts     = ParsedTS,
                          from         = undefined,
                          to           = undefined, 
                          label        = drop, 
                          contents     = [{label, drop},
					  {detail_level, DetailLevel},
					  {from, undefined},
					  {to, undefined},
					  {drop, NumberOfDroppedItems}]}};
        _ ->
            error_logger:format("~p(~p): Ignoring unknown trace type -> ~tp~n~n",
                                [?MODULE, ?LINE, Trace]),
            false
    end.

parse_seq_event(Trace, ParsedTS, ReportedTS, Label, Info) ->
    case Info of
        {send, Serial, From, To, Msg} ->
            DetailLevel = 15,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS, 
                          event_ts     = ParsedTS,
                          from         = From, 
                          to           = To, 
                          label        = {seq_send, Label},
                          contents     = [{label, {seq_send, Label}},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, To},
					  {serial, Serial},
					  {msg, Msg}]}};
        {'receive', Serial, From, To, Msg} ->
            DetailLevel = 10,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From, 
                          to           = To,
                          label        = {seq_receive, Label}, 
                          contents     = [{label, {seq_receive, Label}},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, To},
					  {serial, Serial},
					  {msg, Msg}]}};
        {print, Serial, From, _, UserInfo} ->
            DetailLevel = 5,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS, 
                          event_ts     = ParsedTS,
                          from         = From, 
                          to           = From, 
                          label        = {seq_print, Label},
                          contents     = [{label, {seq_print, Label}},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {serial, Serial},
					  {user_info, UserInfo}]}};
        _ ->
            error_logger:format("~p(~p): Ignoring unknown trace type -> ~tp~n~n",
                                [?MODULE, ?LINE, Trace]),
            false
    end.

parse_event(Mod, Trace, ParsedTS, ReportedTS, From, Label, Contents) ->
    case Label of
        'receive' ->
            DetailLevel = 35,
            [Msg] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {msg, Msg}]}};
        send ->
            DetailLevel = 40,
            [Msg, To] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = To,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, To},
					  {msg, Msg}]}};
        send_to_non_existing_process ->
            DetailLevel = 40,
            [Msg, To] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = To,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, To},
					  {msg, Msg}]}};
        call ->
            case Contents of
                [{M, trace_me, [UserDetailLevel, UserFrom, UserTo, UserLabel, UserContents]}] when M == Mod, Mod /= undefined ->
                    {true, #event{detail_level = UserDetailLevel,
                                  trace_ts     = ReportedTS,
                                  event_ts     = ParsedTS,
                                  from         = UserFrom,
                                  to           = UserTo,
                                  label        = UserLabel,
                                  contents     = UserContents}}; % Term
                [{M, report_event, [UserDetailLevel, UserFrom, UserTo, UserLabel, UserContents]}] when M == Mod, Mod /= undefined ->
                    {true, #event{detail_level = UserDetailLevel,
                                  trace_ts     = ReportedTS,
                                  event_ts     = ParsedTS,
                                  from         = UserFrom,
                                  to           = UserTo,
                                  label        = UserLabel,
                                  contents     = UserContents}}; % Term
                [MFA] ->
                    DetailLevel = 45,
                    {true, #event{detail_level = DetailLevel,
                                  trace_ts     = ReportedTS,
                                  event_ts     = ParsedTS,
                                  from         = From,
                                  to           = From,
                                  label        = Label,
                                  contents     = [{label, Label},
						  {detail_level, DetailLevel},
						  {from, From},
						  {to, From},
						  {mfa, MFA}]}};
                [MFA, PamResult] ->
                    DetailLevel = 45,
                    {true, #event{detail_level = DetailLevel,
                                  trace_ts     = ReportedTS,
                                  event_ts     = ParsedTS,
                                  from         = From,
                                  to           = From,
                                  label        = Label,
                                  contents     = [{label, Label},
						  {detail_level, DetailLevel},
						  {from, From},
						  {to, From},
						  {mfa, MFA},
						  {pam_result, PamResult}]}}
            end;
        return_to ->
            DetailLevel = 50,
            [MFA] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {mfa, MFA}]}};
        return_from ->
            DetailLevel = 52,
            [MFA, ReturnValue] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {mfa, MFA},
					  {return, ReturnValue}]}};
        exception_from ->
            DetailLevel = 54,
            [MFA, Exception] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {mfa, MFA},
					  {exception, Exception}]}};
        spawn ->
            DetailLevel = 25,
            [NewPid, MFA] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = NewPid,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, NewPid},
					  {mfa, MFA}]}};  % MFA | Term
        exit ->
            DetailLevel = 30,
            [Reason] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {reason, Reason}]}};
        link ->
            DetailLevel = 55,
            [LinkTo] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = LinkTo,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, LinkTo}]}};
        unlink ->
            DetailLevel = 60,
            [UnlinkFrom] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = UnlinkFrom,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, UnlinkFrom}]}};
        getting_linked ->
            DetailLevel = 65,
            [LinkTo] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = LinkTo,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, LinkTo}]}};
        getting_unlinked ->
            DetailLevel = 67,
            [UnlinkFrom] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = UnlinkFrom,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, UnlinkFrom}]}};
        register ->
            DetailLevel = 70,
            [Name] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {name, Name}]}};
        unregister ->
            DetailLevel = 75,
            [Name] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {name, Name}]}};
        in ->
            DetailLevel = 90,
            [MFA] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {mfa, MFA}]}}; % MFA | 0
        out ->
            DetailLevel = 95,
            [MFA] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {mfa, MFA}]}}; % MFA | 0
        gc_minor_start ->
            DetailLevel = 80,
            [GcKeyValueList] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
					  {detail_level, DetailLevel},
					  {from, From},
					  {to, From},
					  {gc_items, GcKeyValueList}]}};
        gc_minor_end ->
            DetailLevel = 85,
            [GcKeyValueList] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
                                          {detail_level, DetailLevel},
                                          {from, From},
                                          {to, From},
                                          {gc_items, GcKeyValueList}]}};
        gc_major_start ->
            DetailLevel = 80,
            [GcKeyValueList] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
                                          {detail_level, DetailLevel},
                                          {from, From},
                                          {to, From},
                                          {gc_items, GcKeyValueList}]}};
        gc_major_end ->
            DetailLevel = 85,
            [GcKeyValueList] = Contents,
            {true, #event{detail_level = DetailLevel,
                          trace_ts     = ReportedTS,
                          event_ts     = ParsedTS,
                          from         = From,
                          to           = From,
                          label        = Label,
                          contents     = [{label, Label},
                                          {detail_level, DetailLevel},
                                          {from, From},
                                          {to, From},
                                          {gc_items, GcKeyValueList}]}};
        _ ->
            error_logger:format("~p(~p): Ignoring unknown trace type -> ~tp~n~n",
                                [?MODULE, ?LINE, Trace]),
            false
    end.
