%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2005-2019. 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 : Handle ASN.1 BER encoding of Megaco/H.248
%%----------------------------------------------------------------------

-module(megaco_binary_encoder_lib).

%% API
-export([
	 version_of/4, 
	 decode_message/5, decode_message_dynamic/4, 
	 decode_mini_message/4, decode_mini_message_dynamic/4, 
	 encode_message/5, 
	 encode_transaction/5, 
	 encode_action_requests/5, 
	 encode_action_request/5,
	 encode_action_reply/5
	]).

-include_lib("megaco/src/engine/megaco_message_internal.hrl").


%%----------------------------------------------------------------------
%% Detect (check) which version a message is
%% Return {ok, Version} | {error, Reason}
%%----------------------------------------------------------------------

version_of(_EC, Binary, dynamic, [AsnModV1|_AsnMods]) 
  when is_binary(Binary) andalso is_atom(AsnModV1) ->
    case (catch AsnModV1:decode_message_version(Binary)) of
	{ok, PartialMsg} ->
	    V = (PartialMsg#'MegacoMessage'.mess)#'Message'.version,
	    {ok, V};
	Error ->
	    Error
    end;
version_of(_EC, Binary, 1, AsnMods) 
  when is_binary(Binary) andalso is_list(AsnMods) ->
    version_of(AsnMods, Binary, []);
version_of(_EC, Binary, 2, [AsnModV1, AsnModV2, AsnModV3]) 
  when is_binary(Binary) ->
    version_of([AsnModV2, AsnModV1, AsnModV3], Binary, []);
version_of(_EC, Binary, 3, [AsnModV1, AsnModV2, AsnModV3]) 
  when is_binary(Binary) ->
    version_of([AsnModV3, AsnModV1, AsnModV2], Binary, []).

version_of([], _Binary, Err) ->
    {error, {decode_failed, lists:reverse(Err)}};
version_of([AsnMod|AsnMods], Binary, Errs) when is_atom(AsnMod) ->
    case (catch AsnMod:decode('MegacoMessage', Binary)) of
	{ok, M} ->
	    V = (M#'MegacoMessage'.mess)#'Message'.version,
	    {ok, V};
	Err ->
	    version_of(AsnMods, Binary, [Err|Errs])
    end.

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

encode_message([native], MegaMsg, AsnMod, _TransMod, binary) 
  when is_record(MegaMsg, 'MegacoMessage') ->
    AsnMod:encode('MegacoMessage', MegaMsg);
encode_message(EC, MegaMsg, AsnMod, TransMod, binary) 
  when is_list(EC) andalso is_record(MegaMsg, 'MegacoMessage') ->
    case (catch TransMod:tr_message(MegaMsg, encode, EC)) of
	{'EXIT', Reason} ->
	    {error, Reason};
	MegaMsg2 ->
	    AsnMod:encode('MegacoMessage', MegaMsg2)
    end;
encode_message(EC, MegaMsg, AsnMod, TransMod, io_list) ->
    case encode_message(EC, MegaMsg, AsnMod, TransMod, binary) of
	{ok, Bin} when is_binary(Bin) ->
	    {ok, Bin};
	{ok, DeepIoList} ->
	    Bin = erlang:list_to_binary(DeepIoList),
	    {ok, Bin};
	{error, Reason} ->
	    {error, Reason} 
    end;
encode_message(EC, MegaMsg, _AsnMod, _TransMod, _Type)
  when is_record(MegaMsg, 'MegacoMessage')  ->
    {error, {bad_encoding_config, EC}};
encode_message(_EC, MegaMsg, _AsnMod, _TransMod, _Type) ->
    {error, {no_megaco_message, MegaMsg}}.


%%----------------------------------------------------------------------
%% Convert a transaction (or transactions in the case of ack) record(s) 
%% into a binary
%% Return {ok, Binary} | {error, Reason}
%%----------------------------------------------------------------------

%% Should handle encoding of all types of transactions:
%% TransactionAck, TransactionPending, TransactionRequest
%% and TransactionReply
encode_transaction(EC, {Tag, _} = Trans, AsnMod, TransMod, Type) 
  when (Tag == transactionResponseAck) ->
    do_encode_transaction(EC, Trans, AsnMod, TransMod, Type);
encode_transaction(EC, {Tag, _} = Trans, AsnMod, TransMod, Type) 
  when (Tag == transactionPending) -> 
    do_encode_transaction(EC, Trans, AsnMod, TransMod, Type);
encode_transaction(EC, {Tag, _} = Trans, AsnMod, TransMod, Type) 
  when (Tag == transactionRequest) ->
    do_encode_transaction(EC, Trans, AsnMod, TransMod, Type);
%% TransactionReply has been changed as of v3 so we cannot use 
%% the record definition in this common module.
encode_transaction(EC, {Tag, _} = Trans, AsnMod, TransMod, Type) 
  when (Tag == transactionReply) ->
    do_encode_transaction(EC, Trans, AsnMod, TransMod, Type);
encode_transaction(_EC, T, _AsnMod, _TransMod, _Type) ->
    {error, {no_megaco_transaction, T}}.

-spec do_encode_transaction(EC :: list(), 
			    Trans :: tuple(), 
			    AnsMod :: atom(),
			    TransMod :: atom(),
			    Type :: atom()) ->
    {'ok', binary()} | {'error', any()}.
-dialyzer({nowarn_function, do_encode_transaction/5}). % Future compat
do_encode_transaction([native], _Trans, _AsnMod, _TransMod, binary) ->
    %% asn1rt:encode(AsnMod, element(1, T), T);
    {error, not_implemented};
do_encode_transaction(EC, _Trans, _AsnMod, _TransMod, binary) 
  when is_list(EC) ->
    %% T2 = TransMod:tr_transaction(Trans, encode, EC),
    %% asn1rt:encode(AsnMod, element(1, T), T2);
    {error, not_implemented};
do_encode_transaction(EC, Trans, AsnMod, TransMod, io_list) ->
    case do_encode_transaction(EC, Trans, AsnMod, TransMod, binary) of
	{ok, Bin} when is_binary(Bin) ->
	    {ok, Bin};
	{ok, DeepIoList} ->
	    Bin = erlang:list_to_binary(DeepIoList),
	    {ok, Bin};
	{error, Reason} ->
	    {error, Reason} 
    end;
do_encode_transaction(EC, _Trans, _AsnMod, _TransMod, _Type) ->
    {error, {bad_encoding_config, EC}}.


%%----------------------------------------------------------------------
%% Convert a list of ActionRequest record's into a binary
%% Return {ok, DeepIoList} | {error, Reason}
%%----------------------------------------------------------------------
-spec encode_action_requests(EC :: list(), 
			     ARs :: list(), 
			     AnsMod :: atom(),
			     TransMod :: atom(),
			     Type :: atom()) ->
    {'ok', binary()} | {'error', any()}.
-dialyzer({nowarn_function, encode_action_requests/5}). % Future compat
encode_action_requests([native], _ARs, _AsnMod, _TransMod, binary) ->
    %% asn1rt:encode(AsnMod, element(1, T), T);
    {error, not_implemented};
encode_action_requests(_EC, _ARs0, _AsnMod, _TransMod, binary) ->
    {error, not_implemented};
encode_action_requests(EC, ARs, AsnMod, TransMod, io_list) ->
    case encode_action_requests(EC, ARs, AsnMod, TransMod, binary) of
	{ok, Bin} when is_binary(Bin) ->
	    {ok, Bin};
	{ok, DeepIoList} ->
	    Bin = erlang:list_to_binary(DeepIoList),
	    {ok, Bin};
	{error, Reason} ->
	    {error, Reason} 
    end;
encode_action_requests(EC, _ARs, _AsnMod, _TransMod, _Type) ->
    {error, {bad_encoding_config, EC}}.


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

-spec encode_action_request(EC :: list(), 
			    AR :: tuple(), 
			    AnsMod :: atom(),
			    TransMod :: atom(),
			    Type :: atom()) ->
    {'ok', binary()} | {'error', any()}.
-dialyzer({nowarn_function, encode_action_request/5}). % Future compat
encode_action_request([native], _AR, _AsnMod, _TransMod, binary) ->
    %% asn1rt:encode(AsnMod, element(1, T), T);
    {error, not_implemented};
encode_action_request(_EC, _AR, _AsnMod, _TransMod, binary) ->
    {error, not_implemented};
encode_action_request(EC, AR, AsnMod, TransMod, io_list) ->
    case encode_action_request(EC, AR, AsnMod, TransMod, binary) of
	{ok, Bin} when is_binary(Bin) ->
	    {ok, Bin};
	{ok, DeepIoList} ->
	    Bin = erlang:list_to_binary(DeepIoList),
	    {ok, Bin};
	{error, Reason} ->
	    {error, Reason} 
    end;
encode_action_request(EC, _AR, _AsnMod, _TransMod, _Type) ->
    {error, {bad_encoding_config, EC}}.


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

-dialyzer({nowarn_function, encode_action_reply/5}). % Future compat
encode_action_reply([native], _ARs, _AsnMod, _TransMod, binary) ->
    %% asn1rt:encode(AsnMod, element(1, T), T);
    {error, not_implemented};
encode_action_reply(_EC, _ARs0, _AsnMod, _TransMod, binary) ->
    {error, not_implemented};
encode_action_reply(EC, ARs, AsnMod, TransMod, io_list) ->
    case encode_action_reply(EC, ARs, AsnMod, TransMod, binary) of
	{ok, Bin} when is_binary(Bin) ->
	    {ok, Bin};
	{ok, DeepIoList} ->
	    Bin = erlang:list_to_binary(DeepIoList),
	    {ok, Bin};
	{error, Reason} ->
	    {error, Reason} 
    end;
encode_action_reply(EC, _ARs, _AsnMod, _TransMod, _Type) ->
    {error, {bad_encoding_config, EC}}.


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

decode_message_dynamic(EC, Bin, 
		       [{AsnModV1, TransModV1}, 
			{AsnModV2, TransModV2}, 
			{AsnModV3, TransModV3}], Form)
  when is_list(EC) andalso is_binary(Bin) ->
    case AsnModV1:decode_message_version(Bin) of
	{ok, PartialMsg} ->
	    V = (PartialMsg#'MegacoMessage'.mess)#'Message'.version,
	    case V of
		1 -> 
		    decode_message(EC, Bin, AsnModV1, TransModV1, Form);
		2 ->
		    decode_message(EC, Bin, AsnModV2, TransModV2, Form);
		3 ->
		    decode_message(EC, Bin, AsnModV3, TransModV3, Form)
	    end;
	{error, Reason} ->
	    {error, Reason}
    end;
decode_message_dynamic(EC, Bin, _Mods, _Type) 
  when is_binary(Bin) ->
    {error, {bad_encoding_config, EC}};
decode_message_dynamic(_EC, _BadBin, _Mods, _Type) ->
    {error, no_binary}.


decode_message(EC, Bin, AsnMod, TransMod, _) ->
    case AsnMod:decode('MegacoMessage', Bin) of
	{ok, MegaMsg} ->
	    case EC of
		[native] ->
		    {ok, MegaMsg};
		_ ->		
		    {ok, TransMod:tr_message(MegaMsg, decode, EC)}
	    end;
	{error, Reason} ->
	    {error, Reason}
    end.


%%----------------------------------------------------------------------
%% Convert a binary into a partial 'MegacoMessage' record
%% I.e. only version and Mid is fully decoded.
%% Return {ok, MegacoMessageRecord} | {error, Reason}
%%----------------------------------------------------------------------

decode_mini_message(_, Bin, Mod, _) ->
    case (catch Mod:decode_message_mId(Bin)) of
	{ok, #'MegacoMessage'{mess = Mess} = MegaMsg} ->
	    Mess2 = Mess#'Message'{messageBody = undefined},
	    {ok, MegaMsg#'MegacoMessage'{mess = Mess2}};
	Error ->
	    Error
    end.


decode_mini_message_dynamic(EC, Bin, [Mod1, Mod2, Mod3], Form) ->
    case Mod1:decode_message_version(Bin) of
	{ok, PartialMsg} ->
	    V = (PartialMsg#'MegacoMessage'.mess)#'Message'.version,
	    case V of
		1 -> 
		    decode_mini_message(EC, Bin, Mod1, Form);
		2 ->
		    decode_mini_message(EC, Bin, Mod2, Form);
		3 ->
		    decode_mini_message(EC, Bin, Mod3, Form)
	    end;
	Error ->
	    Error
    end.

