%%--------------------------------------------------------------------
%%<copyright>
%% <year>1997-2007</year>
%% <holder>Ericsson AB, All Rights Reserved</holder>
%%</copyright>
%%<legalnotice>
%% 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.
%%
%% The Initial Developer of the Original Code is Ericsson AB.
%%</legalnotice>
%%
%%-----------------------------------------------------------------
%% File: orber.erl
%% 
%% Description:
%%    This file contains the Orber application interface
%%
%%-----------------------------------------------------------------
-module(orber).

-include_lib("orber/include/corba.hrl").
-include_lib("orber/src/orber_iiop.hrl").
-include_lib("orber/src/ifr_objects.hrl").
%%-----------------------------------------------------------------
%% External exports
%%-----------------------------------------------------------------
-export([start/0, start/1, stop/0, install/1, install/2, orber_nodes/0, iiop_port/0,
	 domain/0, iiop_ssl_port/0, iiop_out_ports/0,
	 ssl_server_certfile/0, ssl_client_certfile/0, set_ssl_client_certfile/1,
	 ssl_server_verify/0, ssl_client_verify/0, set_ssl_client_verify/1,
	 ssl_server_depth/0, ssl_client_depth/0, set_ssl_client_depth/1,
	 ssl_server_cacertfile/0,ssl_client_cacertfile/0, set_ssl_client_cacertfile/1,
	 ssl_client_keyfile/0, ssl_client_password/0, ssl_server_keyfile/0, ssl_server_password/0, 
	 ssl_client_ciphers/0, ssl_server_ciphers/0, ssl_client_cachetimeout/0, ssl_server_cachetimeout/0,
	 uninstall/0, giop_version/0, info/0, info/1, is_running/0, add_node/2, 
	 remove_node/1, iiop_timeout/0, iiop_connection_timeout/0, 
	 iiop_setup_connection_timeout/0, objectkeys_gc_time/0,
	 is_lightweight/0, get_lightweight_nodes/0,
	 start_lightweight/0, start_lightweight/1,
	 get_ORBDefaultInitRef/0, get_ORBInitRef/0,
	 get_interceptors/0, get_local_interceptors/0, 
	 get_cached_interceptors/0, set_interceptors/1,
	 jump_start/0, jump_start/1, jump_stop/0,
	 iiop_connections/0, iiop_connections/1, iiop_connections_pending/0, 
	 typechecking/0,
	 exclude_codeset_ctx/0, exclude_codeset_component/0, bidir_context/0, use_FT/0,
	 use_CSIv2/0, get_flags/0, secure/0, multi_jump_start/1, multi_jump_start/2, 
	 multi_jump_start/3, get_tables/0, iiop_in_connection_timeout/0, 
	 partial_security/0, nat_iiop_ssl_port/0, nat_iiop_port/0, ip_version/0,
	 light_ifr/0, iiop_max_in_requests/0, iiop_max_in_connections/0, 
	 iiop_max_fragments/0, iiop_backlog/0, iiop_ssl_backlog/0,
	 find_sockname_by_peername/2, find_peername_by_sockname/2, iiop_acl/0,
	 add_listen_interface/2, add_listen_interface/3, remove_listen_interface/1,
	 reconfigure_out_connections/1, 
	 reconfigure_out_connection/3, reconfigure_out_connection/4, 
	 reconfigure_in_connections/1, reconfigure_in_connection/2,
	 activate_audit_trail/0, activate_audit_trail/1, deactivate_audit_trail/0,
	 iiop_ssl_ip_address_local/0, ip_address_local/0,
	 close_connection/1, close_connection/2, is_system_exception/1,
	 exception_info/1]).

%%-----------------------------------------------------------------
%% Internal exports
%%-----------------------------------------------------------------
-export([nat_host/0, host/0, ip_address_variable_defined/0, start/2, init/1,
	 get_debug_level/0, debug_level_print/3, dbg/3, error/3,
	 configure/2, configure_override/2, multi_configure/1,
	 mjs/1, mjs/2, js/0, js/1]).

%%-----------------------------------------------------------------
%% Internal definitions
%%-----------------------------------------------------------------
%% Defines possible configuration parameters a user can add when,
%% for example, installing Orber.
-record(options, {ifr_storage_type = disc_copies,
		  install_timeout = infinity,
		  local_content = false,
		  nameservice_storage_type = ram_copies,
		  initialreferences_storage_type = ram_copies,
		  type = temporary,
		  load_order = 0}).

-define(ORBER_TABS, [orber_CosNaming, orber_objkeys, orber_references]).

-define(DEBUG_LEVEL, 5).

-define(FORMAT(_F, _A), lists:flatten(io_lib:format(_F, _A))).
-define(EFORMAT(_F, _A), exit(lists:flatten(io_lib:format(_F, _A)))).


%%-----------------------------------------------------------------
%% External interface functions
%%-----------------------------------------------------------------

jump_stop() ->
    stop(),
    uninstall(),
    mnesia:stop().

js() ->
    application:load(orber),
    jump_start([{iiop_port, iiop_port()},
		{interceptors, {native, [orber_iiop_tracer_silent]}},
		{orber_debug_level, 10}, 
		{flags, (?ORB_ENV_LOCAL_TYPECHECKING bor get_flags())}]).

js(Port) when integer(Port) ->
    application:load(orber),
    jump_start([{iiop_port, Port},
		{interceptors, {native, [orber_iiop_tracer_silent]}},
		{orber_debug_level, 10}, 
		{flags, (?ORB_ENV_LOCAL_TYPECHECKING bor get_flags())}]).

jump_start() ->
    application:load(orber),
    jump_start([{iiop_port, iiop_port()}]).


jump_start(Port) when integer(Port) ->
    application:load(orber),
    jump_start([{iiop_port, Port}]);
jump_start(Options) when list(Options) ->
    application:load(orber),
    mnesia:start(),
    Port = case lists:keysearch(iiop_port, 1, Options) of
	       {value, {iiop_port, Value}} ->
		   Value;
	       _ ->
		   iiop_port()
	   end,
    corba:orb_init([{iiop_port, Port}|Options]),
    install([node()], [{ifr_storage_type, ram_copies}]),
    start(),
    %% We need to use this operation if Port == 0 to see what the OS
    %% assigned.
    NewPort = orber_env:iiop_port(),
    Domain = orber_env:ip_address() ++ [$:|integer_to_list(NewPort)],
    orber_env:configure_override(domain, Domain),
    info();
jump_start(Options) ->
    exit({error, Options}). 


mjs(Nodes) ->
    application:load(orber),
    multi_js_helper(Nodes, iiop_port(),
		    [{interceptors, {native, [orber_iiop_tracer_silent]}},
		     {orber_debug_level, 10}, 
		     {flags, (?ORB_ENV_LOCAL_TYPECHECKING bor get_flags())}]).

mjs(Nodes, Port) ->
    application:load(orber),
    multi_js_helper(Nodes, Port, 
		    [{interceptors, {native, [orber_iiop_tracer_silent]}},
		     {orber_debug_level, 10},
		     {flags, (?ORB_ENV_LOCAL_TYPECHECKING bor get_flags())}]).


multi_jump_start(Nodes) ->
    application:load(orber),
    multi_js_helper(Nodes, iiop_port(), []).

multi_jump_start(Nodes, Port) ->
    multi_js_helper(Nodes, Port, []).

multi_jump_start(Nodes, Port, Options) ->
    multi_js_helper(Nodes, Port, Options).

multi_js_helper(Nodes, Port, InitOptions) when list(Nodes), integer(Port), 
					       list(InitOptions) ->
    %% We MUST delete the option iiop_port.
    Options = lists:keydelete(iiop_port, 1, InitOptions),
    case node() of
	nonode@nohost ->
	    {error, "The distribution is not started"};
	_ ->
	    mnesia:start(),
	    corba:orb_init([{iiop_port, Port}|Options]),
	    install([node()], [{ifr_storage_type, ram_copies}]),
	    start(),
	    NewPort = orber_env:iiop_port(),
	    Domain = orber_env:ip_address() ++ [$:|integer_to_list(NewPort)],
	    orber_env:configure_override(domain, Domain),
	    case jump_start_slaves(Nodes, NewPort, 
				   [{domain, Domain}|Options], [], []) of
		{ok, NodeData} ->
		    info(),
		    {ok, [{node(), NewPort}|NodeData]};
		Other ->
		    Other
	    end
    end.

jump_start_slaves([], _, _, [], NodeData) ->
    rpc:multicall([node() | nodes()], global, sync, []),
    {ok, NodeData};
jump_start_slaves([], _, _, Errors, _) ->
    {error, Errors};
jump_start_slaves([{Host, N}|T], Port, Options, Errors, NodeData) ->
    case create_nodes(Host, N, Port, Options, Errors, NodeData) of
	{ok, NewNodeData} ->
	    jump_start_slaves(T, Port, Options, Errors, NewNodeData);
	{error, NewErrors} ->
	    jump_start_slaves(T, Port, Options, NewErrors, NodeData)
    end;
jump_start_slaves([Host|T], Port, Options, Errors, NodeData) ->
    case catch create_node(Host, Port+1, Options) of
	{ok, NewNode} ->
	    jump_start_slaves(T, Port, Options, Errors, [{NewNode, Port+1}|NodeData]);
	{error, Reason} ->
	    jump_start_slaves(T, Port, Options, [{Host, Port, Reason}|Errors], 
			      NodeData);
	Other ->
	    jump_start_slaves(T, Port, Options, [{Host, Port, Other}|Errors], 
			      NodeData)
    end.

create_nodes(_, 0, _, _, [], NodeData) ->
    {ok, NodeData};
create_nodes(_, 0, _, _, Errors, _) ->
    {error, Errors};
create_nodes(Host, N, Port, Options, Errors, NodeData) ->
    case catch create_node(Host, Port+N, Options) of
	{ok, NewNode} ->
	    create_nodes(Host, N-1, Port, Options, Errors, 
			 [{NewNode, Port+N}|NodeData]);
	{error, Reason} ->
	    create_nodes(Host, N-1, Port, Options, 
			 [{Host, Port+N, Reason}|Errors], NodeData);
	Other ->
	    create_nodes(Host, N-1, Port, Options, 
			 [{Host, Port+N, Other}|Errors], NodeData)
    end.
    

create_node(Host, Port, Options) ->
    case slave:start_link(Host, Port) of
	{ok, NewNode} ->
	    case net_adm:ping(NewNode) of
		pong ->
		    ok = rpc:call(NewNode, mnesia, start, []),
		    {ok,_} = rpc:call(NewNode, mnesia, change_config, [extra_db_nodes, [node()]]),
		    ok = rpc:call(NewNode, corba, orb_init, [[{iiop_port, Port}|Options]]),
		    ok = rpc:call(NewNode, orber, add_node, [NewNode, ram_copies]),
		    {ok, NewNode};
		_ ->
		    {error, "net_adm:ping(Node) failed"}
	    end;
        {error, Reason} ->
            {error, Reason}
    end.


start() ->
    start(temporary).

start(Type) when Type == permanent; Type == temporary ->
    application:start(mnesia),
    TableTest = test_tables(),
    case lists:member(not_member, TableTest) of
	true ->
	    exit({error,"Orber Mnesia Table(s) missing. Orber not properly installed."});
	_->
	    try_starting(Type)
    end.

start_lightweight() ->
    application:start(orber).

start_lightweight(Nodes) when list(Nodes) ->
    configure(lightweight, Nodes),
    application:set_env(orber, lightweight, Nodes),
    application:start(orber);
start_lightweight(_) ->
    exit({error,"Argument not correct; must be a list of nodes."}).

stop() ->
    application:stop(orber).


get_tables() ->
    case light_ifr() of
	false ->
	    ?ifr_object_list++?ORBER_TABS;
	true ->
	    ?ifr_light_object_list ++?ORBER_TABS
    end.

iiop_port() ->
    orber_env:iiop_port().

nat_iiop_port() ->
    orber_env:nat_iiop_port().

iiop_out_ports() ->
    orber_env:iiop_out_ports().

orber_nodes() ->
    case catch mnesia:table_info(orber_objkeys,ram_copies) of
	Nodes when list(Nodes) ->
	    Nodes;
	_ ->
	    [node()]
    end.

domain() -> 
    orber_env:domain().


ip_address_variable_defined() ->
    orber_env:ip_address_variable_defined().


nat_host() ->
    orber_env:nat_host().

host() ->
    orber_env:host().

giop_version() ->
    orber_env:giop_version().

iiop_timeout() ->
    orber_env:iiop_timeout().

iiop_connection_timeout() ->
    orber_env:iiop_connection_timeout().

iiop_setup_connection_timeout() ->
    orber_env:iiop_setup_connection_timeout().

iiop_in_connection_timeout() ->
    orber_env:iiop_in_connection_timeout().
    
find_peername_by_sockname(Host, Port) ->
    orber_iiop_net:sockname2peername(Host, Port) ++ 
	orber_iiop_pm:sockname2peername(Host, Port).

find_sockname_by_peername(Host, Port) ->
    orber_iiop_net:peername2sockname(Host, Port) ++
	orber_iiop_pm:peername2sockname(Host, Port).

%%----------------------------------------------------------------------
%% Function   : iiop_connections
%% Arguments  : Direction   - in | out | inout
%% Returns    : Connections - [{Host, Port}] | [{Host, Port, Interface}] 
%%              Host        - string
%%              Port        - integer
%%              Interface   - string
%% Raises     : 
%% Description: List existing in- and/or out-bound connections.
%%----------------------------------------------------------------------
iiop_connections() ->
    iiop_connections(inout).

iiop_connections(inout) ->
    orber_iiop_pm:list_existing_connections() ++ orber_iiop_net:connections();
iiop_connections(in) ->
    orber_iiop_net:connections();
iiop_connections(out) ->
    orber_iiop_pm:list_existing_connections().

%%----------------------------------------------------------------------
%% Function   : close_connection
%% Arguments  : ObjRef      - #'IOP_IOR'{} | [{Host, Port}] | 
%%              Interface   - string (optional)
%%              Host        - string
%%              Port        - integer
%% Returns    : ok | {'EXCEPTION', #'BAD_PARAM'{}}
%% Raises     : 
%% Description: Close outgoing connections.
%%----------------------------------------------------------------------
close_connection(ObjRef) ->
    close_connection(ObjRef, 0).

close_connection(ObjRef, Interface) when record(ObjRef, 'IOP_IOR') ->
    case iop_ior:get_peerdata(ObjRef) of
	[] ->
	    ok;
	PeerData ->
	    orber_iiop_pm:close_connection(PeerData, Interface)
    end;
close_connection(PeerData, Interface) when list(PeerData) ->
    orber_iiop_pm:close_connection(PeerData, Interface);
close_connection(What, Interface) ->
    orber:dbg("[~p] orber:close_connection(~p, ~p);~n"
	      "Incorrect type of arguments.", 
	      [?LINE, What, Interface], ?DEBUG_LEVEL),
    corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO}).

%%----------------------------------------------------------------------
%% Function   : iiop_connections_pending
%% Arguments  : -
%% Returns    : Connections - [{Host, Port}]
%%              Host        - string
%%              Port        - integer
%% Raises     : 
%% Description: List outbound connections that are being setup. Usefull
%%              when suspecting firewall problems.
%%----------------------------------------------------------------------
iiop_connections_pending() ->
    orber_iiop_pm:list_setup_connections().


iiop_max_fragments() ->
    orber_env:iiop_max_fragments().
    
iiop_max_in_requests() ->
    orber_env:iiop_max_in_requests().

iiop_max_in_connections() ->
    orber_env:iiop_max_in_connections().

iiop_backlog() ->
    orber_env:iiop_backlog().

iiop_acl() ->
    orber_env:iiop_acl().

ip_address_local() ->
    orber_env:ip_address_local().

get_flags() ->
    orber_env:get_flags().

typechecking() ->
    orber_env:typechecking().

exclude_codeset_ctx() ->
    orber_env:exclude_codeset_ctx().

exclude_codeset_component() ->
    orber_env:exclude_codeset_component().

partial_security() ->
    orber_env:partial_security().

use_CSIv2() ->
    orber_env:use_CSIv2().

use_FT() ->
    orber_env:use_FT().

ip_version() ->
    orber_env:ip_version().

light_ifr() ->
    orber_env:light_ifr().

bidir_context() ->
    orber_env:bidir_context().

objectkeys_gc_time() ->
    orber_env:objectkeys_gc_time().


%%-----------------------------------------------------------------
%% CosNaming::NamingContextExt operations
%%-----------------------------------------------------------------
get_ORBInitRef() ->
    orber_env:get_ORBInitRef().

get_ORBDefaultInitRef() ->
    orber_env:get_ORBDefaultInitRef().


%%-----------------------------------------------------------------
%% Interceptor opertaions (see orber_pi.erl)
%%-----------------------------------------------------------------
get_interceptors() ->
    orber_env:get_interceptors().

get_local_interceptors() ->
    orber_env:get_local_interceptors().

get_cached_interceptors() ->
    orber_env:get_cached_interceptors().

set_interceptors(Val) ->
    orber_env:set_interceptors(Val).


%%-----------------------------------------------------------------
%% Light weight Orber operations
%%-----------------------------------------------------------------
is_lightweight() ->
    orber_env:is_lightweight().

get_lightweight_nodes() ->
    orber_env:get_lightweight_nodes().   

%%-----------------------------------------------------------------
%% Security access operations (SSL)
%%-----------------------------------------------------------------
secure() ->
    orber_env:secure().
 
iiop_ssl_backlog() ->
    orber_env:iiop_ssl_backlog().

iiop_ssl_ip_address_local() ->
    orber_env:iiop_ssl_ip_address_local().

iiop_ssl_port() ->
    orber_env:iiop_ssl_port().

nat_iiop_ssl_port() ->
    orber_env:nat_iiop_ssl_port().

ssl_server_certfile() ->
    orber_env:ssl_server_certfile().
    
ssl_client_certfile() ->
    orber_env:ssl_client_certfile().

set_ssl_client_certfile(Value) ->
    orber_env:set_ssl_client_certfile(Value).
    
ssl_server_verify() ->
    orber_env:ssl_server_verify().
    
ssl_client_verify() ->
    orber_env:ssl_client_verify().

set_ssl_client_verify(Value) ->
    orber_env:set_ssl_client_verify(Value).

ssl_server_depth() ->
    orber_env:ssl_server_depth().

ssl_client_depth() ->
    orber_env:ssl_client_depth().

set_ssl_client_depth(Value) ->
    orber_env:set_ssl_client_depth(Value).

ssl_server_cacertfile() ->
    orber_env:ssl_server_cacertfile().
    
ssl_client_cacertfile() ->
    orber_env:ssl_client_cacertfile().

set_ssl_client_cacertfile(Value) ->
    orber_env:set_ssl_client_cacertfile(Value).
    
ssl_client_password() ->
    orber_env:ssl_client_password().

ssl_server_password() ->
    orber_env:ssl_server_password().

ssl_client_keyfile() ->
    orber_env:ssl_client_keyfile().

ssl_server_keyfile() ->
    orber_env:ssl_server_keyfile().

ssl_client_ciphers() ->
    orber_env:ssl_client_ciphers().

ssl_server_ciphers() ->
    orber_env:ssl_server_ciphers().

ssl_client_cachetimeout() ->
    orber_env:ssl_client_cachetimeout().

ssl_server_cachetimeout() ->
    orber_env:ssl_server_cachetimeout().

%%----------------------------------------------------------------------
%% Function   : activate_audit_trail
%% Arguments  : Verbosity - stealth | normal | verbose
%% Returns    : -
%% Raises     : 
%% Description: Activate the appropriate interceptor for the requested direction(s).
%%----------------------------------------------------------------------
activate_audit_trail() ->
    activate_audit_trail(normal).

activate_audit_trail(stealth) ->
    do_activate(orber_iiop_tracer_stealth);
activate_audit_trail(verbose) ->
    do_activate(orber_iiop_tracer);
activate_audit_trail(_) ->
    do_activate(orber_iiop_tracer_silent).

do_activate(Interceptor) ->
    Options =
	case orber_env:get_interceptors() of
	    {native, PIs} ->
		[{interceptors, 
		  {native, [Interceptor|remove_built_in_interceptors(PIs, [])]}}];
	    _ ->
		[{interceptors, {native, [Interceptor]}}]
	end,
    reconfigure_in_connections(Options),
    reconfigure_out_connections(Options).

remove_built_in_interceptors([orber_iiop_tracer_stealth|T], Acc) ->
    remove_built_in_interceptors(T, Acc);
remove_built_in_interceptors([orber_iiop_tracer|T], Acc) ->
    remove_built_in_interceptors(T, Acc);
remove_built_in_interceptors([orber_iiop_tracer_silent|T], Acc) ->
    remove_built_in_interceptors(T, Acc);
remove_built_in_interceptors([H|T], Acc) ->
    remove_built_in_interceptors(T, [H|Acc]);
remove_built_in_interceptors([], Acc) ->
    %% We must use the same order as defined by the interceptors parameter
    lists:reverse(Acc).

%%----------------------------------------------------------------------
%% Function   : deactivate_audit_trail
%% Arguments  : -
%% Returns    : -
%% Raises     : 
%% Description: Dectivate interceptors for the requested direction(s).
%%----------------------------------------------------------------------
deactivate_audit_trail() ->
    Options 
	= case orber_env:get_interceptors() of
	      {native, PIs} ->
		  [{interceptors, {native, PIs}}];
	      _ ->
		  [{interceptors, false}]
	  end,
    reconfigure_in_connections(Options),
    reconfigure_out_connections(Options).

%%----------------------------------------------------------------------
%% Function   : add_listen_interface
%% Arguments  : IP   - string
%%              Type - normal | ssl
%%              Port - integer > 0
%%              Options - [{Key, Value}]
%%              Key - atom() valid configuration parameter
%%              Value - a valid value for the given Key
%% Returns    : #Ref
%% Raises     : 
%% Description: Add a new listen process, which will accept new incoming
%%              connections.
%%----------------------------------------------------------------------
add_listen_interface(IP, normal) ->
    orber_iiop_net:add(IP, normal, [{iiop_port, orber_env:iiop_port()}]);
add_listen_interface(IP, ssl) ->
    orber_iiop_net:add(IP, ssl, [{iiop_ssl_port, orber_env:iiop_ssl_port()}]).

add_listen_interface(IP, normal, Port) when integer(Port), Port > 0 ->
    orber_iiop_net:add(IP, normal, [{iiop_port, Port}]);
add_listen_interface(IP, ssl, Port) when integer(Port), Port > 0 ->
    orber_iiop_net:add(IP, ssl, [{iiop_ssl_port, Port}]);
add_listen_interface(IP, Type, Options) when list(Options) ->
    orber_iiop_net:add(IP, Type, Options);
add_listen_interface(IP, Type, Port) when integer(Port) ->
    orber:dbg("[~p] orber:add_listen_interface(~p, ~p, ~p);~n"
	      "The port number must be greater than 0.", 
	      [?LINE, IP, Type, Port], ?DEBUG_LEVEL),
    corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO});
add_listen_interface(IP, Type, Extra) ->
    orber:dbg("[~p] orber:add_listen_interface(~p, ~p, ~p);~n"
	      "Incorrect argument(s).", 
	      [?LINE, IP, Type, Extra], ?DEBUG_LEVEL),
    corba:raise(#'BAD_PARAM'{completion_status=?COMPLETED_NO}).
    
%%----------------------------------------------------------------------
%% Function   : remove_listen_interface
%% Arguments  : Ref - #Ref
%% Returns    : #Ref
%% Raises     : 
%% Description: Terminate the listen process and all related inproxies
%%              associated with the supplied reference.
%%----------------------------------------------------------------------
remove_listen_interface(Ref) ->
    orber_iiop_net:remove(Ref).

%%----------------------------------------------------------------------
%% Function   : reconfigure_out_connections
%% Arguments  : Options - see corba:orb_init
%% Returns    : ok | {error, Reason}
%% Raises     : 
%% Description: Reconfigure the behavior of all outgoing IIOP connections.
%%----------------------------------------------------------------------
reconfigure_out_connections(Options) ->
    orber_iiop_pm:reconfigure(Options).

%%----------------------------------------------------------------------
%% Function   : reconfigure_out_connections
%% Arguments  : Options - see corba:orb_init
%%              Host      - string()
%%              Port      - integer()
%%              Interface - string
%% Returns    : ok | {error, Reason}
%% Raises     : 
%% Description: Reconfigure the behavior of all outgoing connections.
%%----------------------------------------------------------------------
reconfigure_out_connection(Options, Host, Port) ->
    orber_iiop_pm:reconfigure(Options, Host, Port).
reconfigure_out_connection(Options, Host, Port, Interface) ->
    orber_iiop_pm:reconfigure(Options, Host, Port, Interface).

%%----------------------------------------------------------------------
%% Function   : reconfigure_in_connections
%% Arguments  : Options - see corba:orb_init
%% Returns    : ok | {error, Reason}
%% Raises     : 
%% Description: Reconfigure the behavior of all incoming IIOP connections.
%%----------------------------------------------------------------------
reconfigure_in_connections(Options) ->
    orber_iiop_net:reconfigure(Options).

%%----------------------------------------------------------------------
%% Function   : reconfigure_in_connections
%% Arguments  : Options - see corba:orb_init
%%              Ref       - The #Ref returned by add_listen_interface/2/3
%% Returns    : ok | {error, Reason}
%% Raises     : 
%% Description: Reconfigure the behavior of all incoming IIOP connections.
%%----------------------------------------------------------------------
reconfigure_in_connection(Options, Ref) ->
    orber_iiop_net:reconfigure(Options, Ref).


%%-----------------------------------------------------------------
%% Configuration settings
%%-----------------------------------------------------------------
info() ->
    orber_env:info().

info(IoDevice) ->
    orber_env:info(IoDevice).

%%-----------------------------------------------------------------
%% EXCEPTION mapping
%%-----------------------------------------------------------------
exception_info(Exc) ->
    orber_exceptions:dissect(Exc).

is_system_exception(Exc) ->
    orber_exceptions:is_system_exception(Exc).

%%-----------------------------------------------------------------
%% Installation interface functions
%%-----------------------------------------------------------------
install(Nodes) ->
    install(Nodes, []).

install([], Options) ->
    install([node()], Options);
install(Nodes, Options) when list(Nodes), list(Options)->
    case orber_tb:is_running() of
	false ->
	    application:load(orber),
	    case mnesia:system_info(is_running) of
		no ->
		    application:start(mnesia),
		    Outcome = install_orber(Nodes, Options),
		    application:stop(mnesia),
		    Outcome;
		yes ->
		    install_orber(Nodes, Options)
	    end;
	_ ->
	    exit({error, "Orber is already running on this node."})
    end.



install_orber(Nodes, Options) ->
    #options{ifr_storage_type = IFRType, install_timeout = Timeout,
	     local_content = LocalContent, nameservice_storage_type = NSType,
	     initialreferences_storage_type = InitType,
	     load_order = LoadOrder}
	= check_options(Options, #options{}),
    MnesiaOptions = [{local_content, LocalContent}, 
		     {load_order, LoadOrder}],
    TableTest = test_tables(),
    case lists:member(is_member, TableTest) of
	true ->
	    case LocalContent of
		true ->
		    orber_ifr:initialize(Timeout, {localCopy,IFRType}, 
					 light_ifr());
		_->
		    exit("Orber Mnesia Table(s) already exist. Cannot install Orber.")
	    end;
	_ ->
	    orber_ifr:initialize(Timeout, [{IFRType, Nodes} |MnesiaOptions], 
				 light_ifr())
    end,
    orber_objectkeys:install(Timeout, [{ram_copies, Nodes} |MnesiaOptions]),
    'CosNaming_NamingContextExt_impl':install(Timeout, [{NSType, Nodes} |MnesiaOptions]),
    orber_initial_references:install(Timeout, [{InitType, Nodes} |MnesiaOptions]),
    oe_cos_naming:oe_register(),
    oe_cos_naming_ext:oe_register(),
    oe_erlang:oe_register(),
    oe_OrberIFR:oe_register(),
    oe_CORBA:oe_register(),
    case NSType of
	ram_copies ->
	    case mnesia:dump_tables(['orber_CosNaming']) of
		{atomic, ok} ->
		    ok;
		{aborted, {has_no_disc,_}} ->
		    ok;
		{aborted, Reason} ->
		    ?EFORMAT("Unable to dump mnesia tables: ~p", [Reason])
	    end;
	_ ->
	    ok
    end.

check_options([], Options) ->
    Options;
check_options([{ifr_storage_type, Type}|T], Options) 
  when Type == disc_copies; Type == ram_copies ->
    check_options(T, Options#options{ifr_storage_type = Type}); 
check_options([{nameservice_storage_type, Type}|T], Options) 
  when Type == disc_copies; Type == ram_copies ->
    check_options(T, Options#options{nameservice_storage_type = Type}); 
check_options([{initialreferences_storage_type, Type}|T], Options) 
  when Type == disc_copies; Type == ram_copies ->
    check_options(T, Options#options{initialreferences_storage_type = Type}); 
check_options([{install_timeout, Timeout}|T], Options) 
  when Timeout == infinity; integer(Timeout) ->
    check_options(T, Options#options{install_timeout = Timeout});
check_options([{local_content, Bool}|T], Options) 
  when Bool == true; Bool == false ->
    check_options(T, Options#options{local_content = Bool});
check_options([{type, Type}|T], Options) 
  when Type == temporary; Type == permanent ->
    check_options(T, Options#options{type = Type});
check_options([{load_order, LoadOrder}|T], Options) 
  when integer(LoadOrder) ->
    check_options(T, Options#options{load_order = LoadOrder});
check_options([H|_], _) ->
    ?EFORMAT("Option unknown or incorrect value: ~w", [H]).

  

try_starting(Type) ->
    case application:start(orber, Type) of
	ok ->
	    case partial_security() of
		true ->
		    error_logger:warning_msg(
		      "=================== Orber =================~n"
		      "*******************************************~n"
		      "**** WARNING - WARNING - WARNING **********~n"
		      "**** WARNING - WARNING - WARNING **********~n"
		      "**** WARNING - WARNING - WARNING **********~n"
		      "**** WARNING - WARNING - WARNING **********~n"
		      "*******************************************~n"
		      "  ORBER STARTED WITH AN INSECURE OPTION:~n"
		      " ~n"
		      "             {flags, ~p}~n"
		      " ~n"
		      " THIS OPTION MAY ONLY BE USED DURING TESTS~n"
		      " ~n"
		      "===========================================~n", 
		      [?ORB_ENV_PARTIAL_SECURITY]),
		    ok;
		false ->
		    ok
	    end;
	{error,{already_started,orber}} ->
	    {error,{already_started,orber}};
	Reason ->
	    dbg("[~p] orber:try_starting(~p) failed: ~n~p", 
		[?LINE, Type, Reason], ?DEBUG_LEVEL),
	    {error, "Unable to start Orber. Is the listen port vacant?"}
	end.

test_tables() ->
    AllTabs = mnesia:system_info(tables),
    lists:map(fun(Tab) ->
		      case lists:member(Tab,AllTabs) of
			  false ->
			      not_member;
			  _ ->
			      is_member
		      end
	      end,
	      get_tables()).

%%-----------------------------------------------------------------
%% UnInstallation interface functions
%%-----------------------------------------------------------------
uninstall() ->
    orber_objectkeys:stop_all(),
    application:stop(orber),
    delete_orber_tables(get_tables()).

delete_orber_tables([]) -> ok;
delete_orber_tables([Tab1|Rest]) ->
    mnesia:delete_table(Tab1),
    delete_orber_tables(Rest).

%%-----------------------------------------------------------------
%% Add and remove node interface functions
%%-----------------------------------------------------------------
add_node(Node, StorageType) when atom(Node), atom(StorageType) ->
    add_node(Node, [{ifr_storage_type, StorageType}]);
add_node(Node, OptionList) when atom(Node), list(OptionList) ->
    case rpc:call(Node, mnesia, system_info, [is_running]) of
	{badrpc, Reason} ->
	    ?EFORMAT("Node ~p do not respond. add_node/2 failed: ~p", 
		     [Node, Reason]);
	yes ->
	    case rpc:call(Node, orber, is_running, []) of
		false ->
		    %% We need to "load" orber to make sure that
		    %% application environment variables is loaded.
		    rpc:call(Node, application, load, [orber]),
		    Options = check_options(OptionList, #options{}),
		    case rpc:call(Node, orber, light_ifr, []) of
			false ->
			    copy_tables(?ifr_object_list, Node, Options);
			true ->
			    copy_tables(?ifr_light_object_list, Node, Options)
		    end;
		true ->
		    ?EFORMAT("Orber is already running on ~p. add_node failed.",
			     [Node]);
		Reason ->
		    ?EFORMAT("Unable to reach node ~p. add_node/1 failed: ~p",
			     [Node, Reason])
	    end;
	no ->
	    ?EFORMAT("Mnesia not running on node ~p. add_node/2 failed.",
		     [Node]);
	starting ->
	    ?EFORMAT("Mnesia not fully started on node ~p. add_node/2 failed.",
		     [Node]);
	stopping ->
	    ?EFORMAT("Mnesia stopping  on node ~p. add_node/2 failed.", [Node])
    end.

%% We have to copy the tables in two steps, i.e., orber tables should be ram_copies
%% while the user may choose to install the rest as disc_copies.
copy_tables([], Node, Options) ->
    copy_orber_tables(?ORBER_TABS, Node, Options);
copy_tables([T1|Trest], Node, Options) ->
    case mnesia:add_table_copy(T1, Node, Options#options.ifr_storage_type) of
	{atomic, ok} ->
	    copy_tables(Trest, Node, Options);
	{aborted, Reason} ->
	    ?EFORMAT("orber:add_node/2 failed: ~p. Unable to copy IFR table(s): ~p", 
		     [mnesia:error_description(Reason), [T1|Trest]])
    end.

copy_orber_tables([], Node, Options) ->
    case rpc:call(Node, application, start, [orber, Options#options.type]) of
	ok ->
	    ok;
	Reason ->
	    ?EFORMAT("All tables installed but failed to start orber on node ~p: ~p",
		     [Node, Reason])
    end;
copy_orber_tables([orber_CosNaming|TTail], Node, Options) ->
    case mnesia:add_table_copy(orber_CosNaming, Node, 
			       Options#options.nameservice_storage_type) of
	{atomic, ok} ->
	    copy_orber_tables(TTail, Node, Options);
	{aborted, Reason} ->
	    ?EFORMAT("orber:add_node/2 failed: ~p. Unable to copy system table(s): ~p",
		     [mnesia:error_description(Reason), [orber_CosNaming|TTail]])
    end;
copy_orber_tables([orber_references|TTail], Node, Options) ->
    case mnesia:add_table_copy(orber_references, Node, 
			       Options#options.initialreferences_storage_type) of
	{atomic, ok} ->
	    copy_orber_tables(TTail, Node, Options);
	{aborted, Reason} ->
	    ?EFORMAT("orber:add_node/2 failed: ~p. Unable to copy system table(s): ~p",
		     [mnesia:error_description(Reason), [orber_references|TTail]])
    end;
copy_orber_tables([THead|TTail], Node, Options) ->
    case mnesia:add_table_copy(THead, Node, ram_copies) of
	{atomic, ok} ->
	    copy_orber_tables(TTail, Node, Options);
	{aborted, Reason} ->
	    ?EFORMAT("orber:add_node/2 failed: ~p. Unable to copy system table(s): ~p",
		     [mnesia:error_description(Reason), [THead|TTail]])
    end.

remove_node(Node) when atom(Node) ->
    case rpc:call(Node, mnesia, system_info, [is_running]) of
	yes ->
	    case rpc:call(Node, orber, is_running, []) of
		true ->
		    rpc:call(Node, orber, stop, []),
		    remove_tables(get_tables(), Node);
		false ->
		    remove_tables(get_tables(), Node);
		Reason ->
		    ?EFORMAT("Unable to reach node: ~p. remove_node/1 failed: ~p",
			     [Node, Reason])
	    end;
	no ->
	    case rpc:call(Node, mnesia, start, []) of
		ok ->
		    remove_tables(get_tables(), Node),
		    rpc:call(Node, mnesia, stop, []);
		Reason ->
		    ?EFORMAT("Unable to reach node: ~p. remove_node/1 failed: ~p",
			     [Node, Reason])
	    end;
	Reason ->
	    ?EFORMAT("Problem with ~p. remove_node/1 failed: ~p", [Node, Reason])
    end.


remove_tables(Tables, Node) ->
    remove_tables(Tables, Node, []).

remove_tables([], _, []) -> ok;
remove_tables([], Node, Failed) ->
    ?EFORMAT("orber:remove_node(~p) failed. Unable to remove table(s): ~p", 
	     [Node, Failed]);
remove_tables([T1|Trest], Node, Failed) ->
    case mnesia:del_table_copy(T1, Node) of
	{atomic, ok} ->
	    remove_tables(Trest, Node, Failed);
	{aborted, Reason} ->
	    remove_tables(Trest, Node, [{T1, Reason}|Failed])
    end.



%%-----------------------------------------------------------------
%% Internal interface functions
%%-----------------------------------------------------------------
%%----------------------------------------------------------------------
%% Function   : is_running
%% Arguments  : 
%% Returns    : 
%% Raises     : 
%% Description: 
%%----------------------------------------------------------------------
is_running() ->
    orber_tb:is_running().

%%----------------------------------------------------------------------
%% Function   : check_giop
%% Arguments  : 
%% Returns    : 
%% Raises     : 
%% Description: 
%%----------------------------------------------------------------------
check_giop_version() ->
    case giop_version() of
	{1,0} ->
	    ok;
	{1,1} ->
	    ok;
	{1,2} ->
	    ok;
	X ->
	    X
    end.

%%----------------------------------------------------------------------
%% Function   : dbg
%% Arguments  : 
%% Returns    : 
%% Raises     : 
%% Description: Note, dbg replaces debug_level_print.
%%              
%%              The following levels are used (0-10):
%%              10: cdrlib.erl
%%               9: cdr_encode.erl cdr_decode.erl orber_ifr.erl orber_pi.erl
%%               8: orber_iiop_outrequest.erl orber_iiop_inrequest.erl
%%               7: orber_iiop_outproxy.erl orber_iiop_inproxy.erl
%%               6: iop_ior.erl, orber_objectkeys.erl, Orber_IFR_impl.erl orber_socket.erl
%%               5: corba.erl, corba_boa.erl, corba_object.erl
%%               4: Reserved for Cos-services!
%%               3: Reserved for Cos-services!
%%               2: Reserved for client applications!
%%               1: Reserved for client applications!
%%               0: No logging!
%%
%%              A higher value will result in a finer granularity.
%%----------------------------------------------------------------------
get_debug_level() ->
    orber_env:get_debug_level().

debug_level_print(Format, Data, RequestedLevel) ->
    dbg(Format, Data, RequestedLevel).

dbg(Format, Data, RequestedLevel) ->
    case orber_env:get_debug_level() of
	0 ->
	    ok;
	Level when integer(Level), Level >= RequestedLevel ->
	    if
		RequestedLevel > 4 ->
		    %% Use catch if incorrect format used somewhere.
		    catch error_logger:error_msg("=================== Orber =================~n"++
						 Format++
						 "~n===========================================~n",
						 Data);
		RequestedLevel > 2 ->
		    %% Use catch if incorrect format used somewhere.
		    catch error_logger:error_msg("=========== Orber COS Application =========~n"++
						 Format++
						 "~n===========================================~n",
						 Data);
		true ->
		    %% Use catch if incorrect format used somewhere.
		    catch error_logger:error_msg("========== Orber Client Application =======~n"++
						 Format++
						 "~n===========================================~n",
						 Data)
	    end,
	    ok;
	_ ->
	    ok
    end.

error(Format, Data, RequestedLevel) ->
    if
	RequestedLevel > 4 ->
	    %% Use catch if incorrect format used somewhere.
	    catch error_logger:error_msg("=================== Orber =================~n"++
					 Format++
					 "~n===========================================~n",
					 Data);
	RequestedLevel > 2 ->
	    %% Use catch if incorrect format used somewhere.
	    catch error_logger:error_msg("=========== Orber COS Application =========~n"++
					 Format++
					 "~n===========================================~n",
					 Data);
	true ->
	    %% Use catch if incorrect format used somewhere.
	    catch error_logger:error_msg("========== Orber Client Application =======~n"++
					 Format++
					 "~n===========================================~n",
					 Data)
    end,
    ok.

configure(Key, Value) ->
    orber_env:configure(Key, Value, check).

configure_override(Key, Value) ->
    orber_env:configure(Key, Value, loaded).

multi_configure(KeyValueList) ->
    orber_env:multi_configure(KeyValueList).


%%-----------------------------------------------------------------
%% Server functions
%%-----------------------------------------------------------------
start(_, _) ->
    supervisor:start_link({local, orber_sup}, orber, orb_init).

init(orb_init) ->
    case check_giop_version() of
	ok ->
	    case is_lightweight() of
		true ->
		    SupFlags = {one_for_one, 5, 1000},  
		    ChildSpec = [
				 {orber_iiop_sup, {orber_iiop, start_sup, [[]]},
				  permanent, 
				  10000, supervisor, [orber_iiop]},
				 {orber_reqno, {orber_request_number, start,
						[[]]},
				  permanent, 
				  10000, worker, [orber_request_number]}
				],
		    {ok, {SupFlags, ChildSpec}};
		false ->
		    case orber_tb:wait_for_tables(get_tables()) of
			ok ->
			    orber_objectkeys:remove_old_keys(),
			    SupFlags = {one_for_one, 5, 1000},
			    ChildSpec = [
					 {orber_iiop_sup, {orber_iiop, start_sup, [[]]},
					  permanent, 
					  10000, supervisor, [orber_iiop]},
					 {orber_init, {orber_initial_references, start,
						       [[]]},
					  permanent, 
					  10000, worker, [orber_initial_references]},
					 {orber_reqno, {orber_request_number, start,
							[[]]},
					  permanent, 
					  10000, worker, [orber_request_number]},
					 {orber_objkeyserver, {orber_objectkeys, start,
							       [[orber_nodes(), 0]]},
					  permanent, 
					  10000, worker, [orber_objectkeys]},
					 {orber_env, {orber_env, start, [[]]},
					  permanent, 10000, worker, [orber_env]}
					],
			    {ok, {SupFlags, ChildSpec}};
			StopReason ->
			    {stop, StopReason}
		    end
	    end;
	X ->
	    {stop, ?FORMAT("GIOP ~p not an implemeted version", [X])}
    end.

