%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 1996-2025. 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%
%%

-module(snmpa_mib_data_tttn).
-moduledoc false.


%%%-----------------------------------------------------------------
%%% 
%%%            TTTN - TupleTreeTupleNodes
%%% 
%%% This module implements the MIB (internal) data structures.
%%% The TTTN MIB Data structure consists of three items: 
%%% 
%%%      1) A mib-storage (as specified when new is called) for 
%%%         the data associated with each variable, table, 
%%%         table-entry and table-column in the MIB. 
%%%      2) A tree contains information of the Oids in the MIB. 
%%%      3) A list of registered subagents. 
%%% 
%%% The subagent information is consequently duplicated. It resides
%%% both in the tree and in the list.
%%%
%%% When a mib is loaded, the tree is built from the plain list
%%% in the binary file.
%%% 
%%%-----------------------------------------------------------------

-include_lib("snmp/include/snmp_types.hrl").
-include_lib("snmp/src/misc/snmp_debug.hrl").

-define(VMODULE,"MDATA_TTTN").
-include_lib("snmp/src/misc/snmp_verbosity.hrl").

-behaviour(snmpa_mib_data).

-define(MIB_DATA, snmpa_mib_data).
-define(MIB_NODE, snmpa_mib_node).
-define(MIB_TREE, snmpa_mib_tree).
-define(DUMMY_TREE_GENERATION, 1).
-define(DEFAULT_TREE, {tree,{undefined_node},internal}).


%%%-----------------------------------------------------------------
%%% Table of contents
%%% =================
%%% 1. Interface
%%% 2. Implementation of tree access
%%% 3. Tree building functions
%%% 4. Tree merging
%%% 5. Tree deletion routines
%%% 6. Functions for subagent handling
%%% 7. Misc functions
%%%-----------------------------------------------------------------

-record(mib_data, 
	{
	  %% Mib storage module
	  module :: snmpa:mib_storage_module(),  

	  %% A database of loaded mibs
	  %% #mib_info{}
	  mib_db,  

	  %% A database with information about each node in the tree
	  %% #node_info{}
	  node_db, 

	  %% A database containing _one_ record with the tree
	  %% without the subagent(s). 
	  %% (the reason for this is part to get replication 
	  %% and part out of convenience)
	  %% #tree{}
	  tree_db, 

	  %% The root node (same as the tree part of the tree_db 
	  %% but with the subagents added). 
	  tree, 
	  
	  %% A list of {SAPid, Oid}
	  subagents = []
	 }).

-record(mib_info,  {name, symbolic, file_name}).
-record(node_info, {oid, mib_name, me}).


%% (behaviour) API
-export([new/1, 
	 close/1, 
	 sync/1, 
	 load_mib/4, 
	 unload_mib/4, 
	 lookup/2, 
	 next/3, 
	 register_subagent/3, 
	 unregister_subagent/2, 
	 dump/2, 
	 which_mib/2, which_mibs/1, 
	 whereis_mib/2, 
	 info/1, info/2,
	 backup/2, 
	 code_change/4]).


%%-----------------------------------------------------------------
%% A tree is represented as a N-tuple, where each element is a
%% node. A node is:
%% 1) {tree, Tree, Info} where Info can be {table, Id}, {table_entry, Id}
%%                                        or perhaps 'internal'
%% 2) undefined_node  (memory optimization (instead of {node, undefined}))
%% 3) {node, Info} where Info can be {subagent, Pid}, {variable, Id}, 
%%                                   {table_column, Id}
%% Id is {MibName, MibEntry}
%% The over all root is represented as {tree, Tree, internal}.
%%
%% tree() = {tree, nodes(), tree_info()}
%% nodes() = {tree() | node() | undefined_node, ...}
%% node() = {node, node_info()}
%% tree_info() = {table, Id} | {table_entry, Id} | internal
%% node_info() = {subagent, Pid} | {variable, Id} | {table_colum, Id}
%%-----------------------------------------------------------------

%% This record is what is stored in the database. The 'tree' part
%% is described above...
-record(tree, {generation = ?DUMMY_TREE_GENERATION, root = ?DEFAULT_TREE}).


%%%======================================================================
%%% 1. Interface
%%%======================================================================

%%-----------------------------------------------------------------
%% Func: new/1
%% Returns: A representation of mib data.
%%-----------------------------------------------------------------

%% Where -> A list of nodes where the tables will be created
new(MibStorage) ->
    Mod  = snmp_misc:get_option(module,  MibStorage), 
    Opts = snmp_misc:get_option(options, MibStorage, []), 

    %% First we must check if there is already something to read
    %% If a database already exists, then the tree structure has to be read
    ?vdebug("open (mib) database",[]),
    MibDb = 
	case Mod:open(?MIB_DATA, mib_info, record_info(fields, mib_info), 
		      set, Opts) of
	    {ok, T1} ->
		T1;
	    {error, Reason1} ->
		throw({error, {open, mib_data, Reason1}})
	end,

    ?vdebug("open (mib) node database",[]),
    NodeDb = 
	case Mod:open(?MIB_NODE, node_info, record_info(fields, node_info), 
		      set, Opts) of
	    {ok, T2} ->
		T2;
	    {error, Reason2} ->
		throw({error, {open, mib_node, Reason2}})
	end,

    ?vdebug("open (mib) tree database",[]),
    TreeDb = 
	case Mod:open(?MIB_TREE, tree, record_info(fields, tree), 
		      set, Opts) of
	    {ok, T3} ->
		T3;
	    {error, Reason3} ->
		throw({error, {open, mib_tree, Reason3}})
	end,

    ?vdebug("write the default (mib) tree",[]),
    Tree = 
	case Mod:read(TreeDb, ?DUMMY_TREE_GENERATION) of
	    false ->
		T = #tree{},
		Mod:write(TreeDb, T),
		T;
	    {value, T} ->
		T
	end,
    ?vdebug("install (existing) mibs",[]),
    install_mibs(Mod, MibDb, NodeDb),
    ?vdebug("done",[]),
    #mib_data{module  = Mod, 
	      mib_db  = MibDb, 
	      node_db = NodeDb,
	      tree_db = TreeDb, 
	      tree    = Tree}.


%%----------------------------------------------------------------------
%% Returns: new mib data | {error, Reason}
%%----------------------------------------------------------------------
load_mib(MibData, FileName, MeOverride, TeOverride) 
  when is_record(MibData,mib_data) andalso is_list(FileName) -> 
    ?vlog("load mib file: ~p",[FileName]),
    ActualFileName = filename:rootname(FileName, ".bin") ++ ".bin",
    MibName = list_to_atom(filename:basename(FileName, ".bin")),
    (catch do_load_mib(MibData, ActualFileName, MibName, 
                       MeOverride, TeOverride)).

do_load_mib(MibData, ActualFileName, MibName, MeOverride, TeOverride) ->
    ?vtrace("do_load_mib -> entry with"
        "~n  ActualFileName: ~s"
        "~n  MibName:        ~p",[ActualFileName, MibName]),
    #mib_data{module  = Mod, 
	      mib_db  = MibDb, 
	      node_db = NodeDb, 
	      %% tree_db = TreeDb,
	      tree    = Tree} = MibData,
    verify_not_loaded(Mod, MibDb, MibName),
    ?vtrace("do_load_mib -> already loaded mibs:"
	    "~n   ~p", [loaded(Mod, MibDb)]),
    Mib = do_read_mib(ActualFileName),
    ?vtrace("do_load_mib -> read mib ~s",[Mib#mib.name]),
    NonInternalMes = 
        lists:filter(fun(ME) -> maybe_drop_me(ME) end, Mib#mib.mes),
    OldRoot = Tree#tree.root,
    T = build_tree(NonInternalMes, MibName),
    ?d("load_mib -> "
	"~n   OldRoot: ~p"
	"~n   T:       ~p", [OldRoot, T]),
    case (catch merge_nodes(T, OldRoot)) of
	{error_merge_nodes, Node1, Node2} ->
	    ?vlog("error merging nodes:"
		"~n~p~nand~n~p", [Node1,Node2]),
	    {error, oid_conflict};
	NewRoot when is_tuple(NewRoot) andalso (element(1,NewRoot) =:= tree) ->
 	    ?d("load_mib -> "
 		"~n   NewRoot: ~p", [NewRoot]),
	    Symbolic = not lists:member(no_symbolic_info, Mib#mib.misc),
	    case (catch check_notif_and_mes(TeOverride, MeOverride, Symbolic, 
					    Mib#mib.traps, NonInternalMes)) of
		true ->
		    install_mes(Mod, NodeDb, MibName, NonInternalMes),
		    install_mib(Mod, 
				MibDb, Symbolic, Mib, 
				MibName, ActualFileName, NonInternalMes),
		    ?vtrace("installed mib ~s", [Mib#mib.name]),
		    Tree2 = Tree#tree{root = NewRoot},
		    {ok, MibData#mib_data{tree = Tree2}};
		Else -> 
		    Else
	    end
    end.


verify_not_loaded(Mod, Tab, Name) ->
    case Mod:read(Tab, Name) of
        {value, #mib_info{name = Name}} -> 
            throw({error, already_loaded});
        false ->
            ok
    end.

do_read_mib(ActualFileName) ->
    case snmp_misc:read_mib(ActualFileName) of
        {error, Reason} -> 
            ?vlog("Failed reading mib file ~p with reason: ~p",
		  [ActualFileName, Reason]),
            throw({error, Reason});
        {ok, Mib} ->
            Mib
    end.

%% The Tree DB is handled in a special way since it can be very large.
sync(#mib_data{module  = Mod, 
	       mib_db  = M, 
	       node_db = N, 
	       tree_db = T, tree = Tree, subagents = []}) ->
    Mod:sync(M),
    Mod:sync(N),
    Mod:write(T, Tree),
    Mod:sync(T);
sync(#mib_data{module  = Mod, 
	       mib_db  = M, 
	       node_db = N, 
	       tree_db = T, tree = Tree, subagents = SAs}) ->

    Mod:sync(M),
    Mod:sync(N),

    %% Ouch. Since the subagent info is dynamic we do not 
    %% want to store the tree containing subagent info. So, we 
    %% have to create a tmp tree without those and store it.

    case delete_subagents(Tree, SAs) of
	{ok, TreeWithoutSAs} ->
	    Mod:write(T, TreeWithoutSAs),
	    Mod:sync(T);
	Error ->
	    Error
    end.

delete_subagents(Tree, []) ->
    {ok, Tree};
delete_subagents(Tree0, [{_, Oid}|SAs]) ->
    case (catch delete_subagent(Tree0, Oid)) of
	{tree, _Tree, _Info} = Tree1 ->
	    delete_subagents(Tree1, SAs);
	_Error ->
	    {error, {'invalid oid', Oid}} 
    end.

%%----------------------------------------------------------------------
%% (OTP-3601)
%%----------------------------------------------------------------------
check_notif_and_mes(TeOverride,MeOverride,Symbolic,Traps,MEs) ->
    ?vtrace("check notifications and mib entries",[]),
    check_notifications(TeOverride,Symbolic,Traps),
    check_mes(MeOverride,MEs).

check_notifications(true, _Symbolic, _Traps) ->
    ?vtrace("trapentry override = true => skip check",[]),
    true;
check_notifications(_, Symbolic, Traps) -> 
    check_notifications(Symbolic, Traps).

check_notifications(true, Traps) ->
    check_notifications(Traps);
check_notifications(_, _) -> true.

check_notifications([]) -> true;
check_notifications([#trap{trapname = Key} = Trap | Traps]) ->
    ?vtrace("check notification [trap] with Key: ~p",[Key]),
    case snmpa_symbolic_store:get_notification(Key) of
	{value, Trap} -> check_notifications(Traps);
	{value,    _} -> throw({error, {'trap already defined', Key}});
	undefined     -> check_notifications(Traps)
    end;
check_notifications([#notification{trapname = Key} = Notif | Traps]) ->
    ?vtrace("check notification [notification] with Key: ~p",[Key]),
    case snmpa_symbolic_store:get_notification(Key) of
	{value, Notif} -> 
	    check_notifications(Traps);
	{value,     _} -> 
	    throw({error, {'notification already defined', Key}});
	undefined      -> 
	    check_notifications(Traps)
    end;
check_notifications([Crap | Traps]) ->
    ?vlog("skipped check of: ~n~p",[Crap]),
    check_notifications(Traps).

check_mes(true,_) ->
    ?vtrace("mibentry override = true => skip check",[]),
    true; 
check_mes(_,MEs) ->
    check_mes(MEs).

check_mes([]) -> true;
check_mes([#me{aliasname = Name, oid = Oid1} | MEs]) ->
    ?vtrace("check mib entries with aliasname: ~p",[Name]),
    case snmpa_symbolic_store:aliasname_to_oid(Name) of
	{value, Oid1} -> 
	    check_mes(MEs);
	{value, Oid2} -> 
	    ?vinfo("~n   expecting '~p'~n   but found '~p'",[Oid1, Oid2]),
	    throw({error, {'mibentry already defined', Name}});
	false -> 
	    check_mes(MEs)
    end;
check_mes([Crap | MEs]) ->
    ?vlog("skipped check of: ~n~p",[Crap]),
    check_mes(MEs).
    


%%----------------------------------------------------------------------
%% Returns: new mib data | {error, Reason}
%%----------------------------------------------------------------------
unload_mib(MibData, FileName, _, _) when is_list(FileName) -> 
    MibName = list_to_atom(filename:basename(FileName, ".bin")),
    (catch do_unload_mib(MibData, MibName)).

do_unload_mib(MibData, MibName) ->
    ?vtrace("do_unload_mib -> entry with"
	"~n   MibName: ~p", [MibName]),
    #mib_data{module  = Mod, 
	      mib_db  = MibDb, 
	      node_db = NodeDb, 
	      %% tree_db = TreeDb, 
	      tree    = Tree} = MibData,
    #mib_info{symbolic = Symbolic} = verify_loaded(Mod, MibDb, MibName),
    NewRoot = delete_mib_from_tree(MibName, Tree#tree.root),
    MEs = uninstall_mes(Mod, NodeDb, MibName),
    uninstall_mib(Mod, MibDb, Symbolic, MibName, MEs),
    NewMibData = MibData#mib_data{tree = Tree#tree{root = NewRoot}},
    {ok, NewMibData}.

verify_loaded(Mod, Tab, Name) ->
    case Mod:read(Tab, Name) of
        {value, MibInfo} ->
            MibInfo;
        false ->
            throw({error, not_loaded})
    end.
    
    
close(#mib_data{module  = Mod, 
		mib_db  = MibDb, 
		node_db = NodeDb, 
		tree_db = TreeDb}) ->
    Mod:close(MibDb),
    Mod:close(NodeDb),
    Mod:close(TreeDb),
    ok.

register_subagent(#mib_data{tree = T} = MibData, Oid, Pid) ->
    case insert_subagent(Oid, T#tree.root) of
	{error, Reason} -> 
	    {error, Reason};
	NewRootTree ->
	    SAs = [{Pid, Oid} | MibData#mib_data.subagents],
	    T2 = T#tree{root = NewRootTree},
	    {ok, MibData#mib_data{tree = T2, subagents = SAs}}
    end.


%%----------------------------------------------------------------------
%% Purpose: Get a list of all loaded mibs
%% Returns: [{Name, File}]
%%----------------------------------------------------------------------

which_mibs(#mib_data{module = Mod, mib_db = Db}) ->
    Mibs = Mod:tab2list(Db),
    [{Name, File} || #mib_info{name = Name, file_name = File} <- Mibs].


%%----------------------------------------------------------------------
%% Purpose: Get a list of all loaded mibs
%% Returns: [{Name, File}]
%%----------------------------------------------------------------------

whereis_mib(#mib_data{module = Mod, mib_db = Db}, Name) ->
    case Mod:read(Db, Name) of
        {value, #mib_info{file_name = File}} ->
	    {ok, File};
	false ->
	    {error, not_found}
    end.


%%----------------------------------------------------------------------
%% Purpose: Deletes SA with Pid from all subtrees it handles.
%% Returns: NewMibData.
%%----------------------------------------------------------------------
unregister_subagent(#mib_data{subagents = SAs} = MibData, Pid) 
  when is_pid(Pid) ->
    SAs = MibData#mib_data.subagents,
    case lists:keysearch(Pid, 1, SAs) of
	false -> 
	    {ok, MibData};
	{value, {Pid, Oid}} ->
	    % we should never get an error since Oid is found in MibData.
	    {ok, NewMibData, _DeletedSA} = unregister_subagent(MibData, Oid),
	    % continue if the same Pid handles other mib subtrees.
	    unregister_subagent(NewMibData, Pid)
    end;

%%----------------------------------------------------------------------
%% Purpose: Deletes one unique subagent. 
%% Returns: {error, Reason} | {ok, NewMibData, DeletedSubagentPid}
%%----------------------------------------------------------------------
unregister_subagent(#mib_data{tree = T} = MibData, Oid) when is_list(Oid) ->
    case (catch delete_subagent(T#tree.root, Oid)) of
	{tree, Tree, Info} ->
	    OldSAs = MibData#mib_data.subagents,
	    {value, {Pid, _Oid}} = lists:keysearch(Oid, 2, OldSAs),
	    SAs = lists:keydelete(Oid, 2, OldSAs),
	    T2 = T#tree{root = {tree, Tree, Info}},
	    {ok, 
	     MibData#mib_data{tree = T2, subagents = SAs},
	     Pid};
	_ ->
	    {error, {invalid_oid, Oid}}
    end.

%%----------------------------------------------------------------------
%% Purpose: To inpect memory usage, loaded mibs, registered subagents
%%----------------------------------------------------------------------
info(MibData) ->
    ?vtrace("retrieve info",[]),
    #mib_data{module    = Mod, 
	      mib_db    = MibDb, 
	      node_db   = NodeDb, 
	      tree_db   = TreeDb, 
	      tree      = Tree, 
	      subagents = SAs} = MibData,
    LoadedMibs = old_format(Mod:tab2list(MibDb)),
    TreeSize = snmp_misc:mem_size(Tree),
    {memory, ProcSize} = erlang:process_info(self(), memory),
    MibDbSize  = Mod:info(MibDb,  memory),
    NodeDbSize = Mod:info(NodeDb, memory),
    TreeDbSize = Mod:info(TreeDb, memory),
    [{loaded_mibs, LoadedMibs}, {subagents, SAs}, {tree_size_bytes, TreeSize},
     {process_memory, ProcSize}, 
     {db_memory, [{mib,MibDbSize},{node,NodeDbSize},{tree,TreeDbSize}]}].

info(#mib_data{module = Mod, mib_db = MibDb}, loaded_mibs) ->
    Mibs = Mod:tab2list(MibDb),
    [filename:rootname(FN, ".bin") || #mib_info{file_name = FN} <- Mibs];
info(#mib_data{tree = Tree}, tree_size_bytes) ->
    snmp_misc:mem_size(Tree);
info(_, process_memory) ->
    {memory, ProcSize} = erlang:process_info(self(),memory),
    ProcSize;
info(#mib_data{module  = Mod, 
	       mib_db  = MibDb, 
	       node_db = NodeDb, 
	       tree_db = TreeDb}, 
     db_memory) ->
    MibDbSize  = Mod:info(MibDb,  memory),
    NodeDbSize = Mod:info(NodeDb, memory),
    TreeDbSize = Mod:info(TreeDb, memory),
    [{mib, MibDbSize}, {node, NodeDbSize}, {tree, TreeDbSize}];
info(#mib_data{subagents = SAs}, subagents) ->
    SAs.

old_format(LoadedMibs) ->
    ?vtrace("convert mib info to old format",[]),
    [{N,S,F} || #mib_info{name=N,symbolic=S,file_name=F} <- LoadedMibs].

    
%%----------------------------------------------------------------------
%% A total dump for debugging.
%%----------------------------------------------------------------------

dump(#mib_data{module  = Mod, 
	       mib_db  = MibDb, 
	       node_db = NodeDb, 
	       tree    = Tree}, io) ->
    (catch io:format("MIB-tables:~n~p~n~n", 
		     [Mod:tab2list(MibDb)])),
    (catch io:format("MIB-entries:~n~p~n~n", 
		     [Mod:tab2list(NodeDb)])),
    (catch io:format("Tree:~n~p~n", [Tree])), % good luck reading it!
    ok;

dump(#mib_data{module  = Mod, 
	       mib_db  = MibDb, 
	       node_db = NodeDb, 
	       tree    = Tree}, File) ->
    case file:open(File, [write]) of
	{ok, Fd} ->
	    io:format(Fd,"~s~n", 
		      [snmp:date_and_time_to_string(snmp:date_and_time())]),
	    (catch io:format(Fd,"MIB-tables:~n~p~n~n",
			     [Mod:tab2list(MibDb)])),
	    (catch io:format(Fd, "MIB-entries:~n~p~n~n", 
			     [Mod:tab2list(NodeDb)])),
	    io:format(Fd,"Tree:~n~p~n", [Tree]), % good luck reading it!
	    file:close(Fd),
	    ok;
	{error, Reason} ->
	    ?vinfo("~n   Failed opening file '~s' for reason ~p",
		   [File, Reason]),
	    {error, Reason}
    end.


backup(#mib_data{module  = Mod, 
		 mib_db  = M, 
		 node_db = N, 
		 tree_db = T}, BackupDir) ->
    MRes = Mod:backup(M, BackupDir),
    NRes = Mod:backup(N, BackupDir),
    TRes = Mod:backup(T, BackupDir),
    handle_backup_res([{mib_db, MRes}, {node_db, NRes}, {tree_db, TRes}]).

handle_backup_res(Res) ->
    handle_backup_res(Res, []).

handle_backup_res([], []) ->
    ok;
handle_backup_res([], Err) ->
    {error, lists:reverse(Err)};
handle_backup_res([{_, ok}|Res], Err) ->
    handle_backup_res(Res, Err);
handle_backup_res([{Tag, {error, Reason}}|Res], Err) ->
    handle_backup_res(Res, [{Tag, Reason}|Err]);
handle_backup_res([{Tag, Error}|Res], Err) ->
    handle_backup_res(Res, [{Tag, Error}|Err]).


%%%======================================================================
%%% 2. Implementation of tree access
%%%    lookup and next.
%%%======================================================================


which_mib(#mib_data{tree = T} = D, Oid) ->
    ?vtrace("which_mib -> entry with"
	    "~n   Oid: ~p",[Oid]),	    
    case (catch find_node(D, T#tree.root, Oid, [])) of
	{variable, _ME, Mib} ->
	    ?vtrace("which_mib -> variable:"
		"~n   Mib: ~p", [Mib]),	    
	    {ok, Mib};
	{table, _EntryME, _, Mib} ->
	    ?vtrace("which_mib -> table:"
		"~n   Mib: ~p", [Mib]),	    
	    {ok, Mib};
	{subagent, SubAgentPid, _SANextOid} ->
	    ?vtrace("which_mib -> subagent:"
		"~n   SubAgentPid: ~p", [SubAgentPid]),	    
	    {error, {subagent, SubAgentPid}};
	{false, ErrorCode} -> 
	    ?vtrace("which_mib -> false:"
		"~n   ErrorCode: ~p",[ErrorCode]),	    
	    {error, ErrorCode};
	false -> 
	    ?vtrace("which_mib -> false",[]),	    
	    {error, noSuchObject};
	{'EXIT', R} -> 
	    ?vtrace("which_mib -> exit:"
		"~n   R:  ~p",[R]),	    
	    {error, noSuchObject}
    end.


%%-----------------------------------------------------------------
%% Func: lookup/2
%% Purpose: Finds the mib entry corresponding to the Oid. If it is a
%%          variable, the Oid must be <Oid for var>.0 and if it is
%%          a table, Oid must be <table>.<entry>.<col>.<any>
%% Returns: {variable, MibEntry} |
%%          {table_column, MibEntry, TableEntryOid} |
%%          {subagent, SubAgentPid, SAOid} |
%%          {false, Reason}
%%-----------------------------------------------------------------
lookup(#mib_data{tree = T} = D, Oid) ->
     ?vtrace("lookup -> entry with"
 	"~n   Oid: ~p",[Oid]),	    
    case (catch find_node(D, T#tree.root, Oid, [])) of
	{variable, ME, _Mib} when is_record(ME, me) -> 
	    ?vtrace("lookup -> variable:"
		"~n   ME: ~p",[ME]),	    
	    {variable, ME};
	{table, EntryME, {ColME, TableEntryOid}, _Mib} ->
	    ?vtrace("lookup -> table:"
		"~n   EntryME:          ~p"
		"~n   ColME:            ~p"
		"~n   RevTableEntryOid: ~p",
		[EntryME, ColME, TableEntryOid]),	    
	    MFA = EntryME#me.mfa,
	    RetME = ColME#me{mfa = MFA},
	    {table_column, RetME, TableEntryOid};
	{subagent, SubAgentPid, SANextOid} ->
	    ?vtrace("lookup -> subagent:"
		"~n   SubAgentPid: ~p"
		"~n   SANextOid:   ~p", [SubAgentPid, SANextOid]),	    
	    {subagent, SubAgentPid, SANextOid};
	{false, ErrorCode} -> 
	    ?vtrace("lookup -> false:"
		"~n   ErrorCode: ~p",[ErrorCode]),	    
	    {false, ErrorCode};
	false -> 
	    ?vtrace("lookup -> false",[]),	    
	    {false, noSuchObject};
	{'EXIT', R} -> 
	    ?vtrace("lookup -> exit:"
		"~n   R:  ~p",[R]),	    
	    {false, noSuchObject}
    end.


find_node(D, {tree, Tree, {table, _}}, RestOfOid, RevOid) ->
     ?vtrace("find_node(tree,table) -> entry with"
 	"~n   RestOfOid: ~p"
 	"~n   RevOid:    ~p",[RestOfOid, RevOid]),
    find_node(D, {tree, Tree, internal}, RestOfOid, RevOid);
find_node(D, {tree, Tree, {table_entry, _}}, RestOfOid, RevOid) ->
     ?vtrace("find_node(tree,table_entry) -> entry with"
 	"~n   RestOfOid: ~p"
 	"~n   RevOid:    ~p",[RestOfOid, RevOid]),
    #mib_data{module = Mod, node_db = Db} = D,
    Oid = lists:reverse(RevOid),
    case Mod:read(Db, Oid) of
	{value, #node_info{me = ME, mib_name = Mib}} ->
	    case find_node(D, {tree, Tree, internal}, RestOfOid, RevOid) of
		{false, ErrorCode} -> {false, ErrorCode};
		Val -> {table, ME, Val, Mib}
	    end;
	false ->
	    ?vinfo("find_node -> could not find table_entry ME with"
		"~n   RevOid:    ~p"
		"~n   when"
		"~n   RestOfOid: ~p", 
		[RevOid, RestOfOid]),
	    false
    end;
find_node(D, {tree, Tree, _Internal}, [Int | RestOfOid], RevOid) ->
    ?vtrace("find_node(tree) -> entry with"
	"~n   Int:       ~p"
	"~n   RestOfOid: ~p"
	"~n   RevOid:    ~p",[Int, RestOfOid, RevOid]),
    find_node(D, element(Int+1, Tree), RestOfOid, [Int | RevOid]);
find_node(D, {node, {table_column, _}}, RestOfOid, [ColInt | RevOid]) ->
     ?vtrace("find_node(tree,table_column) -> entry with"
 	"~n   RestOfOid: ~p"
 	"~n   ColInt:    ~p"
 	"~n   RevOid:    ~p",[RestOfOid, ColInt, RevOid]),
    #mib_data{module = Mod, node_db = Db} = D,
    Oid = lists:reverse([ColInt | RevOid]),
    case Mod:read(Db, Oid) of
	{value, #node_info{me = ME}} ->
	    {ME, lists:reverse(RevOid)}; 
	false ->
	    X = Mod:read(Db, lists:reverse([ColInt | RevOid])),
	    ?vinfo("find_node -> could not find table_column ME with"
		"~n   RevOid: ~p"
		"~n   trying  [~p|~p]"
		"~n   X:      ~p", 
		[RevOid, [ColInt | RevOid], X]),
	    false
    end;
find_node(D, {node, {variable, _MibName}}, [0], RevOid) ->
     ?vtrace("find_node(tree,variable,[0]) -> entry with"
 	"~n   RevOid:    ~p",[RevOid]),
    #mib_data{module = Mod, node_db = Db} = D,
    Oid = lists:reverse(RevOid),
    case Mod:read(Db, Oid) of
	{value, #node_info{me = ME, mib_name = Mib}} ->
	    {variable, ME, Mib};
	false ->
	    ?vinfo("find_node -> could not find variable ME with"
		"~n   RevOid: ~p", [RevOid]),
	    false
    end;
find_node(_D, {node, {variable, _MibName}}, [], _RevOid) ->
    ?vtrace("find_node(tree,variable,[]) -> entry",[]),
    {false, noSuchObject};
find_node(_D, {node, {variable, _MibName}}, _, _RevOid) ->
    ?vtrace("find_node(tree,variable) -> entry",[]),
    {false, noSuchInstance};
find_node(D, {node, subagent}, _RestOfOid, SARevOid) ->
    ?vtrace("find_node(tree,subagent) -> entry with"
 	"~n   SARevOid:    ~p",[SARevOid]),
    #mib_data{subagents = SAs} = D,
    SAOid = lists:reverse(SARevOid),
    case lists:keysearch(SAOid, 2, SAs) of
	{value, {SubAgentPid, SAOid}} ->
	    {subagent, SubAgentPid, SAOid};
	false ->
	    ?vinfo("find_node -> could not find subagent with"
		"~n   SAOid: ~p"
		"~n   SAs:   ~p", [SAOid, SAs]),
	    false
    end;    
find_node(_D, Node, _RestOfOid, _RevOid) ->
    ?vtrace("find_node -> failed:~n~p",[Node]),
    {false, noSuchObject}.


%%-----------------------------------------------------------------
%% Func: next/3
%% Purpose: Finds the lexicographically next oid.
%% Returns: endOfMibView |
%%          {subagent, SubAgentPid, SAOid} |
%%          {variable, MibEntry, VarOid} |
%%          {table, TableOid, TableRestOid, MibEntry}
%% If a variable is returns, it is in the MibView.
%% If a table or subagent is returned, it *may* be in the MibView.
%%-----------------------------------------------------------------
next(#mib_data{tree = T} = D, Oid, MibView) ->
    case catch next_node(D, T#tree.root, Oid, [], MibView) of
	false -> endOfMibView;
	Else -> Else
    end.

%%-----------------------------------------------------------------
%% This function is used as long as we have any Oid left. Take
%% one integer at a time from the Oid, and traverse the tree
%% accordingly. When the Oid is empty, call find_next.
%% Returns: {subagent, SubAgentPid, SAOid} |
%%          false |
%%          {variable, MibEntry, VarOid} |
%%          {table, TableOid, TableRestOid, MibEntry}
%%-----------------------------------------------------------------
next_node(_D, undefined_node, _Oid, _RevOidSoFar, _MibView) ->
    ?vtrace("next_node(undefined_node) -> entry", []),
    false;

next_node(_D, {tree, Tree, {table_entry, _Id}}, [Int | _Oid], 
	  _RevOidSoFar, _MibView)
  when Int+1 > tuple_size(Tree) ->
    ?vtrace("next_node(tree,table_entry) -> entry when not found within"
	"~n   Int:        ~p"
	"~n   size(Tree): ~p", [Int, tuple_size(Tree)]),
    false;
next_node(D, {tree, Tree, {table_entry, _MibName}},
	  Oid, RevOidSoFar, MibView) ->
    ?vtrace("next_node(tree,table_entry) -> entry when"
	"~n   size(Tree):  ~p"
	"~n   Oid:         ~p"
	"~n   RevOidSoFar: ~p"
	"~n   MibView:     ~p", [tuple_size(Tree), Oid, RevOidSoFar, MibView]),
    OidSoFar = lists:reverse(RevOidSoFar),
    case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of
	true -> 
	    ?vdebug("next_node(tree,table_entry) -> not in mib view",[]),
	    false;
	_ -> 
	    #mib_data{module = Mod, node_db = Db} = D,
	    case Mod:read(Db, OidSoFar) of
		false ->
		    ?vinfo("next_node -> could not find table_entry with"
			"~n   OidSoFar: ~p", [OidSoFar]),
		    false;
		{value, #node_info{me = ME}} ->
		    ?vtrace("next_node(tree,table_entry) -> found: ~n   ~p",
			[ME]),
		    {table, OidSoFar, Oid, ME}
	    end
    end;

next_node(D, {tree, Tree, _Info}, [Int | RestOfOid], RevOidSoFar, MibView) 
  when (Int < tuple_size(Tree)) andalso (Int >= 0) ->
    ?vtrace("next_node(tree) -> entry when"
	"~n   size(Tree):  ~p"
	"~n   Int:         ~p"
	"~n   RestOfOid:   ~p"
	"~n   RevOidSoFar: ~p"
	"~n   MibView:     ~p", 
	[tuple_size(Tree), Int, RestOfOid, RevOidSoFar, MibView]),
    case next_node(D, element(Int+1,Tree), 
		   RestOfOid, [Int|RevOidSoFar], MibView) of
	false -> 
	    find_next(D, {tree, Tree, _Info}, Int+1, RevOidSoFar, MibView);
	Else -> 
	    Else
    end;
%% no solution
next_node(D, {tree, Tree, _Info}, [], RevOidSoFar, MibView) ->
    ?vtrace("next_node(tree,[]) -> entry when"
	"~n   size(Tree):  ~p"
	"~n   RevOidSoFar: ~p"
	"~n   MibView:     ~p", 
	[tuple_size(Tree), RevOidSoFar, MibView]),
    find_next(D, {tree, Tree, _Info}, 0, RevOidSoFar, MibView);
next_node(_D, {tree, Tree, _Info}, _RestOfOid, _RevOidSoFar, _MibView) ->
    ?vtrace("next_node(tree) -> entry when"
	"~n   size(Tree):  ~p", [tuple_size(Tree)]),
    false;

next_node(D, {node, subagent}, Oid, RevOidSoFar, MibView) ->
    ?vtrace("next_node(node,subagent) -> entry when"
	"~n   Oid:         ~p"
	"~n   RevOidSoFar: ~p"
	"~n   MibView:     ~p", 
	[Oid, RevOidSoFar, MibView]),
    OidSoFar = lists:reverse(RevOidSoFar),
    case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of
	true -> 
	    false;
	_ -> 
	    #mib_data{subagents = SAs} = D,
	    case lists:keysearch(OidSoFar, 2, SAs) of
		{value, {SubAgentPid, OidSoFar}} ->
		    {subagent, SubAgentPid, OidSoFar};
		_ ->
		    ?vinfo("next_node -> could not find subagent with"
			"~n   OidSoFar: ~p"
			"~n   SAs:      ~p", [OidSoFar, SAs]),
		    false
	    end
    end;
    
next_node(D, {node, {variable, _MibName}}, [], RevOidSoFar, MibView) ->
    ?vtrace("next_node(node,variable,[]) -> entry when"
	"~n   RevOidSoFar: ~p"
	"~n   MibView:     ~p", 
	[RevOidSoFar, MibView]),
    OidSoFar = lists:reverse([0 | RevOidSoFar]),
    case snmpa_acm:validate_mib_view(OidSoFar, MibView) of
	true ->
	    #mib_data{module = Mod, node_db = Db} = D,
	    case Mod:read(Db, lists:reverse(RevOidSoFar)) of
		false ->
		    ?vinfo("next_node -> could not find variable with"
			"~n   RevOidSoFar: ~p", [RevOidSoFar]),
		    false;
		{value, #node_info{me = ME}} ->
		    {variable, ME, OidSoFar}
	    end;
	_ -> 
	    false
    end;

next_node(_D, {node, {variable, _MibName}}, _Oid, _RevOidSoFar, _MibView) ->
    ?vtrace("next_node(node,variable) -> entry", []),
    false.


%%-----------------------------------------------------------------
%% This function is used to find the first leaf from where we
%% are.
%% Returns: {subagent, SubAgentPid, SAOid} |
%%          false |
%%          {variable, MibEntry, VarOid} |
%%          {table, TableOid, TableRestOid, MibEntry}
%% PRE: This function must always be called with a {internal, Tree}
%%      node.
%%-----------------------------------------------------------------
find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView) 
  when Idx < tuple_size(Tree) ->
    case find_next(D, element(Idx+1, Tree), 0, [Idx| RevOidSoFar], MibView) of
	false -> 
	    find_next(D, {tree, Tree, internal}, Idx+1, RevOidSoFar, MibView);
	Other -> 
	    Other
    end;
find_next(_D, {tree, _Tree, internal}, _Idx, _RevOidSoFar, _MibView) ->
    false;
find_next(_D, undefined_node, _Idx, _RevOidSoFar, _MibView) ->
    false;
find_next(D, {tree, Tree, {table, _MibName}}, Idx, RevOidSoFar, MibView) ->
    find_next(D, {tree, Tree, internal}, Idx, RevOidSoFar, MibView);
find_next(D, {tree, _Tree, {table_entry, _MibName}}, _Index,
	  RevOidSoFar, MibView) ->
    OidSoFar = lists:reverse(RevOidSoFar),
    case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of
	true -> 
	    false;
	_ -> 
	    #mib_data{module = Mod, node_db = Db} = D,
	    case Mod:read(Db, OidSoFar) of
		false ->
		    ?vinfo("find_next -> could not find table_entry ME with"
			"~n   OidSoFar: ~p", [OidSoFar]),
		    false;
		{value, #node_info{me = ME}} ->
		    {table, OidSoFar, [], ME}
	    end
    end;
find_next(D, {node, {variable, _MibName}}, _Idx, RevOidSoFar, MibView) ->
    OidSoFar = lists:reverse([0 | RevOidSoFar]),
    case snmpa_acm:validate_mib_view(OidSoFar, MibView) of
	true -> 
	    #mib_data{module = Mod, node_db = Db} = D,
	    case Mod:read(Db, lists:reverse(RevOidSoFar)) of
		false ->
		    ?vinfo("find_next -> could not find variable with"
			"~n   RevOidSoFar: ~p", [RevOidSoFar]),
		    false;
		{value, #node_info{me = ME}} ->
		    {variable, ME, OidSoFar}
	    end;
	_ -> 
	    false
    end;
find_next(D, {node, subagent}, _Idx, RevOidSoFar, MibView) ->
    OidSoFar = lists:reverse(RevOidSoFar),
    case snmpa_acm:is_definitely_not_in_mib_view(OidSoFar, MibView) of
	true -> 
	    false;
	_ -> 
	    #mib_data{subagents = SAs} = D,
	    case lists:keysearch(OidSoFar, 2, SAs) of
		{value, {SubAgentPid, OidSoFar}} ->
		    {subagent, SubAgentPid, OidSoFar};
		false ->
		    ?vinfo("find_node -> could not find subagent with"
			"~n   OidSoFar: ~p"
			"~n   SAs:      ~p", [OidSoFar, SAs]),
		    false
	    end
    end.

%%%======================================================================
%%% 3. Tree building functions
%%%    Used when loading mibs.
%%%======================================================================

build_tree(Mes, MibName) ->
    ?d("build_tree -> "
	"~n   Mes: ~p", [Mes]),
    {ListTree, []}  = build_subtree([], Mes, MibName),
    {tree, convert_tree(ListTree), internal}.

%%----------------------------------------------------------------------
%% Purpose: Builds the tree where all oids have prefix equal to LevelPrefix.
%% Returns: {Tree, RestMes}
%% RestMes are Mes that should not be in this subtree.
%% The Tree is a temporary and simplified data structure that is easy to
%% convert to the final tuple tree used by the MIB process.
%% A Node is represented as in the final tree.
%% The tree is not represented as a N-tuple, but as an Index-list.
%% Example: Temporary: [{1, Node1}, {3, Node3}]
%%          Final:     {Node1, undefined_node, Node3}
%% Pre: Mes are sorted on oid.
%%----------------------------------------------------------------------
build_subtree(LevelPrefix, [Me | Mes], MibName) ->
    ?vtrace("build subtree -> ~n"
	   "   oid:         ~p~n"
	   "   LevelPrefix: ~p~n"
	   "   MibName:     ~p", [Me#me.oid, LevelPrefix, MibName]),
    EType = Me#me.entrytype,
    ?vtrace("build subtree -> EType = ~p",[EType]),
    case in_subtree(LevelPrefix, Me) of
	above ->
	    ?vtrace("build subtree -> above",[]),
	    {[], [Me|Mes]};
	{node, Index} ->
	    ?vtrace("build subtree -> node at ~p",[Index]),
	    {Tree, RestMes} = build_subtree(LevelPrefix, Mes, MibName),
	    {[{Index, {node, {EType, MibName}}} | Tree], RestMes};
	{subtree, Index, NewLevelPrefix} ->
	    ?vtrace("build subtree -> subtree at"
		    "~n   ~w with ~w",
		   [Index, NewLevelPrefix]),
	    {BelowTree, RestMes} = 
		build_subtree(NewLevelPrefix, Mes, MibName),
	    {CurTree, RestMes2} = 
		build_subtree(LevelPrefix, RestMes, MibName),
	    {[{Index, {tree, BelowTree, {EType,MibName}}}| CurTree], RestMes2};
	{internal_subtree, Index, NewLevelPrefix} ->
	    ?vtrace("build subtree -> internal_subtree at"
		    "~n   ~w with ~w",
		   [Index,NewLevelPrefix]),
	    {BelowTree, RestMes} =
		build_subtree(NewLevelPrefix, [Me | Mes], MibName),
	    {CurTree, RestMes2} =
		build_subtree(LevelPrefix, RestMes, MibName),
	    {[{Index, {tree, BelowTree, internal}} | CurTree], RestMes2}
    end;

build_subtree(_LevelPrefix, [], _MibName) -> 
    ?vtrace("build subtree -> done", []),
    {[], []}.

%%--------------------------------------------------
%% Purpose: Determine how/if/where Me should be inserted in subtree
%%          with LevelPrefix. This function does not build any tree, only 
%%          determinses what should be done (by build subtree).
%% Returns:
%% above - Indicating that this ME should _not_ be in this subtree.
%% {node, Index} - yes, construct a node with index Index on this level
%% {internal_subtree, Index, NewLevelPrefix} - yes, there should be an
%%   internal subtree at this index.
%% {subtree, Index, NewLevelPrefix} - yes, construct a subtree with 
%%   NewLevelPrefix and insert this on current level in position Index.
%%--------------------------------------------------
in_subtree(LevelPrefix, Me) ->
    case lists:prefix(LevelPrefix, Me#me.oid) of
	true when length(Me#me.oid) > length(LevelPrefix) ->
	    classify_how_in_subtree(LevelPrefix, Me);
	_ ->
	    above
    end.

%%--------------------------------------------------
%% See comment about in_subtree/2.  This function takes care of all cases
%% where the ME really should be in _this_ subtree (not above).
%%--------------------------------------------------
classify_how_in_subtree(LevelPrefix, Me) 
  when (length(Me#me.oid) =:= (length(LevelPrefix) + 1)) ->
    Oid = Me#me.oid,
    case node_or_subtree(Me#me.entrytype) of
	subtree ->
	    {subtree, lists:last(Oid), Oid};
	node ->
	    {node, lists:last(Oid)}
    end;

classify_how_in_subtree(LevelPrefix, Me) 
  when (length(Me#me.oid) > (length(LevelPrefix) + 1)) ->
    L1 = length(LevelPrefix) + 1,
    Oid = Me#me.oid,
    {internal_subtree, lists:nth(L1, Oid), lists:sublist(Oid, 1, L1)}.

%%--------------------------------------------------
%% Determines how to treat different kinds om MEs in the tree building process.
%% Pre: all internal nodes have been removed.
%%--------------------------------------------------
node_or_subtree(table) -> subtree;
node_or_subtree(table_entry) -> subtree;
node_or_subtree(variable) -> node;
node_or_subtree(table_column) -> node.

%%--------------------------------------------------
%% Purpose: (Recursively) Converts a temporary tree (see above) to a final tree.
%% If input is a ListTree, output is a TupleTree.
%% If input is a Node, output is the same Node.
%% Pre: All Indexes are >= 0.
%%--------------------------------------------------
convert_tree({Index, {tree, Tree, Info}}) when Index >= 0 ->
    L = lists:map(fun convert_tree/1, Tree),
    {Index, {tree, dict_list_to_tuple(L), Info}};
convert_tree({Index, {node, Info}}) when Index >= 0 ->
    {Index, {node, Info}};
convert_tree(Tree) when is_list(Tree) ->
    L = lists:map(fun convert_tree/1, Tree),
    dict_list_to_tuple(L).

%%----------------------------------------------------------------------
%% Purpose: Converts a single level (that is non-recursively) from
%%          the temporary indexlist to the N-tuple.
%% Input: A list of {Index, Data}.
%% Output: A tuple where element Index is Data.
%%----------------------------------------------------------------------
dict_list_to_tuple(L) ->
    L2 = lists:keysort(1, L),
    list_to_tuple(integrate_indexes(0, L2)).

%%----------------------------------------------------------------------
%% Purpose: Helper function for dict_list_to_tuple/1.
%%          Converts an indexlist to a N-list.
%% Input: A list of {Index, Data}.
%% Output: A (usually longer, never shorter) list where element Index is Data.
%% Example: [{1,hej}, {3, sven}] will give output 
%% [undefined_node, hej, undefined_node, sven].
%% Initially CurIndex should be 0.
%%----------------------------------------------------------------------
integrate_indexes(CurIndex, [{CurIndex, Data} | T]) ->
    [Data | integrate_indexes(CurIndex + 1, T)];
integrate_indexes(_Index, []) ->
    [];
integrate_indexes(CurIndex, L) ->
    [undefined_node | integrate_indexes(CurIndex + 1, L)].

%%%======================================================================
%%% 4. Tree merging
%%%    Used by: load mib, insert subagent.
%%%======================================================================

%%----------------------------------------------------------------------
%% Arg: Two root nodes (that is to be merged).
%% Returns: A new root node where the nodes have been merger to one.
%%----------------------------------------------------------------------
merge_nodes(Same, Same) -> 
    Same;
merge_nodes(Node, undefined_node) -> 
    Node;
merge_nodes(undefined_node, Node) -> 
    Node;
merge_nodes({tree, Tree1, internal}, {tree, Tree2, internal}) ->
    {tree, merge_levels(tuple_to_list(Tree1),tuple_to_list(Tree2)), internal};
merge_nodes(Node1, Node2) ->
    throw({error_merge_nodes, Node1, Node2}).

%%----------------------------------------------------------------------
%% Arg: Two levels to be merged.
%%      Here, a level is represented as a list of nodes. A list is easier
%%      to extend than a tuple.
%% Returns: The resulting, merged level tuple.
%%----------------------------------------------------------------------
merge_levels(Level1, Level2) when length(Level1) =:= length(Level2) ->
    MergeNodes = fun(N1, N2) -> merge_nodes(N1, N2) end,
    list_to_tuple(snmp_misc:multi_map(MergeNodes, [Level1, Level2]));
merge_levels(Level1, Level2) when length(Level1) > length(Level2) ->
    merge_levels(Level1, Level2 ++ 
		 undefined_nodes_list(length(Level1) - length(Level2)));
merge_levels(Level1, Level2) when length(Level1) < length(Level2) ->
    merge_levels(Level2, Level1).

undefined_nodes_list(N) -> lists:duplicate(N, undefined_node).


%%%======================================================================
%%% 5. Tree deletion routines
%%%    (for unload mib)
%%%======================================================================

%%----------------------------------------------------------------------
%% Purpose:  Actually kicks of the tree reconstruction.
%% Returns: {list of removed MEs, NewTree}
%%----------------------------------------------------------------------
delete_mib_from_tree(MibName, {tree, Tree, internal}) ->
    case delete_tree(Tree, MibName) of
	[] -> 
	    {tree, {undefined_node}, internal}; % reduce
	LevelList -> 
	    {tree, list_to_tuple(LevelList), internal}
    end.

%%----------------------------------------------------------------------
%% Purpose: Deletes all nodes associated to MibName from this level and
%%          all levels below.
%%          If the new level does not contain information (that is, no 
%%          other mibs use it) anymore the empty list is returned.
%% Returns: {MEs, The new level represented as a list}
%%----------------------------------------------------------------------
delete_tree(Tree, MibName) when is_tuple(Tree) ->
    NewLevel = delete_nodes(tuple_to_list(Tree), MibName, []),
    case lists:filter(fun drop_undefined_nodes/1,NewLevel) of
	[] -> [];
	_A_perhaps_shorted_list ->
	    NewLevel  % some other mib needs this level
    end.
    
%%----------------------------------------------------------------------
%% Purpose: Nodes belonging to MibName are removed from the tree.
%%          Recursively deletes sub trees to this node.
%% Returns: {MEs, NewNodesList}
%%----------------------------------------------------------------------
delete_nodes([], _MibName, AccNodes) ->
    lists:reverse(AccNodes);

delete_nodes([{node, {variable, MibName}}|T], MibName, AccNodes) ->
    delete_nodes(T, MibName, [undefined_node | AccNodes]);

delete_nodes([{node, {table_column, MibName}}|T], MibName, AccNodes) ->
    delete_nodes(T, MibName, [undefined_node | AccNodes]);

delete_nodes([{tree, _Tree, {table, MibName}}|T], MibName, AccNodes) ->
    delete_nodes(T, MibName, [undefined_node | AccNodes]);

delete_nodes([{tree, _Tree, {table_entry, MibName}}|T], MibName, AccNodes) ->
    delete_nodes(T, MibName, [undefined_node | AccNodes]);

delete_nodes([{tree, Tree, Info}|T], MibName, AccNodes) ->
    case delete_tree(Tree, MibName) of
	[] -> % tree completely deleted
	    delete_nodes(T, MibName, [undefined_node | AccNodes]);
	LevelList ->
	    delete_nodes(T, MibName, 
			 [{tree, list_to_tuple(LevelList), Info} | AccNodes])
    end;

delete_nodes([NodeToKeep|T], MibName, AccNodes) ->
    delete_nodes(T, MibName, [NodeToKeep | AccNodes]).

drop_undefined_nodes(undefined_node) -> false;
drop_undefined_nodes(_) -> true.


%%%======================================================================
%%% 6. Functions for subagent handling
%%%======================================================================

%%----------------------------------------------------------------------
%% Returns: A new Root|{error, reason}
%%----------------------------------------------------------------------
insert_subagent(Oid, OldRoot) ->
    ListTree = build_tree_for_subagent(Oid),
    case catch convert_tree(ListTree) of
	{'EXIT', _Reason} ->
	    {error, 'cannot construct tree from oid'};
	Level when is_tuple(Level) ->
	    T = {tree, Level, internal},
	    case catch merge_nodes(T, OldRoot) of
		{error_merge_nodes, _Node1, _Node2} ->
		    {error, oid_conflict};
		NewRoot when is_tuple(NewRoot) andalso 
			     (element(1, NewRoot) =:= tree) ->
		    NewRoot
	    end
    end.

build_tree_for_subagent([Index]) ->
    [{Index, {node, subagent}}];

build_tree_for_subagent([Index | T]) ->
    [{Index, {tree, build_tree_for_subagent(T), internal}}].


%%----------------------------------------------------------------------
%% Returns: A new tree where the subagent at Oid (2nd arg) has been deleted.
%%----------------------------------------------------------------------

delete_subagent({tree, Tree, Info}, [Index]) ->
    {node, subagent} = element(Index+1, Tree),
    {tree, setelement(Index+1, Tree, undefined_node), Info};
delete_subagent({tree, Tree, Info}, [Index | TI]) ->
    {tree, setelement(Index+1, Tree,
		      delete_subagent(element(Index+1, Tree), TI)), Info}.


%%%======================================================================
%%% 7. Misc functions
%%%======================================================================

%%----------------------------------------------------------------------
%% Installs the mibs found in the database when starting the agent.
%% Basically calls the instrumentation functions for all non-internal
%% mib-entries
%%----------------------------------------------------------------------
install_mibs(Mod, MibDb, NodeDb) ->
    MibNames = loaded(Mod, MibDb),
    ?vtrace("install_mibs -> found following mibs in database: ~n"
	"~p", [MibNames]),
    install_mibs2(Mod, NodeDb, MibNames).

install_mibs2(_, _, []) ->
    ok;
install_mibs2(Mod, NodeDb, [MibName|MibNames]) ->
    Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'},
    Nodes = Mod:match_object(NodeDb, Pattern),
    MEs = [ME || #node_info{me = ME} <- Nodes],
    ?vtrace("install_mibs2 -> installing ~p MEs for mib ~p", 
	    [length(MEs), MibName]),
    NewF = fun(ME) -> call_instrumentation(ME, new) end,
    lists:foreach(NewF, MEs),
    install_mibs2(Mod, NodeDb, MibNames).
    
    
%%----------------------------------------------------------------------
%% Does all side effect stuff during load_mib.
%%----------------------------------------------------------------------
install_mib(Mod, Db, Symbolic, Mib, MibName, FileName, NonInternalMes) ->
    ?vdebug("install_mib -> entry with"
	    "~n   Symbolic: ~p"
	    "~n   MibName:  ~p"
	    "~n   FileName: ~p", [Symbolic, MibName, FileName]),
    Rec = #mib_info{name = MibName, symbolic = Symbolic, file_name = FileName},
    Mod:write(Db, Rec),
    install_mib2(Symbolic, MibName, Mib),
    NewF = fun(ME) -> call_instrumentation(ME, new) end,
    lists:foreach(NewF, NonInternalMes).

install_mib2(true, MibName, Mib) ->
    #mib{table_infos    = TabInfos,
	 variable_infos = VarInfos,
	 mes            = MEs,
	 asn1_types     = ASN1Types,
	 traps          = Traps} = Mib,
    snmpa_symbolic_store:add_table_infos(MibName, TabInfos),
    snmpa_symbolic_store:add_variable_infos(MibName, VarInfos),
    snmpa_symbolic_store:add_aliasnames(MibName, MEs),
    snmpa_symbolic_store:add_types(MibName, ASN1Types),
    SetF = fun(Trap) -> 
		   snmpa_symbolic_store:set_notification(Trap, MibName)
	   end,
    lists:foreach(SetF, Traps);
install_mib2(_, _, _) ->
    ok.

install_mes(Mod, Db, MibName, MEs) ->
    Write = fun(#me{oid = Oid} = ME) ->
		    Node = #node_info{oid      = Oid, 
				      mib_name = MibName, 
				      me       = ME},
		    Mod:write(Db, Node)
	    end,
    install_mes(Write, MEs).

install_mes(_Write, []) ->
    ok;
install_mes(Write, [ME|MEs]) ->
    Write(ME),
    install_mes(Write, MEs).


%%----------------------------------------------------------------------
%% Does all side effect stuff during unload_mib.
%%----------------------------------------------------------------------
uninstall_mib(Mod, Db, Symbolic, MibName, MEs) ->
    ?vtrace("uninstall_mib -> entry with"
	"~n   Db:       ~p"
	"~n   Symbolic: ~p"
	"~n   MibName:  ~p", [Db, Symbolic, MibName]),
    Res = Mod:delete(Db, MibName),
    ?vtrace("uninstall_mib -> (mib) db delete result: ~p", [Res]),
    uninstall_mib2(Symbolic, MibName),
    DelF = fun(ME) -> call_instrumentation(ME, delete) end,
    lists:foreach(DelF, MEs).

uninstall_mib2(true, MibName) ->
    snmpa_symbolic_store:delete_table_infos(MibName),
    snmpa_symbolic_store:delete_variable_infos(MibName),
    snmpa_symbolic_store:delete_aliasnames(MibName),
    snmpa_symbolic_store:delete_types(MibName),
    snmpa_symbolic_store:delete_notifications(MibName);
uninstall_mib2(_, _) ->
    ok.

uninstall_mes(Mod, Db, MibName) ->
    Pattern = #node_info{oid = '_', mib_name = MibName, me = '_'},
    Mod:match_delete(Db, Pattern).


%%----------------------------------------------------------------------
%% Create a list of the names of all the loaded mibs
%%----------------------------------------------------------------------
loaded(Mod, Db) ->
    [N || #mib_info{name = N} <- Mod:tab2list(Db)].
    

%%----------------------------------------------------------------------
%% Calls MFA-instrumentation with 'new' or 'delete' operation.
%%----------------------------------------------------------------------
call_instrumentation(#me{entrytype = variable, mfa={M,F,A}}, Operation) ->
    ?vtrace("call instrumentation with"
	    "~n   entrytype: variable"
	    "~n   MFA:       {~p,~p,~p}"
	    "~n   Operation: ~p",
	    [M,F,A,Operation]),
    catch apply(M, F, [Operation | A]);
call_instrumentation(#me{entrytype = table_entry, mfa={M,F,A}}, Operation) ->
    ?vtrace("call instrumentation with"
	    "~n   entrytype: table_entry"
	    "~n   MFA:       {~p,~p,~p}"
	    "~n   Operation: ~p",
	    [M,F,A,Operation]),
    catch apply(M, F, [Operation | A]);
call_instrumentation(_ShitME, _Operation) ->
    done.


maybe_drop_me(#me{entrytype = internal}) -> false;
maybe_drop_me(#me{entrytype = group}) -> false;
maybe_drop_me(#me{imported = true}) -> false;
maybe_drop_me(_) -> true.


%%----------------------------------------------------------------------
%% Code change functions
%%----------------------------------------------------------------------

code_change(down, _Vsn, _Extra, State) ->
    ?d("code_change(down) -> entry when"
       "~n   Vsn:   ~p"
       "~n   Extra: ~p", [_Vsn, _Extra]),
    State;

code_change(up, _Vsn, _Extra, State) ->
    ?d("code_change(up) -> entry when"
       "~n   Vsn:   ~p"
       "~n   Extra: ~p", [_Vsn, _Extra]),
    State.


