%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%%
%%----------------------------------------------------------------------
%% Purpose: Basic module for reading and verifying config files
%%----------------------------------------------------------------------
-module(snmp_conf).


%% External exports
-export([read_files/2, read/2]).

%% Basic (type) check functions
-export([check_mandatory/2,
	 check_integer/1, check_integer/2,
	 
	 check_string/1, check_string/2,

	 check_atom/2, 

	 check_timer/1,

	 check_ip/1, check_taddress/1, 
	 
	 check_packet_size/1, 

	 check_oid/1,
	 
	 check_mp_model/1, 
	 check_sec_model/1, check_sec_model/2, check_sec_model/3, 
	 check_sec_level/1,

	 all_integer/1
	]).


-define(SNMP_USE_V3, true).
-include("snmp_types.hrl").

-define(VMODULE,"CONF").
-include("snmp_verbosity.hrl").


%%-----------------------------------------------------------------

read_files(Dir, Files) when list(Dir), list(Files) ->
    read_files(Dir, Files, []).

read_files(_Dir, [], Res) ->
    lists:reverse(Res);
read_files(Dir, [{Gen, Filter, Check, FileName}|Files], Res) 
  when function(Filter), function(Check), list(FileName) ->
    ?vdebug("read_files -> entry with"
	"~n   FileName: ~p", [FileName]),
    File = filename:join(Dir, FileName),
    case file:read_file_info(File) of
        {ok, _} ->
            Confs = read(File, Check),
            read_files(Dir, Files, [Filter(Confs)|Res]);
        {error, R} ->
	    ?vlog("failed reading file info for ~s: "
		  "~n   ~p", [FileName, R]),
            Gen(Dir),
            read_files(Dir, Files, [Filter([])|Res])
    end.


%% Ret. Res | exit(Reason)
read(File, Check) when function(Check) -> 
    ?vdebug("read -> entry with"
	"~n   File: ~p", [File]),
    Fd = open_file(File),

    case loop(Fd, [], Check, 1, File) of
        {error, Reason} ->     
            file:close(Fd),
            error(Reason);
        {ok, Res} ->
            file:close(Fd),
            Res
    end.

open_file(File) ->
    case file:open(File, read) of
        {ok, Fd} -> 
            Fd;
        {error, Reason} ->
            error({failed_open, File, Reason})
    end.

loop(Fd, Res, Check, StartLine, File) ->
    case do_read(Fd, "", StartLine) of
        {ok, Row, EndLine} ->
	    ?vtrace("loop -> "
		    "~n   Row:     ~p"
		    "~n   EndLine: ~p", [Row, EndLine]),
            case (catch Check(Row)) of
                ok -> 
		    ?vtrace("loop -> ok", []),
                    loop(Fd, [Row | Res], Check, EndLine, File);
                {ok, NewRow} ->
		    ?vtrace("loop -> ok: "
			    "~n   NewRow: ~p", [NewRow]),
                    loop(Fd, [NewRow | Res], Check, EndLine, File);
                {error, Reason} ->
		    ?vtrace("loop -> check error: "
			    "~n   Reason: ~p", [Reason]),
                    {error, {failed_check, File, StartLine, EndLine, Reason}};
                Error ->
		    ?vtrace("loop -> check failure: "
			    "~n   Error: ~p", [Error]),
                    {error, {failed_check, File, StartLine, EndLine, Error}}
		end;
        {error, EndLine, Error} ->
	    ?vtrace("loop -> read failure: "
		    "~n   Error: ~p", [Error]),
            {error, {failed_reading, File, StartLine, EndLine, Error}};
        eof ->
            {ok, Res}
    end.


do_read(Io, Prompt, StartLine) ->
    case io:request(Io, {get_until,Prompt,erl_scan,tokens,[StartLine]}) of
        {ok, Toks, EndLine} ->
            case erl_parse:parse_term(Toks) of
                {ok, Term} ->
                    {ok, Term, EndLine};
                {error, {Line, erl_parse, Error}} ->
                    {error, Line, {parse_error, Error}}
            end;
        {error,E,EndLine} ->
            {error, EndLine, E};
        {eof, _EndLine} ->
            eof;
        Other ->
            Other
    end.


%%-----------------------------------------------------------------


check_mandatory(L, [{Key, Value}|T]) ->
    case lists:keymember(Key, 1, L) of
	true -> 
	    check_mandatory(L, T);
	false when Value == mandatory -> 
	    error({missing_mandatory, Key});
	false ->
	    {value, V} = Value,
	    check_mandatory([{Key, V} | L], T)
    end;
check_mandatory(L, []) -> 
    {ok, L}.


%% ---------

check_integer(I) -> check_integer(I, any).

check_integer(I, any) when integer(I) -> ok;
check_integer(I, pos) when integer(I), I > 0 -> ok;
check_integer(I, neg) when integer(I), I < 0 -> ok;
check_integer(I1, {gt,  I2}) when integer(I1), integer(I2), I1 > I2 -> ok;
check_integer(I1, {gte, I2}) when integer(I1), integer(I2), I1 >= I2 -> ok;
check_integer(I1, {lt,  I2}) when integer(I1), integer(I2), I1 < I2 -> ok;
check_integer(I1, {lte, I2}) when integer(I1), integer(I2), I1 =< I2 -> ok;
check_integer(I1, {eq,  I1}) when integer(I1) -> ok;
check_integer(I,  {range, L, U}) 
  when integer(I), integer(L), integer(U), I >= L, I =< U -> ok;
check_integer(I, _)  -> error({invalid_integer, I}).

check_packet_size(S) ->
    case (catch check_integer(S, {range, 484, 2147483647})) of
	ok ->
	    ok;
	{error, _} ->
	    error({invalid_packet_size, S})
    end.

%% ---------

check_string(X) when list(X) -> ok;
check_string(X) -> error({invalid_string, X}).

check_string(X, any)       when list(X) -> ok;
check_string(X,{gt,Len})   when list(X), length(X) > Len -> ok;
check_string(X,{gt,_Len})  when list(X) -> error({invalid_length, X});
check_string(X,{gte,Len})  when list(X), length(X) >= Len -> ok;
check_string(X,{gte,_Len}) when list(X) -> error({invalid_length, X});
check_string(X,{lt,Len})   when list(X), length(X) < Len -> ok;
check_string(X,{lt,_Len})  when list(X) -> error({invalid_length, X});
check_string(X,{lte,Len})  when list(X), length(X) =< Len -> ok;
check_string(X,{lte,_Len}) when list(X) -> error({invalid_length, X});
check_string(X, Len)       when list(X), integer(Len), length(X) == Len -> ok;
check_string(X, _Len)      when list(X) -> error({invalid_length, X});
check_string(X, _Len)                   -> error({invalid_string, X}).


check_atom(X, Atoms) ->
    case lists:keysearch(X, 1, Atoms) of
        {value, {X, Val}} -> 
	    {ok, Val};
        _ -> 
	    error({invalid_atom, X, Atoms})
    end.


%% ---------

check_mp_model(MPModel) when atom(MPModel) ->
    All = [{v1,  ?MP_V1}, {v2c, ?MP_V2C}, {v3,  ?MP_V3}],
    check_atom(MPModel, All);
check_mp_model(?MP_V1) ->
    {ok, ?MP_V1};
check_mp_model(?MP_V2C) ->
    {ok, ?MP_V2C};
check_mp_model(?MP_V3) ->
    {ok, ?MP_V3};
check_mp_model(BadMpModel) ->
    error({invalid_mp_model, BadMpModel}).


%% ---------

check_sec_model(SecModel) when atom(SecModel) ->
    check_sec_model(SecModel, []);
check_sec_model(?SEC_ANY) ->
    {ok, ?SEC_ANY};
check_sec_model(?SEC_V1) ->
    {ok, ?SEC_V1};
check_sec_model(?SEC_V2C) ->
    {ok, ?SEC_V2C};
check_sec_model(?SEC_USM) ->
    {ok, ?SEC_USM};
check_sec_model(BadSecModel) ->
    error({invalid_sec_model, BadSecModel}).

check_sec_model(SecModel, Exclude) when atom(SecModel) ->
    All = [{any, ?SEC_ANY},
           {v1,  ?SEC_V1},
           {v2c, ?SEC_V2C},
           {usm, ?SEC_USM}],
    Alt = [{X, Y} || {X, Y} <- All, not lists:member(X, Exclude)],
    case (catch check_atom(SecModel, Alt) ) of
	{error, _} ->
	    error({invalid_sec_model, SecModel});
	OK ->
	    OK
    end;
check_sec_model(BadSecModel, _Exclude) ->
    error({invalid_sec_model, BadSecModel}).

check_sec_model(v1, v1, Exclude) ->
    check_sec_model2(v1, ?SEC_V1, Exclude);
check_sec_model(v1, SecModel, _Exclude) ->
    error({invalid_sec_model, v1, SecModel});
check_sec_model(v2c, v2c, Exclude) ->
    check_sec_model2(v2c, ?SEC_V2C, Exclude);
check_sec_model(v2c, SecModel, _Exclude) ->
    error({invalid_sec_model, v2c, SecModel});
check_sec_model(v3, usm, Exclude) ->
    check_sec_model2(v3, ?SEC_USM, Exclude);
check_sec_model(v3, SecModel, _Exclude) ->
    error({invalid_sec_model, v3, SecModel});
check_sec_model(M1, M2, _Exclude) ->
    error({invalid_sec_model, M1, M2}).

check_sec_model2(SecModel, SM, Exclude) ->
    case lists:member(SecModel, Exclude) of
	false ->
	    {ok, SM};
	true ->
	    error({invalid_sec_model, SecModel})
    end.


%% ---------

check_sec_level(SecLevel) when atom(SecLevel) ->
    All = [{noAuthNoPriv, 1}, {authNoPriv, 2}, {authPriv, 3}], 
    case (catch check_atom(SecLevel, All)) of
	{error, _} ->
	    error({invalid_sec_level, SecLevel});
	OK ->
	    OK
    end;
check_sec_level(1) ->
    {ok, 1};
check_sec_level(2) ->
    {ok, 2};
check_sec_level(3) ->
    {ok, 3};
check_sec_level(BadSecLevel) ->
    error({invalid_sec_level, BadSecLevel}).


%% ---------

check_taddress(X) when list(X), length(X) == 6 ->
    case (catch all_integer(X)) of
	true  -> 
	    ok;
	false -> 
	    error({invalid_taddress, X})
    end;
check_taddress(X) ->
    error({invalid_taddress, X}).


%% ---------

check_timer(infinity) ->
    {ok, infinity};
check_timer(T) when record(T, snmp_incr_timer) ->
    {ok, T};
check_timer({WaitFor, Factor, Incr, Retry} = T) ->
    case (catch do_check_timer(WaitFor, Factor, Incr, Retry)) of
	ok ->
	    {ok, #snmp_incr_timer{wait_for    = WaitFor,
				  factor      = Factor,
				  incr        = Incr,
				  max_retries = Retry}};
	_Err ->
	    error({invalid_timer, T})
    end;
check_timer(Timeout) ->
    case (catch check_integer(Timeout, {gt, 0})) of
	ok ->
	    {ok, #snmp_incr_timer{wait_for    = Timeout,
				  factor      = 1,
				  incr        = 0,
				  max_retries = 0}};
	_Err ->
	    error({invalid_timer, Timeout})
    end.

do_check_timer(WaitFor, Factor, Incr, Retry) ->
    check_integer(WaitFor, {gt,  0}),
    check_integer(Factor,  {gt,  0}),
    check_integer(Incr,    {gte, 0}),
    check_integer(Retry,   {gte, 0}),
    ok.

%% ---------

check_ip(X) when list(X), length(X) == 4 ->
    case (catch all_integer(X)) of
	true  -> 
	    ok;
	false -> 
	    error({invalid_ip_address, X})
    end;
check_ip(X) ->
    error({invalid_ip_address, X}).


%% ---------

check_oid([E1,E2|_] = X) when E1 * 40 + E2 =< 255 ->
    case all_integer(X) of
	true -> 
	    ok;
	_ -> 
	    error({invalid_object_identifier, X})
    end;
check_oid(X) -> 
    error({invalid_object_identifier, X}).


%% ---------

all_integer([H|T]) when integer(H) -> all_integer(T);
all_integer([_H|_T]) -> false;
all_integer([]) -> true.


%% ---------

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

