/**
 * Copyright (C) 2000-2010 Atomikos <info@atomikos.com>
 *
 * This code ("Atomikos TransactionsEssentials"), by itself,
 * is being distributed under the
 * Apache License, Version 2.0 ("License"), a copy of which may be found at
 * http://www.atomikos.com/licenses/apache-license-2.0.txt .
 * You may not use this file except in compliance with the License.
 *
 * While the License grants certain patent license rights,
 * those patent license rights only extend to the use of
 * Atomikos TransactionsEssentials by itself.
 *
 * This code (Atomikos TransactionsEssentials) contains certain interfaces
 * in package (namespace) com.atomikos.icatch
 * (including com.atomikos.icatch.Participant) which, if implemented, may
 * infringe one or more patents held by Atomikos.
 * It should be appreciated that you may NOT implement such interfaces;
 * licensing to implement these interfaces must be obtained separately from Atomikos.
 *
 * 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.
 */

package com.atomikos.icatch.imp;

import java.util.Hashtable;
import java.util.Properties;
import java.util.Stack;

import com.atomikos.diagnostics.Console;
import com.atomikos.icatch.CompositeTransaction;
import com.atomikos.icatch.CompositeTransactionManager;
import com.atomikos.icatch.Participant;
import com.atomikos.icatch.Propagation;
import com.atomikos.icatch.SubTxAwareParticipant;
import com.atomikos.icatch.SysException;
import com.atomikos.icatch.TxState;
import com.atomikos.icatch.system.Configuration;

/**
 * 
 * 
 * Abstract TM class, to be extended for different communication layers. For
 * instance, transactional RMI could be one extension, JTS another one.
 */

public class BaseTransactionManager implements CompositeTransactionManager,
        SubTxAwareParticipant
{

    /**
	 * 
	 */
	private static final long serialVersionUID = -552994279460833505L;
	private Hashtable threadtotxmap_ = null;
    // maps threads to composite transactions

    private Hashtable txtothreadmap_ = null;
    // inverse map: txs to threads

    private TransactionServiceImp service_;
    // the tx service to use.

    private boolean initialized_;

    // true asa init called

    /**
     * Constructor.
     * 
     */

    protected BaseTransactionManager ()
    {
        threadtotxmap_ = new Hashtable ();
        txtothreadmap_ = new Hashtable ();
        initialized_ = false;
    }

    /**
     * Print a message to the console, with the given level of detail.
     * 
     * @param msg
     * @param level
     */
    protected void printMsg ( String msg , int level )
    {
        try {
            Console console = Configuration.getConsole ();
            if ( console != null ) {
                console.println ( msg, level );
            }
        } catch ( Exception e ) {
            // ignore
        }
    }

    /**
     * Get the thread associated with a given transaction.
     */

    private Thread getThread ( CompositeTransaction ct )
    {
        Thread thread = null;

        synchronized ( txtothreadmap_ ) {
            thread = (Thread) txtothreadmap_.get ( ct );
        }
        return thread;
    }

    /**
     * Remove mappings for given thread.
     * 
     * @return Stack The tx stack that was for current thread.
     */

    private Stack removeThreadMappings ( Thread thread )
    {

        Stack ret = null;
        synchronized ( threadtotxmap_ ) {
            ret = (Stack) threadtotxmap_.remove ( thread );
            CompositeTransaction tx = (CompositeTransaction) ret.peek ();
            txtothreadmap_.remove ( tx );
        }
        return ret;
    }

    /**
     * Set mappings for current thread.
     * 
     * @param ct
     *            The transaction to map to. Also sets the coordinator mapping
     *            by getting ct's coordinator.
     */

    private void setThreadMappings ( CompositeTransaction ct , Thread thread )
            throws IllegalStateException, SysException
    {
        //case 21806: callbacks to ct to be made outside synchronized block
    	ct.addSubTxAwareParticipant ( this ); //step 1
        
        synchronized ( threadtotxmap_ ) {
        	//between step 1 and here, intermediate timeout/rollback of the ct
        	//may have happened; make sure to check or we add a thread mapping
        	//that will never be removed!
        	if ( TxState.ACTIVE.equals ( ct.getState() )) {
        		Stack txs = (Stack) threadtotxmap_.get ( thread );
        		if ( txs == null )
        			txs = new Stack ();
        		txs.push ( ct );
        		threadtotxmap_.put ( thread, txs );
        		txtothreadmap_.put ( ct, thread );
        	}
        }


    }

    private void restoreThreadMappings ( Stack stack , Thread thread )
            throws IllegalStateException
    {
    	//case 21806: callbacks to ct to be made outside synchronized block
    	CompositeTransaction tx = (CompositeTransaction) stack.peek ();
    	tx.addSubTxAwareParticipant ( this ); //step 1
    	
        synchronized ( threadtotxmap_ ) {
        	//between step 1 and here, intermediate timeout/rollback of the ct
        	//may have happened; make sure to check or we add a thread mapping
        	//that will never be removed!
        	Object state = tx.getState();
        	
        	if ( TxState.ACTIVE.equals ( state ) || TxState.MARKED_ABORT.equals ( state ) ) {
        		//also resume for marked abort - see case 26398
        		Stack txs = (Stack) threadtotxmap_.get ( thread );
        		if ( txs != null )
        			throw new IllegalStateException (
        					"Thread already has subtx stack" );
        		threadtotxmap_.put ( thread, stack );
        		txtothreadmap_.put ( tx, thread );
        	}
        }
    }

    private CompositeTransactionImp getCurrentTx ()
    {
        Thread thread = Thread.currentThread ();
        synchronized ( threadtotxmap_ ) {
            Stack txs = (Stack) threadtotxmap_.get ( thread );
            if ( txs == null )
                return null;
            else
                return (CompositeTransactionImp) txs.peek ();

        }
    }

    // protected abstract CompositeTransaction createProxy (
    // CompositeTransaction ct )
    // throws SysException;

    /**
     * Initialize the TM.
     * 
     * @param service
     *            The tx service to use. As part of this method, the service
     *            will also be initialized.
     * @param properties The init properties.
     */

    public void init ( TransactionServiceImp service , Properties properties ) throws SysException
    {
        service_ = service;
        service_.init ( properties );
        initialized_ = true;
    }

    /**
     * Get the participant for the given root. Needed for recovery of JCA
     * inbound transactions.
     * 
     * @param root
     * @return The participant.
     */
    public Participant getParticipant ( String root )
    {
        return service_.getParticipant ( root );
    }

    /**
     * Called if a tx is ended successfully. In order to remove the tx from the
     * mapping.
     * 
     * @see SubTxAwareParticipant
     */

    public void committed ( CompositeTransaction tx )
    {
        removeTransaction ( tx );
    }

    /**
     * Called if a tx is ended with failure. In order to remove tx from mapping.
     * 
     * @see SubTxAwareParticipant
     */

    public void rolledback ( CompositeTransaction tx )
    {
        removeTransaction ( tx );

    }

    /**
     * @see CompositeTransactionManager
     */

    public CompositeTransaction getCompositeTransaction () throws SysException
    {
        if ( !initialized_ )
            throw new IllegalStateException ( "Not initialized" );

        CompositeTransaction ct = null;
        ct = getCurrentTx ();
        if ( ct != null ) {
            printMsg ( "getCompositeTransaction()  returning instance with id "
                    + ct.getTid (), Console.DEBUG );
        } else
            printMsg ( "getCompositeTransaction() returning null",
                    Console.DEBUG );
        return ct;
    }

    /**
     * @see CompositeTransactionManager
     */

    public CompositeTransaction getCompositeTransaction ( String tid )
            throws SysException
    {
        CompositeTransaction ret = service_.getCompositeTransaction ( tid );
        if ( ret != null ) {
            printMsg ( "getCompositeTransaction ( " + tid
                    + " ) returning instance with tid " + ret.getTid (),
                    Console.DEBUG );
        } else {
            printMsg ( "getCompositeTransaction ( " + tid
                    + " ) returning null", Console.DEBUG );
        }
        return ret;
    }

   


    /**
     * Recreate a composite transaction based on an imported context. Needed by
     * the application's communication layer.
     * 
     * @param context
     *            The propagationcontext.
     * @param orphancheck
     *            If true, real composite txs are done. If false, OTS like
     *            behavior applies.
     * @param heur_commit
     *            True for heuristic commit, false for heuristic rollback.
     * 
     * @return CompositeTransaction The recreated local instance.
     * @exception SysException
     *                Failure.
     */

    protected synchronized CompositeTransaction recreateCompositeTransaction (
            Propagation context , boolean orphancheck , boolean heur_commit )
            throws SysException
    {
        CompositeTransaction ct = null;
        
        
        ct = getCurrentTx();
        if ( ct != null ) {
            String msg = "Recreating a transaction with existing transaction: " + ct.getTid();
            printMsg ( msg , Console.WARN );
            //FOLLOWING DISABLED BECAUSE IT MAKES TESTS FAIL
            //throw new IllegalStateException ( msg );
        }
        ct = service_.recreateCompositeTransaction ( context, orphancheck,
                heur_commit );
        Thread t = Thread.currentThread ();
        setThreadMappings ( ct, t );
        return ct;
    }

    /**
     * @see CompositeTransactionManager
     */

    public CompositeTransaction suspend () throws SysException
    {
        Thread thread = Thread.currentThread ();
        // Stack ret = null;
        if ( !initialized_ )
            throw new IllegalStateException ( "Not initialized" );

        CompositeTransaction ret = getCurrentTx ();
        if ( ret != null ) {
            printMsg ( "suspend() for transaction " + ret.getTid (),
                    Console.INFO );
            removeThreadMappings ( thread );
        } else {
            printMsg ( "suspend() called without a transaction context",
                    Console.INFO );
        }
        return ret;

    }

    /**
     * @see CompositeTransactionManager
     */

    public void resume ( CompositeTransaction ct )
            throws IllegalStateException, SysException
    {
        Thread thread = Thread.currentThread ();

        if ( !initialized_ )
            throw new IllegalStateException ( "Not initialized" );

        // CHANGED: restore ancestor stack by inspecting the lineage:
        // the LOCAL ancestors are part of the stack
        Stack ancestors = new Stack ();
        Stack tmp = new Stack ();
        Stack lineage = (Stack) ct.getLineage ().clone ();
        boolean done = false;
        while ( !lineage.isEmpty () && !done ) {
            CompositeTransaction parent = (CompositeTransaction) lineage.pop ();
            if ( !parent.isLocal () )
                done = true;
            else
                tmp.push ( parent );
        }
        // now, reverse order of tmp
        while ( !tmp.isEmpty () ) {
            ancestors.push ( tmp.pop () );
        }
        // lastly, make sure that the ct itself is also in the stack.
        ancestors.push ( ct );

        restoreThreadMappings ( ancestors, thread );
        printMsg (
                "resume ( " + ct + " ) done for transaction " + ct.getTid (),
                Console.INFO );
    }

    /**
     * Shut down the server in a clean way.
     * 
     * @param force
     *            If true, shutdown will not wait for possibly indoubt txs to
     *            finish. Calling shutdown with force being true implies that
     *            shutdown will not fail, but there may be remaining timer
     *            threads that stay asleep until there timeouts expire. Such
     *            remaining active transactions will NOT be able to finish,
     *            because the recovery manager will be shutdown by that time.
     *            New transactions will not be allowed.
     * 
     * @exception SysException
     *                For unexpected errors.
     * @exception IllegalStateException
     *                If active txs exist, and not force.
     */

    public void shutdown ( boolean force ) throws SysException,
            IllegalStateException
    {
        service_.shutdown ( force );
    }

    protected void startlistening ( CompositeTransaction transaction )
            throws SysException
    {
        transaction.addSubTxAwareParticipant ( this );
    }

    /**
     * Removes the tx associated with calling thread. Restores context to the
     * last locally started parent, if any. Does nothing if no thread found or
     * if ct null.
     * 
     * @param ct
     *            The transaction to remove.
     */

    private void removeTransaction ( CompositeTransaction ct )
    {
        if ( ct == null )
            return;

        Thread thread = getThread ( ct );
        if ( thread == null )
            return;

        Stack mappings = removeThreadMappings ( thread );
        if ( mappings != null && !mappings.empty () ) {
            mappings.pop ();
            if ( !mappings.empty () )
                restoreThreadMappings ( mappings, thread );
        }

    }

    /**
     * @see CompositeTransactionManager
     */
    
    public CompositeTransaction createCompositeTransaction ( long timeout ) throws SysException
    {
        Stack errors = new Stack ();
        CompositeTransaction ct = null , ret = null;
        Thread thread = Thread.currentThread ();

        ct = getCurrentTx ();
        if ( ct == null ) {

            ret = service_.createCompositeTransaction ( timeout );
            printMsg ( "createCompositeTransaction ( " + timeout + " ): "
                    + "created new ROOT transaction with id " + ret.getTid (),
                    Console.INFO );
        } else {           
            
            printMsg ( "createCompositeTransaction ( " + timeout + " )",
                    Console.INFO );
            // let CT implementation do the details of logging
            ret = ct.getTransactionControl ().createSubTransaction ();

        }
        setThreadMappings ( ret, thread );

        return ret;
    }

}
