%% 
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2012. All Rights Reserved.
%%
%% 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 online 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.
%%
%% %CopyrightEnd%
%% 

-module(snmpc_lib).

%% API
%% Avoid warning for local function error/2 clashing with autoimported BIF.
-compile({no_auto_import,[error/2]}).
-export([test_father/4, make_ASN1type/1, import/1, makeInternalNode2/2,
	 is_consistent/1, resolve_defval/1, make_variable_info/1,
	 check_trap_name/3, make_table_info/4, get_final_mib/2, set_dir/2,
	 look_at/1, add_cdata/2,
	 check_object_group/4, check_notification_group/4, 
	 check_notification/3, 
	 register_oid/4,
	 error/2, error/3,
	 %% warning/2, warning/3,
	 print_error/2, print_error/3,
	 make_cdata/1,
	 key1search/2, key1search/3]).

%% internal exports
-export([check_of/1, check_trap/2, check_trap/3, get_elem/2]).

%% debug exports
-export([vvalidate/1, vprint/6]).


-include("snmp_types.hrl").
-include("snmpc.hrl").
-include("snmpc_lib.hrl").


%%----------------------------------------------------------------------------
%% Some basic types 
%%----------------------------------------------------------------------------

-type severity() :: 'silence' | 'warning' | 'info' | 'log' | 'debug' | 'trace'.


test_father(FatherName, NewVarName, SubIndex, Line) ->
    CDATA = get(cdata),
    case lists:keysearch(FatherName, #me.aliasname, CDATA#cdata.mes) of
	{value, #me{entrytype = table, aliasname = TableName}} ->
	    print_error("Variable '~w' (sub-index '~w') cannot "
			"be defined under table '~w'.",
			[NewVarName, SubIndex, TableName],Line);
	{value, #me{entrytype = table_entry, aliasname = TableName}} ->
	    print_error("Variable '~w' (sub-index '~w') cannot "
			"be defined under table entry '~w'.",
			[NewVarName, SubIndex, TableName], Line);
	
	_X -> %% internal or variable
	    case lists:last(SubIndex) of
		0 ->
		    print_error("'~w'. A zero-valued final subidentifier is reserved for future use. (RFC1902, 7.10)",[NewVarName],Line);
		_ -> ok
	    end
    end.

make_ASN1type({{type,Type},Line}) ->
    case lookup_vartype(Type) of
        {value, ASN1type} ->  
	    ASN1type;
	false ->
	    print_error("Undefined type '~w'",[Type],Line),
	    guess_integer_type()
    end;
make_ASN1type({{type_with_size,Type,{range,Lo,Hi}},Line}) ->
    case lookup_vartype(Type) of
        {value,ASN1type} ->  
	    case allow_size_rfc1902(BaseType = ASN1type#asn1_type.bertype) of
		true ->
		    ok;
		false ->
		    print_error(
		      "Size refinement is not allowed for subclass from ~w.",
		      [BaseType],Line)
	    end,
	    ASN1type#asn1_type{lo = Lo, hi = Hi};
	false ->
	    print_error("Undefined type '~w'",[Type],Line),
	    guess_string_type()
    end;
make_ASN1type({{integer_with_enum,Type,Enums},Line}) ->
    case lookup_vartype(Type) of
        {value,ASN1type} ->  ASN1type#asn1_type{assocList = [{enums, Enums}]};
	false ->
	    print_error("Undefined type '~w'",[Type],Line),
    	    guess_integer_type()
    end;
make_ASN1type({{bits,Kibbles},Line}) ->
    case get(snmp_version) of
	2 ->
	    {value,Bits} = lookup_vartype('BITS'),
	    Kibbles2 = test_kibbles(Kibbles, Line),
	    Bits#asn1_type{assocList = [{kibbles, Kibbles2}]};
	_ ->
	    guess_integer_type()
    end;
make_ASN1type({{sequence_of, _Type},Line}) ->
    print_error("Use of SEQUENCE OF in non-table context.",[],Line),
    guess_integer_type().

test_kibbles([], Line) ->
    print_error("No kibbles found.",[],Line),
    [];
test_kibbles(Kibbles,Line) ->
    test_kibbles2(R = lists:keysort(2,Kibbles),0,Line),
    R.

test_kibbles2([],_,_) ->
    ok;
test_kibbles2([{_KibbleName,BitNo}|Ks],ExpectBitNo,Line)
  when BitNo >= ExpectBitNo ->
    test_kibbles2(Ks,BitNo+1,Line);
test_kibbles2([{_KibbleName,BitNo}|_Ks],ExpectBitNo,Line) ->
    print_error("Expected kibble no ~p but got ~p.",[ExpectBitNo,BitNo],Line).
    
    
allow_size_rfc1902('INTEGER') -> true;
allow_size_rfc1902('Integer32') -> true;
allow_size_rfc1902('Unsigned32') -> true;
allow_size_rfc1902('OCTET STRING') -> true;
allow_size_rfc1902('Gauge32') -> true;
allow_size_rfc1902(_) -> false.

guess_integer_type() ->
    {value,ASN1int} = lookup_vartype('INTEGER'),
    ASN1int.

guess_string_type() ->
    {value,ASN1str} = lookup_vartype('OCTET STRING'),
    ASN1str.

lookup_vartype(Type) ->
    CDATA = get(cdata),
    lists:keysearch(Type, #asn1_type.aliasname, CDATA#cdata.asn1_types).




%%--------------------------------------------------
%% Reads the oid-function files.
%% Out: A list of {oid, entry}.
%% oid is here either a Oid with integers, or
%% with symbolic names.
%% entry is {M,F,A}.
%%--------------------------------------------------
read_funcs(FileName) ->
    case snmpc_misc:read_noexit(FileName, fun check_of/1) of
	{ok, Res} -> Res;
	{error, LineNo, Reason} ->
	    print_error("~p: ~w: Syntax error: ~p", 
			[FileName, LineNo, Reason]),
	    [];
        {error, open_file} -> []
    end.

check_of({module, M}) when is_atom(M) ->
    {ok, {module, M}};
check_of({Oid, {M, F, A}}) when is_atom(M) andalso is_atom(F) andalso is_list(A) ->
    {ok, {Oid, {M, F, A}}};
check_of({_Oid, {M, F, A}})  ->
    {invalid_argument, {M, F, A}};
check_of(X) ->
    {invalid_func, X}.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section for IMPORT implementation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

import(ImportList) ->
    %% Register some well known top-nodes (directly under the root)
    WellKnownNodes = [ makeInternalNode(ccitt,             [0]),
		       makeInternalNode(iso,               [1]),
		       makeInternalNode('joint-iso-ccitt', [2]) ],
    lists:foreach(
      fun(#me{aliasname = AliasName, oid = Oid}) ->
	      register_oid(undef, AliasName, root, Oid)
      end,
      WellKnownNodes),
    lists:foreach(fun import_mib/1, ImportList).


%%----------------------------------------------------------------------
%% Returns: <nothing> only side effect stuff.
%%----------------------------------------------------------------------
import_mib({{'SNMPv2-SMI', ImportsFromMib},Line}) ->
    Nodes = [makeInternalNode(internet,     [1,3,6,1]),
	     makeInternalNode(directory,    [1,3,6,1,1]),
	     makeInternalNode(mgmt,         [1,3,6,1,2]),
	     makeInternalNode('mib-2',      [1,3,6,1,2,1]),
	     makeInternalNode(transmission, [1,3,6,1,2,1,10]),
	     makeInternalNode(experimental, [1,3,6,1,3]),
	     makeInternalNode(private,      [1,3,6,1,4]),
	     makeInternalNode(enterprises,  [1,3,6,1,4,1]),
	     makeInternalNode(zeroDotZero,  [0,0]),
	     makeInternalNode(security,     [1,3,6,1,5]),
	     makeInternalNode(snmpV2,       [1,3,6,1,6]),
	     makeInternalNode(snmpDomains,  [1,3,6,1,6,1]),
	     makeInternalNode(snmpProxys,   [1,3,6,1,6,2]),
	     makeInternalNode(snmpModules,  [1,3,6,1,6,3])],
    Types = [#asn1_type{bertype   = 'Integer32',
			aliasname = 'Integer32',
			lo = -2147483648, hi = 2147483647},
	     #asn1_type{bertype   = 'IpAddress',
			aliasname = 'IpAddress',
			lo = 4, hi = 4},
	     #asn1_type{bertype   = 'Counter32', 
			aliasname = 'Counter32',
			lo = 0, hi = 4294967295},
	     #asn1_type{bertype   = 'Gauge32', 
			aliasname = 'Gauge32',
			lo = 0, hi = 4294967295},
	     #asn1_type{bertype   = 'Unsigned32', 
			aliasname = 'Unsigned32',
			lo = 0, hi = 4294967295},
	     #asn1_type{bertype   = 'TimeTicks', 
			aliasname = 'TimeTicks',
			lo = 0, hi=4294967295},
	     #asn1_type{bertype   = 'Opaque', 
			aliasname = 'Opaque'},
	     #asn1_type{bertype   = 'Counter64', 
			aliasname = 'Counter64',
			lo = 0, hi = 18446744073709551615}],
    Macros = ['MODULE-IDENTITY','OBJECT-IDENTITY','OBJECT-TYPE',
	      'NOTIFICATION-TYPE'],
    import_built_in_loop(ImportsFromMib,Nodes,Types,Macros,'SNMPv2-SMI',Line);
import_mib({{'RFC-1215', ImportsFromMib},Line}) ->
    Macros = ['TRAP-TYPE'],
    import_built_in_loop(ImportsFromMib, [],[],Macros,'RFC-1215', Line);
import_mib({{'RFC-1212', ImportsFromMib},Line}) ->
    Macros = ['OBJECT-TYPE'],
    import_built_in_loop(ImportsFromMib, [],[],Macros,'RFC-1212', Line);
import_mib({{'SNMPv2-TC', ImportsFromMib},Line}) ->
    Nodes  = [],
    Types  = [#asn1_type{aliasname = 'DisplayString',
			 bertype   = 'OCTET STRING',
			 lo = 0, hi = 255},
	      #asn1_type{aliasname = 'PhysAddress',
			 bertype   = 'OCTET STRING'},
	      #asn1_type{aliasname = 'MacAddress',
			 bertype   = 'OCTET STRING',
			 lo = 6, hi = 6},
	      #asn1_type{aliasname = 'TruthValue',
			 bertype   = 'INTEGER',
			 assocList = [{enums,[{false,2},{true,1}]}]},
	      #asn1_type{aliasname = 'TestAndIncr',
			 bertype   = 'INTEGER',
			 lo = 0, hi = 2147483647},
	      #asn1_type{aliasname = 'AutonomousType',
			 bertype   = 'OBJECT IDENTIFIER'},
	      #asn1_type{aliasname = 'InstancePointer',
			 bertype   = 'OBJECT IDENTIFIER'},
	      #asn1_type{aliasname = 'VariablePointer',
			 bertype   = 'OBJECT IDENTIFIER'},
	      #asn1_type{aliasname = 'RowPointer',
			 bertype   = 'OBJECT IDENTIFIER'},
	      #asn1_type{aliasname = 'RowStatus',
			 bertype   = 'INTEGER',
			 assocList = [{enums,[{destroy,       6},
					      {createAndWait, 5},
					      {createAndGo,   4},
					      {notReady,      3},
					      {notInService,  2},
					      {active,        1}]}]},
	      #asn1_type{aliasname = 'TimeStamp',
			 bertype   = 'TimeTicks'},
	      #asn1_type{aliasname = 'TimeInterval',
			 bertype   = 'INTEGER',
			 lo = 0, hi = 2147483647},
	      #asn1_type{aliasname = 'DateAndTime',
			 bertype   = 'OCTET STRING',
			 lo = 8, hi = 11}, %% Actually 8 | 11
	      #asn1_type{aliasname = 'StorageType',
			 bertype   = 'INTEGER',
			 assocList = [{enums,[{readOnly,    5},
					      {permanent,   4},
					      {nonVolatile, 3},
					      {volatile,    2},
					      {other,       1}]}]},
	      #asn1_type{aliasname = 'TDomain',
			 bertype   = 'OBJECT IDENTIFIER'},
	      #asn1_type{aliasname = 'TAddress',
			 bertype   = 'OCTET STRING',
			 lo = 1, hi = 255}
	     ],
    Macros = ['TEXTUAL-CONVENTION'],
    import_built_in_loop(ImportsFromMib,Nodes,Types,Macros,'SNMPv2-TC',Line);
import_mib({{'SNMPv2-CONF', ImportsFromMib},Line}) ->
    Macros = ['OBJECT-GROUP',
	      'NOTIFICATION-GROUP',
	      'MODULE-COMPLIANCE', 
	      'AGENT-CAPABILITIES'],
    import_built_in_loop(ImportsFromMib,[],[],Macros,'SNMPv2-CONF',Line);
import_mib({{'RFC1155-SMI', ImportsFromMib},Line}) ->
    Nodes = [makeInternalNode(internet, [1,3,6,1]),
	     makeInternalNode(directory, [1,3,6,1,1]),
	     makeInternalNode(mgmt, [1,3,6,1,2]),
	     makeInternalNode(experimental, [1,3,6,1,3]),
	     makeInternalNode(private, [1,3,6,1,4]),
	     makeInternalNode(enterprises, [1,3,6,1,4,1])],
    Types = [#asn1_type{bertype = 'NetworkAddress',
			aliasname = 'NetworkAddress', lo = 4, hi = 4},
	     #asn1_type{bertype='Counter',aliasname='Counter',
			lo=0,hi=4294967295},
	     #asn1_type{bertype='Gauge',aliasname='Gauge',
			lo = 0, hi = 4294967295},
	     #asn1_type{bertype='IpAddress',aliasname='IpAddress',lo=4,hi=4},
	     #asn1_type{bertype = 'TimeTicks', aliasname = 'TimeTicks',
			lo = 0, hi=4294967295},
	     #asn1_type{bertype = 'Opaque', aliasname = 'Opaque'}],
    Macros = ['OBJECT-TYPE'],
    import_built_in_loop(ImportsFromMib,Nodes,Types,Macros,'RFC1155-SMI',Line);
import_mib({{MibName, ImportsFromMib},Line}) ->
    import_from_file({{MibName, ImportsFromMib},Line}).

import_built_in_loop(Objs, Nodes, Types, Macros, MibName, Line) ->
    lists:foreach(fun (Obj) ->
			 import_built_in(Obj,Nodes,Types,Macros,MibName,Line)
		 end, Objs).

import_from_file({{_MibName, []}, _Line}) -> 
    done;
import_from_file({{MibName, ImportsFromMib},Line}) ->
    Filename         = atom_to_list(MibName) ++ ".bin",
    {value, Path}    = snmpc_misc:assq(i, get(options)),
    {value, LibPath} = snmpc_misc:assq(il,get(options)),
    LibPath2         = include_lib(LibPath),
    Path2 = Path++LibPath2++[filename:join(code:priv_dir(snmp),"mibs"),
			     "./"],
    ImportedMib = case read_mib(Line,Filename, Path2) of
		      error ->
			  error("Could not import ~p from mib ~s. "
				"File not found. "
				"Check that the MIB to be IMPORTED "
				"is compiled and present in the import path.",
				[ImportsFromMib, Filename], Line);
		      Mib -> 
			  Mib
		  end,
    lists:foreach(fun (ImpObj) -> import(ImpObj,ImportedMib) end,
		  ImportsFromMib).

import_built_in({_tag,Obj}, Nodes, Types, Macros, MibName, Line) ->
    case lookup(Obj, Nodes) of
	{value, ME} ->
	    register_oid(undef, ME#me.aliasname, root, ME#me.oid),
	    add_cdata(#cdata.mes, [ME#me{imported = true, oid = undefined}]);
	false ->
	    case lists:keysearch(Obj, #asn1_type.aliasname, Types) of
		{value, ASN1Type} ->
		    add_cdata(#cdata.asn1_types,
			      [ASN1Type#asn1_type{imported=true}]);
		false ->
		    case lists:member(Obj, Macros) of
			true ->
			    add_cdata(#cdata.imported_macros,[Obj]);
			false ->
			    print_error("Cannot find '~w' in mib '~s'.",
					[Obj, MibName], Line)
		    end
	    end
    end.

include_lib([]) -> [];
include_lib([Dir|Dirs]) ->
    [Appl|Path] = filename:split(Dir),
    case code:lib_dir(list_to_atom(Appl)) of
	{error, _Reason} ->
	    include_lib(Dirs);
	DirPath ->
	    [filename:join(DirPath,filename:join(Path))|include_lib(Dirs)]
    end.


%%----------------------------------------------------------------------
%% Returns: #mib
%%----------------------------------------------------------------------
read_mib(_Line, _Filename, []) ->
    error;
read_mib(Line, Filename, [Dir|Path]) ->
    Dir2 = snmpc_misc:ensure_trailing_dir_delimiter(Dir),
    case snmpc_misc:read_mib(AbsFile=lists:append(Dir2, Filename)) of
	{ok, MIB} -> MIB;
	{error, enoent} ->
	    read_mib(Line, Filename, Path);
	{error, Reason} ->
	    ?vwarning("~s found but not imported: "
		      "~n   Reason: ~p", [AbsFile,Reason]),
	    read_mib(Line, Filename, Path)
    end.


%%----------------------------------------------------------------------
%% imports ME or Type from other Mib into current compilation data.
%%----------------------------------------------------------------------
import({node, NodeName}, #mib{mes = IMES, name = MibName}) ->
    case lookup(NodeName, IMES) of
	{value, ME} when ME#me.imported == false ->
	    register_oid(undef, ME#me.aliasname, root, ME#me.oid),
	    add_cdata(#cdata.mes, [ME#me{imported = true}]);
	_ ->
	    print_error("Cannot find '~w' among the objects in the mib '~s'.",
			[NodeName, MibName])
    end;
import({type, TypeName}, #mib{asn1_types = Types, name = MibName}) ->
    case lists:keysearch(TypeName, #asn1_type.aliasname, Types) of
	{value, ASN1Type} when is_record(ASN1Type, asn1_type) andalso 
                          (ASN1Type#asn1_type.imported =:= false) ->
	    add_cdata(#cdata.asn1_types, [ASN1Type#asn1_type{imported=true,
							aliasname=TypeName}]);
	_X ->
	    print_error("Cannot find '~w' among the types in the mib '~s'.",
			[TypeName, MibName])
    end;
import({builtin, Obj}, #mib{}) ->
    print_error("~p should be imported from a standard mib.",[Obj]).
    


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section for initialisation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% Types defined in rfc1155 (SMI) are hard coded.
init_types() ->
    VerDep = case get(snmp_version) of
	     1 -> [];
	     2 ->
		  [#asn1_type{imported=true,bertype='BITS',aliasname='BITS'}]
	     end,
    [#asn1_type{imported = true, bertype = 'INTEGER', aliasname = 'INTEGER'},
     #asn1_type{imported=true,bertype='OCTET STRING',aliasname='OCTET STRING'},
     #asn1_type{imported=true,bertype='BIT STRING',aliasname='BIT STRING'},
     #asn1_type{imported = true, bertype = 'OBJECT IDENTIFIER',
		aliasname = 'OBJECT IDENTIFIER'} | VerDep].
     
makeInternalNode(Name, Oid) -> 
    makeInternalNode3(false, Name, Oid).

makeInternalNode2(Imported, Name) ->
    #me{imported = Imported, aliasname = Name, entrytype = internal}.

makeInternalNode3(Imported, Name, Oid) ->
    #me{imported = Imported, oid = Oid, aliasname = Name, entrytype = internal}.

make_cdata(CDataArg) ->
    MibFuncs =
        case CDataArg of
            {module, _Mod} -> [CDataArg];
            {file, MibFuncsFile} -> read_funcs(MibFuncsFile)
        end,
    #cdata{mibfuncs   = MibFuncs, 
	   asn1_types = init_types(),
	   oid_ets    = ets:new(oid_ets,    [set, private]),
	   status_ets = ets:new(status_ets, [set, private])}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section for Intermib consistency checking
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
is_consistent(Filenames) ->
    case catch check_all_consistency(Filenames) of
	ok -> 
	    ok;
	{undef, Format, Data} ->
	    ok = io:format(Format, Data),
	    io:format("~n"),
	    {error, inconsistent}
    end.
    
check_all_consistency(Filenames) ->
    MIBs = [load_mib(Filename) || Filename <- Filenames],
    check_oid_conflicts(MIBs),
    check_trap_conflicts(MIBs),
    ok.

check_oid_conflicts(MIBs) ->
    MEs = lists:append( [get_elem(MIB, #mib.mes) || MIB <- MIBs] ), 
    SortedMEs = lists:keysort(#me.oid, MEs),
    search_for_dublettes2(#me{aliasname=dummy_init}, SortedMEs).

check_trap_conflicts(MIBs) ->
    Traps = lists:append( [get_elem(MIB, #mib.traps) || MIB <- MIBs] ),
    [check_trap(Trap, Traps) || Trap <- Traps].

check_trap(Trap, Traps) ->
    %% check_trap/3 -> error | ok
    Checked = [check_trap(T, Trap, undef) || T <- lists:delete(Trap, Traps)],
    case lists:member(error, Checked) of
	true ->
	    throw({undef,"",[]});
	false ->
	    ok
    end.


%%----------------------------------------------------------------------
%% Returns: {Oid, ASN1Type}
%%----------------------------------------------------------------------
trap_variable_info(Variable, Type, MEs) ->
    case lookup(Variable, MEs) of
	false ->
	    error("Error in ~s definition. Cannot find object '~w'.",
		  [Type, Variable]);
	{value, ME} when ME#me.entrytype == variable ->
	    {{variable, ME#me.aliasname}, ME#me.asn1_type};
	{value, ME} ->
	    {{column, ME#me.aliasname}, ME#me.asn1_type}
    end.

get_elem(MIB, Idx) ->
    element(Idx, MIB).

load_mib(Filename) ->
    F1 = snmpc_misc:strip_extension_from_filename(Filename, ".mib"),
    F2 = lists:append(F1, ".bin"),
    case snmpc_misc:read_mib(F2) of
	{error, Reason} ->
	    throw({undef, "Error reading file: ~w. Reason:~w", [F1, Reason]});
	{ok, Mib} ->
	    Mib
    end.

search_for_dublettes2(_PrevME, []) -> ok;
search_for_dublettes2(PrevME, [ME|MEs])
            when ME#me.imported==true ->
    search_for_dublettes2(PrevME, MEs);
search_for_dublettes2(PrevME, [ME|MEs]) 
    when PrevME#me.oid == ME#me.oid ->
    if PrevME#me.entrytype == internal, ME#me.entrytype == internal,
       PrevME#me.aliasname == ME#me.aliasname ->
	    search_for_dublettes2(ME, MEs);
       true ->
	    throw({undef,"Multiple used object with OBJECT IDENTIFIER '~w'"
			  " Used by '~w' and '~w' ", [PrevME#me.oid,
						      PrevME#me.aliasname, 
						      ME#me.aliasname]})
    end;
search_for_dublettes2(_PrevME, [ME|MEs]) ->
    search_for_dublettes2(ME, MEs).



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section for handling of default value resolving
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

resolve_defval(ME) ->
    case has_complex_defval(ME) of
	true ->
	    CDATA = get(cdata),
	    resolve_complex_defval(ME, CDATA#cdata.mes);
	false -> ME
    end.

has_complex_defval(#me{aliasname = N,
		       assocList = AssocList, 
		       asn1_type = #asn1_type{bertype = BT}}) 
           when is_list(AssocList) ->
    case snmpc_misc:assq(defval, AssocList) of
	{value, Int} when is_integer(Int) ->
	    false;
	{value, Val} when is_atom(Val) andalso (BT =:= 'OBJECT IDENTIFIER') ->
	    false; % resolved in update_me_oids
	{value, Val} when is_atom(Val) andalso (BT =:= 'INTEGER') ->
	    true;
	{value, Bits} when is_list(Bits) andalso (BT =:= 'BITS') ->
	    true;
	{value, Str} when is_list(Str) andalso (BT =:= 'OCTET STRING') ->
	    false; % but ok
	{value, Str} when is_list(Str) andalso (BT =:= 'Opaque') ->
	    false; % but ok
	{value, Str} when is_list(Str) andalso 
			  (length(Str) =:= 4) andalso 
			  (BT =:= 'IpAddress') ->
	    false; % but ok
	{value, Shit} ->
	    print_error("Bad default value for ~p: ~p [~p]",[N,Shit,BT]),
	    false;
	false -> %% no defval (or strings nyi)
	    false
    end;
has_complex_defval(_) -> false.

resolve_complex_defval(ME, _AllMEs) 
     when (ME#me.asn1_type)#asn1_type.bertype == 'INTEGER' ->
    #me{aliasname = MEName, assocList = AssocList} = ME,
    {value, DefVal} = snmpc_misc:assq(defval, AssocList),
    #asn1_type{bertype = TypeName,
	       assocList = AssocListForASN1Type} = ME#me.asn1_type,
    case snmpc_misc:assq(enums, AssocListForASN1Type) of
	false ->
	    print_error("Type '~w' has no defined enums. "
			"Used in DEFVAL for '~w'.", [TypeName, MEName]),
	    ME;
	{value, Enums} ->
	    case snmpc_misc:assq(DefVal, Enums) of
		false ->
		    print_error("Enum '~w' not found. "
				"Used in DEFVAL for '~w'.", [DefVal, MEName]),
		    ME;
		{value, IntVal} when is_integer(IntVal) ->
		    ME#me{assocList = lists:keyreplace(defval, 1, AssocList,
						       {defval, IntVal})}
	    end
    end;

resolve_complex_defval(ME, _AllMEs) 
     when (ME#me.asn1_type)#asn1_type.bertype =:= 'BITS' ->
    #me{aliasname = MEName, assocList = AssocList} = ME,
    {value, DefVal} = snmpc_misc:assq(defval, AssocList),
    #asn1_type{assocList = AssocListForASN1Type} = ME#me.asn1_type,
    {value, Kibbles} = snmpc_misc:assq(kibbles, AssocListForASN1Type),
    case snmpc_misc:bits_to_int(DefVal,Kibbles) of
	error->
	    print_error("Invalid default value ~w for ~w.",[DefVal, MEName]),
	    ME;
	IntVal when is_integer(IntVal) ->
	    ME#me{assocList = lists:keyreplace(defval, 1, AssocList,
					       {defval, IntVal})}
    end.


make_variable_info(#me{asn1_type = Asn1Type, assocList = Alist}) ->
    Defval = 
	case snmpc_misc:assq(defval, Alist) of
	    {value, Val} -> 
		Val;
	    _ -> 
		get_def(Asn1Type)
	end,
    #variable_info{defval = Defval}.

get_def(#asn1_type{bertype = BT, lo = LO, assocList = AL}) ->
    ?vtrace("get_def -> entry with"
	    "~n   BT: ~p"
	    "~n   LO: ~p"
	    "~n   AL: ~p", [BT, LO, AL]),
    get_def(BT, LO, AL).

get_def('INTEGER', Lo, _) when is_integer(Lo) -> Lo;
get_def('INTEGER', _, AL) -> 
    case snmpc_misc:assq(enums, AL) of
	{value, Enums} ->
	    case lists:keysort(2, Enums) of
		[{_, Val}|_] ->
		    Val;
		_ ->
		    0
	    end;
	_ ->
	    0
    end;
get_def('Counter', _, _) -> 0;
get_def('Gauge', _, _) -> 0;
get_def('TimeTicks', _, _) -> 0;
get_def('OCTET STRING', _, _) -> "";
get_def('IpAddress', _, _) -> [0,0,0,0];
get_def('NetworkAddress', _, _) -> [0,0,0,0];
get_def('OBJECT IDENTIFIER', _, _) -> [0, 0];
get_def('Opaque', _, _) -> "";
%v2
get_def('Integer32',Lo, _) when is_integer(Lo) -> Lo;
get_def('Integer32',_, _) -> 0;
get_def('Counter32',_, _) -> 0;
get_def('Gauge32',_, _) -> 0;
get_def('Unsigned32',_, _) -> 0;
get_def('BITS',_, _) -> 0;
get_def('Counter64',_, _) -> 0.

check_trap_name(EnterpriseName, Line, MEs) ->
    case lists:keysearch(EnterpriseName, #me.aliasname, MEs) of
	false -> 
	    error("Error in trap definition. Cannot find object '~w'.",
		  [EnterpriseName],Line);
	{value, _} -> 
	    true
    end.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section for table functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%----------------------------------------------------------------------
%% This information is needed to be able to create default instrumentation
%% functions for tables.
%%----------------------------------------------------------------------
make_table_info(Line, TableName, {augments, SrcTableEntry}, ColumnMEs) ->
    ColMEs = lists:keysort(#me.oid, ColumnMEs),
    Nbr_of_Cols = length(ColMEs), 
    MEs = ColMEs ++ (get(cdata))#cdata.mes,
    Aug = case lookup(SrcTableEntry, MEs) of
	      false ->
		  print_error("Cannot AUGMENT the non-existing table entry ~p",
			      [SrcTableEntry], Line),
		  {augments, error};
	      {value, ME} ->
		  {augments, {SrcTableEntry, translate_type(ME#me.asn1_type)}}
	  end,
    FirstNonIdxCol = augments_first_non_index_column(ColMEs), 
    NoAccs         = list_not_accessible(FirstNonIdxCol, ColMEs),
    FirstAcc       = first_accessible(TableName, ColMEs),
    #table_info{nbr_of_cols      = Nbr_of_Cols,
		first_accessible = FirstAcc, 
		not_accessible   = NoAccs, 
		index_types      = Aug}; 
make_table_info(Line, TableName, {indexes, []}, _ColumnMEs) ->
    print_error("Table ~w lacks indexes.", [TableName],Line),
    #table_info{};
make_table_info(Line, TableName, {indexes, Indexes}, ColumnMEs) ->
    ColMEs = lists:keysort(#me.oid, ColumnMEs),
    NonImpliedIndexes = lists:map(fun non_implied_name/1, Indexes),
    test_read_create_access(ColMEs, Line, dummy),
    NonIndexCol = test_index_positions(Line, NonImpliedIndexes, ColMEs),
    Nbr_of_Cols = length(ColMEs),
    ASN1Indexes = find_asn1_types_for_indexes(Indexes, ColMEs, Line),
    FA = first_accessible(TableName, ColMEs),
    StatCol = find_status_col(Line, TableName, ColMEs),
    NoAccs = list_not_accessible(NonIndexCol,ColMEs),
    case lists:member(StatCol,NoAccs) of
	true ->
	    print_error("Status column cannot be not-accessible. In table ~p.",
			[TableName],Line);
	false -> ok
    end,
    #table_info{nbr_of_cols      = Nbr_of_Cols,
		first_own_index  = find_first_own_index(NonImpliedIndexes,
							ColMEs, 1),
		status_col       = StatCol,
		first_accessible = FA,
		not_accessible   = NoAccs,
		index_types      = ASN1Indexes}.

%% Perkins p110
test_read_create_access([#me{aliasname = N, access = 'read-create'}|_ColMEs],
			Line, 'read-write') ->
    print_error("Column ~p cannot be read-create when another is read-write.",
		[N], Line);
test_read_create_access([#me{aliasname = N, access = 'read-write'}|_ColMEs],
		       Line, 'read-create') ->
    print_error("Column ~p cannot be read-write when another is read-create.",
		[N], Line);
test_read_create_access([#me{access = 'read-write'}|ColMEs],
			Line, _OtherStat) ->
    test_read_create_access(ColMEs,Line,'read-write');
test_read_create_access([#me{access = 'read-create'}|ColMEs],
			Line, _OtherStat) ->
    test_read_create_access(ColMEs,Line,'read-create');
test_read_create_access([_ME|ColMEs],Line,OtherStat) ->
    test_read_create_access(ColMEs,Line,OtherStat);
test_read_create_access([], _Line, _) -> 
    ok.    

find_status_col(_Line, _TableName, []) ->
    undefined;
find_status_col(_Line, _TableName,
		[#me{asn1_type=#asn1_type{aliasname='RowStatus'}}|_]) ->
    1;
find_status_col(Line, TableName, [_ShitME | MEs]) ->
    case find_status_col(Line, TableName, MEs) of
	undefined -> undefined;
	N -> 1+N
    end.

list_not_accessible(none,_) -> [];
list_not_accessible(NonIndexCol, ColMEs) when is_integer(NonIndexCol) ->
    list_not_accessible(lists:nthtail(NonIndexCol - 1,ColMEs)).

list_not_accessible([#me{access='not-accessible', oid=Col}|ColMEs]) ->
    [Col | list_not_accessible(ColMEs)];
list_not_accessible([_ColME|ColMEs]) ->
    list_not_accessible(ColMEs);
list_not_accessible([]) ->
    [].

%%----------------------------------------------------------------------
%% See definition of first_own_index in the table_info record definition.
%%----------------------------------------------------------------------
find_first_own_index([], _ColMEs, _FOI) -> 0;
find_first_own_index([NameOfIndex | Indexes], ColMEs, FOI) ->
    case lists:keysearch(NameOfIndex, #me.aliasname, ColMEs) of
	{value, _ME} ->
	    FOI;
	false ->
	    find_first_own_index(Indexes, ColMEs, FOI + 1)
    end.

first_accessible(TableName, []) ->
    error("Table '~w' must have at least one accessible column.",[TableName]);
first_accessible(TableName, [#me{access = 'not-accessible'} | T]) ->
    first_accessible(TableName, T);
first_accessible(_TableName, [#me{oid = Col} | _]) ->
    Col.

get_defvals(ColMEs) ->
    lists:keysort(1, 
        lists:filter(fun drop_undefined/1,
		     lists:map(fun column_and_defval/1, ColMEs))).

find_asn1_types_for_indexes(Indexes, ColMEs,Line) ->
    MEs = ColMEs ++ (get(cdata))#cdata.mes,
    test_implied(Indexes, Line),
    lists:map(fun (ColumnName) ->
		      translate_type(get_asn1_type(ColumnName, MEs,Line))
	      end, 
	      Indexes).

test_implied([],_) -> ok;
test_implied([{implied, _Type}, _OtherIndexElem|_], Line) ->
    print_error("Implied must be last.", [], Line);
test_implied([{implied, _Type}], _Line) -> 
    ok;
test_implied([_H|T], Line) -> 
    test_implied(T, Line).

drop_undefined({_X, undefined}) -> false;
drop_undefined({_X, _Y}) -> true;
drop_undefined(undefined) -> false;
drop_undefined(_X) -> true.

%% returns: {ColumnNo, Defval}
column_and_defval(#me{oid = Oid, assocList = AssocList}) ->
    ColumnNo = lists:last(Oid),
    case snmpc_misc:assq(defval, AssocList) of
	false -> {ColumnNo, undefined};
	{value, DefVal} -> {ColumnNo, DefVal}
    end.

%% returns: an asn1_type if ColME is an indexfield, otherwise undefined.
get_asn1_type({implied,ColumnName}, MEs, Line) ->
    case lookup(ColumnName, MEs) of
	{value,#me{asn1_type=A}} when A#asn1_type.bertype =:=
				      'OCTET STRING' ->
	    A#asn1_type{implied = true};
	{value,#me{asn1_type=A}} when A#asn1_type.bertype =:= 
				      'OBJECT IDENTIFIER' ->
	    A#asn1_type{implied = true};
	Shit ->
	    print_error("Only OCTET STRINGs and OIDs can be IMPLIED.(~w)", 
			[Shit], Line)
    end;
get_asn1_type(ColumnName, MEs, Line) ->
    case lookup(ColumnName, MEs) of
	{value,ME} -> ME#me.asn1_type;
	false -> error("Can't find object ~p. Used as INDEX in table.",
		       [ColumnName],Line)
    end.

test_index_positions(Line, Indexes, ColMEs) ->
    IsTLI = fun(IndexName) -> is_table_local_index(IndexName, ColMEs) end, 
    TLI   = lists:filter(IsTLI, Indexes),
    test_index_positions_impl(Line, TLI, ColMEs).

%% An table that augments another cannot conatin any index, 
%% so the first non-index column is always the first column.
augments_first_non_index_column([]) ->
    none;
augments_first_non_index_column([#me{oid=Col}|_ColMEs]) ->
    Col.

%% Returns the first non-index column | none
test_index_positions_impl(_Line, [], []) -> none;
test_index_positions_impl(_Line, [], [#me{oid=Col}|_ColMEs]) ->
    Col;
test_index_positions_impl(Line, Indexes,
			  [#me{aliasname = Name, 
			       asn1_type = Asn1} | ColMEs]) ->
    case lists:member(Name, Indexes) of
	true ->
	    if 
		Asn1#asn1_type.bertype =:= 'BITS' ->
		    print_error("Invalid data type 'BITS' for index '~w'.",
				[Name],Line);
	       true -> true
	    end,
	    test_index_positions_impl(Line,
					  lists:delete(Name, Indexes), ColMEs);
	false -> 
	    ?vwarning2("Index columns must be first for "
		       "the default functions to work properly. "
		       "~w is no index column.", [Name], Line),
	    none
    end.

is_table_local_index(IndexName, ColMEs) ->
    case lists:keysearch(IndexName, #me.aliasname, ColMEs) of
	false -> false;
	_Q -> true
    end.

non_implied_name({implied, IndexColumnName}) -> IndexColumnName;
non_implied_name(IndexColumnName) -> IndexColumnName.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section for generationg the final mib
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% returns: {ok, 
%  {snmp_mib, MEs, traps, list of {TrapOid, list of oids (objects)}}}
get_final_mib(Name, Options) ->
    ?vdebug("get_final_mib -> entry", []),
    CDATA = get(cdata),
    #cdata{mes        = MEs,
	   mibfuncs   = MibFuncs,
	   asn1_types = Types,
	   traps      = Traps0,
	   oid_ets    = OidEts} = CDATA,

    ?vdebug("get_final_mib -> resolve oids", []),
    resolve_oids(OidEts),
    %% Reverse so that we get report on objects earlier in the file
    %% before later objects.
    UMEs = update_me_oids(lists:reverse(MEs), OidEts, []),
    ?vtrace("get_final_mib -> "
	    "~n   UMEs: ~p", [UMEs]),

    Traps1 = update_trap_objects(Traps0, MEs, []), 
    Traps2 = update_trap_oids(Traps1, OidEts, []),
    ?vtrace("get_final_mib -> "
	    "~n   Traps2: ~p", [Traps2]),

    SortedMEs = lists:keysort(#me.oid,UMEs),
    ?vdebug("get_final_mib -> search for dublettes", []),
    search_for_dublettes(#me{aliasname=dummy_init}, SortedMEs),

    ?vdebug("get_final_mib -> search for oid conflicts", []),
    search_for_oid_conflicts(Traps2, SortedMEs),

    ?vdebug("get_final_mib -> resolve oid", []),
    %% FIXME: use list comprehension instead
    MibFs = lists:keysort(1,
      lists:zf(fun({module, _Mod}) -> false;
                  (MF) -> {true, resolve_oid(MF,SortedMEs)}
               end, MibFuncs)),
    ?vtrace("get_final_mib -> "
	    "~n   MibFs: ~p", [MibFs]),
    {value, DBName} = snmpc_misc:assq(db, Options),
    Module = key1search(module, MibFuncs, undefined),
    MEsWithMFA = insert_mfa(MibFs, SortedMEs, DBName, Module),
    Misc = [{snmp_version,get(snmp_version)}
	    | case lists:member(no_symbolic_info,Options) of
		  true -> [no_symbolic_info];
		  false -> []
	      end],
    {value, GroupBool} = snmpc_misc:assq(group_check, Options),
    case GroupBool of
	true ->
	    case get(snmp_version) =:= 2 of
		true ->
		    ?vdebug("get_final_mib -> check object groups:"
			    "~n   ~p", [CDATA#cdata.objectgroups]),
		    check_group(CDATA#cdata.mes, 
				CDATA#cdata.objectgroups),
		    ?vdebug("get_final_mib -> check notifications group:"
			    "~n   ~p", [CDATA#cdata.notificationgroups]),
		    check_notification(Traps2,
				       CDATA#cdata.notificationgroups);
		false ->
		    ok
	    end;
	false ->
	    ok
    end,
    MI = module_identity(CDATA),
    Mib = #mib{name            = Name, 
	       misc            = Misc,
	       module_identity = MI,
	       mes            = lists:map(fun(ME) -> translate_me_type(ME) end,
					  MEsWithMFA), 
	       variable_infos = extract_variable_infos(MEsWithMFA),
	       table_infos    = extract_table_infos(MEsWithMFA),
	       traps          = lists:map(fun(T) -> translate_trap_type(T) end,
					  Traps2), 
	       asn1_types     = lists:map(fun(T) -> translate_type(T) end,
					  Types),
	       imports        = CDATA#cdata.imports},
    ?vdebug("get_final_mib -> done", []),
    {ok, Mib}.
	      

module_identity(#cdata{module_identity = MI}) ->
    case lists:member(module_identity, get(options)) of
	true ->
	    MI;
	false ->
	    undefined
    end.


update_trap_objects([], _MEs, Acc) ->
    ?vtrace("update_trap_objects -> done", []),
    lists:reverse(Acc);
update_trap_objects([#trap{trapname   = Name, 
			   oidobjects = Variables} = Trap|Traps], MEs, Acc) ->
    ?vtrace("update_trap_objects -> update objects for trap ~p:"
	    "~n   ~p", [Name, Variables]),
    OidObjects = 
	[trap_variable_info(Var, "trap", MEs) || Var <- Variables],
    UpdTrap = Trap#trap{oidobjects = OidObjects},
    update_trap_objects(Traps, MEs, [UpdTrap|Acc]);
update_trap_objects([#notification{trapname   = Name, 
				   oidobjects = Variables} = Notif|Traps], 
		    MEs, Acc) ->
    ?vtrace("update_trap_objects -> update objects for notification ~p:"
	    "~n   ~p", [Name, Variables]),
    OidObjects = 
	[trap_variable_info(Var, "notification", MEs) || Var <- Variables],
    UpdNotif = Notif#notification{oidobjects = OidObjects},
    update_trap_objects(Traps, MEs, [UpdNotif|Acc]);
update_trap_objects([_|Traps], MEs, Acc) ->
    update_trap_objects(Traps, MEs, Acc).


%% We don't want a zillion aliases for INTEGER (etc),
%% and since they are encoded with the same tag we can treat them as
%% equivalent.
%% The reason for having them at compile time is for example that
%% Unsigned32 is allowed as INDEX but not Gauge.
%% The compiler might want to ensure this and more...
translate_me_type(ME) ->
    ME#me{asn1_type = translate_type(ME#me.asn1_type)}.

translate_trap_type(Trap) when is_record(Trap, notification) ->
    translate_trap_type_notif(Trap);
translate_trap_type(Trap) when is_record(Trap, trap) ->
    translate_trap_type_trap(Trap).

translate_trap_type_notif(Trap)->
    NewOidobjects = 
	lists:map(fun({Oid,ASN1type}) ->{Oid,translate_type(ASN1type)} end,
		  Trap#notification.oidobjects),
    Trap#notification{oidobjects=NewOidobjects}.

translate_trap_type_trap(Trap)->
    NewOidobjects = 
	lists:map(fun({Oid,ASN1type}) ->
			  {Oid, translate_type(ASN1type)} 
		  end,
		  Trap#trap.oidobjects),
    Trap#trap{oidobjects = NewOidobjects}.
    
translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'NetworkAddress' ->
    ASN1type#asn1_type{bertype = 'IpAddress'};
translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Integer32' ->
    ASN1type#asn1_type{bertype = 'INTEGER'};
translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Counter' ->
    ASN1type#asn1_type{bertype = 'Counter32'};
translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Gauge' ->
    ASN1type#asn1_type{bertype = 'Unsigned32'};
translate_type(ASN1type) when ASN1type#asn1_type.bertype =:= 'Gauge32' ->
    ASN1type#asn1_type{bertype = 'Unsigned32'};
translate_type(ASN1type) -> ASN1type.

%% Check for both NOTIFICATION-GROUP and OBJECT-GROUP

check_notification_group(Name, GroupObjects, Line, Status) ->
    #cdata{traps = Traps, status_ets = Ets} = get(cdata),
    Objects = get_notification_names(Traps),
    check_def(notification, Name, Line, Status, GroupObjects, Objects, Ets).

get_notification_names(Traps) when is_list(Traps) ->
    [Name || #notification{trapname = Name} <- Traps].

check_object_group(Name, GroupObjects, Line, Status) ->
    #cdata{mes = MEs, status_ets = Ets} = get(cdata),
    Objects = get_object_names(MEs),
    check_def(object, Name, Line, Status, GroupObjects, Objects, Ets).

get_object_names([])->[];
get_object_names([#me{access=A, entrytype=T, aliasname=N}|MEs]) 
  when (A =/= 'not-accessible') andalso (T =/= 'internal') ->
    [N|get_object_names(MEs)];
get_object_names([_ME|Rest]) ->
    get_object_names(Rest).

%% Strictly we should not need to check more then the status 
%% table, but since error do happen...
check_def(Type, Name, Line, Status, [GroupObject|GroupObjects], Objects, Ets) ->
    ?vdebug2("check definition of ~p [~p]: presumed member of ~p [~p]",
	     [GroupObject, Type, Name, Status], Line),
    case lists:member(GroupObject, Objects) of
	true ->
	    ?vtrace("~p is a member of ~p", [GroupObject, Name]),
	    %% Lucky so far, now lets check that the status is valid
	    case ets:lookup(Ets, GroupObject) of
		[{GroupObject, ObjectStatus}] ->
		    ?vtrace("check that the object status (~p) is valid", 
			    [ObjectStatus]),
		    check_group_member_status(Name, Status,
					      GroupObject, ObjectStatus);
		_ ->
		    print_error("group (~w) member ~w not found"
				" in status table - status check failed", 
				[Name, GroupObject])
	    end;
	false ->
	    %% Ok, this could be because the status is obsolete or
	    %% deprecated (with the deprecated flag = false)
	    ?vtrace("~p is not a member of ~p "
		    "[object status could be obsolete]", 
		    [GroupObject, Name]),
	    case ets:lookup(Ets, GroupObject) of
		[{GroupObject, ObjectStatus}] ->
		    ?vtrace("check that the object status (~p) is valid", 
			    [ObjectStatus]),
		    check_group_member_status(Name, Status,
					      GroupObject, ObjectStatus);
		_ ->
		    group_member_error(Type, GroupObject, Line)
	    end
    end,
    check_def(Type, Name, Line, Status, GroupObjects, Objects, Ets);
check_def(_, _, _, _, [], _, _) ->
    ok.

group_member_error(object, Name, Line) ->
    print_error("OBJECT-TYPE definition missing or "
		"'not-accessible' for '~w'", [Name],Line);
group_member_error(notification, Name, Line) ->
    print_error("NOTIFICATION-TYPE definition missing for '~w'", 
		[Name], Line).
		

check_group_member_status(_GroupName, _GroupStatus, _Member, undefined) ->
    ok;
check_group_member_status(_GroupName, current, _Member, current) ->
    ok;
check_group_member_status(GroupName, current, Member, MemberStatus) ->
    group_member_status_error(GroupName, current, Member, MemberStatus,
			      "current");
check_group_member_status(_GroupName, deprecated, _Member, MemberStatus) 
  when (MemberStatus =:= deprecated) orelse (MemberStatus =:= current) ->
    ok;
check_group_member_status(GroupName, deprecated, Member, MemberStatus) ->
    group_member_status_error(GroupName, deprecated, Member, MemberStatus,
			      "deprecated or current");
check_group_member_status(_GroupName, obsolete, _Member, MemberStatus) 
  when (MemberStatus =:= obsolete) orelse
       (MemberStatus =:= deprecated) orelse
       (MemberStatus =:= current) ->
    ok;
check_group_member_status(GroupName, obsolete, Member, MemberStatus) ->
    group_member_status_error(GroupName, obsolete, Member, MemberStatus,
			      "obsolete, deprecated or current");
check_group_member_status(_GroupName, _GroupStatus, _Member, _MemberStatus) ->
    ok.

group_member_status_error(Name, Status, Member, MemberStatus, Expected) ->
    snmpc_lib:print_error("Invalid status of group member ~p "
			  "in group ~p. "
			  "Group status is ~p "
			  "and member status is ~p "
			  "(should be ~s)", 
			  [Member, Name, Status, MemberStatus, Expected]).



% check_def(Objects,[GroupObject|GroupObjects],Line)->
%     case lists:member(GroupObject,Objects) of
% 	false ->
% 	    print_error("OBJECT-TYPE definition missing or "
% 			"'not-accessible' for '~w'", [GroupObject],Line),
% 	    check_def(Objects,GroupObjects,Line);
% 	true ->
% 	    check_def(Objects,GroupObjects,Line)
%     end;
% check_def(_Objects,[],_Line) ->
%     ok.

%% Checking the definition of OBJECT-GROUP

%%-----------------------------
check_group([#me{imported = true} | T],GroupObjects)->
    ?vtrace("check_group(imported) -> skip", []),
    check_group(T,GroupObjects);
check_group([],_GroupObjects) ->
    ?vtrace("check_group -> done", []),
    ok;
check_group([#me{access    = A, 
		 entrytype = T, 
		 aliasname = N}|MEs], GroupObjects) 
  when ((A =/= 'not-accessible') andalso 
	(T =/= 'internal') andalso 
	(T =/= group)) ->
    ?vtrace("check_group -> "
	    "~n   access:    ~p"
	    "~n   entrytype: ~p"
	    "~n   aliasname: ~p", [A, T, N]),
    check_member_group(N, GroupObjects),
    check_group(MEs,GroupObjects);
check_group([_|MEs],GroupObjects) ->
    check_group(MEs,GroupObjects).

check_member_group(Aliasname, [])->
    print_error("'~w' missing in OBJECT-GROUP",[Aliasname]);
check_member_group(Aliasname, [{Name,GroupObject,Line}|Tl])->
    ?vtrace2("check_member_group -> entry with"
	     "~n   Aliasname:   ~p"
	     "~n   Name:        ~p"
	     "~n   GroupObject: ~p", [Aliasname, Name, GroupObject], Line),
    case lists:member(Aliasname,GroupObject) of
	true ->
	    ok;
	false ->
	    check_member_group(Aliasname,Tl)
    end.                                     


%% Checking definition in NOTIFICATION-GROUP

%%--------------------------
check_notification([],_NotificationObjects) ->
    ok;
check_notification([#notification{trapname=Aliasname}|Traps],
		   NotificationObjects) ->
    check_member_notification(Aliasname, NotificationObjects),
    check_notification(Traps,NotificationObjects);
check_notification([_|Traps],NotificationObjects) ->
    check_notification(Traps,NotificationObjects).

check_member_notification(Aliasname,[])->
    print_error("'~w' missing in NOTIFICATION-GROUP",[Aliasname]);
check_member_notification(Aliasname,[{_Name,NotificationObject,_Line}|Tl]) ->
    case lists:member(Aliasname, NotificationObject) of
	true ->
	    ok;
	false ->
	    check_member_notification(Aliasname,Tl)
    end.                               


%%----------------------------------------------------------------------
%% Purpose: Resolves oids for aliasnames used in .funcs file.
%% Returns: {Oid, X}
%%----------------------------------------------------------------------
resolve_oid({NameOrOid, X}, MEs) ->
    case lookup(NameOrOid, MEs) of
	{value, #me{oid=Oid,entrytype=variable}} -> {Oid, X};
	{value, #me{oid=Oid,entrytype=table}} -> {Oid, X};
	{value, #me{entrytype=table_entry}} ->
		error("Cannot associate an instrumentation function with a "
		      "Table Entry: ~w (must be table or variable)",
		      [NameOrOid]);
	{value, #me{entrytype=table_column}} ->
		error("Cannot associate an instrumentation function with a "
		      "Table Column: ~w (must be table or variable)",
		      [NameOrOid]);
	_Q ->
	    error("Cannot find OBJECT-TYPE definition for '~w'.",
		  [NameOrOid])
    end.

%%----------------------------------------------------------------------
%% Fs is list of {Oid, {M,F,A}}
%% returns: MEs with access-functions.
%% Pre: Fs, MEs are sorted (on Oid) (then we can traverse mib efficiently)
%%----------------------------------------------------------------------
insert_mfa(Fs, [ME | MEs], DBName, Mod) 
  when ME#me.imported =:= true ->
    [ME | insert_mfa(Fs, MEs, DBName, Mod)];

insert_mfa(Fs, [ME | MEs], DBName, Mod) 
  when ME#me.entrytype =:= internal ->
    [ME | insert_mfa(Fs, MEs, DBName, Mod)];

insert_mfa(Fs, [ME|MEs], DBName, Mod) 
  when ME#me.entrytype =:= group ->
    [ME | insert_mfa(Fs, MEs, DBName, Mod)];

insert_mfa([X | Fs], [ME | MEs], DBName, Mod) 
  when ME#me.entrytype =:= variable ->
    {Oid, {M,F,A}} = X,
    case ME#me.oid of
	Oid ->
	    [ME#me{mfa = {M,F,A}} | insert_mfa(Fs, MEs, DBName, Mod)];
	_Q -> 
	    [insert_default_mfa(ME, DBName, Mod) |
	     insert_mfa([X | Fs], MEs, DBName, Mod)]
    end;

insert_mfa([X | Fs], [TableME | MEs], DBName, Mod) 
  when TableME#me.entrytype =:= table ->
    {Oid, {M,F,A}} = X,
    {TableMEs, RestMEs} = collect_mes_for_table(TableME, [TableME | MEs]),
    [TableEntryME | ColMEs] = tl(TableMEs),
    DefVals = get_defvals(ColMEs),
    {value,TableInfo} = snmpc_misc:assq(table_info,TableME#me.assocList),
    NAssocList = [{table_info, TableInfo#table_info{defvals = DefVals}} |
		  lists:keydelete(table_info, 1, TableME#me.assocList)],
    NTableME = TableME#me{assocList = NAssocList},
    case is_same_table(Oid, NTableME#me.oid) of
	true ->  % use mfa from .funcs
	    lists:append([NTableME,
			  TableEntryME#me{mfa = {M, F, A}}
			  | ColMEs],
			 insert_mfa(Fs, RestMEs, DBName, Mod));
	false ->
	    lists:append(insert_default_mfa([NTableME | tl(TableMEs)], 
					    DBName, Mod),
			 insert_mfa([X|Fs], RestMEs, DBName, Mod))
    end;

insert_mfa([], [ME|MEs], DBName, Mod) 
  when ME#me.entrytype =:= variable ->
    [insert_default_mfa(ME, DBName, Mod) | insert_mfa([], MEs, DBName, Mod)];

insert_mfa([], [ME|MEs], DBName, Mod) 
  when ME#me.entrytype =:= table ->
    {TableMEs, RestMEs} = collect_mes_for_table(ME, [ME|MEs]),
    [TableME, _TableEntryME | ColMEs] = TableMEs,
    DefVals = get_defvals(ColMEs),
    {value,TableInfo} = snmpc_misc:assq(table_info,TableME#me.assocList),
    NAssocList = [{table_info, TableInfo#table_info{defvals = DefVals}} |
		  lists:keydelete(table_info, 1, TableME#me.assocList)],
    NTableME = TableME#me{assocList = NAssocList},
    NewTableMEs = insert_default_mfa([NTableME | tl(TableMEs)], DBName, Mod),
    lists:append(NewTableMEs, insert_mfa([], RestMEs, DBName, Mod));

insert_mfa([], [], _DBName, _Mod) -> 
    [];
insert_mfa([], [ME|_MEs], _DBName, _Mod) ->
    error("Missing access-functions for '~w'.",[ME#me.aliasname]).

%%----------------------------------------------------------------------
%% Returns: {[TableME, TableEntryME | ColumnMEs], RestMEs}
%%----------------------------------------------------------------------
collect_mes_for_table(_TableME, []) -> 
    {[], []};

collect_mes_for_table(TableME, [ME|MEs]) ->
    case is_same_table(TableME#me.oid, ME#me.oid) of
	true ->
	    {TableMEs, RestMEs} = collect_mes_for_table(TableME, MEs),
	    {[ME | TableMEs], RestMEs};
	false ->
	    {[], [ME | MEs]}
    end.

%% returns: MibEntry with access-functions.
insert_default_mfa(ME, DBName, undefined) when is_record(ME, me)->
    case lists:member(no_defs, get(options)) of
        true ->
            error("Missing access function for ~s", [ME#me.aliasname]);
        false ->
	    ?vinfo("No accessfunction for '~w' => using default", 
		   [ME#me.aliasname]),
	    set_default_function(ME, DBName)
    end;

insert_default_mfa(ME, _DBName, Mod) when is_record(ME, me)->
    ME#me{mfa = {Mod, ME#me.aliasname, []}};

insert_default_mfa([TableME, EntryME | Columns], DBName, undefined) ->
    case lists:member(no_defs, get(options)) of
        true ->
            error("Missing access function for ~s", [TableME#me.aliasname]);
        false ->
	    ?vinfo("No accessfunction for '~w' => using default", 
		   [TableME#me.aliasname]),
	    set_default_function([TableME, EntryME | Columns], DBName)
    end;

insert_default_mfa([TableME, EntryME | Columns], _DBName, Mod) ->
    [TableME,
     EntryME#me{mfa = {Mod, TableME#me.aliasname, []}} |
     Columns].


%% returns boolean.
is_same_table(Oid, TableOid) ->
    lists:prefix(Oid, TableOid).

%% returns false | {value, ME}
lookup(UniqName, MEs) when is_atom(UniqName) ->
    lists:keysearch(UniqName, #me.aliasname, MEs);
lookup(Oid, MEs) when is_list(Oid) ->
    lists:keysearch(Oid, #me.oid, MEs).

search_for_dublettes(PrevME, [ME|_MEs])
            when PrevME#me.oid =:= ME#me.oid ->
    error("Multiple used object with OBJECT IDENTIFIER '~w'. "
	  "Used in '~w' and '~w'.", [PrevME#me.oid,
				     PrevME#me.aliasname,
				     ME#me.aliasname]);
search_for_dublettes(PrevME, [ME|MEs]) 
       when ((PrevME#me.entrytype =:= variable) andalso 
	     (ME#me.entrytype =:= variable)) ->
    case lists:prefix(PrevME#me.oid, ME#me.oid) of
	true ->
	    error("Variable '~w' (~w) defined below other "
		  "variable '~w' (~w). ",
		  [ME#me.aliasname, ME#me.oid,
		   PrevME#me.aliasname, PrevME#me.oid]);
	false ->
	    search_for_dublettes(ME, MEs)
    end;
search_for_dublettes(_PrevME, [ME|MEs]) ->
    search_for_dublettes(ME, MEs);
search_for_dublettes(_PrevME, []) -> 
    ok.


search_for_oid_conflicts([Rec|Traps],MEs) when is_record(Rec,notification) ->
    #notification{oid = Oid, trapname = Name} = Rec,
    case search_for_oid_conflicts1(Oid,MEs) of
	{error,ME} ->
	    error("Notification with OBJECT IDENTIFIER '~w'. "
		  "Used in '~w' and '~w'.", [Oid,Name,ME#me.aliasname]);
	ok ->
	    search_for_oid_conflicts(Traps,MEs)
    end;
search_for_oid_conflicts([_Trap|Traps],MEs) ->
    search_for_oid_conflicts(Traps,MEs);
search_for_oid_conflicts([],_MEs) ->
    ok.

search_for_oid_conflicts1(_Oid,[]) ->
    ok;
search_for_oid_conflicts1(Oid,[ME|_MEs]) when Oid == ME#me.oid ->
    {error,ME};
search_for_oid_conflicts1(Oid,[_ME|MEs]) ->
    search_for_oid_conflicts1(Oid,MEs).

set_default_function([TableMe, EntryMe | ColMes], DBName) ->
    #me{aliasname = Name} = TableMe,
    check_rowstatus(TableMe),
    [TableMe,
     EntryMe#me{mfa = {snmp_generic, table_func, [{Name, DBName}]}} |
     ColMes];

set_default_function(MibEntry,DBName) when MibEntry#me.entrytype == variable ->
    #me{aliasname = Aname} = MibEntry,
    MibEntry#me{mfa = {snmp_generic, variable_func, [{Aname, DBName}]}}.

check_rowstatus(TableME) ->
    {value,TableInfo} = snmpc_misc:assq(table_info,TableME#me.assocList),
    case TableInfo#table_info.status_col of
	undefined -> 
        ?vwarning("No RowStatus column in table ~w => "
		  "The default functions won't work properly",
		  [TableME#me.aliasname]);
	_Q -> ok
    end.

check_trap(#trap{trapname=N1, specificcode=C, enterpriseoid=E},
	   #trap{trapname=N2, specificcode=C, enterpriseoid=E},Line) ->
    print_error("Trap code collision. Enterprise: ~w. Trapcode: ~w, "
		"Name of traps: ~w, ~w.", [E, C, N1, N2],Line),
    error;
check_trap(#trap{trapname=N, specificcode=C1, enterpriseoid=E1},
	   #trap{trapname=N, specificcode=C2, enterpriseoid=E2},Line) ->
    print_error("Trap name collision. Name: ~w Enterprises: ~w, ~w. "
		"Trapcodes: ~w, ~w", [N, E1, E2, C1, C2],Line),
    error;
check_trap(_OldTrap, _ThisTrap, _Line) ->
    ok.

check_notification(Notif, Line, Notifs) ->
    lists:map(fun (OtherNotif) ->
		      check_notification1(Notifs,OtherNotif,Line) 
	      end, lists:delete(Notif,Notifs)).

check_notification1(#notification{trapname=N},#notification{trapname=N},Line)->
    print_error("Trap name collision for '~w.",[N],Line);
check_notification1(#notification{oid=Oid},#notification{oid=Oid},Line)->
    print_error("Trap oid collision for '~w.",[Oid],Line);
check_notification1(_T1, _T2, _L) ->
    ok.

%%----------------------------------------------------------------------
%% Returns: list of {VariableName, variable_info-record}
%%----------------------------------------------------------------------
extract_variable_infos([]) -> [];
extract_variable_infos([#me{entrytype = variable, 
			    assocList = AL,
			    aliasname = Name} | T]) ->
    {value, VI} = snmpc_misc:assq(variable_info, AL),
    [{Name, VI} | extract_variable_infos(T)];
extract_variable_infos([_ME | T]) ->
    extract_variable_infos(T).

%%----------------------------------------------------------------------
%% Returns: list of {TableName, table_info-record}
%%----------------------------------------------------------------------
extract_table_infos([]) -> [];
extract_table_infos([#me{entrytype = table, 
			 assocList = AL,
			 aliasname = Name} | T]) ->
    {value, VI} = snmpc_misc:assq(table_info, AL),
    [{Name, VI} | extract_table_infos(T)];
extract_table_infos([_ME | T]) ->
    extract_table_infos(T).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section for misc useful functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% m(M) -> {?MODULE, M}.

set_dir(File, NewDir) ->
    case string:chr(File, $/) of
	0 -> lists:append(NewDir, File);
	N -> set_dir(lists:nthtail(N,File), NewDir)
    end.

%% Look up a key in a list, and if found returns the value
%% or if not found returns the default value
key1search(Key, List) ->
    key1search(Key, List, undefined).

key1search(Key, List, Default) ->
    case lists:keysearch(Key, 1, List) of
        {value, {Key, Val}} -> Val;
        _ -> Default
    end.


%% print the compiled mib
look_at(FileName) ->
    case file:read_file(FileName) of
        {ok,Bin} -> 
	    binary_to_term(Bin);     
        {error,Reason} ->
            {error,Reason}
    end.


%% Data is appended to compiler information
add_cdata(OffsetInRecord, ListOfData) ->
    CDATA = get(cdata),
    OldData = element(OffsetInRecord, CDATA),
    put(cdata, setelement(OffsetInRecord, CDATA, lists:append(ListOfData,
							      OldData))),
    undefined.

check_sub_ids([H | _T], Name, Line) when H < 0 ->
    error("OBJECT IDENTIFIER must have all sub "
	  "indexes > 0. Name: '~w'. Illegal sub index: ~w.",
	  [Name, H], Line);
check_sub_ids([H | _T], Name, Line) when H > 4294967295 ->
    error("OBJECT IDENTIFIER must have all sub "
	  "indexes < 4294967295. Name: '~w'. Illegal sub index: ~w.",
	  [Name, H], Line);
check_sub_ids([_H | T], Name, Line) ->
    check_sub_ids(T, Name, Line);
check_sub_ids([], _Name, _Line) ->
    ok.


%%-----------------------------------------------------------------
%% Handle forward references:
%% This code handles OIDs that are defined in terms of a
%% parent OID that is defined later in the file.  Ex:
%%   x OBJECT IDENTIFIER ::= {y 1}
%%   y OBJECT IDENTIFIER ::= {enterprises 1}
%% The following alg is used to handle this:
%% Datastructure:
%%    An ets table, with one entry for each object in the mib:
%%    {Name, FatherName, Line, SubIndex, Children}
%%       Name : aliasname
%%       FatherName : aliasname of parent object
%%       SubIndex : list of subindexes from parent object
%%       Children : list of aliasnames for all objects registered
%%                  under this one
%%    FatherName == 'root' => top-level object
%% 1) When an OID is found in the mib, it is registered using
%%    register_oid/4.  This function updates the parent entry,
%%    by adding the new name to its Children list.  It also
%%    updates the entry for the object defined.
%% 2) When all objects are registered, the ets table contains
%%    a directed graph of all objects.
%% 3) resolve_oids/1 is called.  This function traverses the
%%    graph, starting at 'root', and changes each entry to
%%    {Name, Line, Oid}, where Oid is a list of integers.
%% 4) The list of MibEntries is traversed.  Each object is
%%    looked up in the ets table, and the correspsonding oid
%%    is updated.  The functions for this is update_me_oids/2
%%    and update_trap_oids/2.
%%-----------------------------------------------------------------
register_oid(Line, Name, ParentName, SubIndex) when Name =/= '$no_name$' ->
    ?vtrace2("register_oid -> entry with"
      "~n   Name:       ~p"
      "~n   ParentName: ~p"
      "~n   SubIndex:   ~p", [Name, ParentName, SubIndex], Line),
    check_sub_ids(SubIndex, Name, Line),
    OidEts = (get(cdata))#cdata.oid_ets,
    %% Lookup Parent - if it doesn't already exist, create it
    {_ParentName, ItsParentName, ItsLine, ItsSubIndex, Children} = 
	case ets:lookup(OidEts, ParentName) of
	    [Found] -> 
		?vtrace("register_oid -> parent found: "
			"~n   ~p", [Found]),
		Found;
	    [] -> 
		?vtrace("register_oid -> father not found: "
			"~n   ~p", [ets:tab2list(OidEts)]),
		{ParentName, undef, undef, [], []}
	end,
    %% Update Father with a pointer to us
    NChildren = case lists:member(Name, Children) of
		    true -> Children;
		    false -> [Name | Children]
		end,
    NParent = {ParentName, ItsParentName, ItsLine, ItsSubIndex, NChildren},
    ?vtrace("register_oid -> NParent: ~n~p", [NParent]),
    ets:insert(OidEts, NParent),
    %% Lookup ourselves - if we don't exist, create us
    MyChildren =
	case ets:lookup(OidEts, Name) of
	    [Found2] ->
		?vtrace("register_oid -> children found: "
			"~n   ~p", [Found2]),
		element(5, Found2);
	    [] -> 
		?vtrace("register_oid -> no children found", []),
		[]
	end,
    %% Update ourselves
    NSelf = {Name, ParentName, Line, SubIndex, MyChildren},
    ?vtrace("register_oid -> NSelf: "
	    "~n   ~p", [NSelf]),
    ets:insert(OidEts, NSelf);
register_oid(_Line, _Name, _ParentName, _SubIndex) ->
    ok.


resolve_oids(OidEts) ->
    case ets:lookup(OidEts, root) of
	[{_, _, _, _, RootChildren}] ->
	    resolve_oids(RootChildren, [], OidEts);
	[] ->
	    ok
    end.

resolve_oids([Name | T], FatherOid, OidEts) ->
    {MyOid, MyChildren, MyLine} =
	case ets:lookup(OidEts, Name) of
	    [{Name, Oid, Line}] ->
		print_error("Circular OBJECT IDENTIFIER definitions "
			    "involving ~w\n", [Name], Line),
		{Oid, [], Line};
	    [{Name, _Father, Line, SubIndex, Children}] ->
		{FatherOid ++ SubIndex, Children, Line}
	end,
    ets:insert(OidEts, {Name, MyOid, MyLine}),
    resolve_oids(T, FatherOid, OidEts),
    resolve_oids(MyChildren, MyOid, OidEts);
resolve_oids([], _, _) ->
    ok.
    
		 

update_me_oids([], _OidEts, Acc) ->
    lists:reverse(Acc);
update_me_oids([#me{aliasname = '$no_name$'} | Mes], OidEts, Acc) ->
    update_me_oids(Mes, OidEts, Acc);
update_me_oids([Me | Mes], OidEts, Acc) ->
    ?vtrace("update_me_oids -> entry with"
	    "~n   Me: ~p", [Me]),
    Oid = tr_oid(Me#me.aliasname, OidEts),
    NMe = resolve_oid_defval(Me, OidEts),
    update_me_oids(Mes, OidEts, [NMe#me{oid = Oid} | Acc]).

update_trap_oids([], _OidEts, Acc) ->
    lists:reverse(Acc);
update_trap_oids([#trap{enterpriseoid = EOid, 
			oidobjects    = OidObjs} = Trap | Traps], 
		 OidEts, Acc) ->
    ?vtrace("update_trap_oids -> entry with"
	    "~n   EOid: ~p", [EOid]),
    NEnter   = tr_oid(EOid, OidEts),
    NOidObjs = tr_oid_objs(OidObjs, OidEts),
    NTrap = Trap#trap{enterpriseoid = NEnter,
		      oidobjects    = NOidObjs},
    update_trap_oids(Traps, OidEts, [NTrap|Acc]);
update_trap_oids([#notification{trapname   = Name,
				oidobjects = OidObjs} = Notif | Traps], 
		 OidEts, Acc) ->
    ?vtrace("update_trap_oids -> entry with"
	    "~n   Name: ~p", [Name]),
    Oid      = tr_oid(Name, OidEts),
    NOidObjs = tr_oid_objs(OidObjs, OidEts),
    NNotif   = Notif#notification{oid = Oid, oidobjects = NOidObjs},
    update_trap_oids(Traps, OidEts, [NNotif|Acc]).

tr_oid(Name, OidEts) ->
    ?vtrace("tr_oid -> entry with"
	    "~n   Name: ~p", [Name]),
    case ets:lookup(OidEts, Name) of
	[{Name, MyOid, _MyLine}] ->
	    MyOid;
	[{_Natrap, Parent, Line, SubIndex, _Children}] ->
	    print_error("OBJECT IDENTIFIER [~w] defined in terms "
			"of undefined parent object. Parent: '~w'."
			"(Sub-indexes: ~w.)",
			[Name, Parent, SubIndex], Line),
	    ?vtrace("tr_oid -> ets:tab2list(~w): "
		    "~n   ~p", [OidEts, ets:tab2list(OidEts)]),
	    rnd_oid()
    end.

tr_oid_objs([{{variable, Name}, Type} | T], OidEts) ->
    ?vtrace("tr_oid_objs(variable) -> entry with"
	    "~n   Name: ~p", [Name]),
    Oid = tr_oid(Name, OidEts) ++ [0],
    [{Oid, Type} | tr_oid_objs(T, OidEts)];
tr_oid_objs([{{column, Name}, Type} | T], OidEts) ->
    ?vtrace("tr_oid_objs(column) -> entry with"
	    "~n   Name: ~p", [Name]),
    Oid = tr_oid(Name, OidEts),
    [{Oid, Type} | tr_oid_objs(T, OidEts)];
tr_oid_objs([], _OidEts) ->
    [].
    

resolve_oid_defval(ME, OidEts) 
  when (ME#me.asn1_type)#asn1_type.bertype == 'OBJECT IDENTIFIER' ->
    #me{aliasname = MEName, assocList = AssocList} = ME,
    case snmpc_misc:assq(defval, AssocList) of
	{value, DefVal} when is_atom(DefVal) ->
	    case ets:lookup(OidEts, DefVal) of
		[{_, Oid, _}] ->
		    ME#me{assocList = lists:keyreplace(defval, 1, AssocList,
						       {defval, Oid})};
		_ ->
		    print_error("Can not find OBJECT-TYPE definition for '~w' "
				"Used in DEFVAL for '~w'.", [DefVal, MEName]),
		    ME
	    end;
	_ ->
	    ME
    end;
resolve_oid_defval(ME, _OidEts) ->
    ME.

rnd_oid() ->
    [99,99].  %% '99' means "stop computer" in Y2Kish...

error(FormatStr, Data) when is_list(FormatStr) ->
    print_error(FormatStr,Data),
    exit(error).

error(FormatStr, Data, Line) when is_list(FormatStr) ->
    print_error(FormatStr,Data,Line),
    exit(error).

print_error(FormatStr, Data) when is_list(FormatStr) ->
    ok = io:format("~s: " ++ FormatStr,[get(filename)|Data]),
    put(errors,yes),
    io:format("~n").
    
print_error(FormatStr, Data,Line) when is_list(FormatStr) ->
    ok = io:format("~s: ~w: " ++ FormatStr,[get(filename), Line |Data]),
    put(errors,yes),
    io:format("~n").


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Section for debug functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

vprint(Severity, Mod, Line, MibLine, F, A) ->
    case printable(Severity) of
	standard when is_integer(MibLine) ->
	    io:format("[~s:~w][~s]: " ++ F ++ "~n",
		      [get(filename), MibLine, image_of_severity(Severity)|A]);
	standard ->
	    io:format("[~s][~s]: " ++ F ++ "~n",
		      [get(filename), image_of_severity(Severity)|A]);
	extended when is_integer(MibLine) ->
	    io:format("[~s:~w][~w:~w][~s]: " ++ F ++ "~n",
		      [get(filename), MibLine, Mod, Line, 
		       image_of_severity(Severity)|A]);
	extended ->
	    io:format("[~s][~w:~w][~s]: " ++ F ++ "~n",
		      [get(filename), Mod, Line,
		       image_of_severity(Severity)|A]);
	_ ->
	    ok
    end.

printable(Severity) ->	    
    printable(get(verbosity), Severity).

printable(silence, _)       -> none;
printable(warning, warning) -> standard; 
printable(info,    info)    -> standard;
printable(info,    warning) -> standard;
printable(log,     warning) -> standard;
printable(log,     info)    -> standard;
printable(log,     log)     -> standard;
printable(debug,   warning) -> standard;
printable(debug,   info)    -> standard;
printable(debug,   log)     -> standard;
printable(debug,   debug)   -> standard;
printable(trace,   _Sev)    -> extended;
printable(_Ver,    _Sev)    -> none.

-spec image_of_severity(Sev :: severity()) -> string().
image_of_severity(warning)  -> "WAR";
image_of_severity(info)     -> "INF";
image_of_severity(log)      -> "LOG";
image_of_severity(debug)    -> "DBG";
image_of_severity(trace)    -> "TRC";
image_of_severity(_)        -> " - ".


vvalidate(silence) -> ok;
vvalidate(warning) -> ok;
vvalidate(info)    -> ok;
vvalidate(log)     -> ok;
vvalidate(debug)   -> ok;
vvalidate(trace)   -> ok;
vvalidate(V)       -> exit({invalid_verbosity,V}).


