%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2006-2020. 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: Misc utility functions for the mstone modules
%%----------------------------------------------------------------------

-module(megaco_codec_mstone_lib).


%% API
%% Avoid warning for local function error/1 clashing with autoimported BIF.
-compile({no_auto_import,[error/1]}).
-export([start_flex_scanner/0, stop_flex_scanner/1,
	 expanded_messages/2, expanded_messages/3, 
	 display_os_info/0, 
	 display_system_info/0, 
	 display_alloc_info/0, 
	 display_app_info/0,
	 detect_version/3]).

%% Internal exports
-export([flex_scanner_handler/1]).

-include_lib("kernel/include/file.hrl").


%%----------------------------------------------------------------------
%% 
%% D e t e c t   V e r s i o n
%% 
%%----------------------------------------------------------------------

detect_version(Codec, Conf, Bin) ->
    case (catch Codec:version_of(Conf, Bin)) of
	{ok, V} ->
	    case (catch Codec:decode_message(Conf, V, Bin)) of
		{ok, M} ->
		    case (catch Codec:encode_message(Conf, V, M)) of
			{ok, NewBin} ->
			    {V, NewBin};
			Error1 ->
			    error({encode_failed, Error1, Codec, Conf, M})
		    end;
		Error2 ->
		    error({decode_failed, Error2, Codec, Conf, Bin})
	    end;
	Error3 ->
	    error({version_of_failed, Error3, Codec, Conf, Bin})
    end.


%%----------------------------------------------------------------------
%% 
%% D i s p l a y   O s   I n f o 
%% 
%%----------------------------------------------------------------------

display_os_info() ->
    V = case os:version() of
	    {Major, Minor, Release} ->
		lists:flatten(
		  io_lib:format("~w.~w.~w", [Major, Minor, Release]));
	    Str ->
		Str
	end,
    case os:type() of
	{OsFam, OsName} ->
	    io:format("OS:                  ~p-~p: ~s~n", [OsFam, OsName, V]);
	OsFam ->
	    io:format("OS:                  ~p: ~s~n", [OsFam, V])
    end.
	    

%%----------------------------------------------------------------------
%% 
%% D i s p l a y   S y s t e m   I n f o 
%% 
%%----------------------------------------------------------------------

display_system_info() ->
    SysArch       = system_architecture(),
    OtpRel        = otp_release(),
    SysVer        = system_version(),
    SysHT         = heap_type(),
    SysSMP        = smp_support(),
    SysNumSched   = schedulers(),
    SysProcLimit  = process_limit(),
    SysThreads    = threads(),
    SysTPSz       = thread_pool_size(),
    SchedBindings = scheduler_bindings(),
    SchedBindType = scheduler_bind_type(),
    CpuTopology   = cpu_topology(), 
    io:format("System architecture: ~s~n", [SysArch]),
    io:format("OTP release:         ~s~n", [OtpRel]),
    io:format("System version:      ~s~n", [SysVer]),
    io:format("Heap type:           ~s~n", [SysHT]),
    io:format("Thread support:      ~s~n", [SysThreads]),
    io:format("Thread pool size:    ~s~n", [SysTPSz]),
    io:format("Process limit:       ~s~n", [SysProcLimit]),
    io:format("SMP support:         ~s~n", [SysSMP]),
    io:format("Num schedulers:      ~s~n", [SysNumSched]),
    io:format("Scheduler bindings:  ~s~n", [SchedBindings]),
    io:format("Scheduler bind type: ~s~n", [SchedBindType]),
    io:format("Cpu topology:        ~s~n", [CpuTopology]),
    ok.


system_architecture() ->
    string:strip(system_info(system_architecture, string),right,$\n).

otp_release() ->
    system_info(otp_release, string).

system_version() ->
    string:strip(system_info(system_version, string),right,$\n).

heap_type() ->
    system_info(heap_type, any).

smp_support() ->
    system_info(smp_support, any).

schedulers() ->
    system_info(schedulers, any).

process_limit() ->
    system_info(process_limit, any).

threads() ->
    system_info(threads, any).

thread_pool_size() ->
    system_info(thread_pool_size, any).

scheduler_bindings() ->
    system_info(scheduler_bindings, any).

scheduler_bind_type() ->
    system_info(scheduler_bind_type, any).

cpu_topology() ->
    system_info(cpu_topology, any).

system_info(Tag, Type) ->
    case (catch erlang:system_info(Tag)) of
	{'EXIT', _} ->
	    "-";
	Info when is_list(Info) andalso (Type =:= string) ->
	    Info;
	Info ->
	    lists:flatten(io_lib:format("~w", [Info]))
    end.



%%----------------------------------------------------------------------
%% 
%% D i s p l a y   A l l o c a t o r   I n f o 
%% 
%%----------------------------------------------------------------------

display_alloc_info() ->
    io:format("Allocator memory information:~n", []),
    AllocInfo = alloc_info(),
    display_alloc_info(AllocInfo).

display_alloc_info([]) ->
    ok;
display_alloc_info([{Alloc, Mem}|AllocInfo]) ->
    io:format("  ~15w: ~10w~n", [Alloc, Mem]),
    display_alloc_info(AllocInfo).

alloc_info() ->
    case erlang:system_info(allocator) of
        {_Allocator, _Version, Features, _Settings} ->
            alloc_info(Features);
        _ ->
            []
    end.

alloc_info(Allocators) ->
    Allocs = [temp_alloc, sl_alloc, std_alloc, ll_alloc, eheap_alloc,
              ets_alloc, binary_alloc, driver_alloc],
    alloc_info(Allocators, Allocs, []).

alloc_info([], _, Acc) ->
    lists:reverse(Acc);
alloc_info([Allocator | Allocators], Allocs, Acc) ->
    case lists:member(Allocator, Allocs) of
        true ->
            Instances0 = erlang:system_info({allocator, Allocator}),
            Instances =
                if
                    is_list(Instances0) ->
                        [Instance || Instance <- Instances0,
                                     element(1, Instance) =:= instance];
                    true ->
                        []
                end,
            AllocatorMem = alloc_mem_info(Instances),
            alloc_info(Allocators, Allocs, [{Allocator, AllocatorMem} | Acc]);

        false ->
            alloc_info(Allocators, Allocs, Acc)
    end.


alloc_mem_info(Instances) ->
    alloc_mem_info(Instances, []).

alloc_mem_info([], Acc) ->
    lists:sum([Mem || {instance, _, Mem} <- Acc]);
alloc_mem_info([{instance, N, Info}|Instances], Acc) ->
    InstanceMemInfo = alloc_instance_mem_info(Info),
    alloc_mem_info(Instances, [{instance, N, InstanceMemInfo} | Acc]).

alloc_instance_mem_info(InstanceInfo) ->
    MBCS = alloc_instance_mem_info(mbcs, InstanceInfo),
    SBCS = alloc_instance_mem_info(sbcs, InstanceInfo),
    MBCS + SBCS.

alloc_instance_mem_info(Key, InstanceInfo) ->
    case lists:keysearch(Key, 1, InstanceInfo) of
        {value, {Key, Info}} ->
            case lists:keysearch(blocks_size, 1, Info) of
                {value, {blocks_size, Mem, _, _}} ->
                    Mem;
                _ ->
                    0
            end;
        _ ->
            0
    end.


%%----------------------------------------------------------------------
%% 
%% D i s p l a y   A p p   I n f o 
%% 
%%----------------------------------------------------------------------

display_app_info() ->
    display_megaco_info(),
    display_asn1_info().

display_megaco_info() ->
    MI = megaco:module_info(),
    {value, {attributes, Attr}} = lists:keysearch(attributes, 1, MI),
    {value, {app_vsn,    Ver}}  = lists:keysearch(app_vsn, 1, Attr),
    io:format("Megaco version:      ~s~n", [Ver]).

display_asn1_info() ->
    AI = megaco_ber_media_gateway_control_v1:info(),
    Vsn = 
	case lists:keysearch(vsn, 1, AI) of
	    {value, {vsn, V}} when is_atom(V) ->
		atom_to_list(V);
	    {value, {vsn, V}} when is_list(V) ->
		V;
	    _ ->
		"unknown"
	end,
    io:format("ASN.1 version:       ~s~n", [Vsn]).


%%----------------------------------------------------------------------
%% 
%% E x p a n d   M e s s a g e s
%% 
%%----------------------------------------------------------------------

expanded_messages(Codecs, DrvInclude) ->
    MessagePackage = time_test, 
    expanded_messages(MessagePackage, Codecs, DrvInclude).

expanded_messages(MessagePackage, Codecs, DrvInclude) ->
    ECodecs  = expand_codecs(Codecs, DrvInclude), 
    Messages = megaco_codec_transform:messages(MessagePackage), 
    expanded_messages2(ECodecs, Messages, []).

expanded_messages2([], _Messages, EMessages) ->
    lists:reverse(EMessages);
expanded_messages2([{Codec, Mod, Conf}|ECodecs], Messages, EMessages) ->
    case lists:keysearch(Codec, 1, Messages) of
	{value, {Codec, Msgs}} ->
	    expanded_messages2(ECodecs, Messages, 
			       [{Codec, Mod, Conf, Msgs}|EMessages]);
	false ->
	    exit({error, {no_such_codec_data, Codec}})
    end.


%%----------------------------------------------------------------------
%% 
%% E x p a n d   C o d e c s
%% 
%%----------------------------------------------------------------------

expand_codecs(Codecs, DrvInclude) ->
    expand_codecs(Codecs, DrvInclude, []).

expand_codecs([], _, ECodecs) ->
    lists:reverse(lists:flatten(ECodecs));
expand_codecs([Codec|Codecs], DrvInclude, ECodecs) when is_atom(Codec) ->
    ECodec = expand_codec(Codec, DrvInclude),
    expand_codecs(Codecs, DrvInclude, [ECodec|ECodecs]).

expand_codec(Codec, flex) ->
    case Codec of
	pretty ->
	    [{Codec, megaco_pretty_text_encoder, [flex_scanner]},
	     {Codec, megaco_pretty_text_encoder, [flex_scanner]},
	     {Codec, megaco_pretty_text_encoder, [flex_scanner]},
	     {Codec, megaco_pretty_text_encoder, [flex_scanner]},
	     {Codec, megaco_pretty_text_encoder, [flex_scanner]},
	     {Codec, megaco_pretty_text_encoder, [flex_scanner]},
	     {Codec, megaco_pretty_text_encoder, [flex_scanner]},
	     {Codec, megaco_pretty_text_encoder, [flex_scanner]}];
	compact ->
	    [{Codec, megaco_compact_text_encoder, [flex_scanner]},
	     {Codec, megaco_compact_text_encoder, [flex_scanner]},
	     {Codec, megaco_compact_text_encoder, [flex_scanner]},
	     {Codec, megaco_compact_text_encoder, [flex_scanner]},
	     {Codec, megaco_compact_text_encoder, [flex_scanner]},
	     {Codec, megaco_compact_text_encoder, [flex_scanner]},
	     {Codec, megaco_compact_text_encoder, [flex_scanner]},
	     {Codec, megaco_compact_text_encoder, [flex_scanner]}];
	ber ->
	    [];
	per ->
	    [];
	erlang ->
	    [];
	Else ->
	    error({invalid_codec, Else})
    end;
expand_codec(Codec, only_drv) ->
    case Codec of
	pretty ->
	    [{Codec, megaco_pretty_text_encoder, [flex_scanner]},
	     {Codec, megaco_pretty_text_encoder, [flex_scanner]}];
	compact ->
	    [{Codec, megaco_compact_text_encoder, [flex_scanner]},
	     {Codec, megaco_compact_text_encoder, [flex_scanner]}];
	ber ->
	    [{Codec, megaco_ber_encoder, [native]},
	     {Codec, megaco_ber_encoder, []}];
	per ->
	    [{Codec, megaco_per_encoder, [native]},
	     {Codec, megaco_per_encoder, []}];
	erlang ->
	    Encoder = megaco_erl_dist_encoder,
	    [
	     {Codec, Encoder, [megaco_compressed,compressed]},
	     {Codec, Encoder, [compressed]},
	     {Codec, Encoder, [megaco_compressed,compressed]},
	     {Codec, Encoder, [compressed]}
	    ];
	Else ->
	    error({invalid_codec, Else})
    end;
expand_codec(Codec, no_drv) ->
    case Codec of
	pretty ->
	    [{Codec, megaco_pretty_text_encoder, []},
	     {Codec, megaco_pretty_text_encoder, []}];
	compact ->
	    [{Codec, megaco_compact_text_encoder, []},
	     {Codec, megaco_compact_text_encoder, []}];
	ber ->
	    [{Codec, megaco_ber_encoder, [native]},
	     {Codec, megaco_ber_encoder, []}];
	per ->
	    [{Codec, megaco_per_encoder, [native]},
	     {Codec, megaco_per_encoder, []}];
	erlang ->
	    Encoder = megaco_erl_dist_encoder,
	    [
	     {Codec, Encoder, [megaco_compressed]},
 	     {Codec, Encoder, []},
	     {Codec, Encoder, [megaco_compressed]},
 	     {Codec, Encoder, []}
	    ];
	Else ->
	    error({invalid_codec, Else})
    end;
expand_codec(Codec, _) ->
    case Codec of
	pretty ->
	    [{Codec, megaco_pretty_text_encoder, [flex_scanner]},
	     {Codec, megaco_pretty_text_encoder, []}];
	compact ->
	    [{Codec, megaco_compact_text_encoder, [flex_scanner]},
	     {Codec, megaco_compact_text_encoder, []}];
	ber ->
	    [{Codec, megaco_ber_encoder, [native]},
	     {Codec, megaco_ber_encoder, []}];
	per ->
	    [{Codec, megaco_per_encoder, [native]},
	     {Codec, megaco_per_encoder, []}];
	erlang ->
	    Encoder = megaco_erl_dist_encoder,
	    [
	     {Codec, Encoder, [megaco_compressed,compressed]},
	     {Codec, Encoder, [compressed]},
	     {Codec, Encoder, [megaco_compressed]},
 	     {Codec, Encoder, []}
	    ];
	Else ->
	    error({invalid_codec, Else})
    end.



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

%%----------------------------------------------------------------------
%% 
%% S t a r t   F l e x   S c a n n e r   H a n d l e r
%% 
%%----------------------------------------------------------------------

start_flex_scanner() ->
    Pid = proc_lib:spawn(?MODULE, flex_scanner_handler, [self()]),
    receive
        {flex_scanner_started, Pid, Conf} ->
            {Pid, [Conf]};
        {flex_scanner_error, {failed_loading_flex_scanner_driver, Reason}} ->
            error({failed_loading_flex_scanner_driver, Reason});
        {flex_scanner_error, Reason} ->
            error({failed_loading_flex_scanner_driver, Reason})
    after 10000 ->
            exit(Pid, kill),
            error({failed_starting_flex_scanner, timeout})
    end.

%%----------------------------------------------------------------------
%% 
%% S t o p   F l e x   S c a n n e r   H a n d l e r
%% 
%%----------------------------------------------------------------------

stop_flex_scanner(Pid) ->
    Pid ! stop_flex_scanner.

flex_scanner_handler(Pid) ->
    case (catch megaco_flex_scanner:start()) of
        {ok, PortOrPorts} ->
            Pid ! {flex_scanner_started, self(), {flex, PortOrPorts}},
            flex_scanner_handler_loop(Pid, PortOrPorts);
        {error, {load_driver, {open_error, Reason}}} ->
            Error = {failed_loading_flex_scanner_driver, Reason},
            Pid ! {flex_scanner_error, Error},
            exit(Error);
        Else ->
            Error = {unknown_result_from_start_flex_scanner, Else},
            Pid ! {flex_scanner_error, Error},
            exit(Error)
    end.

flex_scanner_handler_loop(Pid, PortOrPorts) ->
    receive
        {ping, Pinger} ->
            Pinger ! {pong, self()},
            flex_scanner_handler_loop(Pid, PortOrPorts);
        {'EXIT', Port, Reason} when (Port =:= PortOrPorts) ->
            Pid ! {flex_scanner_exit, Reason},
            exit({flex_scanner_exit, Reason});
        {'EXIT', Port, Reason} when is_port(Port) ->
	    case megaco_flex_scanner:is_scanner_port(Port, PortOrPorts) of
		true ->
		    Pid ! {flex_scanner_exit, Reason},
		    exit({flex_scanner_exit, Reason});
		false ->
		    %% Just ignore this crap
		    flex_scanner_handler_loop(Pid, PortOrPorts)
	    end;
        stop_flex_scanner ->
            megaco_flex_scanner:stop(PortOrPorts),
            exit(normal);
        _Other ->
            flex_scanner_handler_loop(Pid, PortOrPorts)
    end.


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

error(Reason) ->
    throw({error, Reason}).

