%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%%
%%----------------------------------------------------------------------
%% Purpose : megaco/H.248 customization of generic event tracer
%%----------------------------------------------------------------------

-module(megaco_filter).

-export([start/0, start/1, filter/1,
	 pretty_error/1, string_to_term/1]).

-include_lib("megaco/include/megaco.hrl").
-include_lib("megaco/include/megaco_message_v1.hrl").
-include("megaco_internal.hrl").
-include_lib("et/include/et.hrl").

start() ->
    start([]).

start(ExtraOptions) ->
    Options =
	[{event_order, event_ts},
	 {scale, 3},
	 {max_actors, infinity},
	 {trace_pattern, {megaco, max}},
	 {trace_global, true},
	 {dict_insert, {filter, ?MODULE}, fun filter/1},
	 {active_filter, ?MODULE},
	 {title, "Megaco tracer - Erlang/OTP"} | ExtraOptions],
    et_viewer:start(Options).

filter(E) when record(E, event) ->
    From = filter_actor(E#event.from),
    To 	 = filter_actor(E#event.to),
    E2 	 = E#event{from = From, to = To},
    E3 	 = filter_contents(E#event.contents, E2, []),
    {true, E3}.

filter_actors(From, To, E) when E#event.from == ?APPLICATION,
				E#event.to   == ?APPLICATION ->
    Label = E#event.label,
    case lists:prefix("callback:", Label) of
	true ->
	    E#event{from = filter_actor(From),
		    to   = filter_user_actor(From)};
	false ->
	    case lists:prefix("return:", Label) of
		true ->
		    E#event{from = filter_user_actor(From),
			    to   = filter_actor(From)};
		false ->
		    case lists:prefix("receive bytes", Label) of
			true ->
			    E#event{from = filter_actor(To),
				    to   = filter_actor(From)};
			false ->
			    E#event{from = filter_actor(From),
				    to   = filter_actor(To)}
		    end
	    end
    end;
filter_actors(_From, _To, E) ->
    E.

filter_actor(Actor) ->
    String = do_filter_actor(Actor),
    if
	length(String) > 21 ->
	    string:substr(String, 1, 21) ++ [$*];
	true ->
	    String
    end.
	
filter_user_actor(Actor) ->
    String = do_filter_actor(Actor) ++ "@user",
    if
	length(String) > 21 ->
	    string:substr(String, 1, 21) ++ [$*];
	true ->
	    String
    end.
	
do_filter_actor(CH) when record(CH, megaco_conn_handle) ->
    Mid = CH#megaco_conn_handle.local_mid,
    do_filter_actor(Mid);
do_filter_actor(Actor) ->
    case Actor of
	{ip4Address, {'IP4Address', [A1,A2,A3,A4], asn1_NOVALUE}} ->
	    integer_to_list(A1) ++ [$.] ++
	    integer_to_list(A2) ++ [$.] ++
	    integer_to_list(A3) ++ [$.] ++
	    integer_to_list(A4);
	{ip4Address, {'IP4Address', [A1,A2,A3,A4], Port}} ->	
	    integer_to_list(A1) ++ [$.] ++
	    integer_to_list(A2) ++ [$.] ++
	    integer_to_list(A3) ++ [$.] ++
	    integer_to_list(A4) ++ [$:] ++
	    integer_to_list(Port);
	{domainName, {'DomainName', Name, asn1_NOVALUE}} ->
	    Name;
	{domainName, {'DomainName', Name, Port}} ->
	    Name ++ [$:] ++ integer_to_list(Port);
	{deviceName, Name} ->
	    Name;
	unknown_remote_mid ->
	    "preliminary_mid";
	preliminary_mid ->
	    "preliminary_mid";
	megaco ->
	    megaco;
	_Other ->
	    "UNKNOWN"
    end.

filter_contents([], E, Contents) ->
    E#event{contents = lists:flatten(lists:reverse(Contents))};
filter_contents([H | T], E, Contents) ->
    case H of
	{line, _Mod, _Line} ->
	    filter_contents(T, E, Contents);
	CD when record(CD, conn_data) ->
	    CH = CD#conn_data.conn_handle,
	    From = CH#megaco_conn_handle.local_mid,
	    To   = CH#megaco_conn_handle.remote_mid,
	    E2 = filter_actors(From, To, E),
	    Serial = CD#conn_data.serial,
	    E3 = append_serial(Serial, E2),
	    filter_contents(T, E3, Contents);
	CH when record(CH, megaco_conn_handle) ->
	    From = CH#megaco_conn_handle.local_mid,
	    To   = CH#megaco_conn_handle.remote_mid,
	    E2 = filter_actors(From, To, E),
	    filter_contents(T, E2, Contents);
	{orig_conn_handle, _CH} ->
	    filter_contents(T, E, Contents);
	RH when record(RH, megaco_receive_handle) ->
	    Actor = RH#megaco_receive_handle.local_mid,
	    E2 = filter_actors(Actor, Actor, E),
	    filter_contents(T, E2, Contents);
	{pid, Pid} when pid(Pid) ->
	    filter_contents(T, E, Contents);
	pending ->
	    filter_contents(T, E, Contents);
	reply ->
	    filter_contents(T, E, Contents);
	{error, Reason} ->
	    Pretty = pretty_error({error, Reason}),
	    E2 = prepend_error(E),
	    filter_contents(T, E2, [[Pretty, "\n"], Contents]);
	{'EXIT', Reason} ->
	    Pretty = pretty_error({'EXIT', Reason}),
	    E2 = prepend_error(E),
	    filter_contents(T, E2, [[Pretty, "\n"], Contents]);
	ED when record(ED, 'ErrorDescriptor') ->
	    Pretty = pretty_error(ED),
	    E2 = prepend_error(E),
	    filter_contents(T, E2, [[Pretty, "\n"], Contents]);
	Trans when record(Trans, 'TransactionRequest') ->
	    Pretty = pretty({trans, {transactionRequest, Trans}}),
	    filter_contents([], E, [[Pretty, "\n"], Contents]);
	Trans when record(Trans, 'TransactionReply') ->
	    Pretty = pretty({trans, {transactionReply, Trans}}),
	    filter_contents([], E, [[Pretty, "\n"], Contents]);
	Trans when record(Trans, 'TransactionPending') ->	    
	    Pretty = pretty({trans, {transactionPending, Trans}}),
	    filter_contents([], E, [[Pretty, "\n"], Contents]);
	Trans when record(Trans, 'TransactionAck') ->	    
	    Pretty = pretty({trans, {transactionResponseAck, [Trans]}}),
	    case Trans#'TransactionAck'.lastAck of
		asn1_NOVALUE ->
		    filter_contents([], E, [[Pretty, "\n"], Contents]);
		Last ->
		    Label = term_to_string(E#event.label),
		    E2 = E#event{label = Label ++ ".." ++ integer_to_list(Last)},
		    filter_contents([], E2, [[Pretty, "\n"], Contents])
	    end;
	{context_id, _ContextId} ->
	    Pretty = pretty(H),
	    filter_contents(T, E, [[Pretty, "\n"], Contents]);
	{command_request, CmdReq} ->
	    Pretty = pretty(CmdReq),
	    filter_contents(T, E, [[Pretty, "\n"], Contents]);
	{user_reply, {ok, ARS}} ->
	    Pretty = [[pretty(AR), "\n"] || AR <- ARS],
	    filter_contents(T, E, [["REPLY: \n", Pretty, "\n"], Contents]);
	{user_reply, Error} ->
	    Pretty = pretty_error(Error),
	    filter_contents(T, E, [["REPLY: \n", Pretty, "\n"], Contents]);
	{actionReplies, ARS} ->
	    Pretty = [[pretty(AR), "\n"] || AR <- ARS],
	    filter_contents(T, E, [["REPLY: \n", Pretty, "\n"], Contents]);
	MegaMsg when record(MegaMsg, 'MegacoMessage') ->
	    Pretty = pretty(MegaMsg),
	    filter_contents(T, E, [["MESSAGE: \n", Pretty, "\n"], Contents]);
	{bytes, Bin} when binary(Bin) ->
            E2 = 
		case E#event.label of
		    [$s, $e, $n, $d, $ , $b, $y, $t, $e, $s | Tail] ->
			L = lists:concat(["send ", size(Bin), " bytes", Tail]),
			E#event{label = L};
		    [$r, $e, $c, $e, $i, $v, $e, $ , $b, $y, $t, $e, $s | Tail] ->
			L = lists:concat(["receive ", size(Bin), " bytes", Tail]),
			E#event{label = L};
		    _ ->
			E
		end,
	    CharList = erlang:binary_to_list(Bin),
	    filter_contents(T, E2, [[CharList , "\n"], Contents]);
	[] ->
	    filter_contents(T, E, Contents);
	{test_lib, _Mod, _Fun} ->
	    filter_contents(T, E, Contents);
	Other ->
	    Pretty = pretty(Other),
	    filter_contents(T, E, [[Pretty, "\n"], Contents])
    end.

append_serial(Serial, E) when integer(Serial) ->
    Label = term_to_string(E#event.label),
    E#event{label = Label ++ " #" ++ integer_to_list(Serial)};
append_serial(_Serial, E) ->
    E.

prepend_error(E) ->
    Label = term_to_string(E#event.label),
    E#event{label = "<ERROR> " ++ Label}.

pretty({context_id, ContextId}) ->
     if
	 ContextId == ?megaco_null_context_id ->
	     ["CONTEXT ID: -\n"];
	 ContextId == ?megaco_choose_context_id ->
	     ["CONTEXT ID: $\n"];
	 ContextId == ?megaco_all_context_id ->
	     ["CONTEXT ID: *\n"];
	 integer(ContextId) ->
	     ["CONTEXT ID: ",integer_to_list(ContextId), "\n"]
     end;
pretty(MegaMsg) when record(MegaMsg, 'MegacoMessage') ->
    case catch megaco_pretty_text_encoder:encode_message([], MegaMsg) of
	{ok, Bin} ->
	    term_to_string(Bin);
	_Bad ->
	    term_to_string(MegaMsg)
    end;
pretty(CmdReq) when record(CmdReq, 'CommandRequest') ->
    case catch megaco_pretty_text_encoder:encode_command_request(CmdReq) of
	{ok, IoList} ->
	    IoList2 = lists:flatten(IoList),
	    term_to_string(IoList2);
	_Bad ->
	    term_to_string(CmdReq)
    end;
pretty({complete_success, ContextId, RepList} = Res) ->
    ActRep = #'ActionReply'{contextId    = ContextId, 
			    commandReply = RepList},
    case catch megaco_pretty_text_encoder:encode_action_reply(ActRep) of
	{ok, IoList} ->
	    IoList2 = lists:flatten(IoList),
	    term_to_string(IoList2);
	_Bad ->
	    term_to_string(Res)
   end;
pretty(AR) when record(AR, 'ActionReply') ->
    case catch megaco_pretty_text_encoder:encode_action_reply(AR) of
	{ok, IoList} ->
	    IoList2 = lists:flatten(IoList),
	    term_to_string(IoList2);
	_Bad ->
	    term_to_string(AR)
   end;
pretty({partial_failure, ContextId, RepList} = Res) ->
    ActRep = #'ActionReply'{contextId    = ContextId, 
			    commandReply = RepList},
    case catch megaco_pretty_text_encoder:encode_action_reply(ActRep) of
	{ok, IoList} ->
	    IoList2 = lists:flatten(IoList),
	    term_to_string(IoList2);
	_Bad ->
	    term_to_string(Res)
   end;
pretty({trans, Trans}) ->
    case catch megaco_pretty_text_encoder:encode_transaction(Trans) of
	{ok, Bin} when binary(Bin) ->
	    IoList2 = lists:flatten(binary_to_list(Bin)),
	    term_to_string(IoList2);
	{ok, IoList} ->
	    IoList2 = lists:flatten(IoList),
	    term_to_string(IoList2);
	_Bad ->
	    term_to_string(Trans)
    end;
pretty(Other) ->
    term_to_string(Other).

pretty_error({error, Reason}) ->
    ["ERROR: ", pretty_error(Reason)];
pretty_error({'EXIT', Reason}) ->
    ["EXIT: ", pretty_error(Reason)];
pretty_error({'ErrorDescriptor', Code, Reason}) ->
    ["CODE: ", integer_to_list(Code), " TEXT: ", pretty_error(Reason)];
pretty_error(Ugly) ->
    case string_to_term(Ugly) of
	{ok, Pretty} -> ["\n", Pretty];
	_            -> ["\n", term_to_string(Ugly)]
    end.

string_to_term(Chars) ->
    do_string_to_term([], Chars, 1).

do_string_to_term(Cont, Chars, Line) ->
    case catch erl_scan:tokens(Cont, Chars, Line) of
	{done, {ok, Tokens, _EndLine}, _Rest} ->
	    case erl_parse:parse_term(Tokens) of
		{ok, Term} ->
		    {ok, Term};
		{error, Reason} ->
		    {error, Reason}
	    end;
	{more, Cont2} ->
	    do_string_to_term(Cont2, ". ", Line);
	Other ->
	    {error, Other}
    end.

term_to_string(Bin) when binary(Bin) ->
    binary_to_list(Bin);
term_to_string(Term) ->
    case catch io_lib:format("~s", [Term]) of
        {'EXIT', _} -> lists:flatten(io_lib:format("~p", [Term]));
        GoodString  -> lists:flatten(GoodString)
    end.
