%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 2000-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: Externalize/internalize Megaco/H.248 messages
%%----------------------------------------------------------------------

-module(megaco_erl_dist_encoder).
-moduledoc false.

-behaviour(megaco_encoder).

-export([encode_message/3, decode_message/3,
         decode_mini_message/3,

	 encode_transaction/3,
	 encode_action_requests/3,
	 encode_action_request/3,
	 encode_command_request/3,
	 encode_action_reply/3
	]).
-export([version_of/2]).

%% Backward compatible funcs:
-export([encode_message/2, decode_message/2]).


-include("megaco_message_internal.hrl").

-define(MC_MOD, megaco_erl_dist_encoder_mc).


%%----------------------------------------------------------------------
%% Convert a 'MegacoMessage' record into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------

encode_message(Config, 
	       #'MegacoMessage'{mess = #'Message'{version = V}} = MegaMsg) ->
    encode_message(Config, V, MegaMsg).

%% <BACKWARD-COMPAT-CLAUSE>
encode_message([{version3, _}|EC], Vsn, MegaMsg) ->
    encode_message(EC, Vsn, MegaMsg);
%% </BACKWARD-COMPAT-CLAUSE>

encode_message([megaco_compressed|Config], Vsn, MegaMsg) 
  when is_record(MegaMsg, 'MegacoMessage') ->
    {ok, erlang:term_to_binary(?MC_MOD:encode(MegaMsg, Vsn), Config)};
encode_message([{megaco_compressed, Mod}|Config], Vsn, MegaMsg) 
  when is_atom(Mod) and is_record(MegaMsg, 'MegacoMessage') ->
    {ok, erlang:term_to_binary(Mod:encode(MegaMsg, Vsn), Config)};
encode_message(Config, _Vsn, MegaMsg) 
  when is_record(MegaMsg, 'MegacoMessage') ->
    {ok, erlang:term_to_binary(MegaMsg, Config)};
encode_message(_Config, _Vsn, _MegaMsg) ->
    {error, not_a_megaco_message}.


%%----------------------------------------------------------------------
%% Convert a transaction record into a binary
%% Return {ok, Bin} | {error, Reason}
%%----------------------------------------------------------------------

%% <BACKWARD-COMPAT-CLAUSE>
encode_transaction([{version3, _}|EC], Vsn, Trans) ->
    encode_transaction(EC, Vsn, Trans);
%% </BACKWARD-COMPAT-CLAUSE>

encode_transaction([megaco_compressed|Config], _Vsn, Trans) ->
    {ok, erlang:term_to_binary(?MC_MOD:encode(Trans), Config)};
encode_transaction([{megaco_compressed, Mod}|Config], _Vsn, Trans) ->
    {ok, erlang:term_to_binary(Mod:encode(Trans), Config)};
encode_transaction(Config, _Vsn, Trans) ->
    {ok, erlang:term_to_binary(Trans, Config)}.


%%----------------------------------------------------------------------
%% Convert a list of ActionRequest record's into a binary
%% Return {ok, Binary} | {error, Reason}
%%----------------------------------------------------------------------

%% <BACKWARD-COMPAT-CLAUSE>
encode_action_requests([{version3, _}|EC], Vsn, ActReqs) ->
    encode_action_requests(EC, Vsn, ActReqs);
%% </BACKWARD-COMPAT-CLAUSE>

encode_action_requests([megaco_compressed|Config], Vsn, ActReqs0) 
  when is_list(ActReqs0) ->
    ActReqs = [?MC_MOD:encode(AR, Vsn) || AR <- ActReqs0],
    {ok, erlang:term_to_binary(ActReqs, Config)};
encode_action_requests([{megaco_compressed, Mod}|Config], Vsn, ActReqs0) 
  when is_list(ActReqs0) ->
    ActReqs = [Mod:encode(AR, Vsn) || AR <- ActReqs0],
    {ok, erlang:term_to_binary(ActReqs, Config)};
encode_action_requests(Config, _Vsn, ActReqs) 
  when is_list(ActReqs) ->
    {ok, erlang:term_to_binary(ActReqs, Config)}.



%%----------------------------------------------------------------------
%% Convert a ActionRequest record into a binary
%% Return {ok, Binary} | {error, Reason}
%%----------------------------------------------------------------------

%% <BACKWARD-COMPAT-CLAUSE>
encode_action_request([{version3, _}|EC], Vsn, ActReq) ->
    encode_action_request(EC, Vsn, ActReq);
%% </BACKWARD-COMPAT-CLAUSE>

encode_action_request([megaco_compressed|Config], Vsn, ActReq) 
  when is_tuple(ActReq) ->
    {ok, erlang:term_to_binary(?MC_MOD:encode(ActReq, Vsn), Config)};
encode_action_request([{megaco_compressed, Mod}|Config], Vsn, ActReq) 
  when is_tuple(ActReq) ->
    {ok, erlang:term_to_binary(Mod:encode(ActReq, Vsn), Config)};
encode_action_request(Config, _Vsn, ActReq) 
  when is_tuple(ActReq) ->
    {ok, erlang:term_to_binary(ActReq, Config)}.



%%----------------------------------------------------------------------
%% Convert a CommandRequest record into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------

%% <BACKWARD-COMPAT-CLAUSE>
encode_command_request([{version3, _}|EC], Vsn, CmdReq) ->
    encode_command_request(EC, Vsn, CmdReq);
%% </BACKWARD-COMPAT-CLAUSE>

encode_command_request([megaco_compressed|Config], Vsn, CmdReq)
  when is_tuple(CmdReq) ->
    {ok, erlang:term_to_binary(?MC_MOD:encode(CmdReq, Vsn), Config)};
encode_command_request([{megaco_compressed, Mod}|Config], Vsn, CmdReq)
  when is_tuple(CmdReq) ->
    {ok, erlang:term_to_binary(Mod:encode(CmdReq, Vsn), Config)};
encode_command_request(Config, _Vsn, CmdReq)
  when is_tuple(CmdReq) ->
    {ok, erlang:term_to_binary(CmdReq, Config)}.
 

%%----------------------------------------------------------------------
%% Convert a action reply into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------

%% <BACKWARD-COMPAT-CLAUSE>
encode_action_reply([{version3, _}|EC], Vsn, ActRep) ->
    encode_action_reply(EC, Vsn, ActRep);
%% </BACKWARD-COMPAT-CLAUSE>

encode_action_reply([megaco_compressed|Config], Vsn, ActRep) 
  when is_tuple(ActRep) ->
    {ok, erlang:term_to_binary(?MC_MOD:encode(ActRep, Vsn), Config)};
encode_action_reply([{megaco_compressed, Mod}|Config], Vsn, ActRep) 
  when is_tuple(ActRep) ->
    {ok, erlang:term_to_binary(Mod:encode(ActRep, Vsn), Config)};
encode_action_reply(Config, _Vsn, ActRep) 
  when is_tuple(ActRep) ->
    {ok, erlang:term_to_binary(ActRep, Config)}.
 
 
%%----------------------------------------------------------------------
%% Get the megaco version of the message
%% Return {ok, Version} | {error, Reason}
%%----------------------------------------------------------------------

version_of(Config, Bin) when is_binary(Bin) ->
    case decode_message(Config, 1, Bin) of
	{ok, M} ->
	    V = (M#'MegacoMessage'.mess)#'Message'.version,
	    {ok, V};
	Error ->
	    Error
    end.

decode_message(Config, Bin) ->
    decode_message(Config, 1, Bin).
				   
%% <BACKWARD-COMPAT-CLAUSE>
decode_message([{version3, _}|EC], V, Bin) ->
    decode_message(EC, V, Bin);
%% </BACKWARD-COMPAT-CLAUSE>

decode_message([megaco_compressed = MC|_Config], Vsn, Bin) ->
    case catch erlang:binary_to_term(Bin) of
	Msg when is_tuple(Msg) ->
	    case (?MC_MOD:decode(Msg, Vsn)) of
		MegaMsg when is_record(MegaMsg, 'MegacoMessage') ->
		    {ok, dm(MegaMsg, MC, Vsn)};
		_ ->
		    {error, {bad_message, Msg}}
	    end;
	{'EXIT', _Reason} ->
	    {error, bad_binary}
    end;
decode_message([{megaco_compressed, Mod} = MC|_Config], Vsn, Bin) 
  when is_atom(Mod) ->
    case catch erlang:binary_to_term(Bin) of
	Msg when is_tuple(Msg) ->
	    case (Mod:decode(Msg, Vsn)) of
		MegaMsg when is_record(MegaMsg, 'MegacoMessage') ->
		    {ok, dm(MegaMsg, MC, Vsn)};
		_ ->
		    {error, {bad_message, Msg}}
	    end;
	{'EXIT', _Reason} ->
	    {error, bad_binary}
    end;
decode_message(_Config, Vsn, Bin) ->
    case catch erlang:binary_to_term(Bin) of
	MegaMsg when is_record(MegaMsg, 'MegacoMessage') ->
	    {ok, dm(MegaMsg, undefined, Vsn)};
	{'EXIT', _Reason} ->
	    {error, bad_binary}
    end.


decode_mini_message(EC, Vsn, Bin) when is_binary(Bin) ->
    decode_message(EC, Vsn, Bin).


%% This crap is because the transactions or the action-requests
%% might have been encoded separetely

dm(#'MegacoMessage'{mess = Mess} = M, MC, Vsn) ->
    #'Message'{messageBody = Body} = Mess,
    case Body of
	{transactions, Transactions} ->
	    Body2 = {transactions, dmt(Transactions, [], MC, Vsn)},
	    Mess2 = Mess#'Message'{messageBody = Body2},
	    M#'MegacoMessage'{mess = Mess2};
	_ ->
	    M
    end.

dmt([], Acc, _, _Vsn) ->
    lists:reverse(Acc);
dmt([Trans0|Transactions], Acc, MC, Vsn) when is_binary(Trans0) ->
    Trans1 = erlang:binary_to_term(Trans0),
    Trans2 = dmt1(Trans1, MC, Vsn),
    dmt(Transactions, [Trans2|Acc], MC, Vsn);
dmt([{Tag, Trans0}|Transactions], Acc, MC, Vsn) when is_binary(Trans0) ->
    Trans1 = erlang:binary_to_term(Trans0),
    Trans2 = dmt1(Trans1, MC, Vsn),
    dmt(Transactions, [{Tag, Trans2}|Acc], MC, Vsn);
dmt([{transactionRequest, 
      #'TransactionRequest'{actions = Acts0} = TR0}|Transactions], 
    Acc, MC, Vsn) 
  when is_binary(Acts0) ->
    Acts1 = erlang:binary_to_term(Acts0),
    Acts2 = dmt1(Acts1, MC, Vsn),
    TR1 = TR0#'TransactionRequest'{actions = Acts2},
    dmt(Transactions, [{transactionRequest, TR1}|Acc], MC, Vsn);
dmt([{transactionRequest, 
      #'TransactionRequest'{actions = Acts0} = TR0}|Transactions], 
    Acc, MC, Vsn) ->
    Acts2 = [dmt2(AR, MC, Vsn) || AR <- Acts0],
    TR1 = TR0#'TransactionRequest'{actions = Acts2},
    dmt(Transactions, [{transactionRequest, TR1}|Acc], MC, Vsn);
dmt([Trans|Transactions], Acc, MC, Vsn) ->
    dmt(Transactions, [Trans|Acc], MC, Vsn).

dmt1(L, megaco_compressed, Vsn) when is_list(L) ->
    [?MC_MOD:decode(E, Vsn) || E <- L];
dmt1(L, {megaco_compressed, Mod}, Vsn) when is_list(L) ->
    [Mod:decode(E, Vsn) || E <- L];
dmt1(T, megaco_compressed, Vsn) when is_tuple(T) ->
    ?MC_MOD:decode(T, Vsn);
dmt1(T, {megaco_compressed, Mod}, Vsn) when is_tuple(T) ->
    Mod:decode(T, Vsn);
dmt1(Else, _, _Vsn) ->
    Else.
	    
dmt2(Bin, MC, Vsn) when is_binary(Bin) ->
    AR = erlang:binary_to_term(Bin),
    dmt1(AR, MC, Vsn);
dmt2(AR, _MC, _Vsn) ->
    AR.


