%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2001-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 : Scanner for text encoded Megaco/H.248 messages
%%----------------------------------------------------------------------

-module(megaco_flex_scanner).

-export([is_enabled/0, is_reentrant_enabled/0, is_scanner_port/2]).
-export([start/0, start/1, stop/1, scan/2]).

-define(NUM_SCHED(),           erlang:system_info(schedulers)).
-define(SCHED_ID(),            erlang:system_info(scheduler_id)).
-define(SMP_SUPPORT_DEFAULT(), erlang:system_info(smp_support)).

-dialyzer({nowarn_function, is_enabled/0}).
is_enabled() -> 
    (true =:= ?ENABLE_MEGACO_FLEX_SCANNER).

-dialyzer({nowarn_function, is_reentrant_enabled/0}).
is_reentrant_enabled() ->
    (true =:= ?MEGACO_REENTRANT_FLEX_SCANNER).

is_scanner_port(Port, Port) when is_port(Port) ->
    true;
is_scanner_port(Port, Ports) when is_tuple(Ports) ->
    is_own_port(Port, Ports);
is_scanner_port(_, _) ->
    false.

is_own_port(Port, Ports) ->
    is_own_port(Port, size(Ports), Ports).

is_own_port(_Port, 0, _Ports) ->
    false;
is_own_port(Port, N, Ports) when (N > 0) ->
    case element(N, Ports) of
	Port ->
	    true;
	_ ->
	    is_own_port(Port, N-1, Ports)
    end.

	    
%%----------------------------------------------------------------------
%% Start the flex scanner
%%----------------------------------------------------------------------

start() ->
    start(?SMP_SUPPORT_DEFAULT()).

start(SMP) when ((SMP =:= true) orelse (SMP =:= false)) ->
    (catch do_start(is_reentrant_enabled() andalso SMP)).

do_start(SMP) ->
    Path = lib_dir(),
    erl_ddll:start(), 
    load_driver(Path),
    PortOrPorts = open_drv_port(SMP),
    {ok, PortOrPorts}.


lib_dir() ->
    case code:priv_dir(megaco) of
	{error, Reason} ->
	    throw({error, {priv_dir, Reason}});
	P when is_list(P) ->
	    P ++ "/lib"
    end.
    

load_driver(Path) ->
    case erl_ddll:load_driver(Path, drv_name()) of
	ok ->
	    ok;
	{error, Reason} ->
	    case (catch erl_ddll:format_error(Reason)) of
		FormatReason when is_list(FormatReason) ->
		    throw({error, {load_driver, FormatReason}});
		_ ->
		    throw({error, {load_driver, Reason}})
	    end
    end.


open_drv_port(true) ->
    open_drv_ports(?NUM_SCHED(), []);
open_drv_port(_) ->
    open_drv_port().

open_drv_ports(0, Acc) ->
    list_to_tuple(Acc);
open_drv_ports(N, Acc) when is_integer(N) andalso (N > 0) ->
    Port = open_drv_port(),
    open_drv_ports(N-1, [Port | Acc]).

open_drv_port() ->
    case (catch erlang:open_port({spawn, drv_name()}, [binary])) of
	Port when is_port(Port) ->
	    Port;
	{'EXIT', Reason} ->
	    erl_ddll:unload_driver(drv_name()),
	    throw({error, {open_port, Reason}})
    end.

drv_name() ->
    case erlang:system_info(threads) of
	true ->
	    "megaco_flex_scanner_drv_mt";
	false ->
	    "megaco_flex_scanner_drv"
    end.


%%----------------------------------------------------------------------
%% Stop the flex scanner
%%----------------------------------------------------------------------

stop(Port) when is_port(Port) ->
    erlang:port_close(Port), 
    erl_ddll:unload_driver(drv_name()),
    stopped;
stop(Ports) when is_tuple(Ports) ->
    stop(tuple_to_list(Ports));
stop(Ports) when is_list(Ports) ->
    lists:foreach(fun(Port) ->  erlang:port_close(Port) end, Ports),
    erl_ddll:unload_driver(drv_name()),
    stopped.


%%----------------------------------------------------------------------
%% Scan a message
%%----------------------------------------------------------------------

scan(Binary, Port) when is_port(Port) ->
    do_scan(Binary, Port);
scan(Binary, Ports) when is_tuple(Ports) ->
%%     p("scan -> entry with"
%%       "~n   Ports: ~p", [Ports]),
    do_scan(Binary, select_port(Ports)).

do_scan(Binary, Port) ->
%%     p("do_scan -> entry with"
%%       "~n   Port: ~p", [Port]),
    case erlang:port_control(Port, $s, Binary) of
	[] ->
	    receive
		{tokens, Tokens, LatestLine} ->
%% 		    p("do_scan -> OK with:"
%% 		      "~n   length(Tokens): ~p"
%% 		      "~n   LatestLine:     ~p", [length(Tokens), LatestLine]),
		    Vsn = version(Tokens),
		    {ok, Tokens, Vsn, LatestLine} 
	    after 5000 ->
%%  		    p("do_scan -> ERROR", []),
		    {error, "Driver term send failure", 1}
	    end;
	Reason ->
%% 	    p("do_scan -> port control failed: "
%% 	      "~n   Reason: ~p", [Reason]),
	    {error, Reason, 1}
    end.

select_port(Ports) ->
    SchedId = ?SCHED_ID(),
    %% lists:nth(SchedId, Ports).
    element(SchedId, Ports).

version([]) ->
    99; % Let the parser deal with this
version([{'SafeChars',_,"!/1"}|_]) ->
    1;
version([{'SafeChars',_,"megaco/1"}|_]) ->
    1;
version([{'SafeChars',_,"!/2"}|_]) ->
    2;
version([{'SafeChars',_,"megaco/2"}|_]) ->
    2;
version([{'SafeChars',_,"!/3"}|_]) ->
    3;
version([{'SafeChars',_,"megaco/3"}|_]) ->
    3;
version([{'SafeChars',_,[$!, $/ | Vstr]}|_]) ->
    guess_version(Vstr);
version([{'SafeChars',_,[$m, $e, $g, $a, $c, $o, $/ | Vstr]}|_]) ->
    guess_version(Vstr);
version([_|T]) ->
    version(T).


guess_version([C]) when (48 =< C) and (C =< 57) ->
    C-48;
guess_version(Str) when is_list(Str) ->
    case (catch list_to_integer(Str)) of
	I when is_integer(I) ->
	    I;
	_ ->
	    99 % Let the parser deal with this
    end;
guess_version(_) ->
    99. % Let the parser deal with this


%% p(F, A) ->
%%     io:format("~w [~p,~p] " ++ F ++ "~n", [?MODULE, self(), ?SCHED_ID() | A]).
