%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2013-2013. 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(snmpa_mib_storage_mnesia).


-behaviour(snmpa_mib_storage).

%%%-----------------------------------------------------------------
%%% This module implements the snmpa_mib_storage behaviour. 
%%% It uses mnesia for storage. 
%%%-----------------------------------------------------------------

-export([
	 open/5, 
	 close/1, 
	 read/2, 
	 write/2, 
	 delete/1, 
	 delete/2, 
	 match_object/2, 
	 match_delete/2, 
	 tab2list/1, 
	 info/1, info/2, 
	 sync/1, 
	 backup/2
	]).


-define(VMODULE,"MS-MNESIA").
-include("snmp_verbosity.hrl").

-record(tab, {id}).


%% ---------------------------------------------------------------
%% open
%% 
%% Open or create a mnesia table. 
%% 
%% Opts    - A list of implementation dependent options
%%           mnesia_open_options() = [mnesia_open_option()]
%%           mnesia_open_option()  = {action, keep | clear} | 
%%                                   {nodes,  [node()]}
%% 
%% ---------------------------------------------------------------

open(Name, RecName, Fields, Type, Opts) ->
    ?vtrace("open ~p table ~p for record ~p",
	    [Type, Name, RecName]),
    Action  = get_action(Opts), 
    Nodes   = get_nodes(Opts), 
    case table_exists(Name) of
	true when (Action =:= keep) -> 
	    ?vtrace("open table ~p - exist (keep)", [Name]),
	    {ok, #tab{id = Name}};
	true when (Action =:= clear) -> 
	    ?vtrace("open table ~p - exist (clear)", [Name]),
	    F = fun() -> mnesia:clear_table(Name) end,
	    case mnesia:transaction(F) of
		{aborted, Reason} ->
		    {error, {clear, Reason}};
		{atomic, _} ->
		    {ok, #tab{id = Name}}
	    end;
	false ->
	    ?vtrace("open table ~p - does not exist", [Name]),
	    Args = [{record_name, RecName}, 
		    {attributes,  Fields},
		    {type,        Type}, 
		    {disc_copies, Nodes}],
	    case mnesia:create_table(Name, Args) of
		{atomic, ok} ->
		    ?vtrace("open table ~p - ok", [Name]),
		    {ok, #tab{id = Name}};
		{aborted, Reason} ->
		    ?vinfo("open table ~p - aborted"
			   "~n   Reason: ~p", [Name, Reason]),
		    {error, {create, Reason}}
	    end
    end.

table_exists(Name) ->
    case (catch mnesia:table_info(Name, type)) of
	{'EXIT', _Reason} ->
	    false;
	_ ->
	    true
    end.
   

%% ---------------------------------------------------------------
%% close
%% 
%% Close the mib-storage table. 
%% This does nothing in the mnesia case.
%% ---------------------------------------------------------------

close(_) ->
    ?vtrace("close mib-storage - ignore",[]),
    ok.


%% ---------------------------------------------------------------
%% read
%% 
%% Retrieve a record from the mib-storage table.
%% ---------------------------------------------------------------

read(#tab{id = ID}, Key) ->
    ?vtrace("read (dirty) from database ~p: ~p", [ID, Key]),
    case (catch mnesia:dirty_read(ID, Key)) of
	[Rec|_] -> {value,Rec};
	_ -> false
    end.
    

%% ---------------------------------------------------------------
%% write
%% 
%% Write a record to the mib-storage table.
%% ---------------------------------------------------------------

write(#tab{id = ID}, Rec) -> 
    ?vtrace("write to database ~p", [ID]),
    F = fun() -> mnesia:write(ID, Rec, write) end,
    case mnesia:transaction(F) of
	{aborted, _Reason} = ABORTED ->
	    {error, ABORTED};
	{atomic,_} ->
	    ok
    end.


%% ---------------------------------------------------------------
%% delete
%% 
%% Delete the mib-storage table. 
%% ---------------------------------------------------------------

delete(#tab{id = ID}) ->
    ?vtrace("delete database: ~p", [ID]),
    mnesia:delete_table(ID).


%% ---------------------------------------------------------------
%% delete
%% 
%% Delete a record from the mib-storage table.
%% ---------------------------------------------------------------

delete(#tab{id = ID}, Key) -> 
    ?vtrace("delete from database ~p: ~p", [ID, Key]),
    F = fun() -> mnesia:delete(ID, Key, write) end,
    case mnesia:transaction(F) of
	{aborted, _Reason} = ABORTED ->
	    {error, ABORTED};
	{atomic, _} ->
	    ok
    end.


%% ---------------------------------------------------------------
%% match_object
%% 
%% Search the mib-storage table for records witch matches 
%% the pattern.
%% ---------------------------------------------------------------

match_object(#tab{id = ID}, Pattern) ->
    ?vtrace("match_object in ~p of ~p", [ID, Pattern]),
    F = fun() -> mnesia:match_object(ID, Pattern, read) end,
    case mnesia:transaction(F) of
	{aborted, _Reason} = ABORTED ->
	    {error, ABORTED};
	{atomic, Rs} ->
	    Rs
    end.
    

%% ---------------------------------------------------------------
%% match_delete
%% 
%% Search the mib-storage table for records witch matches 
%% the pattern and deletes them from the table.
%% ---------------------------------------------------------------

match_delete(#tab{id = ID}, Pattern) -> 
    ?vtrace("match_delete in ~p with pattern ~p", [ID, Pattern]),
    F = fun() -> 
		Recs = mnesia:match_object(ID, Pattern, read),
		lists:foreach(fun(Rec) -> 
				      mnesia:delete_object(ID, Rec, write)
			      end, Recs),
		Recs
	end,
    case mnesia:transaction(F) of
	{aborted, _Reason} = ABORTED ->
	    {error, ABORTED};
	{atomic, Rs} ->
	    Rs
    end.


%% ---------------------------------------------------------------
%% tab2list
%% 
%% Return all records in the mib-storage table in the form of 
%% a list.
%% ---------------------------------------------------------------

tab2list(#tab{id = ID} = Tab) ->
    ?vtrace("tab2list -> list of ~p", [ID]),
    match_object(Tab, mnesia:table_info(ID, wild_pattern)).


%% ---------------------------------------------------------------
%% info
%% 
%% Retrieve implementation dependent mib-storage table 
%% information.
%% ---------------------------------------------------------------

info(#tab{id = ID}) ->
    case (catch mnesia:table_info(ID, all)) of
	Info when is_list(Info) ->
	    Info;
	{'EXIT', {aborted, Reason}} ->
	    {error, Reason}
    end.


info(#tab{id = ID}, Item) ->
    mnesia:table_info(ID, Item).


%% ---------------------------------------------------------------
%% sync
%% 
%% Ignore
%% ---------------------------------------------------------------

sync(_) ->
    ok.


%% ---------------------------------------------------------------
%% backup
%% 
%% Ignore. Mnesia handles its own backups. 
%% ---------------------------------------------------------------

backup(_, _) ->
    ok.

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

get_action(Opts) ->
    snmp_misc:get_option(action, Opts, keep). 

get_nodes(Opts) ->
    case snmp_misc:get_option(nodes, Opts, erlang:nodes()) of
	[] ->
	    [node()];
	Nodes when is_list(Nodes) ->
	    Nodes;
	all ->
	    erlang:nodes();
	visible ->
	    erlang:nodes(visible);
	connected ->
	    erlang:nodes(connected);
	db_nodes -> 
	    try mnesia:system_info(db_nodes) of
		DbNodes when is_list(DbNodes) ->
		    DbNodes;
		_ ->
		    erlang:nodes()
	    catch 
		_:_ ->
		    erlang:nodes()
	    end
    end.
	    
%% user_err(F, A) ->
%%     snmpa_error:user_err(F, A).
