%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2003-2013. 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: Implements an "MG" used by the test suite
%%----------------------------------------------------------------------
-module(megaco_test_mg).

-export([start/5, start/6, stop/1, 
	 get_stats/1, reset_stats/1, 
	 user_info/1, user_info/2, 
	 update_user_info/3, 
	 conn_info/1, conn_info/2, 
	 update_conn_info/3, 
	 service_change/1, 
	 ack_info/2, rep_info/2, 
	 group_requests/2, 
	 notify_request/1, 
	 await_notify_reply/1, 
	 notify_request_and_reply/1, 
	 cancel_request/2, 
	 apply_load/2, 
	 apply_multi_load/3, 
	 enable_test_code/4, 
	 encode_ar_first/2, 
	 verbosity/2]).

-export([mg/3, notify_request_handler_main/5]).
-export([loader_main/4]).

%% Megaco callback api
-export([
	 handle_connect/4,
	 handle_disconnect/5,
	 handle_syntax_error/5,
	 handle_message_error/5,
	 handle_trans_request/5,
	 handle_trans_long_request/5,
	 handle_trans_reply/6,
	 handle_trans_ack/6
	]).

-include("megaco_test_lib.hrl").
-include_lib("megaco/include/megaco.hrl").
-include_lib("megaco/include/megaco_message_v1.hrl").

-define(A4444, tid(255*256*256)                 ).
-define(A4445, tid(255*256*256 + 255)           ).
-define(A5555, tid(255*256*256 + 255*256)       ).
-define(A5556, tid(255*256*256 + 255*256 + 255) ).

-record(mg, {mid             = undefined,
	     state           = initiated,
	     req_handler     = undefined,
	     call_mode       = async,
	     group_size      = 1,
	     encode_ar_first = false,
	     ack_info        = undefined,
	     rep_info        = undefined,
	     load_counter    = 0,
	     reply_counter   = 0,
	     mload_info      = undefined,
	     parent          = undefined,
	     dsi_timer}).


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

start(Node, Mid, Encoding, Transport, Verbosity) ->
    %% Conf = [{megaco_trace, io}],
    %% Conf = [{megaco_trace, "megaco-mg.trace"}],
    Conf = [{megaco_trace, false}],
    start(Node, Mid, Encoding, Transport, Conf, Verbosity).

start(Node, Mid, Encoding, Transport, Conf, Verbosity) ->
    d("start mg[~p]: ~p"
      "~n   Encoding:  ~p"
      "~n   Transport: ~p"
      "~n   Conf:      ~p", [Node, Mid, Encoding, Transport, Conf]),
    RI1    = encoding_config(Encoding),
    RI2    = transport_config(Transport),
    {RI3, Conf1} = transport_opts(Conf), 
    RI     = {receive_info, RI1 ++ RI2 ++ RI3},
    Config = [{local_mid, Mid}, RI] ++ Conf1,
    Self   = self(),
    Fun    = 
	fun() ->
		io:format("LOADER(~p,~p) started~n", [self(),node()]),
		case (catch mg(Self, Verbosity, Config)) of
		    {'EXIT', Reason} ->
			io:format("LOADER(~p,~p) terminating with exit"
				  "~n~p"
				  "~n", [self(), node(), Reason]),
			exit(Reason);
		    Else ->
			io:format("LOADER(~p,~p) terminating with"
				  "~n~p"
				  "~n", [self(), node(), Else]),
			Else
		end
	end,
    true = erlang:monitor_node(Node, true),
    Pid = spawn_link(Node, Fun),
    %% Pid = spawn_link(Node, ?MODULE, mg, [self(), Verbosity, Config]),
    MonRef = (catch erlang:monitor(process, Pid)),
    NodePing = net_adm:ping(Node), 
    ProcInfo = (catch proc_info(Pid)), 
    i("start -> "
      "~n   self():           ~p"
      "~n   node():           ~p"
      "~n   net_adm:ping(~p): ~p" 
      "~n   Loader:           ~p"
      "~n   Monitor ref:      ~p"
      "~n   Process info:     ~p", 
      [self(), node(), 
       Node, NodePing, 
       Pid, 
       MonRef, ProcInfo]),
    await_started(Node, MonRef, Pid).

proc_info(Pid) ->
    rpc:call(node(Pid), erlang, process_info, [Pid]).

encoding_config({Encoding, EC}) when is_atom(Encoding) andalso is_list(EC) ->
    {Mod, Port} = select_encoding(Encoding),
    [{encoding_module, Mod},
     {encoding_config, EC},
     {port,            Port}];
encoding_config(Encoding) when is_atom(Encoding) ->
    {Mod, Port} = select_encoding(Encoding),
    [{encoding_module, Mod},
     {encoding_config, []},
     {port,            Port}];
encoding_config(Encoding) ->
    throw({error, {invalid_encoding, Encoding}}).

select_encoding(text) ->
    {megaco_pretty_text_encoder, 2944};
select_encoding(pretty_text) ->
    {megaco_pretty_text_encoder, 2944};
select_encoding(compact_text) ->
    {megaco_compact_text_encoder, 2944};
select_encoding(binary) ->
    {megaco_ber_encoder, 2945};
select_encoding(erl_dist) ->
    {megaco_erl_dist_encoder, 2946};
select_encoding(Encoding) ->
    throw({error, {invalid_encoding, Encoding}}).

transport_config(tcp) ->
    [{transport_module, megaco_tcp}];
transport_config(udp) ->
    [{transport_module, megaco_udp}];
transport_config(TransportConfig) when is_list(TransportConfig) ->
    {value, {transport, Trans}} = 
	lists:keysearch(transport, 1, TransportConfig),
    transport_config(Trans) ++ 
	case lists:keysearch(host, 1, TransportConfig) of
	    {value, Value} ->
		[Value];
	    false ->
		[]
	end.
    
transport_opts(Config) ->
    case lists:keysearch(transport_opts, 1, Config) of
	{value, TO} ->
	    Config1 = lists:keydelete(transport_opts, 1, Config),
	    {[TO], Config1};
	false ->
	    {[], Config}
    end.
									

await_started(Node, MonRef, Pid) ->
    i("await_started -> entry with"
      "~n   MonRef: ~p"
      "~n   Pid:    ~p", [MonRef, Pid]),
    receive
	{started, Pid} ->
	    d("await_started ~p - started"
	      "~n   Process info: ~p", [Pid, (catch proc_info(Pid))]),
	    true = erlang:monitor_node(Node, false),	    
	    erlang:demonitor(MonRef),
	    {ok, Pid};

	{nodedown, Node} ->
	    i("await_started ~p - received node down", [Pid]),
	    exit({node_down, Node}); 

	{'DOWN', MonRef, process, Pid, Info} ->
	    i("await_started ~p - received down signal: ~p", 
	      [Pid, Info]),
	    true = erlang:monitor_node(Node, false),	    
	    exit({failed_starting, Pid, Info});

	{'EXIT', Pid, Reason} ->
	    i("await_started ~p - received exit signal: ~p", [Pid, Reason]),
	    true = erlang:monitor_node(Node, false),	    
	    exit({failed_starting, Pid, Reason})

    %% This timeout was originally 10 secs, but on some debug compiled
    %% platforms, it was simply not long enough
    after 20000 ->
	    NodePing = net_adm:ping(Node), 
	    ProcInfo = (catch proc_info(Pid)),
	    FlushQ = megaco_test_lib:flush(), 
	    i("await_started ~p - timeout: "
	      "~n   net_adm:ping(~p):     ~p" 
	      "~n   Process info:         ~p"
	      "~n   Messages in my queue: ~p", 
	      [Pid, Node, NodePing, ProcInfo, FlushQ]),
	    true = erlang:monitor_node(Node, false),	    
	    exit({error, timeout})
    end.


verbosity(Pid, V) ->
    Pid ! {verbosity, V, self()}.


stop(Pid) ->
    server_request(Pid, stop, stopped).


get_stats(Pid) ->
    server_request(Pid, statistics, statistics_reply).

reset_stats(Pid) ->
    server_request(Pid, reset_stats, reset_stats_ack).


user_info(Pid) ->
    server_request(Pid, {user_info, all}, user_info_ack).

user_info(Pid, Tag) when is_atom(Tag) ->
    server_request(Pid, {user_info, Tag}, user_info_ack).


update_user_info(Pid, Tag, Val) ->
    server_request(Pid, {update_user_info, Tag, Val}, update_user_info_ack).


conn_info(Pid) ->
    server_request(Pid, {conn_info, all}, conn_info_ack).

conn_info(Pid, Tag) when is_atom(Tag) ->
    server_request(Pid, {conn_info, Tag}, conn_info_ack).


update_conn_info(Pid, Tag, Val) ->
    server_request(Pid, {update_conn_info, Tag, Val}, update_conn_info_ack).


enable_test_code(Pid, Module, Where, Fun) 
  when is_atom(Module) andalso is_atom(Where) andalso is_function(Fun) ->
    Tag = {Module, Where},
    server_request(Pid, {enable_test_code, Tag, Fun}, enable_test_code_reply).

encode_ar_first(Pid, New) when is_atom(New) ->
    server_request(Pid, {encode_ar_first, New}, encode_ar_first_reply).

service_change(Pid) ->
    server_request(Pid, service_change, service_change_reply).


group_requests(Pid, N) ->
    server_request(Pid, {group_requests, N}, group_requests_reply).


ack_info(Pid, InfoPid) ->
    Pid ! {{ack_info, InfoPid}, self()}.

rep_info(Pid, InfoPid) ->
    Pid ! {{rep_info, InfoPid}, self()}.


notify_request(Pid) ->
    Pid ! {notify_request, self()}.


await_notify_reply(Pid) ->
    await_reply(Pid, notify_request_reply).


notify_request_and_reply(Pid) ->
    notify_request(Pid),
    await_notify_reply(Pid).


cancel_request(Pid, Reason) ->
    server_request(Pid, cancel_request, Reason, cancel_request_reply).


apply_load(Pid, CounterStart) ->
    server_request(Pid, apply_load, CounterStart, apply_load_ack).


apply_multi_load(Pid, NumLoaders, NumReq) ->
    server_request(Pid, apply_multi_load, {NumLoaders, NumReq}, apply_multi_load_ack).


server_request(Pid, Req, ReplyTag) ->
    Pid ! {Req, self()},
    await_reply(Pid, ReplyTag).

server_request(Pid, Req, ReqData, ReplyTag) ->
    Pid ! {Req, ReqData, self()},
    await_reply(Pid, ReplyTag).

await_reply(Pid, ReplyTag) ->
    await_reply(Pid, ReplyTag, infinity).

await_reply(Pid, ReplyTag, Timeout) ->
    receive
	{ReplyTag, Reply, Pid} ->
	    Reply;
	{'EXIT', Pid, Reason} ->
	    exit({failed, ReplyTag, Pid, Reason})
    after Timeout ->
	    exit({timeout, ReplyTag, Pid})
    end.


server_reply(Pid, ReplyTag, Reply) ->
    Pid ! {ReplyTag, Reply, self()}.


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


mg(Parent, Verbosity, Config) ->
    process_flag(trap_exit, true),
    put(sname, "MG"),
    %% put(verbosity, Verbosity),
    put(verbosity, debug),  % Enable debug printouts during init
    i("mg -> starting"),
    %% megaco:enable_trace(max, io),
    case (catch init(Config)) of
	{error, _} = Error ->
	    exit(Error);
	{'EXIT', Reason} ->
	    exit({init_failed, Reason});
	{ok, Mid, DSITimer} ->
	    notify_started(Parent),
	    MG = #mg{parent = Parent, mid = Mid, dsi_timer = DSITimer},
	    i("mg -> started"),
	    put(verbosity, Verbosity),
	    case (catch loop(MG)) of
		{'EXIT', normal} ->
		    exit(normal);
		{'EXIT', Reason} ->
		    i("mg failed with reason:~n   ~p", [Reason]),
		    exit(Reason);
		Else ->
		    i("mg terminated: ~n   ~p", [Else]),
		    exit({unexpected, Else})
	    end
    end.

init(Config) ->
    d("init -> entry with"
      "~n   Config: ~p", [Config]),
    random_init(),
    d("init -> random initiated", []),
    Mid = get_conf(local_mid, Config),
    d("init -> Mid: ~p", [Mid]),
    RI  = get_conf(receive_info, Config),
    d("init -> RI: ~p", [RI]),

    d("init -> maybe start the display system info timer"),
    DSITimer = 
	case get_conf(display_system_info, Config, undefined) of
	    Time when is_integer(Time) ->
		d("init -> creating display system info timer"),
		create_timer(Time, display_system_info);
	    _ ->
		undefined
	end,
    Conf0 = lists:keydelete(display_system_info, 1, Config),

    d("init -> start megaco"),
    application:start(megaco),


    d("init -> possibly enable megaco trace"),
    case lists:keysearch(megaco_trace, 1, Conf0) of
	{value, {megaco_trace, true}} ->
	    megaco:enable_trace(max, io);
	{value, {megaco_trace, io}} ->
	    megaco:enable_trace(max, io);
	{value, {megaco_trace, File}} when is_list(File) ->
	    megaco:enable_trace(max, File);
	_ ->
	    ok
    end,
    Conf1 = lists:keydelete(megaco_trace, 1, Conf0),
    
    d("init -> start megaco user"),
    Conf2 = lists:keydelete(local_mid,    1, Conf1),
    Conf3 = lists:keydelete(receive_info, 1, Conf2),
    ok = megaco:start_user(Mid, Conf3),
    d("init -> update user info (user_mod)"),
    ok = megaco:update_user_info(Mid, user_mod,  ?MODULE),
    d("init -> update user info (user_args)"),
    ok = megaco:update_user_info(Mid, user_args, [self(), Mid]),

    d("init -> get user info (receive_handle)"),
    RH = megaco:user_info(Mid, receive_handle),
    d("init -> parse receive info"),
    {MgcPort, MgcHost, RH1, TO} = parse_receive_info(RI, RH),
    d("init -> start transport (with ~p)", [TO]),
    {ok, _CH} = start_transport(MgcPort, MgcHost, RH1, TO),
    {ok, Mid, DSITimer}.


loop(#mg{parent = Parent, mid = Mid} = S) ->
    d("loop -> await request", []),
    receive
	{display_system_info, Time} ->
	    display_system_info(S#mg.mid),
	    NewTimer = create_timer(Time, display_system_info),
	    loop(S#mg{dsi_timer = NewTimer});

	{verbosity, V, Parent} ->
	    i("loop -> received new verbosity: ~p", [V]),
	    put(verbosity,V),
	    loop(S);


	{stop, Parent} ->
	    i("loop -> stopping", []),
	    display_system_info(S#mg.mid, "at finish "),
	    cancel_timer(S#mg.dsi_timer),
	    Res = do_stop(Mid),
	    d("loop -> stop result: ~p", [Res]),
	    server_reply(Parent, stopped, {ok, Res}),
	    exit(normal);

	{{enable_test_code, Tag, Fun}, Parent} ->
	    i("loop -> enable_test_code: ~p, ~p", [Tag, Fun]),
	    Reply = (catch ets:insert(megaco_test_data, {Tag, Fun})),
	    d("loop -> enable_test_code -> "
	      "~n   Reply:                          ~p"
	      "~n   ets:tab2list(megaco_test_data): ~p", 
	      [Reply,ets:tab2list(megaco_test_data)]),
	    server_reply(Parent, enable_test_code_reply, Reply),
	    loop(S);

	{{encode_ar_first, EAF}, Parent} ->
	    i("loop -> encode_ar_first: ~p", [EAF]),
	    {Reply, S1} = handle_encode_ar_first(S, EAF),
	    server_reply(Parent, encode_ar_first_reply, Reply),
	    loop(S1#mg{encode_ar_first = EAF});

	%% Give me statistics
	{statistics, Parent} ->
	    i("loop -> got request for statistics", []),
	    Stats = do_get_statistics(Mid),
	    server_reply(Parent, statistics_reply, {ok, Stats}),
	    loop(S); 

	{reset_stats, Parent} ->
	    i("loop -> got request to reset stats counters", []),
	    do_reset_stats(Mid),
	    server_reply(Parent, reset_stats_ack, ok),
	    loop(S);

	{{user_info, Tag}, Parent} ->
	    i("loop -> got user_info request for ~w", [Tag]),
	    Res = do_get_user_info(Mid, Tag),
	    d("loop -> Res: ~p", [Res]),
	    server_reply(Parent, user_info_ack, Res),
	    loop(S);

	{{update_user_info, Tag, Val}, Parent} ->
	    i("loop -> got update_user_info: ~w -> ~p", [Tag, Val]),
	    Res = do_update_user_info(Mid, Tag, Val),
	    d("loop -> Res: ~p", [Res]),
	    server_reply(Parent, update_user_info_ack, Res),
	    loop(S);

	{{conn_info, Tag}, Parent} ->
	    i("loop -> got conn_info request for ~w", [Tag]),
	    Res = do_get_conn_info(Mid, Tag),
	    server_reply(Parent, conn_info_ack, Res),
	    loop(S);

	{{update_conn_info, Tag, Val}, Parent} ->
	    i("loop -> got update_conn_info: ~w -> ~p", [Tag, Val]),
	    Res = do_update_conn_info(Mid, Tag, Val),
	    server_reply(Parent, update_conn_info_ack, Res),
	    loop(S);


	%% Do a service change
	%% No server-reply here. Since the service change is 
	%% async, the reply (from the MGC) will come later.
	{service_change, Parent} ->
	    i("loop -> received request to perform service change", []),
	    S1 = 
		case (catch do_service_change(S)) of
		    {ok, MG} ->
			d("loop -> service change initiated", []),
			MG;
		    Error ->
			d("loop -> service change failed: ~p", [Error]),
			server_reply(Parent, service_change_reply, Error),
			S
		end,
	    loop(S1); 

	{{group_requests, N}, Parent} when N > 0 ->
	    i("loop -> received group_requests ~p", [N]),
	    Reply = {ok, S#mg.group_size}, 
	    server_reply(Parent, group_requests_reply, Reply),
	    loop(S#mg{group_size = N}); 

	{{ack_info, To}, Parent} ->
	    i("loop -> received request to inform about received ack's ", []),
	    loop(S#mg{ack_info = To});

	{{rep_info, To}, Parent} ->
	    i("loop -> received request to inform about received rep's ", []),
	    loop(S#mg{rep_info = To});

	%% Make a sync-call
	{notify_request, Parent} ->
	    i("loop -> received request to send notify request ", []),
	    {Res, S1} = do_handle_notify_request(S),
	    d("loop -> notify request result: ~p", [Res]),
	    loop(S1);

	%% sync-call complete
	{notify_request_complete, NotifyReply, Pid} ->
	    i("loop -> received notify request complete from "
	      "~n   ~p with"
	      "~n   NotifyReply: ~p", 
	      [Pid, NotifyReply]),
	    server_reply(Parent, notify_request_reply, NotifyReply),
	    loop(S#mg{req_handler = undefined});


	%% cancel requests
	{cancel_request, Reason, Parent} ->
	    i("loop -> received request to cancel (all) megaco requests ", []),
	    Res = do_cancel_requests(Mid, Reason),
	    server_reply(Parent, cancel_request_reply, Res),
	    loop(S);


	%% Apply multi-load
	{apply_multi_load, {NL, NR}, Parent} -> 
	    i("loop -> received apply_multi_load request: ~w, ~w", [NL, NR]),
	    S1 = start_loaders(S, NL, NR),
	    loop(S1);


	%% Apply some load
	{apply_load, Times, Parent} ->
	    i("loop -> received apply_load request: ~w", [Times]),
	    S1 = 
		case update_load_times(S, Times) of
		    {ok, MG} ->
			apply_load_timer(),
			server_reply(Parent, apply_load_ack, ok),
			MG;
		    Error ->
			server_reply(Parent, apply_load_ack, Error),
			S
		end,
	    loop(S1);

	{apply_load_timeout, _} ->
	    d("loop -> received apply_load timeout", []),
	    S1 = do_apply_load(S),
	    loop(S1);


	%% Megaco callback messages
	{request, Request, Mid, From} ->
	    d("loop -> received megaco request: ~n   ~p"
	      "~n   Mid:  ~p"
	      "~n   From: ~p", 
	      [Request, Mid, From]),
	    {Reply, S1} = handle_megaco_request(S, Request),
	    d("loop -> send (megaco callback) request reply: ~n~p", [Reply]),
	    From ! {reply, Reply, self()},
	    loop(S1);


	{'EXIT', Pid, Reason} -> 
	    i("loop -> received exit signal from ~p: ~n~p", [Pid, Reason]),
	    S1 = handle_exit(S, Pid, Reason),
	    loop(S1);


	Invalid ->
	    error_msg("received invalid request: ~n~p", [Invalid]),
	    loop(S)

    end.


handle_encode_ar_first(#mg{encode_ar_first = Old} = MG, New) 
  when (New =:= true) orelse (New =:= false) ->
    {{ok, Old}, MG#mg{encode_ar_first = New}};
handle_encode_ar_first(MG, New) ->
    {{error, {invalid_value, New}}, MG}.
    

%%
%% Stop user
%%
do_stop(Mid) ->
    d("do_stop -> stopping user ~p", [Mid]),
    Disco = fun close_conn/1, 
    lists:map(Disco, megaco:user_info(Mid, connections)),
    megaco:stop_user(Mid).

close_conn(CH) ->
    d("do_stop -> closing connection ~p", [CH]),
    Reason     = {stopped_by_user,self()},
    Pid        = megaco:conn_info(CH, control_pid),
    SendMod    = megaco:conn_info(CH, send_mod),
    SendHandle = megaco:conn_info(CH, send_handle),
    megaco:disconnect(CH, Reason),
    megaco:cancel(CH, Reason),
    case SendMod of
	megaco_tcp -> megaco_tcp:close(SendHandle);
	megaco_udp -> megaco_udp:close(SendHandle);
	SendMod    -> exit(Pid, Reason)
    end.



%% 
%% Get statistics 
%% 
do_get_statistics(Mid) ->
    case megaco:user_info(Mid, connections) of
	[CH] ->
	    do_get_conn_statistics(CH);
	[] ->
	    []
    end.

do_get_conn_statistics(CH) ->
    {ok, Gen}  = megaco:get_stats(),
    %% Pid        = megaco:conn_info(CH, control_pid),
    SendMod    = megaco:conn_info(CH, send_mod),
    SendHandle = megaco:conn_info(CH, send_handle),
    {ok, Trans} = 
	case SendMod of
	    megaco_tcp -> megaco_tcp:get_stats(SendHandle);
	    megaco_udp -> megaco_udp:get_stats(SendHandle)
	end,
    [{gen, Gen}, {trans, Trans}].


%%
%% reset user stats
%%
do_reset_stats(Mid) ->
    %% We only have one connection
    [CH] = megaco:user_info(Mid, connections),
    do_reset_stats1(CH).

do_reset_stats1(CH) ->
    megaco:reset_stats(),
    case (catch megaco:conn_info(CH, send_mod)) of
	{error, Reason} ->
	    error_msg("unexpected result when retrieving send module for "
		      "own connection ~p: ~p. "
		      "~nexiting...", [CH, Reason]),
	    exit({invalid_connection, CH, Reason});
	{'EXIT', Reason} ->
	    error_msg("exit signal when retrieving send module for "
		      "own connection ~p: ~p. "
		      "~nexiting...", [CH, Reason]),
	    exit({invalid_connection, CH, Reason});
	SendMod when is_atom(SendMod) ->
	    SendMod:reset_stats()
    end.


%%
%% Get user info for user
%%
do_get_user_info(Mid, all = Tag) ->
    case (catch megaco:user_info(Mid, Tag)) of
	L when is_list(L) ->
	    lists:sort(L);
	Else ->
	    Else
    end;
do_get_user_info(Mid, Tag) ->
    (catch megaco:user_info(Mid, Tag)).


%%
%% Update user info for user
%%
do_update_user_info(Mid, Tag, Val) ->
    (catch megaco:update_user_info(Mid, Tag, Val)).


%%
%% Get conn info 
%%
do_get_conn_info(CH, all = Tag) when is_record(CH, megaco_conn_handle) ->
    case (catch megaco:conn_info(CH, Tag)) of
	L when is_list(L) ->
	    lists:sort(L);
	Else ->
	    Else
    end;
do_get_conn_info(CH, Tag) when is_record(CH, megaco_conn_handle) ->
    (catch megaco:conn_info(CH, Tag));
do_get_conn_info(Mid, Tag) ->
    case megaco:user_info(Mid, connections) of
	[CH|_] ->
	    do_get_conn_info(CH, Tag);
	[] ->
	    []
    end.

%%
%% Update conn info for user
%%
do_update_conn_info(Mid, Tag, Val) ->
    %% We only have one connection
    [CH] = megaco:user_info(Mid, connections),
    (catch megaco:update_conn_info(CH, Tag, Val)).




%%
%% Perform service change 
%%
do_service_change(#mg{mid             = Mid, 
		      state           = initiated, 
		      encode_ar_first = EAF} = MG) ->
    %% We only have one connection
    d("do service change for ~p", [Mid]),
    [CH]   = megaco:user_info(Mid, connections),
    Method = restart,
    Reason = ?megaco_cold_boot,
    case do_service_change(CH, Method, EAF, Reason) of
	ok ->
	    {ok, MG#mg{state = connecting}};
	Error ->
	    d("service change for ~p failed: ~n~p", [Mid, Error]),
	    Error
    end;
do_service_change(#mg{state = State} = MG) ->
    {{error, {invalid_state, State}}, MG}.

do_service_change(ConnHandle, Method, EAF, Reason) ->
    d("sending service change using:"
      "~n   ConnHandle: ~p"
      "~n   Method:     ~p"
      "~n   EAF:        ~p"
      "~n   Reason:     ~p", [ConnHandle, Method, EAF, Reason]),
    SCP    = cre_serviceChangeParm(Method, [Reason]),
    TermId = [?megaco_root_termination_id],
    SCR    = cre_serviceChangeReq(TermId, SCP),
    CR     = cre_commandReq({serviceChangeReq, SCR}),
    AR     = cre_actionReq(?megaco_null_context_id,[CR]),
    send_async(EAF, ConnHandle, [AR], []).


%% Make a sync call 
do_handle_notify_request(#mg{mid             = Mid, 
			     group_size      = N, 
			     encode_ar_first = EAF, 
			     state           = connected} = MG) ->
    d("do_handle_notify_request -> entry"),
    [CH] = megaco:user_info(Mid, connections),
    Pid = start_notify_request_handler(EAF, CH, N),
    {ok, MG#mg{req_handler = Pid}};
do_handle_notify_request(#mg{state = State} = MG) ->
    d("do_handle_notify_request -> entry with"
      "~n   State: ~p", [State]),
    {{error, {invalid_state, State}}, MG}.



%%
%% Cancel requests 
%%
do_cancel_requests(Mid, Reason) ->
    [CH] = megaco:user_info(Mid, connections),
    megaco:cancel(CH, Reason).    


%% 
%% Apply multi load 
%% 
start_loaders(#mg{mid = Mid, encode_ar_first = EAF} = MG, NumLoaders, Times) ->
    [CH] = megaco:user_info(Mid, connections),
    Env  = get(),
    Loaders = start_loaders1(NumLoaders, [], [Env, EAF, Times, CH]),
    d("start_loaders -> Loaders: ~n~w", [Loaders]),
    MG#mg{mload_info = {Loaders, 0, 0}}.

start_loaders1(0, Acc, _) ->
    Acc;
start_loaders1(N, Acc, Args) ->
    Pid = spawn_link(?MODULE, loader_main, Args),
    start_loaders1(N-1, [Pid|Acc], Args).

loader_main(Env, EAF, N, CH) ->
    lists:foreach(fun({Tag,Val}) -> put(Tag,Val) end, Env),
    loader_main(EAF, N, CH).
			  
loader_main(_EAF, 0, _) ->
    d("loader_main -> done"),
    exit(loader_done);
loader_main(EAF, N, CH) ->
    d("loader_main -> entry with: ~w", [N]),
    {Act, _} = make_notify_request(),
    _Res     = send_sync(EAF, CH, Act, []),
    loader_main(EAF, N-1, CH).



handle_exit(#mg{parent = Pid}, Pid, Reason) ->
    error_msg("received exit from the parent:"
	      "~n   ~p", [Reason]),
    exit({parent_terminated, Reason});

handle_exit(#mg{parent = Parent, req_handler = Pid} = MG, Pid, Reason) ->
    error_msg("received unexpected exit from the request handler:"
	      "~n   ~p", [Reason]),
    server_reply(Parent, notify_request_reply, 
		 {error, {request_handler_exit, Reason}}),
    MG#mg{req_handler = undefined};

handle_exit(#mg{parent = Parent, mload_info = {Loaders0, Ok, Err}} = MG, 
	    Pid, loader_done) ->
    d("handle_exit(loader_done) -> entry when"
      "~n   Loaders0: ~p"
      "~n   Ok:       ~p"
      "~n   Err:      ~p", [Loaders0, Ok, Err]),
    Loaders = lists:delete(Pid, Loaders0),
    LoadInfo =
	case Loaders of
	    [] ->
		d("handle_exit -> multi load done", []),
		server_reply(Parent, apply_multi_load_ack, {ok, Ok+1, Err}),
		undefined;
	    _ ->
		{Loaders, Ok+1, Err}
	end,
    MG#mg{mload_info = LoadInfo};


handle_exit(#mg{parent = Parent, mload_info = {Loaders, Ok, Err}} = MG, 
	    Pid, Reason) 
  when length(Loaders) > 0 ->
    d("handle_exit -> entry when"
      "~n   Reason:  ~p"
      "~n   Loaders: ~p"
      "~n   Ok:      ~p"
      "~n   Err:     ~p", [Reason, Loaders, Ok, Err]),
    case lists:delete(Pid, Loaders) of
	[] -> 
	    %% since we cannot be empty prior the delete, 
	    %% the last one exited...
	    server_reply(Parent, apply_multi_load, {ok, Ok, Err+1}),
	    MG#mg{mload_info = undefined};
	Loaders ->
	    %% Could not be this MG, so go on to the next
	    error_msg("received unexpected exit signal from ~p:~n~p", 
		      [Pid, Reason]);
	Loaders1 ->
	    %% Not empty, but we removed one
	    MG#mg{mload_info = {Loaders1,Ok,Err+1}}
    end;
handle_exit(_MG, Pid, Reason) ->
    error_msg("received unexpected exit signal from ~p:~n~p", 
	      [Pid, Reason]).

		      
parse_receive_info(RI, RH) ->
    d("parse_receive_info -> get encoding module"),
    EM = get_encoding_module(RI),
    d("parse_receive_info -> get encoding config"),
    EC = get_encoding_config(RI, EM),
    d("parse_receive_info -> get transport module"),
    TM = get_transport_module(RI),
    d("parse_receive_info -> get transport port"),
    TP = get_transport_port(RI),
    d("parse_receive_info -> get transport host"),
    TH = get_transport_host(RI),
    d("parse_receive_info -> get transport opts"),
    TO = get_transport_opts(RI),
    RH1 = RH#megaco_receive_handle{send_mod        = TM,
				   encoding_mod    = EM,
				   encoding_config = EC},
    {TP, TH, RH1, TO}.


start_transport(MgcPort, MgcHost, 
		   #megaco_receive_handle{send_mod = megaco_tcp} = RH, TO) ->
    start_tcp(MgcPort, MgcHost, RH, TO);
start_transport(MgcPort, MgcHost, 
		   #megaco_receive_handle{send_mod = megaco_udp} = RH, TO) ->
    start_udp(MgcPort, MgcHost, RH, TO);
start_transport(_, _, #megaco_receive_handle{send_mod = Mod}, _TO) ->
    throw({error, {bad_send_mod, Mod}}).


start_tcp(MgcPort, MgcHost, RH, TO) ->
    d("start tcp transport: "
      "~n   MGC Port:          ~p"
      "~n   MGC Host:          ~p"
      "~n   Receive handle:    ~p"
      "~n   Transport options: ~p", [MgcPort, MgcHost, RH, TO]),
    case megaco_tcp:start_transport() of
	{ok, Sup} ->
	    d("tcp transport started: ~p", [Sup]),
	    start_tcp_connect(TO, RH, MgcPort, MgcHost, Sup);
        {error, Reason} ->
            throw({error, {megaco_tcp_start_transport, Reason}})
    end.

start_tcp_connect(TO, RH, Port, Host, Sup) ->
    d("try tcp connecting to: ~p:~p", [Host, Port]),
    Opts = [{host,           Host},
	    {port,           Port}, 
	    {receive_handle, RH},
	    {tcp_options,    [{nodelay, true}]}] ++ TO,
    try_start_tcp_connect(RH, Sup, Opts, 250, noError).

try_start_tcp_connect(RH, Sup, Opts, Timeout, Error0) when (Timeout < 5000) ->
    Sleep = random(Timeout) + 100,
    d("try tcp connect (~p,~p)", [Timeout, Sleep]),
    case megaco_tcp:connect(Sup, Opts) of
	{ok, SendHandle, ControlPid} ->
	    d("tcp connected: ~p, ~p", [SendHandle, ControlPid]),
	    megaco_tcp_connect(RH, SendHandle, ControlPid);
	Error1 when Error0 =:= noError -> % Keep the first error
	    d("failed connecting [1]: ~p", [Error1]),
	    sleep(Sleep),
	    try_start_tcp_connect(RH, Sup, Opts, Timeout*2, Error1);
	Error2 ->	
	    d("failed connecting [2]: ~p", [Error2]),
	    sleep(Sleep),
	    try_start_tcp_connect(RH, Sup, Opts, Timeout*2, Error0)
    end;
try_start_tcp_connect(_RH, Sup, _Opts, _Timeout, Error) ->
    megaco_tcp:stop_transport(Sup),
    throw({error, {megaco_tcp_connect, Error}}).
    
megaco_tcp_connect(RH, SendHandle, ControlPid) ->
    PrelMgcMid = preliminary_mid,
    d("megaco connect", []),    
    {ok, CH} = megaco:connect(RH, PrelMgcMid, SendHandle, ControlPid),
    d("megaco connected: ~p", [CH]),    
    {ok, CH}.

start_udp(MgcPort, MgcHost, RH, TO) ->
    d("start udp transport (~p)", [MgcPort]),
    case megaco_udp:start_transport() of
	{ok, Sup} ->
	    d("udp transport started: ~p", [Sup]),
	    Opts = [{port, 0}, {receive_handle, RH}] ++ TO,
	    d("udp open", []),
	    case megaco_udp:open(Sup, Opts) of
		{ok, Handle, ControlPid} ->
		    d("udp opened: ~p, ~p", [Handle, ControlPid]),
		    megaco_udp_connect(MgcPort, MgcHost, 
				       RH, Handle, ControlPid);
		{error, Reason} ->
                    throw({error, {megaco_udp_open, Reason}})
	    end;
        {error, Reason} ->
            throw({error, {megaco_udp_start_transport, Reason}})
    end.

megaco_udp_connect(MgcPort, MgcHost, RH, Handle, ControlPid) ->
    MgcMid     = preliminary_mid,
    SendHandle = megaco_udp:create_send_handle(Handle, MgcHost, MgcPort),
    d("megaco connect", []),    
    {ok, CH} = megaco:connect(RH, MgcMid, SendHandle, ControlPid),
    d("megaco connected: ~p", [CH]),    
    {ok, CH}.


update_load_times(#mg{load_counter = 0} = MG, Times) ->
    d("update_load_times(0) -> entry with"
      "~n   Times: ~p", [Times]),
    {ok, MG#mg{load_counter = Times}};
update_load_times(#mg{load_counter = N}, Times) ->
    d("update_load_times(~p) -> entry with"
      "~n   Times: ~p", [N, Times]),
    {error, {already_counting, N}}.


do_apply_load(#mg{mid = Mid} = MG) ->
    d("do_apply_load -> entry"),
    case megaco:user_info(Mid, connections) of
	[CH] ->
	    do_apply_load(MG, CH);
	[] ->
	    i("failed to apply load: no connections for ~p", [Mid]),
	    MG
    end.

do_apply_load(#mg{parent          = Parent,
		  encode_ar_first = EAF,
		  call_mode       = Mode, 
		  group_size      = Sz, 
		  load_counter    = N0} = MG, CH) ->
    d("do_apply_load -> entry with"
      "~n   Mode: ~p"
      "~n   Sz:   ~p"
      "~n   N0:   ~p", [Mode, Sz, N0]),
    {NofSent, Actions, ReplyData} = make_notify_request(N0, Sz),
    d("do_apply_load -> notifications constructed:"
      "~n   NofSent:   ~p"
      "~n   Actions:   ~p"
      "~n   ReplyData: ~p", [NofSent, Actions, ReplyData]),
    N = N0 - NofSent,
    case Mode of
	sync ->
	    Result = send_sync(EAF, CH, Actions, []),
	    d("do_apply_load -> call result when N = ~p: ~n~p", [N,Result]),
	    case N of
		0 ->
		    d("do_apply_load -> load complete"),
		    Parent ! {load_complete, self()},
		    MG#mg{call_mode = async, load_counter = 0};
		_ ->
		    d("do_apply_load -> make another round"),
		    apply_load_timer(),
		    MG#mg{call_mode = async, load_counter = N}
	    end;
	async ->
	    Result = send_async(EAF, CH, Actions, [{reply_data, ReplyData}]),
	    d("do_apply_load -> cast result:~n   ~p", [Result]),
	    MG#mg{call_mode     = sync, 
		  load_counter  = N, 
		  reply_counter = NofSent} % Outstanding replies
    end.


start_notify_request_handler(EAF, CH, N) ->
    d("start_notify_request_handler -> entry with"
      "~n   EAF: ~p"
      "~n   CH:  ~p"
      "~n   N:   ~p", [EAF, CH, N]),
    Env = get(),
    spawn_link(?MODULE, notify_request_handler_main, [self(), Env, EAF, CH, N]).

notify_request_handler_main(Parent, Env, EAF, CH, N) ->
    F = fun({Tag, Val}) -> put(Tag, Val) end,
    lists:foreach(F, Env),
    d("notify_request_handler_main -> entry with"
      "~n   Parent: ~p"
      "~n   EAF:    ~p"
      "~n   CH:     ~p"
      "~n   N:      ~p", [Parent, EAF, CH, N]),
    Res = do_notify_request(EAF, CH, N),
    d("notify_request_handler_main -> notify complete:"
      "~n   Res: ~p", [Res]),
    Parent ! {notify_request_complete, {ok, Res}, self()},
    unlink(Parent),
    exit(normal).

do_notify_request(_EAF, _CH, N) when N =< 0 ->
    d("do_notify_request(~p) -> ignoring", [N]),
    ignore;
do_notify_request(EAF, CH, 1) ->
    d("do_notify_request(1) -> entry with"),
    {Action, _} = make_notify_request(),
    send_sync(EAF, CH, Action, []);
do_notify_request(EAF, CH, N) ->
    d("do_notify_request(~p) -> entry with", [N]),
    {N, Actions, _} = make_notify_request(N,N),
    send_sync(EAF, CH, Actions, []).

make_notify_request(N, Sz) when (N >= Sz) andalso (Sz > 0) ->
    {Req, ReplyData} = make_notify_request(N, Sz, [], []),
    {Sz, Req, ReplyData};
make_notify_request(N, _Sz) when N > 0 ->
    {Req, ReplyData} = make_notify_request(N, N, [], []),
    {N, Req, ReplyData}.


    
make_notify_request(_Offset, 0, Actions, ReplyDatas) ->
    {lists:reverse(Actions), lists:reverse(ReplyDatas)};
make_notify_request(Offset, N, Actions, ReplyDatas) when N > 0 ->
    TimeStamp = cre_timeNotation(),
    Event     = cre_observedEvent("al/of", TimeStamp),
    Desc      = cre_observedEventsDesc(2000 + N, [Event]),
    TidNum    = 2#10000000 + Offset - N,
    NotifyReq = cre_notifyReq([#megaco_term_id{id = tid(TidNum)}],Desc),
    CmdReq    = cre_commandReq({notifyReq, NotifyReq}),
    ActReq    = cre_actionReq(?megaco_null_context_id, [CmdReq]),
    make_notify_request(Offset, N-1, [[ActReq]|Actions], [Desc|ReplyDatas]).
    
make_notify_request() ->
    TimeStamp  = cre_timeNotation("19990729", "22000000"),
    Event      = cre_observedEvent("al/of",   TimeStamp),
    Desc1      = cre_observedEventsDesc(2221, [Event]),
    Desc2      = cre_observedEventsDesc(2222, [Event]),
    Desc3      = cre_observedEventsDesc(2223, [Event]),
    Desc4      = cre_observedEventsDesc(2224, [Event]),
    NotifyReq1 = cre_notifyReq([#megaco_term_id{id = ?A4444}], Desc1),
    NotifyReq2 = cre_notifyReq([#megaco_term_id{id = ?A4445}], Desc2),
    CmdReq1    = cre_commandReq({notifyReq, NotifyReq1}),
    CmdReq2    = cre_commandReq({notifyReq, NotifyReq2}),
    ActReq     = cre_actionReq(?megaco_null_context_id, [CmdReq1,CmdReq2]),
    {[ActReq], [Desc3,Desc4]}.


cre_actionReq(Cid, Cmds) ->
    #'ActionRequest'{contextId       = Cid,
		     commandRequests = Cmds}.

cre_commandReq(Cmd) ->
    #'CommandRequest'{command = Cmd}.

cre_serviceChangeReq(TermId, Parms) ->
    #'ServiceChangeRequest'{terminationID      = TermId,
			    serviceChangeParms = Parms}.

cre_serviceChangeParm(Method, Reason) ->
    #'ServiceChangeParm'{serviceChangeMethod = Method,
			 serviceChangeReason = Reason}.

cre_notifyReq(Tid, EvsDesc) ->
    #'NotifyRequest'{terminationID = Tid, observedEventsDescriptor = EvsDesc}.

% cre_notifyRep(Tid) ->
%     #'NotifyReply'{terminationID = [Tid]}.

% cre_notifyRep(Tid,Err) ->
%     #'NotifyReply'{terminationID = [Tid], errorDescriptor = Err}.

cre_observedEventsDesc(Id, EvList) ->
    #'ObservedEventsDescriptor'{requestId = Id, observedEventLst = EvList}.

cre_observedEvent(Name, Not) ->
    #'ObservedEvent'{eventName = Name, timeNotation = Not}.

cre_timeNotation() ->
    {{Year,Month,Day},{Hour,Min,Sec}} = calendar:universal_time(),
    D = lists:flatten(io_lib:format("~4..0w~2..0w~2..0w", [Year,Month,Day])),
    T = lists:flatten(io_lib:format("~2..0w~2..0w~4..0w", [Hour,Min,Sec])),
    cre_timeNotation(D, T).
    
cre_timeNotation(D,T) ->
    #'TimeNotation'{date = D, time = T}.

cre_error_descr(Code,Text) ->
    #'ErrorDescriptor'{errorCode = Code, errorText = Text}.

% cre_error_descr(Code,FormatString,Args) ->
%     Text = lists:flatten(io_lib:format(FormatString,Args)),
%     cre_error_descr(Code,Text).


%% -----------------------
%% Handle megaco callbacks
%%

handle_megaco_request(#mg{state = connecting} = MG,
		      {handle_connect, _CH, _PV}) ->
    d("handle_megaco_request(handle_connect,connecting) -> entry"),
    {ok, MG};
handle_megaco_request(#mg{state = S} = MG,
		      {handle_connect, _CH, _PV}) ->
    d("handle_megaco_request(handle_connect) -> entry"),
    Desc = 
	lists:flatten(io_lib:format("not ready for connect in state ~p", [S])),
    ED   =  cre_error_descr(?megaco_internal_gateway_error, Desc),
    {{discard_ack, ED}, MG};

handle_megaco_request(#mg{req_handler = Pid} = MG,
		      {handle_disconnect, _CH, _PV, R}) 
  when is_pid(Pid) ->
    d("handle_megaco_request(handle_disconnect) -> entry with"
      "~n   Pid: ~p", [Pid]),
    Error = {error, {disconnected, R}},
    self() ! {notify_request_complete, Error, Pid},
    unlink(Pid),
    exit(Pid, kill),
    {ok, MG#mg{req_handler = undefined, state = initiated}};
handle_megaco_request(MG, {handle_disconnect, _CH, _PV, _R}) ->
    d("handle_megaco_request(handle_disconnect) -> entry"),
    {ok, MG#mg{state = initiated}};

handle_megaco_request(MG, 
		      {handle_syntax_error, _RH, _PV, _ED}) ->
    {reply, MG};

handle_megaco_request(#mg{req_handler = Pid} = MG,
		      {handle_message_error, CH, PV, ED}) 
  when is_pid(Pid) ->
    d("handle_megaco_request(handle_message_error) -> entry with"
      "~n   Pid:    ~p"
      "~n   CH:     ~p"
      "~n   PV:     ~p"
      "~n   ED:     ~p", [Pid, CH, PV, ED]),    
    self() ! {notify_request_complete, ED, Pid},
    unlink(Pid),
    exit(Pid, kill),
    {no_reply, MG#mg{req_handler = undefined}};
handle_megaco_request(MG, {handle_message_error, CH, PV, ED}) ->
    d("handle_megaco_request(handle_message_error) -> entry with"
      "~n   CH:     ~p"
      "~n   PV:     ~p"
      "~n   ED:     ~p", [CH, PV, ED]),    
    {no_reply, MG};

handle_megaco_request(MG, {handle_trans_request, _CH, _PV, _AR}) ->
    ED =  cre_error_descr(?megaco_not_implemented,
			  "Transaction requests not handled"),
    {{discard_ack, ED}, MG};

handle_megaco_request(MG,
		      {handle_trans_long_request, _CH, _PV, _RD}) ->
    ED = cre_error_descr(?megaco_not_implemented,
			 "Long transaction requests not handled"),
    {{discard_ack, ED}, MG};

handle_megaco_request(#mg{rep_info = P} = MG, 
		      {handle_trans_reply, CH, PV, AR, RD}) when is_pid(P) ->
    P ! {rep_received, self(), AR},
    do_handle_trans_reply(MG, CH, PV, AR, RD);
handle_megaco_request(MG, {handle_trans_reply, CH, PV, AR, RD}) ->
    do_handle_trans_reply(MG, CH, PV, AR, RD);

handle_megaco_request(#mg{ack_info = P} = MG, 
		      {handle_trans_ack, _CH, _PV, AS, _AD}) when is_pid(P) ->
    d("handle_megaco_request(handle_trans_ack,~p) -> entry",[P]),
    P ! {ack_received, self(), AS},
    {ok, MG};
handle_megaco_request(MG, {handle_trans_ack, _CH, _PV, _AS, _AD}) ->
    d("handle_megaco_request(handle_trans_ack) -> entry"),
    {ok, MG}.

do_handle_trans_reply(#mg{parent = Parent, state = connecting} = MG,
		      CH, _PV, {ok, Rep}, _RD) ->
    d("do_handle_trans_reply(connecting) -> entry with"
      "~n   CH:     ~p"
      "~n   Rep:    ~p", [CH, Rep]),
    server_reply(Parent, service_change_reply, ok),
    {ok, MG#mg{state = connected}};
do_handle_trans_reply(#mg{parent = Parent, load_counter = 0} = MG,
		      CH, _PV, {ok, Rep}, _RD) ->
    d("do_handle_trans_reply(load_counter = 0) -> entry with"
      "~n   CH:     ~p"
      "~n   Rep:    ~p", [CH, Rep, Parent]),
    handle_trans_reply_verify_act(Rep),
    server_reply(Parent, load_complete, ok),
    {ok, MG#mg{reply_counter = 0}};
do_handle_trans_reply(#mg{reply_counter = 0} = MG,
		      CH, _PV, {ok, Rep}, _RD) ->
    d("do_handle_trans_reply(reply_counter = 0) -> entry with"
      "~n   CH:     ~p"
      "~n   Rep:    ~p", [CH, Rep]),
    handle_trans_reply_verify_act(Rep),
    apply_load_timer(),
    {ok, MG};
do_handle_trans_reply(#mg{reply_counter = N} = MG,
		      CH, _PV, {ok, Rep}, _RD) ->
    d("do_handle_trans_reply(reply_counter = ~p) -> entry with"
      "~n   CH:     ~p"
      "~n   Rep:    ~p", [N, CH, Rep]),
    handle_trans_reply_verify_act(Rep),
    apply_load_timer(),
    {ok, MG#mg{reply_counter = N-1}};
do_handle_trans_reply(MG, _CH, _PV, {error, ED}, _RD) ->
    i("unexpected error transaction: ~p", [ED]),
    {ok, MG}.


handle_trans_reply_verify_act([]) ->
    ok;
handle_trans_reply_verify_act([#'ActionReply'{commandReply = Rep}|Reps]) ->
    handle_trans_reply_verify_cmd(Rep),
    handle_trans_reply_verify_act(Reps);
handle_trans_reply_verify_act([Rep|Reps]) ->
    i("received 'propably' unexpected reply: ~n~p", [Rep]),
    handle_trans_reply_verify_act(Reps).

handle_trans_reply_verify_cmd([]) ->
    ok;
handle_trans_reply_verify_cmd([Cmd|Cmds]) ->
    case Cmd of
	{notifyReply, #'NotifyReply'{terminationID = [Tid]}} ->
	    d("received expected notification reply from ~n   ~p", [Tid]);
	Else ->
	    i("received unexpected notification reply ~n~p", [Else])
    end,
    handle_trans_reply_verify_cmd(Cmds).
    
    
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

notify_started(Parent) ->
    Parent ! {started, self()}.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% The megaco user callback interface 

handle_connect(CH, PV, Pid, Mid) ->
    case CH#megaco_conn_handle.remote_mid of
        preliminary_mid ->
	    %% Avoids deadlock
	    ok;
	_ ->
	    Reply = request(Pid, {handle_connect, CH, PV}, Mid),
	    Reply
    end.

handle_disconnect(_CH, _PV, 
		  {user_disconnect, {stopped_by_user, Pid}}, 
		  Pid, _Mid) ->
    ok;
handle_disconnect(CH, PV, R, Pid, Mid) ->
    request(Pid, {handle_disconnect, CH, PV, R}, Mid).

handle_syntax_error(ReceiveHandle, ProtocolVersion, ErrorDescriptor, Pid, Mid) ->
    Req = {handle_syntax_error, ReceiveHandle, ProtocolVersion, 
	   ErrorDescriptor},
    request(Pid, Req, Mid).
    
handle_message_error(ConnHandle, ProtocolVersion, ErrorDescriptor, Pid, Mid) ->
    Req = {handle_message_error, ConnHandle, ProtocolVersion, ErrorDescriptor},
    request(Pid, Req, Mid).

handle_trans_request(CH, PV, AR, Pid, Mid) ->
    Reply = request(Pid, {handle_trans_request, CH, PV, AR}, Mid),
    Reply.

handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Pid, Mid) ->
    Req = {handle_trans_long_request, ConnHandle, ProtocolVersion, ReqData},
    request(Pid, Req, Mid).

handle_trans_reply(ConnHandle, ProtocolVersion, ActualReply, ReplyData, Pid, Mid) ->
    Req = {handle_trans_reply, ConnHandle, ProtocolVersion, 
	   ActualReply, ReplyData},
    request(Pid, Req, Mid).

handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Pid, Mid) ->
    Req = {handle_trans_ack, ConnHandle, ProtocolVersion, AckStatus, AckData},
    request(Pid, Req, Mid).


request(Pid, Request, Mid) ->
    Pid ! {request, Request, Mid, self()},
    receive
	{reply, {delay, To, ED}, Pid} ->
	    sleep(To),
	    {discard_ack, ED};
	{reply, Reply, Pid} ->
	    Reply
    end.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

send_async(true, CH, Actions, Opts) ->
    d("send_async(true) -> encode actions first"),
    case megaco:encode_actions(CH, Actions, Opts) of
	{ok, BinOrBins} ->
	    d("send_async(true) -> send message"),
	    megaco:cast(CH, BinOrBins, Opts);
	Error ->
	    d("send_async(true) -> encode failed: ~n~p", [Error]),
	    Error
    end;
send_async(_, CH, Actions, Opts) ->
    d("send_async(true) -> send message"),
    megaco:cast(CH, Actions, Opts).

send_sync(true, CH, Actions, Opts) ->
    d("send_sync(true) -> encode actions first"),
    case megaco:encode_actions(CH, Actions, Opts) of
	{ok, BinOrBins} ->
	    d("send_sync(true) -> send message"),
	    megaco:call(CH, BinOrBins, Opts);
	Error ->
	    d("send_sync(true) -> encode failed: ~n~p", [Error]),
	    Error
    end;
send_sync(_, CH, Actions, Opts) ->
    d("send_sync(false) -> send message"),
    megaco:call(CH, Actions, Opts).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

sleep(X) ->
    receive after X -> ok end.


error_msg(F,A) -> error_logger:error_msg("MG: " ++ F ++ "~n",A).


get_encoding_module(RI) ->
    case (catch get_conf(encoding_module, RI)) of
	{error, _} ->
	    undefined;
	Val ->
	    Val
    end.

get_encoding_config(RI, EM) ->
    case text_codec(EM) of
	true ->
	    case megaco:system_info(text_config) of
		[Conf] when is_list(Conf) ->
		    Conf;
		_ ->
		    []
	    end;

	false ->
	    get_conf(encoding_config, RI)
    end.

text_codec(megaco_compact_text_encoder) ->
    true;
text_codec(megaco_pretty_text_encoder) ->
    true;
text_codec(_) ->
    false.


get_transport_module(RI) ->
    get_conf(transport_module, RI).

get_transport_port(RI) ->
    get_conf(port, RI).

get_transport_host(RI) ->
    {ok, LocalHost} = inet:gethostname(),
    get_conf(host, RI, LocalHost).

get_transport_opts(RI) ->
    get_conf(transport_opts, RI, []).


get_conf(Key, Config) ->
    case lists:keysearch(Key, 1, Config) of
	{value, {Key, Val}} ->
	    Val;
	_ ->
	    exit({error, {not_found, Key, Config}})
    end.

get_conf(Key, Config, Default) ->
    case lists:keysearch(Key, 1, Config) of
	{value, {Key, Val}} ->
	    Val;
	_ ->
	    Default
    end.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

tid(N) when N >= 0 ->
    {Rem1, Val1} = num2str(N),
    {Rem2, Val2} = num2str(Rem1),
    {0,    Val3} = num2str(Rem2),
    [Val3, Val2, Val1].

num2str(N) when N >= 0 ->
    num2str(N, []).

num2str(Rem, Val) when length(Val) == 8 ->
    {Rem, Val};
num2str(N, Val) ->
    D = N div 2,
    case N rem 2 of
	1 ->
	    num2str(D, [$1|Val]);
	0 -> 
	    num2str(D, [$0|Val])
    end.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

i(F) ->
    i(F, []).

i(F, A) ->
    print(info, get(verbosity), "", F, A).


d(F) ->
    d(F, []).

d(F, A) ->
    print(debug, get(verbosity), "DBG", F, A).


printable(_, debug)   -> true;
printable(info, info) -> true;
printable(_,_)        -> false.

print(Severity, Verbosity, P, F, A) ->
    print(printable(Severity,Verbosity), P, F, A).

print(true, P, F, A) ->
    io:format("*** [~s] ~s ~p ~s ***"
              "~n   " ++ F ++ "~n~n", 
              [format_timestamp(now()), P, self(), get(sname) | A]);
print(_, _, _, _) ->
    ok.

format_timestamp({_N1, _N2, N3} = Now) ->
    {Date, Time}   = calendar:now_to_datetime(Now),
    {YYYY,MM,DD}   = Date,
    {Hour,Min,Sec} = Time,
    FormatDate = 
        io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w",
                      [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]),  
    lists:flatten(FormatDate).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

random_init() ->
    {A,B,C} = now(),
    random:seed(A,B,C).

random() ->
    random(50).
random(N) ->
    random:uniform(N).


display_system_info(Mid) ->
    display_system_info(Mid, "").

display_system_info(Mid, Pre) ->
    TimeStr = format_timestamp(now()),
    MibStr  = lists:flatten(io_lib:format("~p ", [Mid])), 
    megaco_test_lib:display_system_info(MibStr ++ Pre ++ TimeStr).


create_timer(Time, Event) ->
    erlang:send_after(Time, self(), {Event, Time}).

cancel_timer(undefined) ->
    ok;
cancel_timer(Ref) ->
    erlang:cancel_timer(Ref).


apply_load_timer() ->
    create_timer(random(), apply_load_timeout).