%%--------------------------------------------------------------------
%% ``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 via the world wide web 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 Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%% 
%%     $Id$
%%
%%----------------------------------------------------------------------
%% File    : CosTransactions_Terminator_impl.erl
%% Author  : Nicklas Eklund <nick@gwaihir>
%% Purpose : Support operations to commit or roll-back a transaction.
%% Created : 11 Jun 1998 by Nicklas Eklund <nick@gwaihir>
%%----------------------------------------------------------------------

-module('CosTransactions_Terminator_impl').

%%--------------- INCLUDES -----------------------------------
-include_lib("orber/include/corba.hrl").
%% Local
-include_lib("ETraP_Common.hrl").
-include_lib("CosTransactions.hrl").

%%--------------- IMPORTS-------------------------------------
-import(etrap_logmgr, [log_safe/2, get_next/2]).

%%--------------- EXPORTS-------------------------------------
%%-compile(export_all).
-export([commit/3, rollback/2]).
-export([init/1, terminate/2]).
-export([handle_call/3, handle_cast/2, handle_info/2, code_change/3]).

%%--------------- LOCAL DATA ---------------------------------
%-record(terminator, {reg_resources, rollback_only, regname, coordinator}).

%%------------------------------------------------------------
%% function : init, terminate
%% Arguments: 
%% Returns  : 
%% Effect   : Functions demanded by the module ic. Used to initiate
%%            and terminate a gen_server.
%%------------------------------------------------------------

init(State) ->
    process_flag(trap_exit,true),
    case catch start_object(State) of
	{'EXIT', Reason} ->
	    %% Happens when, for example, we encounter an 
	    %% error when reading from the log file.
	    {stop, Reason};
	Other ->
	    Other
    end.

start_object(State) ->
    case catch file:read_file_info(?tr_get_terminator(State)) of
	{error, enoent} -> 
	    %% File does not exist. It's the first time. No restart.
	    ?debug_print("Terminator:init(~p)~n", [?tr_get_terminator(State)]),
	    etrap_logmgr:start(?tr_get_terminator(State)),
	    {ok, State, ?tr_get_timeout(State)};
	{error, Reason} -> % File exist but error occurred.
	    ?tr_error_msg("CosTransactions_Terminator( ~p ) Cannot open log file: ~p~n",
			  [?tr_get_terminator(State), Reason]),
	    {stop, {error, "unable_to_open_log"}};
	_ -> % File exists, perform restart.
	    etrap_logmgr:start(?tr_get_terminator(State)),
	    ?debug_print("RESTART Terminator:init(~p)~n", 
			 [?tr_get_terminator(State)]),
	    do_restart(State, get_next(?tr_get_terminator(State), start), init)
    end.


terminate(Reason, State) ->
    ?debug_print("STOP ~p   ~p~n", [?tr_get_terminator(State), Reason]),
    case Reason of
	normal -> 
	    %% normal termination. Transaction completed. 
	    log_safe(?tr_get_terminator(State), done),
	    etrap_logmgr:stop(?tr_get_terminator(State)),
	    file:delete(?tr_get_terminator(State)),
	    ok;
	_ ->
	    ok
    end.

%%------------------------------------------------------------
%% function : handle_call, handle_cast, handle_info, code_change
%% Arguments: 
%% Returns  : 
%% Effect   : Functions demanded by the module ic. 
%%------------------------------------------------------------

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

handle_call(_,_, State) ->
    {noreply, State}.


handle_cast(_, State) ->
    {noreply, State}.
    

handle_info(Info, State) ->
    ?debug_print("Terminator:handle_info(~p)~n", [Info]),
    Pid = self(),
    case Info of
	timeout ->
	    ?tr_error_msg("Object( ~p ) timeout. Rolling back.~n",
			  [?tr_get_terminator(State)]),
            {stop, normal, State};
        {suicide, Pid} ->
            {stop, normal, State};
        _->
            {noreply, State}
    end.

%%------------------------------------------------------------
%% function : commit
%% Arguments: Self  - its own object reference.
%%            State - Gen-Server State
%%            Heuristics - boolean; report heuristic decisions?
%% Returns  : ok - equal to void
%% Effect   : 
%% Exception: HeuristicMixed  - Highest priority
%%            HeuristicHazard - Lowest  priority
%%------------------------------------------------------------

commit(_Self, State, _Heuristics) when ?tr_is_retransmit(State) ->
    ?debug_print("Terminator:commit() recalled.~n", []),
    {stop, normal, ?tr_get_reportH(State), State};
commit(Self, State, Heuristics) ->
    ?debug_print("Terminator:commit() called.~n", []),
    NewState = ?tr_set_reportH(State, Heuristics),
    log_safe(?tr_get_terminator(NewState), {init_commit, NewState}),
    transmit(Self, NewState, Heuristics).


transmit(Self, State, Heuristics) ->
    case catch 'ETraP_Common':try_timeout(?tr_get_alarm(State)) of
        false ->
%	    catch 'ETraP_Server':before_completion(?tr_get_etrap(State)),
	    case catch 'CosTransactions_Resource':prepare(?tr_get_etrap(State)) of
		'VoteCommit' ->
		    evaluate_answer(Self, State, Heuristics, 
				    'ETraP_Common':try_timeout(?tr_get_alarm(State)));
		'VoteRollback' ->
		    {stop, normal, 
		     {'EXCEPTION', 
		      #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}}, 
		     State};
		'VoteReadOnly' ->
		    {stop, normal, ok, State};
		{'EXCEPTION', E} when record(E,'CosTransactions_HeuristicMixed'),
				      Heuristics==true->
		    catch 'ETraP_Server':forget(?tr_get_etrap(State)),
		    {stop, normal, {'EXCEPTION', E}, State};
		{'EXCEPTION', E} when record(E,'CosTransactions_HeuristicHazard'),
				      Heuristics==true->
		    catch 'ETraP_Server':forget(?tr_get_etrap(State)),
		    {stop, normal, {'EXCEPTION', E}, State};
		{'EXCEPTION', E} when record(E,'CosTransactions_HeuristicMixed') ->
		    catch 'ETraP_Server':forget(?tr_get_etrap(State)),
		    {stop, normal, 
		     {'EXCEPTION',#'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}},
		     State};
		{'EXCEPTION', E} when record(E,'CosTransactions_HeuristicHazard') ->
		    catch 'ETraP_Server':forget(?tr_get_etrap(State)),
		    {stop, normal, 
		     {'EXCEPTION', #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}},
		     State};
		Other ->
		    ?tr_error_msg("Coordinator:prepare( ~p ) failed. REASON ~p~n", 
				  [?tr_get_etrap(State), Other]),
		    {stop, normal, 
		     {'EXCEPTION', 
		      #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}}, 
		     State}
	    end;
	_ ->
	    %% Timeout, rollback.
	    log_safe(?tr_get_terminator(State), rolled_back),
	    catch 'ETraP_Server':rollback(?tr_get_etrap(State)),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusRolledBack'),
	    {stop, normal, 
	     {'EXCEPTION', #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}}, 
	     State}
    end.

evaluate_answer(Self, State, Heuristics, false) ->
    evaluate_answer(Self, State, Heuristics, commit);
evaluate_answer(Self, State, Heuristics, true) ->
    evaluate_answer(Self, State, Heuristics, rollback);
evaluate_answer(_Self, State, Heuristics, Vote) ->
    case catch 'ETraP_Common':send_stubborn('ETraP_Server', Vote, 
					    ?tr_get_etrap(State), 
					    ?tr_get_maxR(State), 
					    ?tr_get_maxW(State)) of
	ok ->
	    ?eval_debug_fun({_Self, commit_ok1}, State),
	    log_safe(?tr_get_terminator(State), committed),
	    ?eval_debug_fun({_Self, commit_ok2}, State),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusCommitted'),
	    {stop, normal, ok, State};
	{'EXCEPTION', E} when Heuristics == true,
			      record(E,'CosTransactions_HeuristicMixed') ->
	    log_safe(?tr_get_terminator(State), {heuristic, State, E}),
	    ?eval_debug_fun({_Self, commit_heuristic1}, State),
	    catch 'ETraP_Server':forget(?tr_get_etrap(State)),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusRolledBack'),
	    {stop, normal, {'EXCEPTION', E}, State};
	{'EXCEPTION', E} when Heuristics == true,
			      record(E, 'CosTransactions_HeuristicHazard') ->
	    log_safe(?tr_get_terminator(State), {heuristic, State, E}),
	    catch 'ETraP_Server':forget(?tr_get_etrap(State)),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusRolledBack'),
	    {stop, normal, {'EXCEPTION', E}, State};
	{'EXCEPTION', E} when record(E, 'OBJECT_NOT_EXIST') ->
	    log_safe(?tr_get_terminator(State), rolled_back),
	    {stop, normal, {'EXCEPTION', ?tr_hazard}, State};
	{'EXCEPTION', E} when record(E, 'TRANSACTION_ROLLEDBACK') ->
	    log_safe(?tr_get_terminator(State), rolled_back),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusRolledBack'),
	    {stop, normal, {'EXCEPTION', E}, State};
	{'EXCEPTION', E} when record(E, 'CosTransactions_HeuristicCommit') ->
	    catch 'ETraP_Server':forget(?tr_get_etrap(State)),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusRolledBack'),
	    {stop, normal, ok, State};
	{'EXCEPTION', E} when record(E, 'CosTransactions_HeuristicRollback') ->
	    catch 'ETraP_Server':forget(?tr_get_etrap(State)),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusCommitted'),
	    {stop, normal, 
	     {'EXCEPTION', 
	      #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}}, 
	     State};
	_Other when Heuristics == true ->
	    log_safe(?tr_get_terminator(State), rolled_back),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusRolledBack'),
	    {stop, normal, {'EXCEPTION', ?tr_hazard}, State};
	_Other ->
	    log_safe(?tr_get_terminator(State), rolled_back),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusRolledBack'),
	    {stop, normal, ok, State}
    end.

%%-----------------------------------------------------------%
%% function : rollback
%% Arguments: Self  - its own object reference.
%%            State - Gen-Server State
%% Returns  : ok - equal to void
%% Effect   : 
%%------------------------------------------------------------

rollback(_Self, State) ->
    ?debug_print("Terminator:rollback() called.~n", []),
    log_safe(?tr_get_terminator(State), rolled_back),
    catch 'ETraP_Server':rollback(?tr_get_etrap(State)),
    {stop, normal, ok, State}.

%%-----------------------------------------------------------%
%% function : do_restart
%% Arguments: State - server context
%% Returns  : 
%% Effect   : 
%%------------------------------------------------------------

%% No data in file. Commit never initiated so we rollback (presumed rollback.
do_restart(State, eof, init) -> 
    log_safe(?tr_get_terminator(State), rolled_back),
    catch 'ETraP_Server':rollback(?tr_get_etrap(State)),
%    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),  'StatusRolledBack'),
    self() ! {suicide, self()},
    {ok, State};

do_restart(State, {error, Reason}, _) ->
    ?tr_error_msg("CosTransactions_Terminator (~p) failed. Cannot read log file: ~p~n",
		  [?tr_get_terminator(State), Reason]),
    {stop, Reason};
do_restart(State, eof, Phase) ->
    ?debug_print("Terminator:do_restart(~p)~n", [Phase]),
    case Phase of
	committed ->
	    {ok, ?tr_set_reportH(State, ok)};
	rolled_back ->
	    self() ! {suicide, self()},
	    {ok, State};
        init_commit ->
	    case catch corba_object:non_existent(?tr_get_etrap(State)) of
		true ->
		    self() ! {suicide, self()},
		    {ok, State};
		_->
		    case transmit(false, State, ?tr_get_reportH(State)) of
			{stop, normal, ok, NewState} ->
			    {ok, NewState};
			{stop, normal, 
			 {'EXCEPTION', #'TRANSACTION_ROLLEDBACK'{completion_status=?COMPLETED_YES}}, 
			 NewState} ->
			    self() ! {suicide, self()},
			    {ok, NewState};
			{stop, normal, {'EXCEPTION', Exc}, NewState} ->
			    if
				?tr_dont_reportH(State) ->
				    self() ! {suicide, self()},
				    {ok, NewState};
				true ->
				    {ok, ?tr_set_reportH(NewState, Exc)}
			    end;
			Reason ->
			    ?tr_error_msg("CosTransactions_Terminator (~p) failed. Unknown error: ~p~n",
					  [?tr_get_terminator(State), Reason]),
			    {stop, Reason}
		    end
	    end;
        {heuristic, Exc} ->
	    catch 'ETraP_Server':forget(?tr_get_etrap(State)),
%	    catch 'ETraP_Server':after_completion(?tr_get_etrap(State),
%						  'StatusRolledBack'),
	    if
		?tr_dont_reportH(State) ->
		    self() ! {suicide, self()},
		    {ok, State};
		true ->
		    {ok, ?tr_set_reportH(State, {'EXCEPTION',Exc})}
	    end
    end;
%% All done.
do_restart(State, {done, _Cursor}, _Phase) ->
    ?debug_print("Terminator:do_restart(~p)~n", [_Phase]),
    self() ! {suicide, self()},
    {ok, State};
do_restart(State, {rolled_back, Cursor}, _Phase) ->
    ?debug_print("Terminator:do_restart(~p)~n", [_Phase]),
    do_restart(State, get_next(?tr_get_terminator(State), Cursor), rolled_back);
do_restart(State, {committed, Cursor}, _Phase) ->
    ?debug_print("Terminator:do_restart(~p)~n", [_Phase]),
    do_restart(State, get_next(?tr_get_terminator(State), Cursor), committed);
do_restart(State, {{heuristic, SavedState, Exc}, Cursor}, _Phase) ->
    ?debug_print("Terminator:do_restart(~p)~n", [_Phase]),
    do_restart(SavedState, get_next(?tr_get_terminator(State), Cursor), 
	       {heuristic, Exc});
do_restart(State, {{init_commit, SavedState}, Cursor}, _) ->
    ?debug_print("Terminator:do_restart(~p)~n", [init_commit]),
    do_restart(SavedState, get_next(?tr_get_terminator(State), Cursor), init_commit).

%%--------------- END OF MODULE ------------------------------
