%%--------------------------------------------------------------------
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1997-2017. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%% 
%% %CopyrightEnd%
%%
%%
%%-----------------------------------------------------------------
%% File: orber_initial_references.erl
%% 
%% Description:
%%    This file contains the CORBA::InitialReferences interface
%%
%%-----------------------------------------------------------------
-module(orber_initial_references).

-behaviour(gen_server).

-include_lib("orber/include/corba.hrl").

%%-----------------------------------------------------------------
%% External exports
%%-----------------------------------------------------------------
-export([start/1, shutdown/1, init/1,
	 terminate/2, handle_call/3, code_change/3, 
	 get/2, list/1, add/3, remove/2, 
	 get/1, list/0, add/2, remove/1, 
	 typeID/0, install/2, oe_is_a/1, oe_tc/1, oe_get_interface/0]).

%%-----------------------------------------------------------------
%% Internal exports
%%-----------------------------------------------------------------
-export([handle_cast/2, handle_info/2]).

%%-----------------------------------------------------------------
%% Mnesia Table definition record
%%-----------------------------------------------------------------
-record(orber_references, {key, objref, type}).

-define(DEBUG_LEVEL, 6).

%%-----------------------------------------------------------------
%% External interface functions
%%-----------------------------------------------------------------
start(Env) ->
    gen_server:start_link({local, 'orber_init'}, ?MODULE, Env, []).

shutdown(EO_this) ->
    gen_server:call(EO_this, stop).


install(Timeout, Options) ->
    AllTabs = mnesia:system_info(tables),
    DB_Result = case lists:member(orber_references, AllTabs) of
		    true ->
			case lists:member({local_content, true},
					  Options) of
			    true->
				mnesia:add_table_copy(orber_references,
						      node(),
						      ram_copies);
			    _ ->
				mnesia:create_table(orber_references,
						    [{attributes,
						      record_info(fields,
								  orber_references)}
						     |Options])
			end;
		    _ ->
			mnesia:create_table(orber_references,
					    [{attributes,
					      record_info(fields,
							  orber_references)}
					     |Options])	
		end,
    
    Wait = mnesia:wait_for_tables([orber_references], Timeout),
    %% Check if any error has occurred yet. If there are errors, return them.
    if
	DB_Result == {atomic, ok},
	Wait == ok ->
	    ok;
	true ->
	    {error, {DB_Result, Wait}}
    end.


%%-----------------------------------------------------------------
%% InitialReferences Interface 
%%-----------------------------------------------------------------
'get'(Id) ->
    case read(Id) of
	{'EXCEPTION', E} ->
	    corba:raise(E);
	Result ->
	    Result
    end.

list() ->
    case list_keys() of
	{'EXCEPTION', E} ->
	    corba:raise(E);
	Result ->
	    Result
    end.


add(Id, ObjRef) ->
    case write(Id, ObjRef, external) of
	{'EXCEPTION', E} ->
	    corba:raise(E);
	Result ->
	    Result
    end.


remove(Id) ->
    case delete(Id) of
	{'EXCEPTION', E} ->
	    corba:raise(E);
	Result ->
	    Result
    end.


'get'(EO_this, Id) ->
    corba:call(EO_this, 'get', [Id], ?MODULE).

list(EO_this) ->
    corba:call(EO_this, 'list', [], ?MODULE).

add(EO_this, Id, ObjRef) ->
    corba:call(EO_this, 'add', [Id, ObjRef], ?MODULE).

remove(EO_this, Id) ->
    corba:call(EO_this, 'remove', [Id], ?MODULE).

typeID() ->
    "IDL:Orber/InitialReferences:1.0".

oe_is_a("IDL:Orber/InitialReferences:1.0") ->
    true;
oe_is_a(_) ->
    false.

%%-----------------------------------------------------------------
%% Internal interface functions
%%-----------------------------------------------------------------
init([]) ->
    case mnesia:wait_for_tables(['orber_references'], infinity) of
	ok ->
	    NSObjKey = 'CosNaming_NamingContextExt':oe_create([], [{pseudo, true},
								   {no_security, orber:partial_security()}]),
	    rewrite("NameService", NSObjKey),
	    ErlIfr = 'OrberApp_IFR':oe_create([], [{pseudo, true}]),
	    rewrite("OrberIFR", ErlIfr),
	    {ok, []};
	StopReason ->
	    {stop, StopReason}
    end.

terminate(_Reason, _State) ->
    ok.


%%-----------------------------------------------------------------
%% Handle incomming calls 
handle_call({_EO_this, _OE_Context, 'get', [Id]}, _From, State) ->
    {'reply', read(Id), State};
handle_call({_EO_this, _OE_Context, 'list', []}, _From, State) ->
    {'reply', list_keys(), State};

handle_call({_EO_this, _OE_Context, 'add', [Id, ObjectRef]}, _From, State) ->
    {'reply', write(Id, ObjectRef, external), State};

handle_call({_EO_this, _OE_Context, 'remove', [Id]}, _From, State) ->
    {'reply', delete(Id), State};
handle_call('stop', _From, State) ->
    {'stop', normal, 'ok', State};
handle_call(_Req, _From,State) ->
    {'reply', {'ok', 'nil', 'nil'}, State}.

oe_tc(get) ->
    {{'tk_objref', 12, "object"}, [{'tk_string', 0}], []};
oe_tc(list) -> 
    {{'tk_sequence',{'tk_string', 0}, 0}, [], []};
oe_tc(add) ->
    {'tk_boolean', [{'tk_string', 0}, {'tk_objref', 12, "object"}], []};
oe_tc(remove) ->
    {'tk_boolean', [{'tk_string', 0}], []};
oe_tc(_) -> 
    undefined.

oe_get_interface() ->
    [{"get", oe_tc(get)},
     {"list", oe_tc(list)},
     {"add", oe_tc(add)}].


%%-----------------------------------------------------------------
%% Standard gen_server cast handle
%%-----------------------------------------------------------------
handle_cast(_, State) ->
    {noreply,  State}.


%%-----------------------------------------------------------------
%% Standard gen_server handles
%%-----------------------------------------------------------------
handle_info(_, State) ->
    {noreply,  State}.

%%-----------------------------------------------------------------
%% Func: code_change/3
%%-----------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

read(Key) ->
    case mnesia:dirty_read({orber_references, Key}) of
	[] ->
	    corba:create_nil_objref();	    
	[#orber_references{objref = ObjRef}] ->
	    ObjRef;
	What ->
	    orber:dbg("[~p] orber_initial_references:lookup(~p);~n"
		      "Failed to read from DB: ~p", 
		      [?LINE, Key, What], ?DEBUG_LEVEL),
	    {'EXCEPTION', #'INTERNAL'{completion_status=?COMPLETED_NO}}
    end.

write(Key, ObjRef, Type) ->
    _WF = fun() ->
		  case mnesia:wread({orber_references, Key}) of
		      [] ->
			  %% No key exists. Ok to register.
			  mnesia:write(#orber_references{key=Key, objref = ObjRef,
							 type=Type});
		      [X] ->
			  orber:dbg("[~p] orber_initial_references:write(~p);~n"
				    "Already bound to: ~p", 
				    [?LINE, Key, X], ?DEBUG_LEVEL),
			  false;
		      Why ->
			  %% Something else occured.
			  orber:dbg("[~p] orber_initial_references:write(~p);~n"
				    "Error reading from DB (~p)", [?LINE, Key, Why], ?DEBUG_LEVEL),
			  mnesia:abort({'EXCEPTION', #'INTERNAL'{completion_status=?COMPLETED_NO}})
		  end
	  end,
    case mnesia:transaction(_WF) of
	{atomic, ok} ->
	    true;
	{atomic, Result} ->
	    Result;
	{aborted, Reason} ->
	    Reason
    end.

rewrite(Key, ObjRef) ->
    rewrite(Key, ObjRef, internal).
rewrite(Key, ObjRef, Type) ->
    _WF = fun() ->
		  mnesia:write(#orber_references{key=Key, objref = ObjRef, type=Type})
	  end,
    case mnesia:transaction(_WF) of
	{atomic, ok} ->
	    true;
	{aborted, Reason} ->
	    orber:dbg("[~p] orber_initial_references:rewrite(~p);~n"
		      "Error over writing in DB (~p)", 
		      [?LINE, Key, Reason], ?DEBUG_LEVEL),
	    corba:raise(#'INTERNAL'{completion_status=?COMPLETED_NO})
    end.


delete(Key) ->
    _DF = fun() ->
		  case mnesia:read({orber_references, Key}) of
		      [] ->
			  %% No key exists.
			  orber:dbg("[~p] orber_initial_references:delete(~p);~n"
				    "Does not exist.", [?LINE, Key], ?DEBUG_LEVEL),
			  false;
		      [_X] ->
			  mnesia:delete({orber_references, Key});
		      Why ->
			  %% Something else occured.
			  orber:dbg("[~p] orber_initial_references:delete(~p);~n"
				    "Error reading from DB (~p)",
				    [?LINE, Key, Why], ?DEBUG_LEVEL),
			  mnesia:abort({'EXCEPTION', #'INTERNAL'{completion_status=?COMPLETED_NO}})
		  end
	  end,
    case mnesia:transaction(_DF) of
	{atomic, ok} ->
	    true;
	{atomic, Result} ->
	    Result;
	{aborted, Reason} ->
	    Reason
    end.

list_keys() ->    
    _LF = fun() -> mnesia:all_keys(orber_references) end,
    case mnesia:transaction(_LF) of
	{atomic, Result} ->
	    %% We do not want OrberIFR to exported, remove it.
	    lists:delete("OrberIFR", Result);
	{aborted, Reason} ->
	    orber:dbg("[~p] orber_initial_references:list_keys();~n"
		      "Error reading from DB (~p)", [?LINE, Reason], ?DEBUG_LEVEL),
	    {'EXCEPTION', #'INTERNAL'{completion_status=?COMPLETED_NO}}
    end.
