package org.neo4j.kernel.impl.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;

import org.neo4j.graphdb.event.TransactionData;
import org.neo4j.graphdb.event.TransactionEventHandler;

public class TransactionEventsSyncHook implements Synchronization
{
    private final Collection<TransactionEventHandler<?>> handlers;
    private final NodeManager nodeManager;

    /**
     * This is null at construction time, then populated in beforeCompletion and
     * used in afterCompletion.
     */
    private List<HandlerAndState> states;
    private TransactionData transactionData;
    private final TransactionManager tm;

    public TransactionEventsSyncHook(
            NodeManager nodeManager,
            Collection<TransactionEventHandler<?>> transactionEventHandlers, 
            TransactionManager tm )
    {
        this.nodeManager = nodeManager;
        this.handlers = transactionEventHandlers;
        this.tm = tm;
    }

    public void beforeCompletion()
    {
        this.transactionData = nodeManager.getTransactionData();
        try
        {
            if ( tm.getStatus() != Status.STATUS_ACTIVE ) 
            {
                return;
            }
        }
        catch ( SystemException e )
        {
            e.printStackTrace();
        }
        states = new ArrayList<HandlerAndState>();
        for ( TransactionEventHandler<?> handler : this.handlers )
        {
            try
            {
                Object state = handler.beforeCommit( transactionData );
                states.add( new HandlerAndState( handler, state ) );
            }
            catch ( Throwable t )
            {
                // TODO Do something more than calling failure and
                // throw exception?
                try
                {
                    tm.setRollbackOnly();
                }
                catch ( Exception e )
                {
                    // TODO Correct?
                    e.printStackTrace();
                }
                
                // This will cause the transaction to throw a
                // TransactionFailureException
                throw new RuntimeException( t );
            }
        }
    }

    @SuppressWarnings("unchecked")
    public void afterCompletion( int status )
    {
        if ( status == Status.STATUS_COMMITTED )
        {
            for ( HandlerAndState state : this.states )
            {
                state.handler.afterCommit( this.transactionData, state.state );
            }
        }
        else if ( status == Status.STATUS_ROLLEDBACK )
        {
            if ( this.states == null )
            {
                // This means that the transaction was never successful
                return;
            }
            
            for ( HandlerAndState state : this.states )
            {
                state.handler.afterRollback( this.transactionData, state.state );
            }
        }
        else
        {
            throw new RuntimeException( "Unknown status " + status );
        }
    }

    private class HandlerAndState
    {
        @SuppressWarnings("unchecked")
        private final TransactionEventHandler handler;
        private final Object state;

        public HandlerAndState( TransactionEventHandler<?> handler, Object state )
        {
            this.handler = handler;
            this.state = state;
        }
    }
}
