-module(baselib).

-export([loop/1]).

-record(state,{data=dict:new(),proto='SELF'}).

%% this is the only export - it acts as the main loop for the base
%% instances, and is started by the basedb server.

loop(S) ->
    receive
	{look,Req,Ref,From} ->
	    spawn( fun() -> do_look(Req,Ref,From,S) end),
	    loop(S);
	{ask,Req,Ref,From} ->
	    {Reply,NewS} = 
		safewrap(
		  fun() -> handle(Req,S) end,
		  {{error_with,Req},S}),
	    reply(From,Ref,Reply),
	    loop(NewS);
	{tell,Req} ->
	    {_,NewS} = 
		safewrap(
		  fun() -> handle(Req,S) end,
		  {none,S}),
	    loop(NewS);
	Got -> 
	    io:format("got message: ~p~n",[Got]),
	    loop(S)
    end.


do_look(Req,Ref,From,S) ->
    {Reply,_} = 
	safewrap(
	  fun() -> handle(Req,S) end, 
	  {{error_with,Req},none}),
    reply(From,Ref,Reply).

reply(From,Ref,Reply) ->
    From ! {Ref,Reply},
    ok.

safewrap(Fun,Default) ->
    case catch Fun() of
	{'EXIT',E} -> 
	    io:format("Error: ~p~n",[E]),
	    Default;
	Return -> Return
    end.
	    
handle({data,NewData},S) ->
    {{ok,S#state.data},S#state{data=NewData}};
handle(data,S) ->
    {{ok,S#state.data},S};
handle(attributes,#state{data=Data,proto=P}=S) ->
    case P of
	'SELF' -> {dict:fetch_keys(Data),S};
	_Ob  -> {dict:fetch_keys(Data) ++ P:attributes(),S}
    end;
handle({select,List},S) ->
    {selection(List,S),S};
handle({insert,List},S) ->
    {ok,insertion(List,S)};
handle(proto,S) ->
    {S#state.proto,S};
handle({proto,ProtoId},S) ->
    case ProtoId of
	'SELF' -> {{ok,S#state.proto},S#state{proto='SELF'}};
	_ ->
	    NewProto = base:new(ProtoId),
	    {{ok,S#state.proto},S#state{proto=NewProto}}
    end.

insertion([],S) -> S;
insertion([{K,V}|More],#state{data=Data}=S) ->
    NewData = dict:append(K,V,Data),
    insertion(More,S#state{data=NewData}).

selection(Keys,S) -> selection(Keys,S,[]).
selection([],_,Vals) -> lists:reverse(Vals);
selection([K|Keys],#state{data=Data,proto=Proto}=S,Vals) ->
    Val = 
	%error when proto is 'SELF' and the key is not found
	case dict:find(K,Data) of 
	    error when Proto /= 'SELF' ->
		[Ret] = Proto:select([K]),
		Ret;
	    {ok,List} -> lists:last(List)
	end,
    selection(Keys,S,[Val|Vals]).
