%%--------------------------------------------------------------------
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1997-2015. 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_ifr_utils.erl
%% Purpose : Common function for the Interface Repository
%%----------------------------------------------------------------------

-module(orber_ifr_utils).

-export([
	 select/2,
	 index/2,
	 construct/3,
	 get_object/1,
	 set_object/1,
	 get_field/2,
	 set_field/3,
	 write_result/1,
	 read_result/1,
	 ifr_transaction_read/1,
	 ifr_transaction_write/1,
	 ifr_transaction_read_write/1,
	 makeref/1,
	 unique/0,
	 existence_check/2,
	 existence_check/3,
	 create_repository/0,
	 init_DB/2, init_DB/3
	]).

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


%%======================================================================
%% Internal stuff

%%----------------------------------------------------------------------
%% Make a record selection.
%%
%% This code *must* be amended whenever a new record is added in the
%% files ifr_objects.hrl or ../include/ifr_types.hrl

select(Record,Field) when is_record(Record,ir_IRObject) ->
    select(Record,record_info(fields,ir_IRObject),Field);
select(Record,Field) when is_record(Record,ir_Contained) ->
    select(Record,record_info(fields,ir_Contained),Field);
select(Record,Field) when is_record(Record,ir_Container) ->
    select(Record,record_info(fields,ir_Container),Field);
select(Record,Field) when is_record(Record,ir_IDLType) ->
    select(Record,record_info(fields,ir_IDLType),Field);
select(Record,Field) when is_record(Record,ir_Repository) ->
    select(Record,record_info(fields,ir_Repository),Field);
select(Record,Field) when is_record(Record,ir_ModuleDef) ->
    select(Record,record_info(fields,ir_ModuleDef),Field);
select(Record,Field) when is_record(Record,ir_ConstantDef) ->
    select(Record,record_info(fields,ir_ConstantDef),Field);
select(Record,Field) when is_record(Record,ir_TypedefDef) ->
    select(Record,record_info(fields,ir_TypedefDef),Field);
select(Record,Field) when is_record(Record,ir_StructDef) ->
    select(Record,record_info(fields,ir_StructDef),Field);
select(Record,Field) when is_record(Record,ir_UnionDef) ->
    select(Record,record_info(fields,ir_UnionDef),Field);
select(Record,Field) when is_record(Record,ir_EnumDef) ->
    select(Record,record_info(fields,ir_EnumDef),Field);
select(Record,Field) when is_record(Record,ir_AliasDef) ->
    select(Record,record_info(fields,ir_AliasDef),Field);
select(Record,Field) when is_record(Record,ir_PrimitiveDef) ->
    select(Record,record_info(fields,ir_PrimitiveDef),Field);
select(Record,Field) when is_record(Record,ir_StringDef) ->
    select(Record,record_info(fields,ir_StringDef),Field);
select(Record,Field) when is_record(Record,ir_WstringDef) ->
    select(Record,record_info(fields,ir_WstringDef),Field);
select(Record,Field) when is_record(Record,ir_SequenceDef) ->
    select(Record,record_info(fields,ir_SequenceDef),Field);
select(Record,Field) when is_record(Record,ir_ArrayDef) ->
    select(Record,record_info(fields,ir_ArrayDef),Field);
select(Record,Field) when is_record(Record,ir_ExceptionDef) ->
    select(Record,record_info(fields,ir_ExceptionDef),Field);
select(Record,Field) when is_record(Record,ir_AttributeDef) ->
    select(Record,record_info(fields,ir_AttributeDef),Field);
select(Record,Field) when is_record(Record,ir_OperationDef) ->
    select(Record,record_info(fields,ir_OperationDef),Field);
select(Record,Field) when is_record(Record,ir_InterfaceDef) ->
    select(Record,record_info(fields,ir_InterfaceDef),Field);
select(Record,Field) when is_record(Record,ir_FixedDef) ->
    select(Record,record_info(fields,ir_FixedDef),Field);
select([],_) -> [];
select(Record,Field) ->
    orber:dbg("[~p] orber_ifr_utils:select(~p, ~p);~n"
	      "Unknown Record Type~n", [?LINE, Record,Field], ?DEBUG_LEVEL),
    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO}).

-define(ELEMENT_OFFSET, 2).

select(Record,Fields,Field) ->
    Index = index(Fields,Field),
    element(?ELEMENT_OFFSET + Index, Record).

index(List,Element) ->
    index(List,Element,0).

index([H|_T],Element,Index) when H == Element ->
    Index;
index([_H|T],Element,Index) ->
    index(T,Element,Index+1);
index([],Element,Index) ->
    orber:dbg("[~p] orber_ifr_utils:index(~p, ~p);~n"
	      "Index error.~n", [?LINE, Element, Index], ?DEBUG_LEVEL),
    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO}).

%%%----------------------------------------------------------------------
%%% Construct a record.
%%%
%%% This code *must* be amended whenever a new record is added in the
%%% files ifr_objects.hrl or ../include/ifr_types.hrl

construct(Record,Field,Value) when is_record(Record,ir_IRObject) ->
    construct(Record,record_info(fields,ir_IRObject),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_Contained) ->
    construct(Record,record_info(fields,ir_Contained),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_Container) ->
    construct(Record,record_info(fields,ir_Container),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_IDLType) ->
    construct(Record,record_info(fields,ir_IDLType),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_Repository) ->
    construct(Record,record_info(fields,ir_Repository),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_ModuleDef) ->
    construct(Record,record_info(fields,ir_ModuleDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_ConstantDef) ->
    construct(Record,record_info(fields,ir_ConstantDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_TypedefDef) ->
    construct(Record,record_info(fields,ir_TypedefDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_StructDef) ->
    construct(Record,record_info(fields,ir_StructDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_UnionDef) ->
    construct(Record,record_info(fields,ir_UnionDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_EnumDef) ->
    construct(Record,record_info(fields,ir_EnumDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_AliasDef) ->
    construct(Record,record_info(fields,ir_AliasDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_PrimitiveDef) ->
    construct(Record,record_info(fields,ir_PrimitiveDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_StringDef) ->
    construct(Record,record_info(fields,ir_StringDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_WstringDef) ->
    construct(Record,record_info(fields,ir_WstringDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_SequenceDef) ->
    construct(Record,record_info(fields,ir_SequenceDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_ArrayDef) ->
    construct(Record,record_info(fields,ir_ArrayDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_ExceptionDef) ->
    construct(Record,record_info(fields,ir_ExceptionDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_AttributeDef) ->
    construct(Record,record_info(fields,ir_AttributeDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_OperationDef) ->
    construct(Record,record_info(fields,ir_OperationDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_InterfaceDef) ->
    construct(Record,record_info(fields,ir_InterfaceDef),Field,Value);
construct(Record,Field,Value) when is_record(Record,ir_FixedDef) ->
    construct(Record,record_info(fields,ir_FixedDef),Field,Value);
construct(Record,Field,Value) ->
    orber:dbg("[~p] orber_ifr_utils:construct(~p, ~p, ~p);~n"
	      "Unknown Record Type~n", 
	      [?LINE, Record,Field,Value], ?DEBUG_LEVEL),
    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO}).

construct(Record,Fields,Field,Value) ->
    Index = index(Fields,Field),
    setelement(?ELEMENT_OFFSET + Index,Record,Value).

%%%----------------------------------------------------------------------
%%% Read an object from the database

get_object(Objref) ->
%%% Use mnesia:dirty_read/1. It is much faster than doing a transaction.
    case mnesia:dirty_read(Objref) of
	[Res] ->
	    Res;
	[] ->
	    [];
	Other ->
	    orber:dbg("[~p] orber_ifr_utils:get_object(~p);~n", 
		      [?LINE, Other], ?DEBUG_LEVEL),
	    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO})
    end.
%%% This is the old code, with a transaction. We might have to revert back
%%% to this at some future time...
%%    _F = ?read_function(Objref),
%%    read_result(ifr_transaction_read(_F)).

%%%----------------------------------------------------------------------
%%% Write an object to the database

set_object(Object) ->
    _F = fun() -> mnesia:write(Object) end,
    write_result(ifr_transaction_write(_F)).

%%%----------------------------------------------------------------------
%%% Get the value of a field in a record in the DB

get_field(Objref,FieldName) ->
    Object = get_object(Objref),
    select(Object,FieldName).

%%%----------------------------------------------------------------------
%%% Atomically set the value of a field in a record in the DB

set_field(Objref,FieldName,Value) ->
    _F = fun() -> Object = get_object(Objref),
		  New_object = construct(Object,FieldName,Value),
		  mnesia:write(New_object)
	 end,
    write_result(ifr_transaction_write(_F)).


%%%----------------------------------------------------------------------
%%% Check a write transaction

write_result({atomic,ok}) -> ok;
write_result(Wres) ->
    orber:dbg("[~p] orber_ifr_utils:write_result(~p);~n", 
	      [?LINE, Wres], ?DEBUG_LEVEL),
    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO}).

%%%----------------------------------------------------------------------
%%% Extract the data from a read

read_result({atomic,[Qres]}) -> Qres;
read_result({atomic,[]}) -> [];
read_result(Qres) ->
    orber:dbg("[~p] orber_ifr_utils:read_result(~p);~n", 
	      [?LINE, Qres], ?DEBUG_LEVEL),
    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO}).

%%%----------------------------------------------------------------------
%%% Execute a transaction or a dirty read/write.
%%%
%%% Since nested transctions will upgrade the inner activity to the
%%% same kind as the outer, we cannot use the check the result in the
%%% above simplistic manner. Therefore we will not mix transaction
%%% with async_dirty (or any of the other transaction-like
%%% activities). A rather extensive rewrite of the query extraction
%%% code must be done first.

ifr_transaction_read(Fun) ->			% read synchronously
    Tr = mnesia:transaction(Fun),
    {atomic, _} = Tr,
    Tr.
ifr_transaction_write(Fun) ->			% write synchronously
    Tr = mnesia:transaction(Fun),
    {atomic, _} = Tr,
    Tr.
ifr_transaction_read_write(Fun) ->		% write synchronously
    Tr = mnesia:transaction(Fun),
    {atomic, _} = Tr,
    Tr.

%%%----------------------------------------------------------------------
%%% Make an object reference from an object

makeref(Obj) ->
    [ObjType, ObjID | _] = tuple_to_list(Obj),
    {ObjType, ObjID}.

%%%----------------------------------------------------------------------
%%% Make a unique tag.
%%%
%%% The call to term_to_binary is made to hide the representation of the
%%% unique tag. I do this because the tuple generated takes a lot of space
%%% when I dump the database. A binary is simply printed as #Bin, which
%%% is much less obtrusive.

unique() -> term_to_binary({node(), {erlang:system_time(), 
				     erlang:unique_integer()}}).

%%%----------------------------------------------------------------------
%%% Check for an existing object with the Id of the object which is
%%% about to be created.

existence_check({ObjType, ObjID}, Id) ->
    Rep = case ObjType of
	      ir_Repository ->
		  {ObjType, ObjID};
	      _ ->
		  orber_ifr_contained:'_get_containing_repository'({ObjType,
								    ObjID})
	  end,
    case orber_ifr_repository:lookup_id(Rep, Id) of
	[] ->
	    ok;
	What ->
	    orber:dbg("[~p] orber_ifr_utils:existence_check(~p, ~p, ~p);~n"
		      "Name clash(?): ~p", 
		      [?LINE, ObjType, ObjID, Id, What], ?DEBUG_LEVEL),
	    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO})
    end.

existence_check(Id, Tab, FieldNum) ->
    case mnesia:dirty_index_read(Tab, Id, FieldNum) of
	[] ->
	    ok;
	What ->
	    orber:dbg("[~p] orber_ifr_utils:existence_check(~p, ~p, ~p);~n"
		      "Name clash(?): ~p", 
		      [?LINE, Id, Tab, FieldNum, What], ?DEBUG_LEVEL),
	    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO})
    end.

%%======================================================================
%% Database initialization

init_DB(Timeout, Options) ->
    init_DB(Timeout, Options, false).

init_DB(Timeout, Options, LightIFR) ->
    Func = case Options of
	       {localCopy, IFR_storage_type} when LightIFR == true ->
		   ?ifr_light_record_tuple_list_local(IFR_storage_type);
	       {localCopy, IFR_storage_type} ->
		   ?ifr_record_tuple_list_local(IFR_storage_type);
	       _ when LightIFR == true ->
		   ?ifr_light_record_tuple_list(Options);
	       _ ->
		   ?ifr_record_tuple_list(Options)
    end,
    create_tables(Func), 
    Wait = wait_for_tables(LightIFR, Timeout),
    db_error_check([Wait],"Database table waiting failed.").

wait_for_tables(true, Timeout) ->
    mnesia:wait_for_tables(?ifr_light_object_list, Timeout);
wait_for_tables(_, Timeout) ->
    mnesia:wait_for_tables(?ifr_object_list, Timeout).

db_error_check(Checkval,_Message) ->
    case lists:any(fun(X) -> X/= ok end, Checkval) of
	true ->
	    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO});
	false ->
	    ok
    end.   

create_tables([{T,F}|Rest]) -> 
    case F() of
	ok ->
	    create_tables2(Rest);
	{aborted,{already_exists,_}} ->
	    exit({error, "Orber Mnesia Table(s) already exist. Cannot install Orber."});
	Reason ->
	    orber:dbg("[~p] orber_ifr_utils:create_tables(~p);~n"
		      "Failed to create the Mnesia table.~n"
		      "Reason: ~p", [?LINE, T, Reason], ?DEBUG_LEVEL),
	    exit({error, "Unable to create Mnesia Table"})
    end.

create_tables2([]) -> 
    ok;
create_tables2([{T,F}|Rest]) -> 
    case F() of
	ok ->
	    create_tables2(Rest);
	Reason ->
	    orber:dbg("[~p] orber_ifr_utils:create_tables2(~p);~n"
		      "Failed to create the Mnesia table.~n"
		      "Reason: ~p", [?LINE, T, Reason], ?DEBUG_LEVEL),
	    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO})
    end.


%%%----------------------------------------------------------------------
%%% Create an interface repository. This function should only be called
%%% once, after the database has been set up and initialized.

create_repository() ->
    case orber:light_ifr() of
	true ->
	    #orber_light_ifr_ref{data = #lightdata{scope = "",
						   id = ""}}; 
	false ->
	    _R = fun() ->
			 Pat = mnesia:table_info(ir_Repository, wild_pattern),
			 case [X#ir_Repository.ir_Internal_ID ||
				  X <- mnesia:match_object(Pat)] of
			     [] ->
				 PrimitiveDefs = create_primitivedefs(),
				 New = #ir_Repository{ir_Internal_ID = unique(),
						      def_kind = dk_Repository,
						      contents = [],
						      primitivedefs = PrimitiveDefs},
				 mnesia:write(New), 
				 {ir_Repository,New#ir_Repository.ir_Internal_ID};
			     [Rep_ID] ->
				 {ir_Repository,Rep_ID};
			     Error ->
				 mnesia:abort(Error)
			 end
		 end,
	    case mnesia:transaction(_R) of
		{atomic, RepRef} ->
		    RepRef;
		{aborted, Error} ->
		    orber:dbg("[~p] orber_ifr_utils:create_repository() failed;~n"
			      "Reason: ~p", [?LINE, Error], ?DEBUG_LEVEL),
		    corba:raise(#'INTF_REPOS'{completion_status=?COMPLETED_NO})
	    end
    end.

create_primitivedefs() ->
    lists:map(fun(Pk) ->
		      orber_ifr_repository:create_primitivedef(Pk, false)
	      end,
	      [pk_void,pk_short,pk_long,pk_longlong,pk_ulonglong,pk_ushort,pk_ulong,
	       pk_float,pk_double,pk_boolean,pk_char,pk_wchar,pk_octet,pk_any,
	       pk_TypeCode,pk_Principal,pk_string,pk_wstring,pk_objref]).


