%%% Copyright (c) 2016, NORDUnet A/S.
%%% See LICENSE for licensing information.

-module(dnssecport).
-behaviour(gen_server).
-export([start_link/0, stop/0]).
-export([validate/2]).
%% gen_server callbacks.
-export([init/1, handle_call/3, terminate/2, handle_cast/2, handle_info/2,
         code_change/3]).

-include_lib("eunit/include/eunit.hrl").

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE,
                          [code:priv_dir(catlfish) ++ "/dnssecport"], []).

stop() ->
    gen_server:call(?MODULE, stop).

validate(ValidateRR, SupportRRs) ->
    gen_server:call(?MODULE, {validate, [ValidateRR, SupportRRs]}).

-record(state, {port :: port()}).

init(Program) ->
    lager:debug("starting dnssec service"),
    Port = create_port(Program, []), % TODO: Pass path to dir with trust root.
    {ok, #state{port = Port}}.

handle_call(stop, _From, State) ->
    lager:debug("dnssec stop request received"),
    stop_port(State);
handle_call({validate, [ValidateRR, SupportRRs]}, _From, State) ->
    lager:debug("dnssec validate incoming request: ~p", [ValidateRR]),
    case State#state.port of
        undefined ->
            {error, noport};
        Port when is_port(Port) ->
            S = list_to_binary(SupportRRs),
            Port ! {self(), {command, <<ValidateRR/binary, S/binary>>}},
            receive
                {Port, {data, Response}} ->
                    lager:debug("received response from dnssec port: ~p", 
                                [Response]),
                    case Response of
                        "valid" ->
                            {reply, ok, State};
                        Err ->
                            {reply, {error, Err}, State}
                    end;
                {Port, {exit_status, ExitStatus}} ->
                    lager:error("dnssec port ~p exiting with status ~p",
                                [Port, ExitStatus]),
                    {stop, portexit, State#state{port = undefined}}
            after
                3000 ->
                    lager:error("dnssec port timeout"),
                    {stop, timeout, State}
            end
    end.

handle_info(_Info, State)           ->
    {noreply, State}.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

handle_cast(_Request, State)        ->
    {noreply, State}.

terminate(Reason, _State)           ->
    lager:info("dnssec port terminating: ~p", [Reason]),
    ok.

%%%%%%%%%%%%%%%%%%%%
create_port(Program, Args)          ->
    open_port({spawn_executable, Program},
              [{args, Args},
               exit_status,             % Let us know if process dies.
               {packet, 4}]).

stop_port(State) ->
    Port = State#state.port,
    Port ! {self(), close},
    receive
        {Port, closed} ->
            {stop, closed, State#state{port = undefined}};
        {Port, Msg} ->
            lager:debug("message received from dying port: ~p", [Msg]),
            {stop, unknown, State#state{port = undefined}}
    after
        2000 ->
            lager:error("dnssec port ~p refuses to die", [Port]),
            {stop, timeout, State}
    end.

%%%%%%%%%%%%%%%%%%%%
%% Unit tests.
start_test_port(TestType) ->
    Port = create_port("priv/dnssecport", ["--testmode", atom_to_list(TestType)]),
    ?debugFmt("Port: ~p", [Port]),
    Port.
stop_test_port(Port) ->
    {stop, closed, _State} = stop_port(#state{port = Port}),
    ok.

err_test_() ->
    {setup,
     fun() -> start_test_port(err) end,
     fun(Port) -> stop_test_port(Port) end,
     fun(Port)  ->
             R = handle_call({validate, [<<"invalid-DS">>, []]}, 
                             self(), #state{port = Port}),
             [
              ?_assertMatch({reply, {error, "err"}, _State}, R)
             ] 
     end}.

ok_test_() ->
    {setup,
     fun() -> start_test_port(ok) end,
     fun(Port) -> stop_test_port(Port) end,
     fun(Port)  ->
             R = handle_call({validate, [<<"invalid-DS">>, []]},
                             self(), #state{port = Port}),
             [
              ?_assertMatch({reply, ok, _State}, R)
             ]
     end}.