%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2001-2025. 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: Simple example of an MGC
%% 
%% Example usage:
%%
%%   cd megaco/examples/simple
%%   erl -pa ../../../megaco/ebin -s megaco_filter -s megaco
%%   megaco_simple_mgc:start().
%%----------------------------------------------------------------------

-module(megaco_simple_mgc).

-behaviour(megaco_user).

-export([
	 start_batch/0, start_batch/1, init_batch/3,
	 start/0, start/2, 
	 start/4,
	 stop/0, stop/1
	]).

-export([
         handle_connect/2,
         handle_disconnect/3,
         handle_syntax_error/3,
         handle_message_error/3,
         handle_trans_request/3,
         handle_trans_long_request/3,
         handle_trans_reply/4,
         handle_trans_ack/4,
	 handle_unexpected_trans/3,
	 handle_trans_request_abort/4
        ]).

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


%%----------------------------------------------------------------------
%% Starting the MGC
%%----------------------------------------------------------------------

start() ->
    start(false, false).

start(Trace, Debug) ->
    start({deviceName, "controller"}, [], Trace, Debug).

start(Mid, Config, Trace, Debug) ->
    put(debug, Debug),
    d("start -> entry with"
      "~n   Mid:    ~p"
      "~n   Config: ~p"
      "~n   Trace:  ~p", [Mid, Config, Trace]),
    init_inline_trace(Trace),
    case megaco:start_user(Mid, [{user_mod, ?MODULE} | Config]) of
	ok ->
	    d("start -> user started"),
	    case catch do_start(Mid) of
		{'EXIT', Reason} ->
		    d("start -> exited: ~n~p",[Reason]),
		    {error, Reason};
		Other ->
		    d("start -> Other: ~n~p",[Other]),
		    Other
	    end;
	{error, Reason} ->
	    d("start -> user start failed: ~n~p", [Reason]),
	    {error, {start_user, Reason}}
    end.


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

init_inline_trace(true) ->
    megaco:enable_trace(max, io);
init_inline_trace(_) ->
    ok.

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

do_start(Mid) ->
    d("do_start -> entry"),
    RecHandle = megaco:user_info(Mid, receive_handle),
    d("do_start -> RecHandle: ~n~p",[RecHandle]),

    TextMod  = megaco_pretty_text_encoder,
    TextTcp  = RecHandle#megaco_receive_handle{encoding_mod    = TextMod,
					       encoding_config = [],
					       send_mod        = megaco_tcp},
    d("do_start -> TextTcp: ~n~p",[TextTcp]),
    TextUdp  = TextTcp#megaco_receive_handle{send_mod = megaco_udp},
    d("do_start -> TextUdp: ~n~p",[TextUdp]),

    BinMod  = megaco_binary_encoder,
    BinTcp  = RecHandle#megaco_receive_handle{encoding_mod    = BinMod,
					      encoding_config = [],
					      send_mod        = megaco_tcp},
    d("do_start -> BinTcp: ~n~p",[BinTcp]),
    BinUdp  = BinTcp#megaco_receive_handle{send_mod = megaco_udp},
    d("do_start -> BinUdp: ~n~p",[BinUdp]),

    ListenTo = [{?megaco_ip_port_text, TextTcp},
		{?megaco_ip_port_text, TextUdp},
		{?megaco_ip_port_binary,  BinTcp},
		{?megaco_ip_port_binary,  BinUdp}
	       ],
    
    d("do_start -> start transports"),
    Transports =
	[{start_transport(Port, RH), Port, RH} || {Port, RH} <- ListenTo],
    d("do_start -> Transports: ~n~p",[Transports]),
    
    {ok, Transports}.

start_transport(MgcPort, RecHandle) ->
    case RecHandle#megaco_receive_handle.send_mod of
	megaco_tcp -> start_tcp(MgcPort, RecHandle);
	megaco_udp -> start_udp(MgcPort, RecHandle);
	SendMod    -> {error, {bad_send_mod, SendMod}}
    end.

start_udp(MgcPort, RecHandle) ->
    d("start_udp -> entry with"
      "~n   MgcPort:   ~p"
      "~n   RecHandle: ~p", [MgcPort, RecHandle]),
    case megaco_udp:start_transport() of
	{ok, SupPid} ->
	    Options = [{port, MgcPort}, {receive_handle, RecHandle}],
	    case megaco_udp:open(SupPid, Options) of
		{ok, _SendHandle, _ControlPid} ->
		    ok;
		{error, Reason} ->
		    {error, {megaco_udp_open, Reason}}
	    end;
	{error, Reason} ->
	    {error, {megaco_udp_start_transport, Reason}}
    end.

start_tcp(MgcPort, RecHandle) ->
    d("start_tcp -> entry with"
      "~n   MgcPort:   ~p"
      "~n   RecHandle: ~p", [MgcPort, RecHandle]),
    case megaco_tcp:start_transport() of
	{ok, SupPid} ->
	    d("start_tcp -> transport started: "
	      "~n   SupPid: ~p", [SupPid]),
	    Options = [{port, MgcPort}, {receive_handle, RecHandle}],
	    case megaco_tcp:listen(SupPid, Options) of
		ok ->
		    d("start_tcp -> listen ok"),
		    ok;
		{error, Reason} ->
		    d("start_tcp -> listen failed: "
		      "~n   Reason: ~p", [Reason]),
		    {error, {megaco_tcp_listen, Reason}}
	    end;
	{error, Reason} ->
	    d("start_tcp -> transport start failed: "
	      "~n   Reason: ~p", [Reason]),
	    {error, {megaco_tcp_start_transport, Reason}}
    end.

%%----------------------------------------------------------------------
%% Stopping the MGC
%%----------------------------------------------------------------------

stop() ->
    [{Mid, stop(Mid)} || Mid <- megaco:system_info(users)].

stop(Mid) ->
    d("stop -> entry with~n   Mid: ~p", [Mid]),
    Disco = fun(CH) ->
		    d("stop -> CH: ~p", [CH]),
		    Reason     = stopped_by_user, 
		    Pid        = megaco:conn_info(CH, control_pid),
		    SendMod    = megaco:conn_info(CH, send_mod),
		    SendHandle = megaco:conn_info(CH, send_handle),

		    d("stop -> disconnect", []),
		    megaco:disconnect(CH, Reason),

		    d("stop -> cancel", []),
		    megaco:cancel(CH, Reason), % see handle_disconnect

		    d("stop -> close transport"
		      "~n   SendMod:    ~p"
		      "~n   SendHandle: ~p", [SendMod, SendHandle]),
		    case SendMod of
			megaco_tcp -> megaco_tcp:close(SendHandle);
			megaco_udp -> megaco_udp:close(SendHandle);
			SendMod    -> exit(Pid, Reason)
		    end
	    end,
    Conns = megaco:user_info(Mid, connections),
    d("stop -> Conns: ~p", [Conns]),
    Disconns = lists:map(Disco, Conns),
    d("stop -> Disconns: ~p", [Disconns]),
    megaco:stop_user(Mid),
    case whereis(?MODULE) of
	undefined ->
	    ignore;
	Pid ->
	    d("stop -> Pid: ~p", [Pid]),
	    unlink(Pid),
	    exit(Pid, shutdown)
    end,
    ok.

%%----------------------------------------------------------------------
%% Invoked when a new connection is established
%%----------------------------------------------------------------------

handle_connect(ConnHandle, ProtocolVersion) ->
    d("handle_connect -> entry with"
      "~n   ConnHandle:      ~p"
      "~n   ProtocolVersion: ~p"
      "", [ConnHandle, ProtocolVersion]),
    ok.

%%----------------------------------------------------------------------
%% Invoked when a connection is teared down
%%----------------------------------------------------------------------

handle_disconnect(ConnHandle, ProtocolVersion, Reason) ->
    d("handle_disconnect -> entry with"
      "~n   ConnHandle:      ~p"
      "~n   ProtocolVersion: ~p"
      "~n   Reason:          ~p"
      "", [ConnHandle, ProtocolVersion, Reason]),
    info_msg("handle_disconnect - cancel outstanding messages~n"),
    megaco:cancel(ConnHandle, Reason), % Cancel the outstanding messages
    ok.

%%----------------------------------------------------------------------
%% Invoked when  a received message had syntax errors
%%----------------------------------------------------------------------

handle_syntax_error(ReceiveHandle, ProtocolVersion, ErrorDescriptor) ->
    d("handle_syntax_error -> entry with"
      "~n   ReceiveHandle:   ~p"
      "~n   ProtocolVersion: ~p"
      "~n   ErrorDescriptor: ~p"
      "", [ReceiveHandle, ProtocolVersion, ErrorDescriptor]),
    reply.

%%----------------------------------------------------------------------
%% Invoked when a received message contained no transactions
%%----------------------------------------------------------------------

handle_message_error(ConnHandle, ProtocolVersion, ErrorDescriptor) ->
    d("handle_message_error -> entry with"
      "~n   ConnHandle:      ~p"
      "~n   ProtocolVersion: ~p"
      "~n   ErrorDescriptor: ~p"
      "", [ConnHandle, ProtocolVersion, ErrorDescriptor]),
    no_reply.

%%----------------------------------------------------------------------
%% Invoked for each transaction request
%%----------------------------------------------------------------------

handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) ->
    d("handle_trans_request -> entry with"
      "~n   ConnHandle:      ~p"
      "~n   ProtocolVersion: ~p"
      "~n   ActionRequests:  ~p"
      "", [ConnHandle, ProtocolVersion, ActionRequests]),
    ED =  #'ErrorDescriptor'{errorCode = ?megaco_not_implemented,
                             errorText = "Only single service change on null context handled"},
    case ActionRequests of
	[AR] ->
	    ContextId = AR#'ActionRequest'.contextId,
	    case AR#'ActionRequest'.commandRequests of
		[CR] when ContextId =:= ?megaco_null_context_id ->
		    case CR#'CommandRequest'.command of
			{serviceChangeReq, Req} ->
			    Rep = service_change(ConnHandle, ProtocolVersion, Req),
			    {discard_ack,
			     [#'ActionReply'{contextId = ContextId,
					     commandReply = [{serviceChangeReply, Rep}]}]};
			_ ->
			    {discard_ack, ED}
		    end;
		_ ->
		    {discard_ack, ED}
	    end;
	_ ->
	    {discard_ack, ED}
    end.

service_change(ConnHandle, _ProtocolVersion, SCR) ->
    SCP = SCR#'ServiceChangeRequest'.serviceChangeParms,
    #'ServiceChangeParm'{serviceChangeAddress = Address,
			 serviceChangeProfile = Profile} = SCP,
    TermId = SCR#'ServiceChangeRequest'.terminationID,
    if
	TermId == [?megaco_root_termination_id] ->
	    MyMid = ConnHandle#megaco_conn_handle.local_mid,
	    Res = {serviceChangeResParms,
		   #'ServiceChangeResParm'{serviceChangeMgcId   = MyMid,
					   serviceChangeAddress = Address,
					   serviceChangeProfile = Profile}},
	    #'ServiceChangeReply'{terminationID       = TermId,
				  serviceChangeResult = Res};
	true ->
	    Res = {errorDescriptor,
		   #'ErrorDescriptor'{errorCode = ?megaco_not_implemented,
				      errorText = "Only handled for root"}},
	    
	     #'ServiceChangeReply'{terminationID       = TermId,
				   serviceChangeResult = Res}
    end.
	
%%----------------------------------------------------------------------
%% Optionally invoked for a time consuming transaction request
%%----------------------------------------------------------------------

handle_trans_long_request(_ConnHandle, _ProtocolVersion, _ReqData) ->
    ED = #'ErrorDescriptor'{errorCode = ?megaco_not_implemented,
			    errorText = "Long transaction requests not handled"},
    {discard_ack, ED}.

%%----------------------------------------------------------------------
%% Optionally invoked for a transaction reply
%%----------------------------------------------------------------------

handle_trans_reply(ConnHandle, ProtocolVersion, ActualReply, ReplyData) ->
    d("handle_trans_eply -> entry with"
      "~n   ConnHandle:      ~p"
      "~n   ProtocolVersion: ~p"
      "~n   ActualReply:     ~p"
      "~n   ReplyData:       ~p"
      "", [ConnHandle, ProtocolVersion, ActualReply, ReplyData]),
    ok.

%%----------------------------------------------------------------------
%% Optionally invoked for a transaction acknowledgement
%%----------------------------------------------------------------------

handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData) ->
    d("handle_trans_ack -> entry with"
      "~n   ConnHandle:      ~p"
      "~n   ProtocolVersion: ~p"
      "~n   ckStatus:        ~p"
      "~n   AckData:         ~p"
      "", [ConnHandle, ProtocolVersion, AckStatus, AckData]),
    ok.

%%----------------------------------------------------------------------
%% Invoked when  an unexpected message has been received
%%----------------------------------------------------------------------

handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans) ->
    d("handle_unexpected_trans -> entry with"
      "~n   ConnHandle:      ~p"
      "~n   ProtocolVersion: ~p"
      "~n   Trans:           ~p"
      "", [ConnHandle, ProtocolVersion, Trans]),
    ok.


%%----------------------------------------------------------------------
%% Invoked when  an unexpected message has been received
%%----------------------------------------------------------------------

handle_trans_request_abort(_ConnHandle, _ProtocolVersion, _TransId, _Pid) ->
    ok.

%%----------------------------------------------------------------------
%% To be used at command line: erl -s ?MODULE start_batch
%%----------------------------------------------------------------------

start_batch() ->
    start_batch([false]).

start_batch(Args0) ->
    Defs  = [{trace,false}, {debug, false}],
    Args  = parse_args(Args0, Defs),
    Trace = get_arg(trace, Args),
    Debug = get_arg(debug, Args),
    Pid = spawn(?MODULE, init_batch, [self(), Trace, Debug]),
    receive
	{init_batch, Pid, Res} ->
	    io:format("~p(~p): ~p~n", [?MODULE, ?LINE, Res]),
	    Res
    end.

	    
-spec init_batch(ReplyTo :: pid(),
                 Trace   :: term(),
                 Debug   :: term()) ->
           no_return().

init_batch(ReplyTo, Trace, Debug) ->
    register(?MODULE, self()),
    Res = start(Trace, Debug),
    ReplyTo ! {init_batch, self(), Res},
    receive
    after infinity -> Res
    end.


parse_args([], Acc) ->
    Acc;
parse_args([Arg|Args], Acc) when is_atom(Arg) ->
    case string:tokens(atom_to_list(Arg),"{},") of
	["trace",Trace] ->
	    parse_args(Args, parse_args(trace, list_to_atom(Trace), Acc));
	["debug",Debug] ->
	    parse_args(Args, parse_args(debug, list_to_atom(Debug), Acc));
	_Invalid ->
	    parse_args(Args, Acc)
    end.

parse_args(Key, Val, Args) ->
    Entry = {Key, Val},
    case lists:keyreplace(Key, 1, Args, {Key, Val}) of
	Args ->
	    [Entry|Args];
	Args2 ->
	    Args2
    end.

get_arg(Key, Args) ->
    {value, {Key, Val}} = lists:keysearch(Key, 1, Args),
    Val.


%%----------------------------------------------------------------------
%% DEBUGGING
%%----------------------------------------------------------------------

info_msg(F) ->
    info_msg(F, []).
info_msg(F, A) ->
    io:format("~p MGC: " ++ F ++ "~n", [self()|A]).

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

d(F,A) ->
    d(get(debug),F,A).

d(true,F,A) ->
    io:format("SIMPLE_MGC: " ++ F ++ "~n", A);
d(_, _F, _A) ->
    ok.

