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

-export([dbstart/1,dbnew/1,dbsave/2,dbput/3,dbget/2]).
-export([dbstop/0]).

-record(state, {parent, monitor, includes, table}).

%% Interface
dbstart(Includes) ->
    Parent = self(),
    undefined = get(?MODULE),			%Assertion.
    put(?MODULE, spawn_link(fun() -> init(Parent, Includes) end)),
    ok.

dbnew(Module)              -> req({new, Module}).
dbsave(OutFile, Module)    -> cast({save, OutFile, Module}).
dbput(Module, K, V)        -> cast({set, Module, K, V}).
dbget(Module, K)           -> req({get, Module, K}).
dbstop()                   -> Resp = req(stop), erase(?MODULE), Resp.

%% Internal functions
req(Request) ->
    DbPid = get(?MODULE),
    Ref = erlang:monitor(process,DbPid),
    get(?MODULE) ! {{Ref, self()}, Request},
    receive 
	{{Ref,?MODULE}, Reply} ->
	    erlang:demonitor(Ref,[flush]),
	    Reply; 
	{'DOWN',Ref,_,_,Info} -> 
	    exit({db_error,Info}) 
    end.

cast(Request) ->
    get(?MODULE) ! Request,
    ok.

reply({Ref,From}, Response) ->
    From ! {{Ref,?MODULE}, Response},
    ok.

init(Parent, Includes) ->
    MRef = erlang:monitor(process, Parent),
    loop(#state{parent = Parent, monitor = MRef, includes = Includes,
                table = ets:new(?MODULE, [])}).

loop(#state{parent = Parent, monitor = MRef, table = Table,
            includes = Includes} = State) ->
    receive
        {set, Mod, K2, V} ->
            [{_, Modtab}] = ets:lookup(Table, Mod),
            ets:insert(Modtab, {K2, V}),
            loop(State);
        {From, {get, Mod, K2}} ->
            Result = case ets:lookup(Table, Mod) of
                         []            -> opentab(Table, Mod, Includes);
                         [{_, Modtab}] -> {ok, Modtab}
                     end,
            case Result of
                {ok, Newtab} -> reply(From, lookup(Newtab, K2));
                _Error       -> reply(From, undefined)
            end,
            loop(State);
        {save, OutFile, Mod} ->
            [{_,Mtab}] = ets:lookup(Table, Mod),
            ok = ets:tab2file(Mtab, OutFile),
            loop(State);
        {From, {new, Mod}} ->
            [] = ets:lookup(Table, Mod),	%Assertion.
            ModTableId = ets:new(list_to_atom(lists:concat(["asn1_",Mod])), []),
            ets:insert(Table, {Mod, ModTableId}),
            reply(From, ok),
            loop(State);
        {From, stop} ->
            reply(From, stopped); %% Nothing to store
        {'DOWN', MRef, process, Parent, Reason} ->
            exit(Reason)
    end.

opentab(Tab, Mod, []) ->
    opentab(Tab, Mod, ["."]);
opentab(Tab, Mod, Includes) ->
    Base = lists:concat([Mod, ".asn1db"]),
    opentab2(Tab, Base, Mod, Includes, ok).

opentab2(_Tab, _Base, _Mod, [], Error) ->
    Error;
opentab2(Tab, Base, Mod, [Ih|It], _Error) ->
    File = filename:join(Ih, Base),
    case ets:file2tab(File) of
        {ok, Modtab} ->
            ets:insert(Tab, {Mod, Modtab}),
            {ok, Modtab};
        NewErr ->
            opentab2(Tab, Base, Mod, It, NewErr)
    end.

lookup(Tab, K) ->
    case ets:lookup(Tab, K) of
        []      -> undefined;
        [{K,V}] -> V
    end.
