%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.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.erlang.org/EPL1_0.txt
%% 
%% 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 Original Code is Erlang-4.7.3, December, 1998.
%% 
%% The Initial Developer of the Original Code is Ericsson Telecom
%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson
%% Telecom AB. All Rights Reserved.
%% 
%% Contributor(s): ______________________________________.''
%%
-module(disk_log_server).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/free/1').
-author('mbj@erlang.ericsson.se').
-behaviour(gen_server).

-export([start_link/0, start/0, open/1]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_info/2, terminate/2]).

-include("disk_log.hrl").

%%%-----------------------------------------------------------------
%%% This module implements the disk_log server.  Its primary purpose
%%% is to keep the ets table 'disk_log_names' updated.
%%%-----------------------------------------------------------------
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
start_link() ->  
    gen_server:start_link({local, disk_log_server}, disk_log_server, [], []).
start() -> 
    ensure_started().

open({ok, A}) ->
    ensure_started(),
    gen_server:call(disk_log_server, {open, A, true}, infinity);
open(Other) ->
    Other.

%%%----------------------------------------------------------------------
%%% Callback functions from gen_server
%%%----------------------------------------------------------------------

init([]) ->
    process_flag(trap_exit, true),
    ets:new(disk_log_names, [public, named_table, set]),
    {ok, []}.

handle_call({open, A, FirstCall}, _From, State) ->
    Name = A#arg.name,
    {IsDistributed, MyNode} = init_distributed(FirstCall, A),
    LogPids = disk_log:get_log_pids(Name),
    CreateLog = (not IsDistributed) or MyNode,
    Reply =
	case get_local_pid(Name) of
	    undefined when CreateLog == true -> % I want booleans in guards...
		Repl =
		    case supervisor:start_child(disk_log_sup, []) of 
			{ok, Pid} ->
			    link(Pid),
			    put(Pid, Name),
			    R = disk_log:internal_open(Pid, A),
			    if
				IsDistributed == true ->
				    join_distributed(Name, Pid),
				    open_distributed(FirstCall,A,[{node(),R}]);
				true ->
				    ets:insert(disk_log_names, {Name, Pid}),
				    R
			    end;
			Error ->
			    Error
		    end;
	    undefined ->
		% IsDistributed == true
		open_distributed(FirstCall, A, []);
	    LogPid ->
		disk_log:internal_open(LogPid, A)
	end,
    {reply, Reply, State}.

handle_info({'EXIT', Pid, Reason}, State) ->
    case get(Pid) of
	undefined ->
	    {noreply, State};
	Name ->
	    ets:delete(disk_log_names, Name),
	    erase(Pid),
	    {noreply, State}
    end;
handle_info(_, State) ->
    {noreply, State}.
	    
terminate(Reason, _) ->
    ok.

%%%-----------------------------------------------------------------
%%% Internal functions
%%%-----------------------------------------------------------------
ensure_started() ->
    case whereis(disk_log_server) of
	undefined ->
	    LogServer = {disk_log_server,
			 {disk_log_server, start_link, []},
			 permanent, 2000, worker, [disk_log_server]},
	    LogSup = {disk_log_sup, {disk_log_sup, start_link, []}, permanent,
		      1000, supervisor, [disk_log_sup]},
	    supervisor:start_child(kernel_safe_sup, LogServer),
	    supervisor:start_child(kernel_safe_sup, LogSup);
	_ -> ok
    end.

get_local_pid(Name) ->
    case disk_log:get_log_pids(Name) of
	undefined -> undefined;
	Pids -> own_pid(Pids, node())
    end.

own_pid([Pid | _], Node) when node(Pid) == Node ->
    Pid;
own_pid([_ | T], Node) ->
    own_pid(T, Node);
own_pid([], _) ->
    undefined.

init_distributed(true, #arg{distributed = {true, Nodes}, name = Name}) ->
    pg2:create(Name),
    {true, lists:member(node(), Nodes)};
init_distributed(false, #arg{distributed = {true, Nodes}}) ->
    {true, lists:member(node(), Nodes)};
init_distributed(_, _) ->
    {false, false}.

join_distributed(Name, Pid) ->
    pg2:join(Name, Pid).

open_distributed(true, A, Res) ->
    {true, N1} = A#arg.distributed,
    Name = A#arg.name,
    Nodes = N1 -- [node()],
    {Replies, BadNodes} =
	gen_server:multi_call(Nodes, disk_log_server, {open, A, false}),
    check_reply(Res ++ Replies, BadNodes);
open_distributed(false, _, Res) ->
    Res.

check_reply(Res, Bad) ->
    cr(Res, [], 
       lists:map(fun(BadNode) -> {BadNode, {error, nodedown}} end, Bad)).

cr([{Node, {error, R}} | T], Nodes, Bad) -> cr(T, Nodes, [{Node, R} | Bad]);
cr([Ok | T], Nodes, Bad) -> cr(T, [Ok | Nodes], Bad);
cr([], [], Bad) -> Bad;
cr([], Nodes, Bad) -> {Nodes, Bad}.
