%% 
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2004-2009. 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(snmpm_server).

%%----------------------------------------------------------------------
%% This module implements a simple SNMP manager for Erlang.
%%
%%----------------------------------------------------------------------

%% User interface
-export([start_link/0, stop/0, 
	 is_started/0, 

	 load_mib/1, unload_mib/1, 

	 register_user/4, register_user_monitor/4, unregister_user/1, 

	 sync_get/4,       sync_get/5,       sync_get/6, 
	 async_get/4,      async_get/5,      async_get/6, 
	 sync_get_next/4,  sync_get_next/5,  sync_get_next/6, 
	 async_get_next/4, async_get_next/5, async_get_next/6, 
	 sync_get_bulk/6,  sync_get_bulk/7,  sync_get_bulk/8, 
	 async_get_bulk/6, async_get_bulk/7, async_get_bulk/8, 
	 sync_set/4,       sync_set/5,       sync_set/6, 
	 async_set/4,      async_set/5,      async_set/6, 
	 cancel_async_request/2,

	 %% discovery/2, discovery/3, discovery/4, discovery/5, discovery/6, 

	 %% system_info_updated/2, 
	 get_log_type/0,      set_log_type/1, 

	 reconfigure/0,

	 info/0, 
	 verbosity/1, verbosity/2 

	]).


%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, 
	 code_change/3, terminate/2]).

%% GCT exports
-export([gct_init/1, gct/2]).


-include("snmpm_internal.hrl").
-include("snmp_debug.hrl").
-include("snmp_types.hrl").
-include("STANDARD-MIB.hrl").
-include("SNMP-FRAMEWORK-MIB.hrl").
-include("snmp_verbosity.hrl").


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

-define(SERVER, ?MODULE).

-define(SYNC_GET_TIMEOUT,     5000).
-define(SYNC_SET_TIMEOUT,     5000).
-define(DEFAULT_ASYNC_EXPIRE, 5000).
-define(EXTRA_INFO,           undefined).

-define(SNMP_AGENT_PORT,      161).


-ifdef(snmp_debug).
-define(GS_START_LINK(Args),
	gen_server:start_link({local, ?SERVER}, ?MODULE, Args, 
			      [{debug,[trace]}])).
-else.
-define(GS_START_LINK(Args),
	gen_server:start_link({local, ?SERVER}, ?MODULE, Args, [])).
-endif.



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

-record(state,
	{parent,
	 gct,
	 note_store,
	 note_store_ref,
	 net_if,
	 net_if_mod,
	 net_if_ref,
	 req,  %%  ???? Last request id in outgoing message
	 oid,  %%  ???? Last oid in request outgoing message
	 mini_mib
	}
       ).

%% The active state is to ensure that nothing unpleasant happens
%% during (after) a code_change. At the initial start of the
%% application, this process (GCT) will make one run and then
%% deactivate (unless some async request has been issued in the
%% meantime).
-record(gct, {parent, state = active, timeout}).

-record(request, 
	{id, 
	 user_id,
	 reg_type,
	 target, 
	 addr, 
	 port, 
	 type, 
	 data, 
	 ref, 
	 mon, 
	 from,
	 discovery = false, 
	 expire = infinity % When shall the request expire (time in ms)
	}
       ). 

-record(monitor,
	{id, 
	 mon,
	 proc
	}
       ).


%%%-------------------------------------------------------------------
%%% API
%%%-------------------------------------------------------------------

start_link() ->
    ?d("start_link -> entry", []),
    Args = [],
    ?GS_START_LINK(Args).

stop() ->
    call(stop).

is_started() ->
    case (catch call(is_started, 1000)) of
	Bool when ((Bool =:= true) orelse (Bool =:= false)) ->
	    Bool;
	_ ->
	    false
    end.

load_mib(MibFile) when is_list(MibFile) ->
    call({load_mib, MibFile}).

unload_mib(Mib) when is_list(Mib) ->
    call({unload_mib, Mib}).


register_user(UserId, UserMod, UserData, DefaultAgentConfig) ->
    snmpm_config:register_user(UserId, UserMod, UserData, DefaultAgentConfig).

register_user_monitor(Id, Module, Data, DefaultAgentConfig) ->
    case register_user(Id, Module, Data, DefaultAgentConfig) of
	ok ->
	    case call({monitor_user, Id, self()}) of
		ok ->
		    ok;
		Error ->
		    unregister_user(Id),
		    Error
	    end;
	Error ->
	    Error
    end.

unregister_user(UserId) ->
    call({unregister_user, UserId}).


%% -- [sync] get --

sync_get(UserId, TargetName, CtxName, Oids) ->
    sync_get(UserId, TargetName, CtxName, Oids, 
	     ?SYNC_GET_TIMEOUT).

sync_get(UserId, TargetName, CtxName, Oids, Timeout) ->
    sync_get(UserId, TargetName, CtxName, Oids, Timeout, ?EXTRA_INFO).

sync_get(UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo) 
  when is_list(TargetName) andalso 
       is_list(CtxName) andalso 
       is_list(Oids) andalso 
       is_integer(Timeout) ->
    call({sync_get, self(), UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo}).

%% -- [async] get --

async_get(UserId, TargetName, CtxName, Oids) ->
    async_get(UserId, TargetName, CtxName, Oids, 
	      ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO).

async_get(UserId, TargetName, CtxName, Oids, Expire) ->
    async_get(UserId, TargetName, CtxName, Oids, Expire, ?EXTRA_INFO).

async_get(UserId, TargetName, CtxName, Oids, Expire, ExtraInfo) 
  when (is_list(TargetName) andalso 
	is_list(CtxName) andalso 
	is_list(Oids) andalso 
	is_integer(Expire) andalso (Expire >= 0)) ->
    call({async_get, self(), UserId, TargetName, CtxName, Oids, Expire, 
	  ExtraInfo}).

%% -- [sync] get-next --

sync_get_next(UserId, TargetName, CtxName, Oids) ->
    sync_get_next(UserId, TargetName, CtxName, Oids, ?SYNC_GET_TIMEOUT, 
		  ?EXTRA_INFO).

sync_get_next(UserId, TargetName, CtxName, Oids, Timeout) ->
    sync_get_next(UserId, TargetName, CtxName, Oids, Timeout, ?EXTRA_INFO).

sync_get_next(UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo) 
  when is_list(TargetName) andalso 
       is_list(CtxName) andalso 
       is_list(Oids) andalso 
       is_integer(Timeout) ->
    call({sync_get_next, self(), UserId, TargetName, CtxName, Oids, Timeout, 
	  ExtraInfo}).

%% -- [async] get-next --

async_get_next(UserId, TargetName, CtxName, Oids) ->
    async_get_next(UserId, TargetName, CtxName, Oids, 
		   ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO).

async_get_next(UserId, TargetName, CtxName, Oids, Expire) ->
    async_get_next(UserId, TargetName, CtxName, Oids, Expire, ?EXTRA_INFO).

async_get_next(UserId, TargetName, CtxName, Oids, Expire, ExtraInfo) 
  when (is_list(TargetName) andalso 
	is_list(CtxName) andalso 
	is_list(Oids) andalso 
	is_integer(Expire) andalso (Expire >= 0)) ->
    call({async_get_next, self(), UserId, TargetName, CtxName, Oids, 
	  Expire, ExtraInfo}).

%% -- [sync] get-bulk --

sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids) ->
    sync_get_bulk(UserId, TargetName, 
		  NonRep, MaxRep, CtxName, Oids, 
		  ?SYNC_GET_TIMEOUT, ?EXTRA_INFO).

sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Timeout) ->
    sync_get_bulk(UserId, TargetName, 
		  NonRep, MaxRep, CtxName, Oids, 
		  Timeout, ?EXTRA_INFO).

sync_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Timeout, 
	      ExtraInfo) 
  when is_list(TargetName) andalso 
       is_integer(NonRep) andalso 
       is_integer(MaxRep) andalso 
       is_list(CtxName) andalso 
       is_list(Oids) andalso 
       is_integer(Timeout) ->
    call({sync_get_bulk, self(), UserId, TargetName, 
	  NonRep, MaxRep, CtxName, Oids, Timeout, ExtraInfo}).

%% -- [async] get-bulk --

async_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids) ->
    async_get_bulk(UserId, TargetName, 
		   NonRep, MaxRep, CtxName, Oids, 
		   ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO).

async_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Expire) ->
    async_get_bulk(UserId, TargetName, 
		   NonRep, MaxRep, CtxName, Oids, 
		   Expire, ?EXTRA_INFO).

async_get_bulk(UserId, TargetName, NonRep, MaxRep, CtxName, Oids, Expire, 
	       ExtraInfo) 
  when is_list(TargetName) andalso 
       is_integer(NonRep) andalso 
       is_integer(MaxRep) andalso 
       is_list(CtxName) andalso 
       is_list(Oids) andalso 
       is_integer(Expire) ->
    call({async_get_bulk, self(), UserId, TargetName, 
	  NonRep, MaxRep, CtxName, Oids, Expire, ExtraInfo}).

%% -- [sync] set --

%% VarsAndValues is: {PlainOid, o|s|i, Value} (unknown mibs) | {Oid, Value} 
sync_set(UserId, TargetName, CtxName, VarsAndVals) ->
    sync_set(UserId, TargetName, CtxName, VarsAndVals, 
	     ?SYNC_SET_TIMEOUT, ?EXTRA_INFO).

sync_set(UserId, TargetName, CtxName, VarsAndVals, Timeout) ->
    sync_set(UserId, TargetName, CtxName, VarsAndVals, 
	     Timeout, ?EXTRA_INFO).

sync_set(UserId, TargetName, CtxName, VarsAndVals, Timeout, ExtraInfo) 
  when is_list(TargetName) andalso 
       is_list(CtxName) andalso 
       is_list(VarsAndVals) andalso 
       is_integer(Timeout) ->
    call({sync_set, self(), UserId, TargetName, 
	  CtxName, VarsAndVals, Timeout, ExtraInfo}).

%% -- [async] set --

async_set(UserId, TargetName, CtxName, VarsAndVals) ->
    async_set(UserId, TargetName, CtxName, VarsAndVals, 
	      ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO).

async_set(UserId, TargetName, CtxName, VarsAndVals, Expire) ->
    async_set(UserId, TargetName, CtxName, VarsAndVals, 
	      Expire, ?EXTRA_INFO).

async_set(UserId, TargetName, CtxName, VarsAndVals, Expire, ExtraInfo) 
  when (is_list(TargetName) andalso 
	is_list(CtxName) andalso 
	is_list(VarsAndVals) andalso 
	is_integer(Expire) andalso (Expire >= 0)) ->
    call({async_set, self(), UserId, TargetName, 
	  CtxName, VarsAndVals, Expire, ExtraInfo}).


cancel_async_request(UserId, ReqId) ->
    call({cancel_async_request, UserId, ReqId}).


%% discovery(UserId, BAddr) ->
%%     discovery(UserId, BAddr, ?SNMP_AGENT_PORT, [], 
%% 	      ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO).

%% discovery(UserId, BAddr, Config) when is_list(Config) ->
%%     discovery(UserId, BAddr, ?SNMP_AGENT_PORT, Config, 
%% 	      ?DEFAULT_ASYNC_EXPIRE, ?EXTRA_INFO);

%% discovery(UserId, BAddr, Expire) when is_integer(Expire) ->
%%     discovery(UserId, BAddr, ?SNMP_AGENT_PORT, [], Expire, ?EXTRA_INFO).

%% discovery(UserId, BAddr, Config, Expire) ->
%%     discovery(UserId, BAddr, ?SNMP_AGENT_PORT, Config, Expire, ?EXTRA_INFO).

%% discovery(UserId, BAddr, Port, Config, Expire) ->
%%     discovery(UserId, BAddr, Port, Config, Expire, ?EXTRA_INFO).

%% discovery(UserId, BAddr, Port, Config, Expire, ExtraInfo) ->
%%     call({discovery, self(), UserId, BAddr, Port, Config, Expire, ExtraInfo}).

    
verbosity(Verbosity) ->
    case ?vvalidate(Verbosity) of
	Verbosity ->
	    call({verbosity, Verbosity});
	_ ->
	    {error, {invalid_verbosity, Verbosity}}
    end.

info() ->
    call(info).

verbosity(net_if = Ref, Verbosity) ->
    verbosity2(Ref, Verbosity);
verbosity(note_store = Ref, Verbosity) ->
    verbosity2(Ref, Verbosity).

verbosity2(Ref, Verbosity) ->
    case ?vvalidate(Verbosity) of
	Verbosity ->
	    call({verbosity, Ref, Verbosity});
	_ ->
	    {error, {invalid_verbosity, Verbosity}}
    end.

%% Target -> all | server | net_if
%% system_info_updated(Target, What) ->
%%     call({system_info_updated, Target, What}).

get_log_type() ->
    call(get_log_type).

set_log_type(NewType) ->
    call({set_log_type, NewType}).

reconfigure() ->
    call(reconfigure).


%%----------------------------------------------------------------------
%% Options: List of
%%  {community, String ("public" is default} 
%%  {mibs, List of Filenames}
%%  {trap_udp, integer() (default 5000)}
%%  {conf_dir, string()}
%%  {log_dir,  string()}
%%  {db_dir,   string()}
%%  {db_repair, true | false}
%%----------------------------------------------------------------------
init(_) ->
    ?d("init -> entry", []),
    case (catch do_init()) of
	{ok, State} ->
	    {ok, State};
	{error, Reason} ->
	    {stop, Reason}
    end.


%% Put all config stuff in a snmpm_config module/process.
%% Tables should be protected so that it is cheap to 
%% read. Writing has to go through the interface...

do_init() ->
    process_flag(trap_exit, true),
    {ok, Prio} = snmpm_config:system_info(prio),
    process_flag(priority, Prio),

    {ok, Verbosity} = snmpm_config:system_info(server_verbosity),
    put(sname, mse),
    put(verbosity, Verbosity),
    ?vlog("starting", []),

    %% Start the garbage collector timer process
    {ok, Timeout} = snmpm_config:system_info(server_timeout),
    {ok, GCT} = gct_start(Timeout),

    %% -- Create request table --
    ets:new(snmpm_request_table, 
	    [set, protected, named_table, {keypos, #request.id}]),

    %% -- Create monitor table --
    ets:new(snmpm_monitor_table, 
	    [set, protected, named_table, {keypos, #monitor.id}]),

    %% -- Start the note-store and net-if processes --
    {NoteStore, NoteStoreRef} = do_init_note_store(Prio),
    {NetIf, NetIfModule, NetIfRef} = do_init_net_if(NoteStore),

    MiniMIB = snmpm_config:make_mini_mib(),
    State = #state{mini_mib       = MiniMIB,
		   gct            = GCT,
		   note_store     = NoteStore,
		   note_store_ref = NoteStoreRef,
		   net_if         = NetIf,
		   net_if_mod     = NetIfModule,
		   net_if_ref     = NetIfRef},
    ?vlog("started", []),
    {ok, State}.


do_init_note_store(Prio) ->
    ?vdebug("try start note store", []),
    {ok, Verbosity} = snmpm_config:system_info(note_store_verbosity),
    {ok, Timeout}   = snmpm_config:system_info(note_store_timeout),
    Opts = [{sname,     mns}, 
	    {verbosity, Verbosity}, 
	    {timeout,   Timeout}],
    case snmpm_misc_sup:start_note_store(Prio, Opts) of
	{ok, Pid} ->
	    ?vtrace("do_init_note_store -> Pid: ~p", [Pid]),
	    Ref = erlang:monitor(process, Pid),
	    {Pid, Ref};
	{error, Reason} ->
	    ?vlog("failed starting note-store - Reason: "
		  "~n   Reason: ~p"
		  "~n", [Reason]),
	    throw({error, {failed_starting_note_store, Reason}})
    end.

do_init_net_if(NoteStore) ->
    ?vdebug("try start net if", []),
    {ok, NetIfModule} = snmpm_config:system_info(net_if_module),
    case snmpm_misc_sup:start_net_if(NetIfModule, NoteStore) of
	{ok, Pid} ->
	    ?vtrace("do_init_net_if -> Pid: ~p", [Pid]),
	    Ref = erlang:monitor(process, Pid),
	    {Pid, NetIfModule, Ref};
	{error, Reason} ->
	    ?vlog("failed starting net-if - Reason: "
		  "~n   Reason: ~p"
		  "~n", [Reason]),
	    throw({error, {failed_starting_net_if, Reason}})
    end.

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

handle_call({monitor_user, Id, Pid}, _From, State) when is_pid(Pid) ->
    ?vlog("received monitor_user request for ~w [~w]", [Id, Pid]),
    Reply = 
	case ets:lookup(snmpm_monitor_table, Id) of
	    [#monitor{proc = Pid}] ->
		?vdebug("already monitored", []),
		ok;

	    [#monitor{proc = OtherPid}] ->
		?vinfo("already registered to ~w", [OtherPid]),
		{error, {already_monitored, OtherPid}};

	    [] ->
		Ref = erlang:monitor(process, Pid),
		?vtrace("monitor ref: ~w", [Ref]),
		Mon = #monitor{id = Id, mon = Ref, proc = Pid},
		ets:insert(snmpm_monitor_table, Mon),
		ok
	end,
    {reply, Reply, State};

handle_call({unregister_user, UserId}, _From, State) ->
    ?vlog("received request to unregister user ~p", [UserId]),

    %% 1) If this user is monitored, then demonitor
    ?vtrace("handle_call(unregister_user) -> maybe demonitor", []),
    case ets:lookup(snmpm_monitor_table, UserId) of
	[] ->
	    ok;
	[#monitor{mon = M}] ->
	    maybe_demonitor(M), % This is really overkill (meybe_), but...
	    ok
    end,

    %% 2) Delete all outstanding requests from this user
    ?vtrace("handle_call(unregister_user) -> "
	    "delete all outstanding requests for user", []),
    Pat = #request{user_id = UserId, 
		   id = '$1', ref = '$2', mon = '$3', _ = '_'},
    Match = ets:match(snmpm_request_table, Pat),
    ?vtrace("handle_call(unregister_user) -> Match: ~p", [Match]),
    F1 = fun([ReqId, Ref, MonRef]) -> 
		 ets:delete(snmpm_request_table, ReqId),
		 cancel_timer(Ref),
		 maybe_demonitor(MonRef),
		 ok
	end,
    lists:foreach(F1, Match),
    
    %% 3) Unregister all agents registered by this user
    ?vdebug("handle_call(unregister_user) -> "
	    "unregister all agents registered by user", []),
    Agents = snmpm_config:which_agents(UserId),
    ?vtrace("handle_call(unregister_user) -> Agents: ~p", [Agents]),
    F2 = fun(TargetName) ->
		 snmpm_config:unregister_agent(UserId, TargetName)
	 end,
    lists:foreach(F2, Agents),

    %% 4) Unregister the user
    ?vdebug("handle_call(unregister_user) -> unregister user", []),
    Reply = snmpm_config:unregister_user(UserId),
    ?vtrace("handle_call(unregister_user) -> Reply: ~p", [Reply]),
    {reply, Reply, State};


%% We will reply to this request later, when the reply comes in from the
%% agent, or when the timeout hits (unless we get an error now).
handle_call({sync_get, Pid, UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo}, 
	    From, State) ->
    ?vlog("received sync_get [~p] request", [CtxName]),
    case (catch handle_sync_get(Pid, 
				UserId, TargetName, CtxName, Oids, 
				Timeout, ExtraInfo, From, State)) of
	ok ->
	    {noreply, State};
	Error ->
	    {reply, Error, State}
    end;


handle_call({sync_get_next, Pid, UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo}, From, State) ->
    ?vlog("received sync_get_next [~p] request", [CtxName]),
    case (catch handle_sync_get_next(Pid, 
				     UserId, TargetName, CtxName, Oids, 
				     Timeout, ExtraInfo, From, State)) of
	ok ->
	    {noreply, State};
	Error ->
	    {reply, Error, State}
    end;


%% Check agent version? This op not in v1
handle_call({sync_get_bulk, Pid, UserId, TargetName, 
	     NonRep, MaxRep, CtxName, Oids, Timeout, ExtraInfo}, 
	    From, State) ->
    ?vlog("received sync_get_bulk [~p] request", [CtxName]),
    case (catch handle_sync_get_bulk(Pid, 
				     UserId, TargetName, CtxName, 
				     NonRep, MaxRep, Oids, 
				     Timeout, ExtraInfo, From, State)) of
	ok ->
	    {noreply, State};
	Error ->
	    {reply, Error, State}
    end;


handle_call({sync_set, Pid, UserId, TargetName, 
	     CtxName, VarsAndVals, Timeout, ExtraInfo}, 
	    From, State) ->
    ?vlog("received sync_set [~p] request", [CtxName]),
    case (catch handle_sync_set(Pid, 
				UserId, TargetName, CtxName, VarsAndVals, 
				Timeout, ExtraInfo, From, State)) of
	ok ->
	    {noreply, State};
	Error ->
	    {reply, Error, State}
    end;


handle_call({async_get, Pid, UserId, TargetName, 
	     CtxName, Oids, Expire, ExtraInfo}, 
	    _From, State) ->
    ?vlog("received async_get [~p] request", [CtxName]),
    Reply = (catch handle_async_get(Pid, UserId, TargetName, CtxName, Oids, 
				    Expire, ExtraInfo, State)),
    {reply, Reply, State};


handle_call({async_get_next, Pid, UserId, TargetName, 
	     CtxName, Oids, Expire, ExtraInfo}, 
	    _From, State) ->
    ?vlog("received async_get_next [~p] request", [CtxName]),
    Reply = (catch handle_async_get_next(Pid, UserId, TargetName, CtxName, 
					 Oids, Expire, ExtraInfo, State)),
    {reply, Reply, State};


%% Check agent version? This op not in v1
handle_call({async_get_bulk, Pid, UserId, TargetName, 
	     NonRep, MaxRep, CtxName, Oids, Expire, ExtraInfo}, 
	    _From, State) ->
    ?vlog("received async_get_bulk [~p] request", [CtxName]),
    Reply = (catch handle_async_get_bulk(Pid, 
					 UserId, TargetName, CtxName, 
					 NonRep, MaxRep, Oids, 
					 Expire, ExtraInfo, State)),
    {reply, Reply, State};


handle_call({async_set, Pid, UserId, TargetName, 
	     CtxName, VarsAndVals, Expire, ExtraInfo}, 
	    _From, State) ->
    ?vlog("received async_set [~p] request", [CtxName]),
    Reply = (catch handle_async_set(Pid, UserId, TargetName, CtxName, 
				    VarsAndVals, Expire, ExtraInfo, State)),
    {reply, Reply, State};


handle_call({cancel_async_request, UserId, ReqId}, _From, State) ->
    ?vlog("received cancel_async_request request", []),
    Reply = (catch handle_cancel_async_request(UserId, ReqId, State)),
    {reply, Reply, State};


%% handle_call({discovery, Pid, UserId, BAddr, Port, Config, Expire, ExtraInfo}, 
%% 	    _From, State) ->
%%     ?vlog("received discovery request", []),
%%     Reply = (catch handle_discovery(Pid, UserId, BAddr, Port, Config, 
%% 				    Expire, ExtraInfo, State)),
%%     {reply, Reply, State};


handle_call({load_mib, Mib}, _From, State) ->
    ?vlog("received load_mib request", []),
    case snmpm_config:load_mib(Mib) of
	ok ->
	    MiniMIB = snmpm_config:make_mini_mib(),
	    {reply, ok, State#state{mini_mib = MiniMIB}};
	Error ->
	    {reply, Error, State}
    end;


handle_call({unload_mib, Mib}, _From, State) ->
    ?vlog("received unload_mib request", []),
    case snmpm_config:unload_mib(Mib) of
	ok ->
	    MiniMIB = snmpm_config:make_mini_mib(),
	    {reply, ok, State#state{mini_mib = MiniMIB}};
	Error ->
	    {reply, Error, State}
    end;

handle_call({verbosity, Verbosity}, _From, State) ->
    ?vlog("received verbosity request", []),
    put(verbosity, Verbosity),
    {reply, ok, State};

handle_call({verbosity, net_if, Verbosity}, _From, 
	    #state{net_if = Pid, net_if_mod = Mod} = State) ->
    ?vlog("received net_if verbosity request", []),
    Mod:verbosity(Pid, Verbosity),
    {reply, ok, State};

handle_call({verbosity, note_store, Verbosity}, _From, 
	    #state{note_store = Pid} = State) ->
    ?vlog("received note_store verbosity request", []),
    snmp_note_store:verbosity(Pid, Verbosity),
    {reply, ok, State};

handle_call(reconfigure, _From, State) ->
    ?vlog("received reconfigure request", []),
    Reply = {error, not_implemented},
    {reply, Reply, State};

handle_call(info, _From, State) ->
    ?vlog("received info request", []),
    Reply = get_info(State), 
    {reply, Reply, State};

handle_call(is_started, _From, State) ->
    ?vlog("received is_started request", []),
    IsStarted = is_started(State), 
    {reply, IsStarted, State};

%% handle_call({system_info_updated, Target, What}, _From, State) ->
%%     ?vlog("received system_info_updated request: "
%% 	  "~n   Target: ~p"
%% 	  "~n   What:   ~p", [Target, What]),
%%     Reply = handle_system_info_updated(State, Target, What), 
%%     {reply, Reply, State};

handle_call(get_log_type, _From, State) ->
    ?vlog("received get_log_type request", []),
    Reply = handle_get_log_type(State), 
    {reply, Reply, State};

handle_call({set_log_type, NewType}, _From, State) ->
    ?vlog("received set_log_type request: "
	  "~n   NewType: ~p", [NewType]),
    Reply = handle_set_log_type(State, NewType), 
    {reply, Reply, State};

handle_call(stop, _From, State) ->
    ?vlog("received stop request", []),
    {stop, normal, ok, State};


handle_call(Req, _From, State) ->
    warning_msg("received unknown request: ~n~p", [Req]),
    {reply, {error, unknown_request}, State}.


handle_cast(Msg, State) ->
    warning_msg("received unknown message: ~n~p", [Msg]),
    {noreply, State}.


handle_info({sync_timeout, ReqId, From}, State) ->
    ?vlog("received sync_timeout [~w] message", [ReqId]),
    handle_sync_timeout(ReqId, From, State),
    {noreply, State};


handle_info({snmp_error, Pdu, Reason}, State) ->
    ?vlog("received snmp_error message", []),
    handle_snmp_error(Pdu, Reason, State),
    {noreply, State};

handle_info({snmp_error, Reason, Addr, Port}, State) ->
    ?vlog("received snmp_error message", []),
    handle_snmp_error(Addr, Port, -1, Reason, State),
    {noreply, State};

handle_info({snmp_error, ReqId, Reason, Addr, Port}, State) ->
    ?vlog("received snmp_error message", []),
    handle_snmp_error(Addr, Port, ReqId, Reason, State),
    {noreply, State};

%% handle_info({snmp_error, ReqId, Pdu, Reason, Addr, Port}, State) ->
%%     ?vlog("received snmp_error message", []),
%%     handle_snmp_error(Pdu, ReqId, Reason, Addr, Port, State),
%%     {noreply, State};


handle_info({snmp_pdu, Pdu, Addr, Port}, State) ->
    ?vlog("received snmp_pdu message", []),
    handle_snmp_pdu(Pdu, Addr, Port, State),
    {noreply, State};


handle_info({snmp_trap, Trap, Addr, Port}, State) ->
    ?vlog("received snmp_trap message", []),
    handle_snmp_trap(Trap, Addr, Port, State),
    {noreply, State};


handle_info({snmp_inform, Ref, Pdu, Addr, Port}, State) ->
    ?vlog("received snmp_inform message", []),
    handle_snmp_inform(Ref, Pdu, Addr, Port, State),
    {noreply, State};


handle_info({snmp_report, {ok, Pdu}, Addr, Port}, State) ->
    handle_snmp_report(Pdu, Addr, Port, State),
    {noreply, State};

handle_info({snmp_report, {error, ReqId, Info, Pdu}, Addr, Port}, State) ->
    handle_snmp_report(ReqId, Pdu, Info, Addr, Port, State),
    {noreply, State};


handle_info(gc_timeout, #state{gct = GCT} = State) ->
    ?vlog("received gc_timeout message", []),
    handle_gc(GCT),
    {noreply, State};


handle_info({'DOWN', _MonRef, process, Pid, _Reason}, 
	    #state{note_store = NoteStore, 
		   net_if     = Pid} = State) ->
    ?vlog("received 'DOWN' message regarding net_if", []),
    {NetIf, _, Ref} = do_init_net_if(NoteStore),
    {noreply, State#state{net_if = NetIf, net_if_ref = Ref}};


handle_info({'DOWN', _MonRef, process, Pid, _Reason}, 
	    #state{note_store = Pid, 
		   net_if     = NetIf,
		   net_if_mod = Mod} = State) ->
    ?vlog("received 'DOWN' message regarding note_store", []),
    {ok, Prio} = snmpm_config:system_info(prio),
    {NoteStore, Ref} = do_init_note_store(Prio),
    Mod:note_store(NetIf, NoteStore),
    {noreply, State#state{note_store = NoteStore, note_store_ref = Ref}};


handle_info({'DOWN', MonRef, process, Pid, Reason}, State) ->
    ?vlog("received 'DOWN' message (~w) from ~w "
	  "~n   Reason: ~p", [MonRef, Pid, Reason]),
    handle_down(MonRef),
    {noreply, State};


handle_info({'EXIT', Pid, Reason}, #state{gct = Pid} = State) ->
    ?vlog("received 'EXIT' message from the GCT (~w) process: "
	  "~n   ~p", [Pid, Reason]),
    {ok, Timeout} = snmpm_config:system_info(server_timeout),
    {ok, GCT} = gct_start(Timeout),
    {noreply, State#state{gct = GCT}};


handle_info(Info, State) ->
    warning_msg("received unknown info: ~n~p", [Info]),
    {noreply, State}.


%%----------------------------------------------------------
%% Code change
%%----------------------------------------------------------
                                                                              
% downgrade
code_change({down, _Vsn}, #state{gct = Pid} = State, _Extra) ->
    ?d("code_change(down) -> entry", []),
    gct_code_change(Pid),
    {ok, State};
 
% upgrade
code_change(_Vsn, #state{gct = Pid} = State0, _Extra) ->
    ?d("code_change(up) -> entry", []),
    gct_code_change(Pid),
    MiniMIB = snmpm_config:make_mini_mib(),
    State = State0#state{mini_mib = MiniMIB},
    {ok, State}.

 
%%----------------------------------------------------------
%% Terminate
%%----------------------------------------------------------
                                                                              
terminate(Reason, #state{gct = GCT}) ->
    ?vdebug("terminate: ~p",[Reason]),
    gct_stop(GCT),
    snmpm_misc_sup:stop_note_store(),
    snmpm_misc_sup:stop_net_if(),
    ok.


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

handle_sync_get(Pid, UserId, TargetName, CtxName, Oids, Timeout, ExtraInfo, 
		From, State) ->    
    ?vtrace("handle_sync_get -> entry with"
	    "~n   Pid:        ~p"
	    "~n   UserId:     ~p"
	    "~n   TargetName: ~p"
	    "~n   CtxName:    ~p"
	    "~n   Oids:       ~p"
	    "~n   Timeout:    ~p"
	    "~n   From:       ~p", 
	    [Pid, UserId, TargetName, CtxName, Oids, Timeout, From]),
    case agent_data(TargetName, CtxName) of
	{ok, RegType, Addr, Port, Vsn, MsgData} ->
	    ?vtrace("handle_sync_get -> send a ~p message", [Vsn]),
	    ReqId  = send_get_request(Oids, Vsn, MsgData, Addr, Port, 
				      ExtraInfo, State),
	    ?vdebug("handle_sync_get -> ReqId: ~p", [ReqId]),
	    Msg    = {sync_timeout, ReqId, From},
	    Ref    = erlang:send_after(Timeout, self(), Msg),
	    MonRef = erlang:monitor(process, Pid),
	    ?vtrace("handle_sync_get -> MonRef: ~p", [MonRef]),
	    Req    = #request{id       = ReqId,
			      user_id  = UserId, 
			      reg_type = RegType, 
			      target   = TargetName, 
			      addr     = Addr,
			      port     = Port,
			      type     = get, 
			      data     = MsgData, 
			      ref      = Ref, 
			      mon      = MonRef, 
			      from     = From},
	    ets:insert(snmpm_request_table, Req),
	    ok;
	Error ->
	    ?vinfo("failed retrieving agent data for get:"
		   "~n   TargetName: ~p"
		   "~n   Error:      ~p", [TargetName, Error]),
	    Error
    end.
    

handle_sync_get_next(Pid, UserId, TargetName, CtxName, Oids, Timeout, 
		     ExtraInfo, From, State) ->
    ?vtrace("handle_sync_get_next -> entry with"
	    "~n   Pid:        ~p"
	    "~n   UserId:     ~p"
	    "~n   TargetName: ~p"
	    "~n   CtxName:    ~p"
	    "~n   Oids:       ~p"
	    "~n   Timeout:    ~p"
	    "~n   From:       ~p", 
	    [Pid, UserId, TargetName, CtxName, Oids, Timeout, From]),
    case agent_data(TargetName, CtxName) of
	{ok, RegType, Addr, Port, Vsn, MsgData} ->
	    ?vtrace("handle_sync_get_next -> send a ~p message", [Vsn]),
	    ReqId  = send_get_next_request(Oids, Vsn, MsgData, 
					   Addr, Port, ExtraInfo, State),
	    ?vdebug("handle_sync_get_next -> ReqId: ~p", [ReqId]),
	    Msg    = {sync_timeout, ReqId, From},
	    Ref    = erlang:send_after(Timeout, self(), Msg),
	    MonRef = erlang:monitor(process, Pid),
	    ?vtrace("handle_sync_get_next -> MonRef: ~p", [MonRef]),
	    Req    = #request{id       = ReqId,
			      user_id  = UserId, 
			      reg_type = RegType, 
			      target   = TargetName, 
			      addr     = Addr,
			      port     = Port,
			      type     = get_next, 
			      data     = MsgData, 
			      ref      = Ref, 
			      mon      = MonRef, 
			      from     = From},
	    ets:insert(snmpm_request_table, Req),
	    ok;

	Error ->
	    ?vinfo("failed retrieving agent data for get-next:"
		   "~n   TargetName: ~p"
		   "~n   Error:      ~p", [TargetName, Error]),
	    Error
    end.


handle_sync_get_bulk(Pid, UserId, TargetName, CtxName, 
		     NonRep, MaxRep, Oids, Timeout, 
		     ExtraInfo, From, State) ->
    ?vtrace("handle_sync_get_bulk -> entry with"
	    "~n   Pid:        ~p"
	    "~n   UserId:     ~p"
	    "~n   TargetName: ~p"
	    "~n   CtxName:    ~p"
	    "~n   NonRep:     ~p"
	    "~n   MaxRep:     ~p"
	    "~n   Oids:       ~p"
	    "~n   Timeout:    ~p"
	    "~n   From:       ~p", 
	    [Pid, UserId, TargetName, CtxName, NonRep, MaxRep, Oids, 
	     Timeout, From]),
    case agent_data(TargetName, CtxName) of
	{ok, RegType, Addr, Port, Vsn, MsgData} ->
	    ?vtrace("handle_sync_get_bulk -> send a ~p message", [Vsn]),
	    ReqId  = send_get_bulk_request(Oids, Vsn, MsgData, Addr, Port, 
					   NonRep, MaxRep, ExtraInfo, State),
	    ?vdebug("handle_sync_get_bulk -> ReqId: ~p", [ReqId]),
	    Msg    = {sync_timeout, ReqId, From},
	    Ref    = erlang:send_after(Timeout, self(), Msg),
	    MonRef = erlang:monitor(process, Pid),
	    ?vtrace("handle_sync_get_bulk -> MonRef: ~p", [MonRef]),
	    Req    = #request{id       = ReqId,
			      user_id  = UserId, 
			      reg_type = RegType, 
			      target   = TargetName, 
			      addr     = Addr,
			      port     = Port,
			      type     = get_bulk, 
			      data     = MsgData, 
			      ref      = Ref, 
			      mon      = MonRef, 
			      from     = From},
	    ets:insert(snmpm_request_table, Req),
	    ok;

	Error ->
	    ?vinfo("failed retrieving agent data for get-bulk:"
		   "~n   TargetName: ~p"
		   "~n   Error:      ~p", [TargetName, Error]),
	    Error
    end.


handle_sync_set(Pid, UserId, TargetName, CtxName, VarsAndVals, Timeout, 
		ExtraInfo, From, State) ->
    ?vtrace("handle_sync_set -> entry with"
	    "~n   Pid:         ~p"
	    "~n   UserId:      ~p"
	    "~n   TargetName:  ~p"
	    "~n   CtxName:     ~p"
	    "~n   VarsAndVals: ~p"
	    "~n   Timeout:     ~p"
	    "~n   From:        ~p", 
	    [Pid, UserId, TargetName, CtxName, VarsAndVals, Timeout, From]),
    case agent_data(TargetName, CtxName) of
	{ok, RegType, Addr, Port, Vsn, MsgData} ->
	    ?vtrace("handle_sync_set -> send a ~p message", [Vsn]),
	    ReqId  = send_set_request(VarsAndVals, Vsn, MsgData, 
				      Addr, Port, ExtraInfo, State),
	    ?vdebug("handle_sync_set -> ReqId: ~p", [ReqId]),
	    Msg    = {sync_timeout, ReqId, From},
	    Ref    = erlang:send_after(Timeout, self(), Msg),
            MonRef = erlang:monitor(process, Pid),
	    ?vtrace("handle_sync_set -> MonRef: ~p", [MonRef]),
	    Req    = #request{id       = ReqId,
			      user_id  = UserId, 
			      reg_type = RegType, 
			      target   = TargetName, 
			      addr     = Addr,
			      port     = Port,
			      type     = set, 
			      data     = MsgData, 
			      ref      = Ref, 
			      mon      = MonRef, 
			      from     = From},
	    ets:insert(snmpm_request_table, Req),
	    ok;

	Error ->
	    ?vinfo("failed retrieving agent data for set:"
		   "~n   TargetName: ~p"
		   "~n   Error:      ~p", [TargetName, Error]),
	    Error
    end.

 
handle_async_get(Pid, UserId, TargetName, CtxName, Oids, Expire, ExtraInfo, 
		 State) ->
    ?vtrace("handle_async_get -> entry with"
	    "~n   Pid:        ~p"
	    "~n   UserId:     ~p"
	    "~n   TargetName: ~p"
	    "~n   CtxName:    ~p"
	    "~n   Oids:       ~p"
	    "~n   Expire:     ~p",
	    [Pid, UserId, TargetName, CtxName, Oids, Expire]),
    case agent_data(TargetName, CtxName) of
	{ok, RegType, Addr, Port, Vsn, MsgData} ->
	    ?vtrace("handle_async_get -> send a ~p message", [Vsn]),
	    ReqId  = send_get_request(Oids, Vsn, MsgData, Addr, Port, 
				      ExtraInfo, State),
	    ?vdebug("handle_async_get -> ReqId: ~p", [ReqId]),
	    Req    = #request{id       = ReqId,
			      user_id  = UserId, 
			      reg_type = RegType, 
			      target   = TargetName, 
			      addr     = Addr,
			      port     = Port,
			      type     = get, 
			      data     = MsgData, 
			      expire   = t() + Expire},

	    ets:insert(snmpm_request_table, Req),
	    gct_activate(State#state.gct),
	    {ok, ReqId};

	Error ->
	    ?vinfo("failed retrieving agent data for get:"
		   "~n   TargetName: ~p"
		   "~n   Error:      ~p", [TargetName, Error]),
	    Error
    end.


handle_async_get_next(Pid, UserId, TargetName, CtxName, Oids, Expire, 
		      ExtraInfo, State) ->
    ?vtrace("handle_async_get_next -> entry with"
	    "~n   Pid:        ~p"
	    "~n   UserId:     ~p"
	    "~n   TargetName: ~p"
	    "~n   CtxName:    ~p"
	    "~n   Oids:       ~p"
	    "~n   Expire:     ~p",
	    [Pid, UserId, TargetName, CtxName, Oids, Expire]),
    case agent_data(TargetName, CtxName) of
	{ok, RegType, Addr, Port, Vsn, MsgData} ->
	    ?vtrace("handle_async_get_next -> send a ~p message", [Vsn]),
	    ReqId  = send_get_next_request(Oids, Vsn, MsgData, 
					   Addr, Port, ExtraInfo, State),
	    ?vdebug("handle_async_get_next -> ReqId: ~p", [ReqId]),
	    Req    = #request{id       = ReqId,
			      user_id  = UserId, 
			      reg_type = RegType, 
			      target   = TargetName, 
			      addr     = Addr,
			      port     = Port,
			      type     = get_next, 
			      data     = MsgData, 
			      expire   = t() + Expire},

	    ets:insert(snmpm_request_table, Req),
	    gct_activate(State#state.gct),
	    {ok, ReqId};

	Error ->
	    ?vinfo("failed retrieving agent data for get-next:"
		   "~n   TargetName: ~p"
		   "~n   Error:      ~p", [TargetName, Error]),
	    Error
    end.


handle_async_get_bulk(Pid, UserId, TargetName, CtxName, 
		      NonRep, MaxRep, Oids, Expire, 
		      ExtraInfo, State) ->
    ?vtrace("handle_async_get_bulk -> entry with"
	    "~n   Pid:        ~p"
	    "~n   UserId:     ~p"
	    "~n   TargetName: ~p"
	    "~n   CtxName:    ~p"
	    "~n   NonRep:     ~p"
	    "~n   MaxRep:     ~p"
	    "~n   Oids:       ~p"
	    "~n   Expire:     ~p", 
	    [Pid, UserId, TargetName, CtxName, NonRep, MaxRep, Oids, Expire]),
    case agent_data(TargetName, CtxName) of
	{ok, RegType, Addr, Port, Vsn, MsgData} ->
	    ?vtrace("handle_async_get_bulk -> send a ~p message", [Vsn]),
	    ReqId  = send_get_bulk_request(Oids, Vsn, MsgData, Addr, Port, 
					   NonRep, MaxRep, ExtraInfo, State),
	    ?vdebug("handle_async_get_bulk -> ReqId: ~p", [ReqId]),
	    Req    = #request{id       = ReqId,
			      user_id  = UserId, 
			      reg_type = RegType, 
			      target   = TargetName, 
			      addr     = Addr,
			      port     = Port,
			      type     = get_bulk, 
			      data     = MsgData, 
			      expire   = t() + Expire},
	    ets:insert(snmpm_request_table, Req),
	    gct_activate(State#state.gct),
	    {ok, ReqId};

	Error ->
	    ?vinfo("failed retrieving agent data for get-bulk:"
		   "~n   TargetName: ~p"
		   "~n   Error:      ~p", [TargetName, Error]),
	    Error
    end.


handle_async_set(Pid, UserId, TargetName, CtxName, VarsAndVals, Expire, 
		 ExtraInfo, State) ->
    ?vtrace("handle_async_set -> entry with"
	    "~n   Pid:         ~p"
	    "~n   UserId:      ~p"
	    "~n   TargetName:  ~p"
	    "~n   CtxName:     ~p"
	    "~n   VarsAndVals: ~p"
	    "~n   Expire:      ~p",
	    [Pid, UserId, TargetName, CtxName, VarsAndVals, Expire]),
    case agent_data(TargetName, CtxName) of
	{ok, RegType, Addr, Port, Vsn, MsgData} ->
	    ?vtrace("handle_async_set -> send a ~p message", [Vsn]),
	    ReqId  = send_set_request(VarsAndVals, Vsn, MsgData, 
				      Addr, Port, ExtraInfo, State),
	    ?vdebug("handle_async_set -> ReqId: ~p", [ReqId]),
	    Req    = #request{id       = ReqId,
			      user_id  = UserId, 
			      reg_type = RegType, 
			      target   = TargetName, 
			      addr     = Addr,
			      port     = Port,
			      type     = set, 
			      data     = MsgData, 
			      expire   = t() + Expire},

	    ets:insert(snmpm_request_table, Req),
	    gct_activate(State#state.gct),
	    {ok, ReqId};

	Error ->
	    ?vinfo("failed retrieving agent data for set:"
		   "~n   TargetName: ~p"
		   "~n   Error:      ~p", [TargetName, Error]),
	    Error
    end.


handle_cancel_async_request(UserId, ReqId, _State) ->
    ?vtrace("handle_cancel_async_request -> entry with"
	    "~n   UserId: ~p"
	    "~n   ReqId:  ~p", [UserId, ReqId]),
    case ets:lookup(snmpm_request_table, ReqId) of
	[#request{user_id = UserId,
		  ref     = Ref}] ->
	    ?vdebug("handle_cancel_async_request -> demonitor and cancel timer"
		    "~n   Ref: ~p", [Ref]),
	    cancel_timer(Ref),
	    ets:delete(snmpm_request_table, ReqId),
	    ok;
	
	[#request{user_id = OtherUserId}] ->
	    ?vinfo("handle_cancel_async_request -> Not request owner"
		    "~n   OtherUserId: ~p", [OtherUserId]),
	    {error, {not_owner, OtherUserId}};
	
	[] ->
	    ?vlog("handle_cancel_async_request -> not found", []),
	    {error, not_found}
    end.
    

%% handle_system_info_updated(#state{net_if = Pid, net_if_mod = Mod} = _State,
%% 			   net_if = _Target, What) ->
%%     case (catch Mod:system_info_updated(Pid, What)) of
%% 	{'EXIT', _} ->
%% 	    {error, not_supported};
%% 	Else ->
%% 	    Else
%%     end;
%% handle_system_info_updated(_State, Target, What) ->
%%     {error, {bad_target, Target, What}}.

handle_get_log_type(#state{net_if = Pid, net_if_mod = Mod}) ->
    case (catch Mod:get_log_type(Pid)) of
	{'EXIT', _} ->
	    {error, not_supported};
	Else ->
	    Else
    end.

handle_set_log_type(#state{net_if = Pid, net_if_mod = Mod}, NewType) ->
    case (catch Mod:set_log_type(Pid, NewType)) of
	{'EXIT', _} ->
	    {error, not_supported};
	Else ->
	    Else
    end.


%% handle_discovery(Pid, UserId, BAddr, Port, Config, Expire, ExtraInfo, State) ->
%%     ?vtrace("handle_discovery -> entry with"
%% 	    "~n   Pid:         ~p"
%% 	    "~n   UserId:      ~p"
%% 	    "~n   BAddr:       ~p"
%% 	    "~n   Port:        ~p"
%% 	    "~n   Config:      ~p"
%% 	    "~n   Expire:      ~p",
%% 	    [Pid, UserId, BAddr, Port, Config, Expire]),
%%     case agent_data(default, default, "", Config) of
%% 	{ok, Addr, Port, Vsn, MsgData} ->
%% 	    ?vtrace("handle_discovery -> send a ~p disco message", [Vsn]),
%% 	    ReqId  = send_discovery(Vsn, MsgData, BAddr, Port, ExtraInfo, 
%% 				    State),
%% 	    ?vdebug("handle_discovery -> ReqId: ~p", [ReqId]),
%% 	    MonRef = erlang:monitor(process, Pid),
%% 	    ?vtrace("handle_discovery -> MonRef: ~p", [MonRef]),
%% 	    Req    = #request{id        = ReqId,
%% 			      user_id   = UserId, 
%%			      target    = TargetName, 
%% 			      addr      = BAddr, 
%% 			      port      = Port,
%% 			      type      = get, 
%% 			      data      = MsgData, 
%% 			      mon       = MonRef,
%% 			      discovery = true, 
%% 			      expire    = t() + Expire},
%% 	    ets:insert(snmpm_request_table, Req),
%% 	    gct_activate(State#state.gct),
%% 	    {ok, ReqId};

%% 	Error ->
%% 	    ?vinfo("failed retrieving agent data for discovery (get):"
%% 		   "~n   BAddr: ~p"
%% 		   "~n   Port:  ~p"
%% 		   "~n   Error: ~p", [BAddr, Port, Error]),
%% 	    Error
%%     end.


handle_sync_timeout(ReqId, From, State) ->
    ?vtrace("handle_sync_timeout -> entry with"
	    "~n   ReqId: ~p"
	    "~n   From:  ~p", [ReqId, From]),
    case ets:lookup(snmpm_request_table, ReqId) of
	[#request{mon = MonRef, from = From} = Req0] ->
	    ?vdebug("handle_sync_timeout -> "
		    "deliver reply (timeout) and demonitor: "
		    "~n   Monref: ~p"
		    "~n   From:   ~p", [MonRef, From]),
	    gen_server:reply(From, {error, {timeout, ReqId}}),
	    maybe_demonitor(MonRef),
	    
	    %% 
	    %% Instead of deleting the request record now,
	    %% we leave it to the gc. But for that to work 
	    %% we must update the expire value (which for
	    %% sync requests is infinity).
	    %% 

	    Req = Req0#request{ref    = undefined, 
			       mon    = undefined, 
			       from   = undefined, 
			       expire = t()},
	    ets:insert(snmpm_request_table, Req),
	    gct_activate(State#state.gct),
	    ok;
	_ ->
	    ok
    end.

    
handle_snmp_error(#pdu{request_id = ReqId} = Pdu, Reason, State) ->

    ?vtrace("handle_snmp_error -> entry with"
	    "~n   Reason: ~p"
	    "~n   Pdu:    ~p", [Reason, Pdu]),

    case ets:lookup(snmpm_request_table, ReqId) of

	%% Failed async request
	[#request{user_id   = UserId, 
		  from      = undefined, 
		  ref       = undefined, 
		  mon       = MonRef,
		  discovery = Disco}] ->

	    ?vdebug("handle_snmp_error -> "
		    "found corresponding request: "
		    "~n   failed async request"
		    "~n   UserId: ~p"
		    "~n   ModRef: ~p"
		    "~n   Disco:  ~p", [UserId, MonRef, Disco]),

	    maybe_demonitor(MonRef),
	    case snmpm_config:user_info(UserId) of
		{ok, UserMod, UserData} ->
		    handle_error(UserId, UserMod, Reason, ReqId, 
				 UserData, State),
		    maybe_delete(Disco, ReqId);
		_ ->
		    %% reply to outstanding request, for which there is no
		    %% longer any owner (the user has unregistered).
		    %% Therefor send it to the default user
		    case snmpm_config:user_info() of
			{ok, DefUserId, DefMod, DefData} ->
			    handle_error(DefUserId, DefMod, Reason, ReqId, 
					 DefData, State),
			    maybe_delete(Disco, ReqId);
			_ ->
			    error_msg("failed retreiving the default user "
				      "info handling error [~w]: "
				      "~n~w", [ReqId, Reason])
		    end
	    end;


	%% Failed sync request
	%%
	[#request{ref = Ref, mon = MonRef, from = From}] -> 

	    ?vdebug("handle_snmp_error -> "
		    "found corresponding request: "
		    "~n   failed sync request"
		    "~n   Ref:    ~p"
		    "~n   ModRef: ~p"
		    "~n   From:   ~p", [Ref, MonRef, From]),

	    Remaining = 
		case (catch cancel_timer(Ref)) of
		    Rem when is_integer(Rem) ->
			Rem;
		    _ ->
			0
		end,

	    ?vtrace("handle_snmp_error -> Remaining: ~p", [Remaining]),

	    maybe_demonitor(MonRef),
	    Reply = {error, {send_failed, ReqId, Reason}},
	    ?vtrace("handle_snmp_error -> deliver (error-) reply",[]), 
	    gen_server:reply(From, Reply),
	    ets:delete(snmpm_request_table, ReqId),
	    ok;


	%% A very old reply, see if this agent is handled by
	%% a user. In that case send it there, else to the 
	%% default user.
	_ ->

	    ?vdebug("handle_snmp_error -> no request?", []), 

	    case snmpm_config:user_info() of
		{ok, DefUserId, DefMod, DefData} ->
		    handle_error(DefUserId, DefMod, Reason, 
				 ReqId, DefData, State);
		_ ->
		    error_msg("failed retreiving the default "
			      "user info handling error [~w]: "
			      "~n~w",[ReqId, Reason])
	    end
    end;

handle_snmp_error(CrapError, Reason, _State) ->
    error_msg("received crap (snmp) error =>"
	      "~n~p~n~p", [CrapError, Reason]),
    ok.

handle_snmp_error(Addr, Port, ReqId, Reason, State) ->

    ?vtrace("handle_snmp_error -> entry with"
	    "~n   Addr:   ~p"
	    "~n   Port:   ~p"
	    "~n   ReqId:  ~p"
	    "~n   Reason: ~p", [Addr, Port, ReqId, Reason]),

    case snmpm_config:get_agent_user_id(Addr, Port) of
	{ok, UserId} ->
	    case snmpm_config:user_info(UserId) of
		{ok, UserMod, UserData} ->
		    handle_error(UserId, UserMod, Reason, ReqId, 
				 UserData, State);
		_Error ->
		    case snmpm_config:user_info() of
			{ok, DefUserId, DefMod, DefData} ->
			    handle_error(DefUserId, DefMod, Reason, 
					 ReqId, DefData, State);
			_Error ->
			    error_msg("failed retreiving the default user "
				      "info handling snmp error "
				      "<~p,~p>: ~n~w~n~w",
				      [Addr, Port, ReqId, Reason])
		    end
	    end;
	_Error ->
	    case snmpm_config:user_info() of
		{ok, DefUserId, DefMod, DefData} ->
		    handle_error(DefUserId, DefMod, Reason, 
				 ReqId, DefData, State);
		_Error ->
		    error_msg("failed retreiving the default user "
			      "info handling snmp error "
			      "<~p,~p>: ~n~w~n~w",
			      [Addr, Port, ReqId, Reason])
	    end
    end.


handle_error(_UserId, Mod, Reason, ReqId, Data, _State) ->
    ?vtrace("handle_error -> entry when"
	    "~n   Mod: ~p", [Mod]),
    F = fun() -> (catch Mod:handle_error(ReqId, Reason, Data)) end,
    handle_callback(F),
    ok.


handle_snmp_pdu(#pdu{type = 'get-response', request_id = ReqId} = Pdu, 
		Addr, Port, State) ->

    ?vtrace("handle_snmp_pdu(get-response) -> entry with"
	    "~n   Addr: ~p"
	    "~n   Port: ~p"
	    "~n   Pdu:  ~p", [Addr, Port, Pdu]),

    case ets:lookup(snmpm_request_table, ReqId) of

	%% Reply to a async request or 
	%% possibly a late reply to a sync request
	%% (ref is also undefined)
	[#request{user_id   = UserId, 
		  reg_type  = RegType, 
		  target    = Target, 
		  from      = undefined, 
		  ref       = undefined, 
		  mon       = MonRef,
		  discovery = Disco}] ->

	    ?vdebug("handle_snmp_pdu(get-response) -> "
		    "found corresponding request: "
		    "~n   reply to async request or late reply to sync request"
		    "~n   UserId: ~p"
		    "~n   ModRef: ~p"
		    "~n   Disco:  ~p", [UserId, MonRef, Disco]),

	    maybe_demonitor(MonRef),
	    #pdu{error_status = EStatus, 
		 error_index  = EIndex, 
		 varbinds     = Varbinds} = Pdu,
	    Varbinds2 = fix_vbs_BITS(Varbinds), 
	    SnmpResponse = {EStatus, EIndex, Varbinds2},
	    case snmpm_config:user_info(UserId) of
		{ok, UserMod, UserData} ->
		    handle_pdu(UserId, UserMod, 
			       RegType, Target, Addr, Port, 
			       ReqId, SnmpResponse, UserData, State),
		    maybe_delete(Disco, ReqId);
		_Error ->
		    %% reply to outstanding request, for which there is no
		    %% longer any owner (the user has unregistered).
		    %% Therefor send it to the default user
		    case snmpm_config:user_info() of
			{ok, DefUserId, DefMod, DefData} ->
			    handle_pdu(DefUserId, DefMod, 
				       RegType, Target, Addr, Port, 
				       ReqId, SnmpResponse, DefData, State),
			    maybe_delete(Disco, ReqId);
			Error ->
			    error_msg("failed retreiving the default user "
				      "info handling pdu from "
				      "~p <~p,~p>: ~n~w~n~w",
				      [Target, Addr, Port, Error, Pdu])
		    end
	    end;


	%% Reply to a sync request
	%%
	[#request{ref = Ref, mon = MonRef, from = From}] -> 

	    ?vdebug("handle_snmp_pdu(get-response) -> "
		    "found corresponding request: "
		    "~n   reply to sync request"
		    "~n   Ref:    ~p"
		    "~n   ModRef: ~p"
		    "~n   From:   ~p", [Ref, MonRef, From]),

	    Remaining = 
		case (catch cancel_timer(Ref)) of
		    Rem when is_integer(Rem) ->
			Rem;
		    _ ->
			0
		end,

	    ?vtrace("handle_snmp_pdu(get-response) -> Remaining: ~p", 
		    [Remaining]),

	    maybe_demonitor(MonRef),
	    #pdu{error_status = EStatus, 
		 error_index  = EIndex, 
		 varbinds     = Varbinds} = Pdu,
	    Varbinds2 = fix_vbs_BITS(Varbinds), 
	    SnmpReply = {EStatus, EIndex, Varbinds2},
	    Reply = {ok, SnmpReply, Remaining},
	    ?vtrace("handle_snmp_pdu(get-response) -> deliver reply",[]), 
	    gen_server:reply(From, Reply),
	    ets:delete(snmpm_request_table, ReqId),
	    ok;
		

	%% A very old reply, see if this agent is handled by
	%% a user. In that case send it there, else to the 
	%% default user.
	_ ->

	    ?vdebug("handle_snmp_pdu(get-response) -> "
		    "no corresponding request: "
		    "~n   a very old reply", []),

	    #pdu{error_status = EStatus, 
		 error_index  = EIndex, 
		 varbinds     = Varbinds} = Pdu,
	    Varbinds2 = fix_vbs_BITS(Varbinds), 
	    SnmpInfo = {EStatus, EIndex, Varbinds2},
	    case snmpm_config:get_agent_user_id(Addr, Port) of
		{ok, UserId} ->
		    %% A very late reply or a reply to a request
		    %% that has been cancelled.
		    %% 
		    ?vtrace("handle_snmp_pdu(get-response) -> "
			    "a very late reply:"
			    "~n   UserId: ~p",[UserId]), 
		    case snmpm_config:user_info(UserId) of
			{ok, UserMod, UserData} ->
			    Reason = {unexpected_pdu, SnmpInfo},
			    handle_error(UserId, UserMod, Reason, ReqId, 
					 UserData, State);
			_Error ->
			    %% Ouch, found an agent but not it's user!!
			    case snmpm_config:user_info() of
				{ok, DefUserId, DefMod, DefData} ->
				    Reason = {unexpected_pdu, SnmpInfo}, 
				    handle_error(DefUserId, DefMod, Reason, 
						 ReqId, DefData, State);
				Error ->
				    error_msg("failed retreiving the default "
					      "user info handling (old) "
					      "pdu from "
					      "<~p,~p>: ~n~w~n~w",
					      [Addr, Port, Error, Pdu])
			    end
		    end;

		{error, _} -> 
		    %% No agent, so either this agent has been 
		    %% unregistered, or this is a very late reply 
		    %% to a request (possibly a discovery), which 
		    %% has since been cancelled (either because of
		    %% a timeout or that the user has unregistered 
		    %% itself (and with it all it's requests)). 
		    %% No way to know which.
		    %% 
		    ?vtrace("handle_snmp_pdu(get-response) -> "
			    "no agent info found", []),
		    case snmpm_config:user_info() of
			{ok, DefUserId, DefMod, DefData} ->
			    handle_agent(DefUserId, DefMod, 
					 Addr, Port, 
					 pdu, ignore, 
					 SnmpInfo, DefData, State);
			Error ->
			    error_msg("failed retreiving the default user "
				      "info handling (old) pdu when no user "
				      "found from "
				      "<~p,~p>: ~n~w~n~w",
				      [Addr, Port, Error, Pdu])
		    end
	    end
    end;

handle_snmp_pdu(CrapPdu, Addr, Port, _State) ->
    error_msg("received crap (snmp) Pdu from ~w:~w =>"
	      "~p", [Addr, Port, CrapPdu]),
    ok.


handle_pdu(_UserId, Mod, target_name = _RegType, TargetName, _Addr, _Port, 
	   ReqId, SnmpResponse, Data, _State) ->
    ?vtrace("handle_pdu(target_name) -> entry when"
	    "~n   Mod: ~p", [Mod]),
    F = fun() ->
		(catch Mod:handle_pdu(TargetName, ReqId, SnmpResponse, Data))
	end,
    handle_callback(F),
    ok;
handle_pdu(_UserId, Mod, addr_port = _RegType, _TargetName, Addr, Port, 
	   ReqId, SnmpResponse, Data, _State) ->
    ?vtrace("handle_pdu(addr_port) -> entry when"
	    "~n   Mod: ~p", [Mod]),
    F = fun() ->
		(catch Mod:handle_pdu(Addr, Port, ReqId, SnmpResponse, Data))
	end,
    handle_callback(F),
    ok.


handle_agent(UserId, Mod, Addr, Port, Type, Ref, SnmpInfo, Data, State) ->
    ?vtrace("handle_agent -> entry when"
	    "~n   UserId: ~p"
	    "~n   Type:   ~p"
	    "~n   Mod:    ~p", [UserId, Type, Mod]),
    F = fun() ->
		do_handle_agent(UserId, Mod, Addr, Port, 
				Type, Ref, SnmpInfo, Data, State)
	end,
    handle_callback(F),
    ok.

do_handle_agent(DefUserId, DefMod, 
		Addr, Port, 
		Type, Ref, 
		SnmpInfo, DefData, State) ->
    ?vdebug("do_handle_agent -> entry when"
	    "~n   DefUserId: ~p", [DefUserId]),
    case (catch DefMod:handle_agent(Addr, Port, Type, SnmpInfo, DefData)) of
	{'EXIT', {undef, _}} when Type =:= pdu ->
	    %% Maybe, still on the old API
	    ?vdebug("do_handle_agent -> maybe still on the old api", []),
	    case (catch DefMod:handle_agent(Addr, Port, SnmpInfo, DefData)) of
		{register, UserId2, Config} ->  
		    ?vtrace("do_handle_agent -> register: "
			    "~n   UserId2: ~p"
			    "~n   Config:  ~p", [UserId2, Config]),
		    TargetName = mk_target_name(Addr, Port, Config),
		    Config2    = [{reg_type, addr_port}, 
				  {address,  Addr}, 
				  {port,     Port} | Config], 
		    case snmpm_config:register_agent(UserId2, 
						     TargetName, Config2) of
			ok ->
			    ok;
			{error, Reason} ->
			    error_msg("failed registering agent - "
				      "handling agent "
				      "~p <~p,~p>: ~n~w", 
				      [TargetName, Addr, Port, Reason]),
			    ok
		    end;
		{register, UserId2, TargetName, Config} ->  
		    ?vtrace("do_handle_agent -> register: "
			    "~n   UserId2:    ~p"
			    "~n   TargetName: ~p"
			    "~n   Config:     ~p", 
			    [UserId2, TargetName, Config]),
		    Config2 = ensure_present([{address, Addr}, {port, Port}], 
					     Config),
		    Config3 = [{reg_type, target_name} | Config2], 
		    case snmpm_config:register_agent(UserId2, 
						     TargetName, Config3) of
			ok ->
			    ok;
			{error, Reason} ->
			    error_msg("failed registering agent - "
				      "handling agent "
				      "~p <~p,~p>: ~n~w", 
				      [TargetName, Addr, Port, Reason]),
			    ok
		    end;
		_Ignore ->
		    ?vdebug("do_handle_agent -> ignore", []),
		    ok
	    end;

	{'EXIT', {undef, _}} ->
	    %% If the user does not implement the new API (but the
	    %% old), then this clause catches all non-pdu handle_agent 
	    %% calls. These calls was previously never made,so we make 
	    %% a best-effert call (using reg-type target_name) to the 
	    %% various callback functions, and leave it to the user to
	    %% figure out

	    %% Backward compatibillity crap
	    RegType = target_name,
	    Target  = mk_target_name(Addr, Port, default_agent_config()),
	    case Type of
		report ->
		    SnmpInform = SnmpInfo, 
		    handle_report(DefUserId, DefMod, 
				  RegType, Target, Addr, Port, 
				  SnmpInform, DefData, State);

		inform ->
		    SnmpInform = SnmpInfo, 
		    handle_inform(DefUserId, DefMod, Ref, 
				  RegType, Target, Addr, Port, 
				  SnmpInform, DefData, State);

		trap ->
		    SnmpTrapInfo = SnmpInfo, 
		    handle_trap(DefUserId, DefMod, 
				RegType, Target, Addr, Port, 
				SnmpTrapInfo, DefData, State);

		_ ->
		    error_msg("failed delivering ~w info to default user - "
			      "regarding agent "
			      "<~p,~p>: ~n~w", [Type, Addr, Port, SnmpInfo])
	    end;

	{register, UserId2, TargetName, Config} ->  
	    ?vtrace("do_handle_agent -> register: "
		    "~n   UserId2:    ~p"
		    "~n   TargetName: ~p"
		    "~n   Config:     ~p", 
		    [UserId2, TargetName, Config]),
	    Config2 = ensure_present([{address, Addr}, {port, Port}], Config),
	    Config3 = [{reg_type, target_name} | Config2], 
	    case snmpm_config:register_agent(UserId2, 
					     TargetName, Config3) of
		ok ->
		    ok;
		{error, Reason} ->
		    error_msg("failed registering agent - "
			      "handling agent "
			      "~p <~p,~p>: ~n~w", 
			      [TargetName, Addr, Port, Reason]),
		    ok
	    end;
	
	_Ignore ->
	    ?vdebug("do_handle_agent -> ignore", []),
	    ok

    end.

ensure_present([], Config) ->
    Config;
ensure_present([{Key, _Val} = Elem|Ensure], Config) ->
    case lists:keymember(Key, 1, Config) of
	false ->
	    ensure_present(Ensure, [Elem|Config]);
	true ->
	    ensure_present(Ensure, Config)
    end.
    
    
%% Retrieve user info for this agent.
%% If this is an unknown agent, then use the default user
handle_snmp_trap(#trappdu{enterprise    = Enteprise, 
			  generic_trap  = Generic, 
			  specific_trap = Spec,
			  time_stamp    = Timestamp, 
			  varbinds      = Varbinds} = Trap, 
		 Addr, Port, State) ->

    ?vtrace("handle_snmp_trap [trappdu] -> entry with"
	    "~n   Addr: ~p"
	    "~n   Port: ~p"
	    "~n   Trap: ~p", [Addr, Port, Trap]),

    Varbinds2 = fix_vbs_BITS(Varbinds), 
    SnmpTrapInfo = {Enteprise, Generic, Spec, Timestamp, Varbinds2},
    do_handle_snmp_trap(SnmpTrapInfo, Addr, Port, State);

handle_snmp_trap(#pdu{error_status = EStatus, 
		      error_index  = EIndex, 
		      varbinds     = Varbinds} = Trap, 
		 Addr, Port, State) ->

    ?vtrace("handle_snmp_trap [pdu] -> entry with"
	    "~n   Addr: ~p"
	    "~n   Port: ~p"
	    "~n   Trap: ~p", [Addr, Port, Trap]),

    Varbinds2 = fix_vbs_BITS(Varbinds), 
    SnmpTrapInfo = {EStatus, EIndex, Varbinds2},
    do_handle_snmp_trap(SnmpTrapInfo, Addr, Port, State);

handle_snmp_trap(CrapTrap, Addr, Port, _State) ->
    error_msg("received crap (snmp) trap from ~w:~w =>"
	      "~p", [Addr, Port, CrapTrap]),
    ok.

do_handle_snmp_trap(SnmpTrapInfo, Addr, Port, State) ->
    case snmpm_config:get_agent_user_info(Addr, Port) of
	{ok, UserId, Target, RegType} ->
	    ?vtrace("handle_snmp_trap -> found user: ~p", [UserId]), 
	    case snmpm_config:user_info(UserId) of
		{ok, Mod, Data} ->
		    handle_trap(UserId, Mod, 
				RegType, Target, Addr, Port, 
				SnmpTrapInfo, Data, State);
		
		Error1 ->
		    %% User no longer exists, unregister agent
		    ?vlog("[trap] failed retreiving user info for "
			  "user ~p: "
			  "~n   ~p", [UserId, Error1]),
		    case snmpm_config:unregister_agent(UserId, Target) of
			ok ->
			    %% Try use the default user
			    case snmpm_config:user_info() of
				{ok, DefUserId, DefMod, DefData} ->
				    handle_agent(DefUserId, DefMod, 
						 Addr, Port, 
						 trap, ignore, 
						 SnmpTrapInfo, DefData, State);
				Error2 ->
				    error_msg("failed retreiving the default "
					      "user info handling report from "
					      "~p <~p,~p>: ~n~w~n~w",
					      [Target, Addr, Port, 
					       Error2, SnmpTrapInfo])
			    end;
			Error3 ->
			    %% Failed unregister agent, 
			    %% now its getting messy...
			    warning_msg("failed unregister agent ~p <~p,~p> "
					"belonging to non-existing "
					"user ~p, handling trap: "
					"~n   Error:     ~w"
					"~n   Trap info: ~w",
					[Target, Addr, Port, UserId, 
					 Error3, SnmpTrapInfo])
		    end
	    end;
	
	Error4 ->
	    %% Unknown agent, pass it on to the default user
	    ?vlog("[trap] failed retreiving user id for agent <~p,~p>: "
		  "~n   ~p", [Addr, Port, Error4]),
	    case snmpm_config:user_info() of
		{ok, DefUserId, DefMod, DefData} ->
		    handle_agent(DefUserId, DefMod, 
				 Addr, Port, 
				 trap, ignore, 
				 SnmpTrapInfo, DefData, State);
		Error5 ->
		    error_msg("failed retreiving "
			      "the default user info handling trap from "
			      "<~p,~p>: ~n~w~n~w",
			      [Addr, Port, Error5, SnmpTrapInfo])
	    end
    end,
    ok.


handle_trap(UserId, Mod, 
	    RegType, Target, Addr, Port, SnmpTrapInfo, Data, State) ->
    ?vtrace("handle_trap -> entry with"
	    "~n   UserId: ~p"
	    "~n   Mod:    ~p", [UserId, Mod]),
    F = fun() ->
		do_handle_trap(UserId, Mod, 
			       RegType, Target, Addr, Port, 
			       SnmpTrapInfo, Data, State)
	end,
    handle_callback(F),
    ok.
    

do_handle_trap(UserId, Mod, 
	       RegType, Target, Addr, Port, SnmpTrapInfo, Data, _State) ->
    ?vdebug("do_handle_trap -> entry with"
	    "~n   UserId: ~p", [UserId]),
    HandleTrap = 
	case RegType of
	    target_name ->
		fun() -> Mod:handle_trap(Target, SnmpTrapInfo, Data) end;
	    addr_port ->
		fun() -> Mod:handle_trap(Addr, Port, SnmpTrapInfo, Data) end
	end,

    case (catch HandleTrap()) of
	{register, UserId2, Config} -> 
	    ?vtrace("do_handle_trap -> register: "
		    "~n   UserId2: ~p"
		    "~n   Config:  ~p", [UserId2, Config]),
	    Target2 = mk_target_name(Addr, Port, Config),
	    Config2 = [{reg_type, target_name}, 
		       {address, Addr}, {port, Port} | Config], 
	    case snmpm_config:register_agent(UserId2, Target2, Config2) of
		ok ->
		    ok;
		{error, Reason} ->
		    error_msg("failed registering agent "
			      "handling trap "
			      "<~p,~p>: ~n~w", 
			      [Addr, Port, Reason]),
		    ok
	    end;
	{register, UserId2, Target2, Config} -> 
	    ?vtrace("do_handle_trap -> register: "
		    "~n   UserId2: ~p"
		    "~n   Target2: ~p"
		    "~n   Config:  ~p", [UserId2, Target2, Config]),
	    %% The only user which would do this is the
	    %% default user
	    Config2 = [{reg_type, target_name} | Config], 
	    case snmpm_config:register_agent(UserId2, Target2, Config2) of
		ok ->
		    reply;
		{error, Reason} ->
		    error_msg("failed registering agent "
			      "handling trap "
			      "~p <~p,~p>: ~n~w", 
			      [Target2, Addr, Port, Reason]),
		    reply
	    end;
	unregister ->
	    ?vtrace("do_handle_trap -> unregister", []),
	    case snmpm_config:unregister_agent(UserId, 
					       Addr, Port) of
		ok ->
		    ok;
		{error, Reason} ->
		    error_msg("failed unregistering agent "
			      "handling trap "
			      "<~p,~p>: ~n~w", 
			      [Addr, Port, Reason]),
		    ok
	    end;	    
	_Ignore ->
	    ?vtrace("do_handle_trap -> ignore", []),
	    ok
    end.


handle_snmp_inform(Ref, 
		   #pdu{error_status = EStatus, 
			error_index  = EIndex, 
			varbinds     = Varbinds} = Pdu, Addr, Port, State) ->
 
    ?vtrace("handle_snmp_inform -> entry with"
	    "~n   Addr: ~p"
	    "~n   Port: ~p"
	    "~n   Pdu:  ~p", [Addr, Port, Pdu]),

    Varbinds2 = fix_vbs_BITS(Varbinds), 
    SnmpInform = {EStatus, EIndex, Varbinds2},
    case snmpm_config:get_agent_user_info(Addr, Port) of
	{ok, UserId, Target, RegType} ->
	    case snmpm_config:user_info(UserId) of
		{ok, Mod, Data} ->
		    ?vdebug("[inform] callback handle_inform with: "
			    "~n   UserId: ~p"
			    "~n   Mod:    ~p", [UserId, Mod]),
		    handle_inform(UserId, Mod, Ref, 
				  RegType, Target, Addr, Port, 
				  SnmpInform, Data, State);
		Error1 ->
		    %% User no longer exists, unregister agent
		    case snmpm_config:unregister_agent(UserId, Target) of
			ok ->
			    %% Try use the default user
			    ?vlog("[inform] failed retreiving user "
				  "info for user ~p:"
				  "~n   ~p", [UserId, Error1]),
			    case snmpm_config:user_info() of
				{ok, DefUserId, DefMod, DefData} ->
				    handle_agent(DefUserId, DefMod, 
						 Addr, Port,
						 inform, Ref, 
						 SnmpInform, DefData, State);
				Error2 ->
				    error_msg("failed retreiving the default "
					      "user info handling inform from "
					      "~p <~p,~p>: ~n~w~n~w",
					      [Target, Addr, Port, 
					       Error2, Pdu])
			    end;
			Error3 ->
			    %% Failed unregister agent, 
			    %% now its getting messy...
			    warning_msg("failed unregister agent ~p <~p,~p> "
					"~n   belonging to non-existing "
					"user ~p, handling inform: "
					"~n   Error: ~w"
					"~n   Pdu:   ~w",
					[Target, Addr, Port, UserId, 
					 Error3, Pdu])
		    end
	    end;

	Error4 ->
	    %% Unknown agent, pass it on to the default user
	    ?vlog("[inform] failed retreiving user id for agent <~p,~p>: "
		  "~n   ~p", [Addr, Port, Error4]),
	    case snmpm_config:user_info() of
		{ok, DefUserId, DefMod, DefData} ->
		    handle_agent(DefUserId, DefMod, 
				 Addr, Port, 
				 inform, Ref, 
				 SnmpInform, DefData, State);
		Error5 ->
		    error_msg("failed retreiving "
			      "the default user info handling inform from "
			      "<~p,~p>: ~n~w~n~w",
			      [Addr, Port, Error5, Pdu])
	    end
    end,
    ok;

handle_snmp_inform(_Ref, CrapInform, Addr, Port, _State) ->
    error_msg("received crap (snmp) inform from ~w:~w =>"
	      "~p", [Addr, Port, CrapInform]),
    ok.

handle_inform(UserId, Mod, Ref, 
	      RegType, Target, Addr, Port, SnmpInform, Data, State) ->
    ?vtrace("handle_inform -> entry with"
	    "~n   UserId: ~p"
	    "~n   Mod:    ~p", [UserId, Mod]),
    F = fun() ->
		do_handle_inform(UserId, Mod, Ref, 
				 RegType, Target, Addr, Port, SnmpInform, 
				 Data, State)
	end,
    handle_callback(F),
    ok.

do_handle_inform(UserId, Mod, Ref, 
		 RegType, Target, Addr, Port, SnmpInform, Data, State) ->
    ?vdebug("do_handle_inform -> entry with"
	    "~n   UserId: ~p", [UserId]),
    HandleInform = 
	case RegType of
	    target_name ->
		fun() -> Mod:handle_inform(Target, SnmpInform, Data) end;
	    addr_port ->
		fun() -> Mod:handle_inform(Addr, Port, SnmpInform, Data) end
	end,

     Rep = 
	case (catch HandleInform()) of
	    {register, UserId2, Config} -> 
		?vtrace("do_handle_inform -> register: "
			"~n   UserId2: ~p"
			"~n   Config:  ~p", [UserId2, Config]),
		%% The only user which would do this is the
		%% default user
		Target2 = mk_target_name(Addr, Port, Config),
		Config2 = [{reg_type, target_name}, 
			   {address, Addr}, {port, Port} | Config], 
		case snmpm_config:register_agent(UserId2, Target2, Config2) of
		    ok ->
			reply;
		    {error, Reason} ->
			error_msg("failed registering agent "
				  "handling inform "
				  "~p <~p,~p>: ~n~w", 
				  [Target2, Addr, Port, Reason]),
			reply
		end;
	    {register, UserId2, Target2, Config} -> 
		?vtrace("do_handle_inform -> register: "
			"~n   UserId2: ~p"
			"~n   Target2: ~p"
			"~n   Config:  ~p", [UserId2, Target2, Config]),
		%% The only user which would do this is the
		%% default user
		Config2 = [{reg_type, target_name} | Config], 
		case snmpm_config:register_agent(UserId2, Target2, Config2) of
		    ok ->
			reply;
		    {error, Reason} ->
			error_msg("failed registering agent "
				  "handling inform "
				  "~p <~p,~p>: ~n~w", 
				  [Target2, Addr, Port, Reason]),
			reply
		end;
	    unregister ->
		?vtrace("do_handle_inform -> unregister", []),
		case snmpm_config:unregister_agent(UserId, 
						   Addr, Port) of
		    ok ->
			reply;
		    {error, Reason} ->
			error_msg("failed unregistering agent "
				  "handling inform "
				  "<~p,~p>: ~n~w", 
				  [Addr, Port, Reason]),
			reply
		end;	    
	    no_reply ->
		?vtrace("do_handle_inform -> no_reply", []),
		no_reply;
	    _Ignore ->
		?vtrace("do_handle_inform -> ignore", []),
		reply
	end,
    handle_inform_response(Rep, Ref, Addr, Port, State),
    ok.


handle_inform_response(_, ignore, _Addr, _Port, _State) ->
    ignore;
handle_inform_response(no_reply, _Ref, _Addr, _Port, _State) ->
    no_reply;
handle_inform_response(_, Ref, Addr, Port, 
		       #state{net_if = Pid, net_if_mod = Mod}) ->
    ?vdebug("handle_inform -> response", []),
    (catch Mod:inform_response(Pid, Ref, Addr, Port)).
    
handle_snmp_report(#pdu{error_status = EStatus, 
			error_index  = EIndex, 
			varbinds     = Varbinds} = Pdu, Addr, Port, State) ->

    ?vtrace("handle_snmp_report -> entry with"
	    "~n   Addr: ~p"
	    "~n   Port: ~p"
	    "~n   Pdu:  ~p", [Addr, Port, Pdu]),

    Varbinds2  = fix_vbs_BITS(Varbinds), 
    SnmpReport = {EStatus, EIndex, Varbinds2},
    case snmpm_config:get_agent_user_info(Addr, Port) of
 	{ok, UserId, Target, RegType} ->
 	    case snmpm_config:user_info(UserId) of
 		{ok, Mod, Data} ->
 		    ?vdebug("[report] callback handle_report with: "
 			    "~n   ~p"
 			    "~n   ~p"
 			    "~n   ~p"
 			    "~n   ~p", [UserId, Mod, Target, SnmpReport]),
 		    handle_report(UserId, Mod, 
				  RegType, Target, Addr, Port, 
				  SnmpReport, Data, State);
 		Error1 ->
		    %% User no longer exists, unregister agent
		    ?vlog("[report] failed retreiving user info "
			  "for user ~p:"
			  " ~n   ~p", [UserId, Error1]),
		    case snmpm_config:unregister_agent(UserId, Target) of
			ok ->
			    %% Try use the default user
			    case snmpm_config:user_info() of
				{ok, DefUserId, DefMod, DefData} ->
				    handle_agent(DefUserId, DefMod, 
						 Addr, Port, 
						 report, ignore, 
						 SnmpReport, DefData, State);
				
				Error2 ->
				    error_msg("failed retreiving the default "
					      "user info handling report from "
					      "~p <~p,~p>: ~n~w~n~w",
					      [Target, Addr, Port, 
					       Error2, Pdu])
			    end;
			Error3 ->
			    %% Failed unregister agent, 
			    %% now its getting messy...
			    warning_msg("failed unregister agent ~p <~p,~p> "
					"belonging to non-existing "
					"user ~p, handling report: "
					"~n   Error:  ~w"
					"~n   Report: ~w",
					[Target, Addr, Port, UserId, 
					 Error3, Pdu])
		    end
	    end;
		
	Error4 ->
	    %% Unknown agent, pass it on to the default user
	    ?vlog("[report] failed retreiving user id for agent <~p,~p>: "
		  "~n   ~p", [Addr, Port, Error4]),
	    case snmpm_config:user_info() of
		{ok, DefUserId, DefMod, DefData} ->
		    handle_agent(DefUserId, DefMod, 
				 Addr, Port, 
				 report, ignore, 
				 SnmpReport, DefData, State);
		Error5 ->
		    error_msg("failed retreiving "
			      "the default user info handling report from "
			      "<~p,~p>: ~n~w~n~w",
			      [Addr, Port, Error5, Pdu])
	    end
    end,
    ok;

handle_snmp_report(CrapReport, Addr, Port, _State) ->
    error_msg("received crap (snmp) report from ~w:~w =>"
	      "~p", [Addr, Port, CrapReport]),
    ok.

%% This could be from a failed get-request, so we might have a user
%% waiting for a reply here. If there is, we handle this as an failed
%% get-response (except for tha data which is different). Otherwise,
%% we handle it as an error (reported via the handle_error callback
%% function).
handle_snmp_report(ReqId, 
		   #pdu{error_status = EStatus, 
			error_index  = EIndex, 
			varbinds     = Varbinds} = Pdu, 
		   {ReportReason, Info} = Rep, 
		   Addr, Port, State) 
  when is_integer(ReqId) ->

    ?vtrace("handle_snmp_report -> entry with"
	    "~n   Addr:   ~p"
	    "~n   Port:   ~p"
	    "~n   ReqId:  ~p"
	    "~n   Rep:    ~p"
	    "~n   Pdu:    ~p", [Addr, Port, ReqId, Rep, Pdu]),

    Varbinds2 = fix_vbs_BITS(Varbinds), 
    SnmpReport = {EStatus, EIndex, Varbinds2},
    Reason     = {ReportReason, Info, SnmpReport},
    
    %% Check if there is someone waiting for this request

    case ets:lookup(snmpm_request_table, ReqId) of

	[#request{from     = From, 
		  ref      = Ref, 
		  mon      = MonRef}] when (From =/= undefined) andalso 
					   (Ref  =/= undefined) ->

	    ?vdebug("handle_snmp_report -> "
		    "found corresponding request: "
		    "~n   reply to sync request"
		    "~n   Ref:    ~p"
		    "~n   ModRef: ~p"
		    "~n   From:   ~p", [Ref, MonRef, From]),

	    Remaining = 
		case (catch cancel_timer(Ref)) of
		    Rem when is_integer(Rem) ->
			Rem;
		    _ ->
			0
		end,

	    ?vtrace("handle_snmp_pdu(get-response) -> Remaining: ~p", 
		    [Remaining]),

	    maybe_demonitor(MonRef),

	    Reply = {error, Reason},
	    ?vtrace("handle_snmp_report -> deliver reply",[]), 
	    gen_server:reply(From, Reply),
	    ets:delete(snmpm_request_table, ReqId),
	    ok;
	
	_ ->
	    %% Either not a sync request or no such request. Either
	    %% way, this is error info, so handle it as such.

	    case snmpm_config:get_agent_user_id(Addr, Port) of
		{ok, UserId} ->
		    case snmpm_config:user_info(UserId) of
			{ok, Mod, Data} ->
			    ?vdebug("[report] callback handle_error with: "
				    "~n   ~p"
				    "~n   ~p"
				    "~n   ~p", [UserId, Mod, Reason]),
			    handle_error(UserId, Mod, Reason, ReqId, 
					 Data, State);
			Error ->
			    %% Oh crap, use the default user
			    ?vlog("[report] failed retreiving user info for "
				  "user ~p:"
				  " ~n   ~p", [UserId, Error]),
			    case snmpm_config:user_info() of
				{ok, DefUserId, DefMod, DefData} ->
				    handle_error(DefUserId, DefMod, Reason, 
						 ReqId, DefData, State);
				Error ->
				    error_msg("failed retreiving the "
					      "default user "
					      "info handling report from "
					      "<~p,~p>: ~n~w~n~w~n~w",
					      [Addr, Port, Error, 
					       ReqId, Reason])
			    end
		    end;
		Error ->
		    %% Unknown agent, pass it on to the default user
		    ?vlog("[report] failed retreiving user id for "
			  "agent <~p,~p>: "
			  "~n   ~p", [Addr, Port, Error]),
		    case snmpm_config:user_info() of
			{ok, DefUserId, DefMod, DefData} ->
			    handle_error(DefUserId, DefMod, Reason, ReqId, 
					 DefData, State);
			Error ->
			    error_msg("failed retreiving "
				      "the default user info handling "
				      "report from "
				      "<~p,~p>: ~n~w~n~w~n~w",
				      [Addr, Port, Error, ReqId, Reason])
		    end
	    end
    end,
    ok;

handle_snmp_report(CrapReqId, CrapReport, CrapInfo, Addr, Port, _State) ->
    error_msg("received crap (snmp) report from ~w:~w =>"
	      "~n~p~n~p~n~p", [Addr, Port, CrapReqId, CrapReport, CrapInfo]),
    ok.
   

handle_report(UserId, Mod, RegType, Target, Addr, Port, 
	      SnmpReport, Data, State) ->
    ?vtrace("handle_report -> entry with"
	    "~n   UserId: ~p"
	    "~n   Mod:    ~p", [UserId, Mod]),
    F = fun() ->
		do_handle_report(UserId, Mod, RegType, Target, Addr, Port, 
				 SnmpReport, Data, State)
	end,
    handle_callback(F),
    ok.

do_handle_report(UserId, Mod, 
		 RegType, Target, Addr, Port, SnmpReport, Data, _State) ->
    ?vdebug("do_handle_report -> entry with"
	    "~n   UserId: ~p", [UserId]),
    HandleReport = 
	case RegType of
	    target_name ->
		fun() -> Mod:handle_report(Target, SnmpReport, Data) end;
	    addr_port ->
		fun() -> Mod:handle_report(Addr, Port, SnmpReport, Data) end
	end,

    case (catch HandleReport()) of
	{register, UserId2, Config} -> 
	    ?vtrace("do_handle_report -> register: "
		    "~n   UserId2: ~p"
		    "~n   Config:  ~p", [UserId2, Config]),
	    %% The only user which would do this is the
	    %% default user
	    Target2 = mk_target_name(Addr, Port, Config),
	    Config2 = [{reg_type, target_name}, 
		       {address, Addr}, {port, Port} | Config], 
	    case snmpm_config:register_agent(UserId2, Target2, Config2) of
		ok ->
		    ok;
		{error, Reason} ->
		    error_msg("failed registering agent "
			      "handling report "
			      "<~p,~p>: ~n~w", 
			      [Addr, Port, Reason]),
		    ok
	    end;
	{register, UserId2, Target2, Config} -> 
	    ?vtrace("do_handle_report -> register: "
		    "~n   UserId2: ~p"
		    "~n   Target2: ~p"
		    "~n   Config:  ~p", [UserId2, Target2, Config]),
	    %% The only user which would do this is the
	    %% default user
	    Config2 = [{reg_type, target_name} | Config], 
	    case snmpm_config:register_agent(UserId2, Target2, Config2) of
		ok ->
		    reply;
		{error, Reason} ->
		    error_msg("failed registering agent "
			      "handling report "
			      "~p <~p,~p>: ~n~w", 
			      [Target2, Addr, Port, Reason]),
		    reply
	    end;
	unregister ->
	    ?vtrace("do_handle_trap -> unregister", []),
	    case snmpm_config:unregister_agent(UserId, 
					       Addr, Port) of
		ok ->
		    ok;
		{error, Reason} ->
		    error_msg("failed unregistering agent "
			      "handling report "
			      "<~p,~p>: ~n~w", 
			      [Addr, Port, Reason]),
		    ok
	    end;	    
	_Ignore ->
	    ?vtrace("do_handle_report -> ignore", []),
	    ok
    end.


handle_callback(F) ->
    V = get(verbosity),
    erlang:spawn(
      fun() -> 
	      put(sname, msew), 
	      put(verbosity, V), 
	      F() 
      end).

    
handle_down(MonRef) ->
    (catch do_handle_down(MonRef)).

do_handle_down(MonRef) ->
    %% Clear out all requests from this client
    handle_down_requests_cleanup(MonRef),

    %% 
    %% Check also if this was a monitored user, and if so
    %% unregister all agents registered by this user, and
    %% finally unregister the user itself
    %% 
    handle_down_user_cleanup(MonRef),
    
    ok.


handle_down_requests_cleanup(MonRef) ->
    Pat = #request{id = '$1', ref = '$2', mon = MonRef, _ = '_'},
    Match = ets:match(snmpm_request_table, Pat),
    Fun = fun([Id, Ref]) -> 
		  ?vtrace("delete request: ~p", [Id]),
		  ets:delete(snmpm_request_table, Id),
		  cancel_timer(Ref),
		  ok
	  end,
    lists:foreach(Fun, Match).

handle_down_user_cleanup(MonRef) ->
    Pat = #monitor{id = '$1', mon = MonRef, _ = '_'},
    Match = ets:match(snmpm_monitor_table, Pat),
    Fun = fun([Id]) -> 
		  Agents = snmpm_config:which_agents(Id),
		  lists:foreach(
		    fun({Addr,Port}) -> 
			    %% ** Previous format **
			    %% Just in case this happens during code upgrade
			    ?vtrace("unregister agent of monitored user "
				    "~w: <~w,~w>", [Id,Addr,Port]),
			    snmpm_config:unregister_agent(Id, Addr, Port);
		       (TargetName) ->
			    ?vtrace("unregister agent of monitored user "
				    "~w: ~p", [Id,TargetName]),
			    snmpm_config:unregister_agent(Id, TargetName)
		    end,
		    Agents),
		  ?vtrace("unregister monitored user: ~w", [Id]),
		  ets:delete(snmpm_monitor_table, Id),
		  snmpm_config:unregister_user(Id),
		  ok
	     end,
    lists:foreach(Fun, Match).
    
cancel_timer(undefined) ->
    ok;
cancel_timer(Ref) ->
    (catch erlang:cancel_timer(Ref)).

handle_gc(GCT) ->
    ets:safe_fixtable(snmpm_request_table, true),
    case do_gc(ets:first(snmpm_request_table), t()) of
	0 ->
	    gct_deactivate(GCT);
	_ ->
	    ok
    end,
    ets:safe_fixtable(snmpm_request_table, false).



%% We are deleting at the same time as we are traversing the table!!!
do_gc('$end_of_table', _) ->
    ets:info(snmpm_request_table, size);
do_gc(Key, Now) ->
    Next = ets:next(snmpm_request_table, Key),
    case ets:lookup(snmpm_request_table, Key) of
	[#request{expire = BestBefore}] when (BestBefore < Now) ->
	    ets:delete(snmpm_request_table, Key);
	_ ->
	    ok
    end,
    do_gc(Next, Now).
	    


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

send_get_request(Oids, Vsn, MsgData, Addr, Port, ExtraInfo, 
		 #state{net_if     = NetIf, 
			net_if_mod = Mod,
			mini_mib   = MiniMIB}) ->
    Pdu = make_pdu(get, Oids, MiniMIB),
    ?vtrace("send_get_request -> send get-request:"
	    "~n   Mod:     ~p"
	    "~n   NetIf:   ~p"
	    "~n   Pdu:     ~p"
	    "~n   Vsn:     ~p"
	    "~n   MsgData: ~p"
	    "~n   Addr:    ~p"
	    "~n   Port:    ~p", [Mod, NetIf, Pdu, Vsn, MsgData, Addr, Port]),
    (catch Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo)),
    Pdu#pdu.request_id.

send_get_next_request(Oids, Vsn, MsgData, Addr, Port, ExtraInfo, 
		      #state{mini_mib   = MiniMIB, 
			     net_if     = NetIf, 
			     net_if_mod = Mod}) ->
    Pdu = make_pdu(get_next, Oids, MiniMIB),
    Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo),
    Pdu#pdu.request_id.

send_get_bulk_request(Oids, Vsn, MsgData, Addr, Port, 
		      NonRep, MaxRep, ExtraInfo, 
		      #state{mini_mib   = MiniMIB, 
			     net_if     = NetIf, 
			     net_if_mod = Mod}) ->
    Pdu = make_pdu(bulk, {NonRep, MaxRep, Oids}, MiniMIB),
    Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo),
    Pdu#pdu.request_id.

send_set_request(VarsAndVals, Vsn, MsgData, Addr, Port, ExtraInfo, 
		 #state{mini_mib   = MiniMIB,
			net_if     = NetIf, 
			net_if_mod = Mod}) ->
    Pdu = make_pdu(set, VarsAndVals, MiniMIB),
    Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo),
    Pdu#pdu.request_id.

%% send_discovery(Vsn, MsgData, Addr, Port, ExtraInfo, 
%% 	       #state{net_if     = NetIf, 
%% 		      net_if_mod = Mod}) ->
%%     Pdu = make_discovery_pdu(),
%%     Mod:send_pdu(NetIf, Pdu, Vsn, MsgData, Addr, Port, ExtraInfo),
%%     Pdu#pdu.request_id.
							  


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

%% make_discovery_pdu() ->
%%     Oids = [?sysObjectID_instance, ?sysDescr_instance, ?sysUpTime_instance],
%%     make_pdu_impl(get, Oids).

make_pdu(set, VarsAndVals, MiniMIB) ->
    VBs = [var_and_value_to_varbind(VAV, MiniMIB) || VAV <- VarsAndVals],
    make_pdu_impl(set, VBs);

make_pdu(bulk, {NonRepeaters, MaxRepetitions, Oids}, MiniMIB) ->
    Foids = [flatten_oid(Oid, MiniMIB) || Oid <- Oids],
    #pdu{type         = 'get-bulk-request', 
	 request_id   = request_id(),
	 error_status = NonRepeaters, 
	 error_index  = MaxRepetitions,
	 varbinds     = [make_vb(Foid) || Foid <- Foids]};

make_pdu(Op, Oids, MiniMIB) ->
    Foids = [flatten_oid(Oid, MiniMIB) || Oid <- Oids],
    make_pdu_impl(Op, Foids).


make_pdu_impl(get, Oids) ->
    #pdu{type         = 'get-request',
	 request_id   = request_id(),
	 error_status = noError, 
	 error_index  = 0,
	 varbinds     = [make_vb(Oid) || Oid <- Oids]};

make_pdu_impl(get_next, Oids) ->
    #pdu{type         = 'get-next-request', 
	 request_id   = request_id(), 
	 error_status = noError, 
	 error_index  = 0,
	 varbinds     = [make_vb(Oid) || Oid <- Oids]};

make_pdu_impl(set, Varbinds) ->
    #pdu{type         = 'set-request', 
	 request_id   = request_id(),
	 error_status = noError, 
	 error_index  = 0, 
	 varbinds     = Varbinds}.


fix_vbs_BITS(Varbinds) ->
    [fix_vb_BITS(Varbind) || Varbind <- Varbinds].

fix_vb_BITS(#varbind{oid          = Oid,
		     variabletype = 'OCTET STRING' = _Type,
		     value        = Value} = Varbind) ->
    %% BITS are encoded as OCTET STRING, so this could be a BITS
    %% check with the MiniMIB
    case type_of_oid(Oid) of
	{error, _} ->
	    Varbind;
	{ok, NewType = 'BITS'} ->
	    NewValue = snmp_pdus:octet_str_to_bits(Value),
	    Varbind#varbind{variabletype = NewType,
			    value        = NewValue};
	_ ->
	    Varbind
    end;
fix_vb_BITS(Vb) ->
    Vb.

type_of_oid(Oid) ->
    Oid2 = case lists:reverse(Oid) of
	       [0|T] ->
		   lists:reverse(T);
	       _ ->
		   Oid
	   end,
    snmpm_config:oid_to_type(Oid2).


%%----------------------------------------------------------------------
%% Purpose: Unnesting of oids like [myTable, 3, 4, "hej", 45] to
%%          [1,2,3,3,4,104,101,106,45]
%%----------------------------------------------------------------------

flatten_oid([A|T], MiniMIB) when is_atom(A) ->
    Oid = [alias2oid(A, MiniMIB)|T],
    check_is_pure_oid(lists:flatten(Oid));
flatten_oid(Oid, _) when is_list(Oid) ->
    check_is_pure_oid(lists:flatten(Oid));
flatten_oid(Shit, _) ->
    throw({error, {invalid_oid, Shit}}).
	       
check_is_pure_oid([]) -> [];
check_is_pure_oid([X | T]) when is_integer(X) andalso (X >= 0) ->
    [X | check_is_pure_oid(T)];
check_is_pure_oid([X | _T]) ->
    throw({error, {invalid_oid, X}}).


var_and_value_to_varbind({Oid, Type, Value}, MiniMIB) ->
    Oid2 = flatten_oid(Oid, MiniMIB), 
    #varbind{oid          = Oid2, 
	     variabletype = char_to_type(Type), 
	     value        = Value};
var_and_value_to_varbind({Oid, Value}, MiniMIB) ->
    Oid2 = flatten_oid(Oid, MiniMIB), 
    #varbind{oid          = Oid2, 
	     variabletype = oid2type(Oid2, MiniMIB),
	     value        = Value}.

char_to_type(i) ->
    'INTEGER';
char_to_type(u) ->
    'Unsigned32';
char_to_type(g) -> % Gauge, Gauge32
    'Unsigned32';
char_to_type(b) -> 
    'BITS';
char_to_type(ip) -> 
    'IpAddress';
char_to_type(ia) -> 
    'IpAddress';
char_to_type(op) -> 
    'Opaque';
char_to_type(c32) -> 
    'Counter32';
char_to_type(c64) -> 
    'Counter64';
char_to_type(tt) -> 
    'TimeTicks';
char_to_type(o) ->
    'OBJECT IDENTIFIER';
char_to_type(s) ->
    'OCTET STRING';
char_to_type(C) ->
    throw({error, {invalid_value_type, C}}).


alias2oid(AliasName, MiniMIB) when is_atom(AliasName) ->
    case lists:keysearch(AliasName, 2, MiniMIB) of
	{value, {Oid, _Aliasname, _Type}} -> 
 	    Oid;
	false -> 
 	    throw({error, {unknown_aliasname, AliasName}})
    end.

oid2type(Oid, MiniMIB) ->
    Oid2 = case lists:reverse(Oid) of
	       [0|T] ->
		   lists:reverse(T);
	       _ ->
		   Oid
	   end,
    oid2type(Oid2, MiniMIB, utter_nonsense).

oid2type(_Oid, [], utter_nonsense) ->
    throw({error, no_type});
oid2type(_Oid, [], Type) ->
    Type;
oid2type(Oid, [{Oid2, _, Type}|MiniMIB], Res) when (Oid2 =< Oid) ->
    case lists:prefix(Oid2, Oid) of
        true ->
            oid2type(Oid, MiniMIB, Type); % A better guess
        false ->
            oid2type(Oid, MiniMIB, Res)
    end;
oid2type(_Oid, _MiniMIB, utter_nonsense) ->
    throw({error, no_type});
oid2type(_Oid, _MiniMIB, Type) ->
    Type.
    

make_vb(Oid) ->
    #varbind{oid = Oid, variabletype = 'NULL', value = 'NULL'}.


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

request_id() ->
    snmpm_mpd:next_req_id().


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

agent_data(TargetName, CtxName) ->
    agent_data(TargetName, CtxName, []).

agent_data(TargetName, CtxName, Config) ->
    case snmpm_config:agent_info(TargetName, all) of
	{ok, Info} ->
	    {value, {_, Version}} = lists:keysearch(version, 1, Info),
	    MsgData = 
		case Version of
		    v3 ->
			DefSecModel = agent_data_item(sec_model, Info),
			DefSecName  = agent_data_item(sec_name,  Info),
			DefSecLevel = agent_data_item(sec_level, Info),
				   
			EngineId    = agent_data_item(engine_id, Info),

			SecModel    = agent_data_item(sec_model,   
						      Config, 
						      DefSecModel),
			SecName     = agent_data_item(sec_name,    
						      Config, 
						      DefSecName),
			SecLevel    = agent_data_item(sec_level,   
						      Config, 
						      DefSecLevel),
			
			{SecModel, SecName, mk_sec_level_flag(SecLevel), 
			 EngineId, CtxName, TargetName};
		    _ ->
			DefComm     = agent_data_item(community, Info),
			DefSecModel = agent_data_item(sec_model, Info),

			Comm        = agent_data_item(community, 
						      Config, 
						      DefComm),
			SecModel    = agent_data_item(sec_model, 
						      Config, 
						      DefSecModel),
			
			{Comm, SecModel}
		end,
	    Addr    = agent_data_item(address,  Info),
	    Port    = agent_data_item(port,     Info),
	    RegType = agent_data_item(reg_type, Info),
	    {ok, RegType, Addr, Port, version(Version), MsgData};
	Error ->
	    Error
    end.

agent_data_item(Item, Info) ->
    {value, {_, Val}} = lists:keysearch(Item, 1, Info),
    Val.

agent_data_item(Item, Info, Default) ->
    case lists:keysearch(Item, 1, Info) of
	{value, {_, Val}} ->
	    Val;
	false ->
	    Default
    end.


version(v1) ->
    'version-1';
version(v2) ->
    'version-2';
version(v3) ->
    'version-3'.


%%-----------------------------------------------------------------
%% Convert the SecurityLevel into a flag value used by snmpm_mpd
%%-----------------------------------------------------------------
mk_sec_level_flag(?'SnmpSecurityLevel_noAuthNoPriv') -> 0;
mk_sec_level_flag(?'SnmpSecurityLevel_authNoPriv') -> 1;
mk_sec_level_flag(?'SnmpSecurityLevel_authPriv') -> 3.


%%----------------------------------------------------------------------
%% Request Garbage Collector timer
%%----------------------------------------------------------------------

gct_start(Timeout) ->
    ?vdebug("start gc timer process (~p)", [Timeout]),    
    State = #gct{parent = self(), timeout = Timeout},
    proc_lib:start_link(?MODULE, gct_init, [State]).

gct_stop(GCT) ->
    GCT ! {stop, self()}.

gct_activate(GCT) ->
    GCT ! {activate, self()}.

gct_deactivate(GCT) ->
    GCT ! {deactivate, self()}.

gct_code_change(GCT) ->
    GCT ! {code_change, self()}.

gct_init(#gct{parent = Parent, timeout = Timeout} = State) ->
    proc_lib:init_ack(Parent, {ok, self()}),
    gct(State, Timeout).

gct(#gct{parent = Parent, state = active} = State, Timeout) ->
    T = t(),
    receive
	{stop, Parent} ->
	    ok;

	%% This happens when a new request is received.
	{activate, Parent}  ->
	    ?MODULE:gct(State, new_timeout(Timeout, T)); 

	{deactivate, Parent} ->
	    %% Timeout is of no consequence in the idle state, 
	    %% but just to be sure
	    NewTimeout = State#gct.timeout,
	    ?MODULE:gct(State#gct{state = idle}, NewTimeout);

	{code_change, Parent} ->
	    %% Let the server take care of this
	    exit(normal);

	{'EXIT', Parent, _Reason} ->
	    ok;

	_ -> % Crap
	    ?MODULE:gct(State, Timeout)

    after Timeout ->
	    Parent ! gc_timeout,
	    NewTimeout = State#gct.timeout,
	    ?MODULE:gct(State, NewTimeout)
    end;

gct(#gct{parent = Parent, state = idle} = State, Timeout) ->
    receive
	{stop, Parent} ->
	    ok;

	{deactivate, Parent} ->
	    ?MODULE:gct(State, Timeout);

	{activate, Parent} ->
	    NewTimeout = State#gct.timeout,
	    ?MODULE:gct(State#gct{state = active}, NewTimeout);

	{code_change, Parent} ->
	    %% Let the server take care of this
	    exit(normal);

	{'EXIT', Parent, _Reason} ->
	    ok;

	_ -> % Crap
	    ?MODULE:gct(State, Timeout)

    after Timeout ->
	    ?MODULE:gct(State, Timeout)
    end.

new_timeout(T1, T2) ->
    case T1 - (t() - T2) of
	T when (T > 0) ->
	    T;
	_ ->
	    0
    end.


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

maybe_delete(false, ReqId) ->
    ets:delete(snmpm_request_table, ReqId);
maybe_delete(true, _) ->
    ok.
    
maybe_demonitor(undefined) ->
    ok;
maybe_demonitor(MonRef) ->
    erlang:demonitor(MonRef).

%% Time in milli seconds
t() ->
    {A,B,C} = erlang:now(),
    A*1000000000+B*1000+(C div 1000).
    
mk_target_name(Addr, Port, Config) ->
    snmpm_config:mk_target_name(Addr, Port, Config).

default_agent_config() ->
    case snmpm_config:agent_info() of
	{ok, Config} ->
	    Config;
	_ ->
	    []
    end.


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

is_started(#state{net_if = _Pid, net_if_mod = _Mod}) ->
    %% Mod:is_started(Pid) and snmpm_config:is_started().
    case snmpm_config:is_started() of
	true ->
	    true;
	_ ->
	    false
    end.


%%----------------------------------------------------------------------
	
call(Req) ->
    call(Req, infinity).

call(Req, To) ->
    gen_server:call(?SERVER, Req, To).

%% cast(Msg) ->
%%     gen_server:cast(?SERVER, Msg).

%% info_msg(F, A) ->
%%     ?snmpm_info("Server: " ++ F, A).

warning_msg(F, A) ->
    ?snmpm_warning("Server: " ++ F, A).

error_msg(F, A) ->
    ?snmpm_error("Server: " ++ F, A).
 

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

get_info(#state{gct = GCT, 
		net_if = NI, net_if_mod = NIMod, 
		note_store = NS}) ->
    Info = [{server,         server_info(GCT)},
	    {config,         config_info()},
	    {net_if,         net_if_info(NI, NIMod)},
	    {note_store,     note_store_info(NS)},
	    {stats_counters, get_stats_counters()}],
    Info.

server_info(GCT) ->
    ProcSize = proc_mem(self()),
    GCTSz    = proc_mem(GCT),
    RTSz     = tab_size(snmpm_request_table),
    MTSz     = tab_size(snmpm_monitor_table),
    [{process_memory, [{server, ProcSize}, {gct, GCTSz}]},
     {db_memory, [{request, RTSz}, {monitor, MTSz}]}].

proc_mem(P) when is_pid(P) ->
    case (catch erlang:process_info(P, memory)) of
	{memory, Sz} when is_integer(Sz) ->
	    Sz;
	_ ->
	    undefined
    end;
proc_mem(_) ->
    undefined.

tab_size(T) ->
    case (catch ets:info(T, memory)) of
	Sz when is_integer(Sz) ->
	    Sz;
	_ ->
	    undefined
    end.

config_info() ->
    case (catch snmpm_config:info()) of
	Info when is_list(Info) ->
	    Info;
	E ->
	    [{error, E}]
    end.

get_stats_counters() ->
    lists:sort(snmpm_config:get_stats_counters()).
    

net_if_info(Pid, Mod) ->
    case (catch Mod:info(Pid)) of
	Info when is_list(Info) ->
	    Info;
	E ->
	    [{error, E}]
    end.

note_store_info(Pid) ->
    case (catch snmp_note_store:info(Pid)) of
	Info when is_list(Info) ->
	    Info;
	E ->
	    [{error, E}]
    end.


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


%%----------------------------------------------------------------------
%% Debug
%%----------------------------------------------------------------------

% sz(L) when is_list(L) ->
%     length(lists:flatten(L));
% sz(B) when is_binary(B) ->
%     size(B).

%% p(F) ->
%%     p(F, []).

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

