/*
 * Copyright (c) 2002-2009 "Neo Technology,"
 *     Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.neo4j.kernel.impl.transaction;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;

import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.neo4j.graphdb.TransactionFailureException;
import org.neo4j.kernel.DeadlockDetectedException;
import org.neo4j.kernel.impl.util.ArrayMap;

/**
 * The Resource Allocation Graph manager is used for deadlock detection. It
 * keeps track of all locked resources and transactions waiting for resources.
 * When a {@link RWLock} cannot give the lock to a transaction the tx has to
 * wait and that may lead to a deadlock. So before the tx is put into wait mode
 * the {@link RagManager#checkWaitOn} method is invoked to check if a wait of
 * this transaction will lead to a deadlock.
 * <p>
 * The <CODE>checkWaitOn</CODE> throws a {@link DeadlockDetectedException} if
 * a deadlock would occur when the transaction would wait for the resource. That
 * will guarantee that a deadlock never occurs on a RWLock basis.
 * <p>
 * Think of the resource allocation graph as a node space. We have two node
 * types, resource nodes (R) and tx/process nodes (T). When a transaction
 * acquires lock on some resource a relationship is added from the resource to
 * the tx (R->T) and when a transaction waits for a resource a relationship is
 * added from the tx to the resource (T->R). The only thing we need to do to see
 * if a deadlock occurs when some transaction waits for a resource is to
 * traverse node nodespace starting on the resource and see if we can get back
 * to the tx ( T1 wants to wait on R1 and R1->T2->R2->T3->R8->T1 <==>
 * deadlock!).
 */
class RagManager
{
    // if a runtime exception is thrown from any method it means that the
    // RWLock class hasn't kept the contract to the RagManager
    // The contract is:
    // o When a transaction gets a lock on a resource and both the readCount and
    // writeCount for that transaction on the resource was 0
    // RagManager.lockAcquired( resource ) must be invoked
    // o When a tx releases a lock on a resource and both the readCount and
    // writeCount for that transaction on the resource goes down to zero
    // RagManager.lockReleased( resource ) must be invoked
    // o After invoke to the checkWaitOn( resource ) method that didn't result
    // in a DeadlockDetectedException the transaction must wait
    // o When the transaction wakes up from waiting on a resource the
    // stopWaitOn( resource ) method must be invoked

    private final Map<Object,List<Transaction>> resourceMap =
        new HashMap<Object,List<Transaction>>();

    private final ArrayMap<Transaction,Object> waitingTxMap =
        new ArrayMap<Transaction,Object>( 5, false, true );

    private final TransactionManager tm;

    private final AtomicInteger deadlockCount = new AtomicInteger();

    RagManager( TransactionManager tm )
    {
        this.tm = tm;
    }

    long getDeadlockCount()
    {
        return deadlockCount.longValue();
    }

    synchronized void lockAcquired( Object resource, Transaction tx )
    {
        List<Transaction> lockingTxList = resourceMap.get( resource );
        if ( lockingTxList != null )
        {
            assert !lockingTxList.contains( tx );
            lockingTxList.add( tx );
        }
        else
        {
            lockingTxList = new LinkedList<Transaction>();
            lockingTxList.add( tx );
            resourceMap.put( resource, lockingTxList );
        }
    }

    synchronized void lockReleased( Object resource, Transaction tx )
    {
        List<Transaction> lockingTxList = resourceMap.get( resource );
        if ( lockingTxList == null )
        {
            throw new LockException( resource + " not found in resource map" );
        }

        if ( !lockingTxList.remove( tx ) )
        {
            throw new LockException( tx + "not found in locking tx list" );
        }
        if ( lockingTxList.size() == 0 )
        {
            resourceMap.remove( resource );
        }
    }

    synchronized void stopWaitOn( Object resource, Transaction tx )
    {
        if ( waitingTxMap.remove( tx ) == null )
        {
            throw new LockException( tx + " not waiting on " + resource );
        }
    }

    // after invoke the transaction must wait on the resource
    synchronized void checkWaitOn( Object resource, Transaction tx )
        throws DeadlockDetectedException
    {
        List<Transaction> lockingTxList = resourceMap.get( resource );
        if ( lockingTxList == null )
        {
            throw new LockException( "Illegal resource[" + resource
                + "], not found in map" );
        }

        if ( waitingTxMap.get( tx ) != null )
        {
            throw new LockException( tx + " already waiting for resource" );
        }

        Iterator<Transaction> itr = lockingTxList.iterator();
        List<Transaction> checkedTransactions = new LinkedList<Transaction>();
        Stack<Object> graphStack = new Stack<Object>();
        // has resource,transaction interleaved
        graphStack.push( resource );
        while ( itr.hasNext() )
        {
            Transaction lockingTx = itr.next();
            // the if statement bellow is valid because:
            // t1 -> r1 -> t1 (can happened with RW locks) is ok but,
            // t1 -> r1 -> t1&t2 where t2 -> r1 is a deadlock
            // think like this, we have two transactions and one resource
            // o t1 takes read lock on r1
            // o t2 takes read lock on r1
            // o t1 wanna take write lock on r1 but has to wait for t2
            // to release the read lock ( t1->r1->(t1&t2), ok not deadlock yet
            // o t2 wanna take write lock on r1 but has to wait for t1
            // to release read lock....
            // DEADLOCK t1->r1->(t1&t2) and t2->r1->(t1&t2) ===>
            // t1->r1->t2->r1->t1, t2->r1->t1->r1->t2 etc...
            // to allow the first three steps above we check if lockingTx ==
            // waitingTx on first level.
            // because of this special case we have to keep track on the
            // already "checked" tx since it is (now) legal for one type of
            // circular reference to exist (t1->r1->t1) otherwise we may
            // traverse t1->r1->t2->r1->t2->r1->t2... until SOE
            // ... KISS to you too
            if ( lockingTx.equals( tx ) )
            {
                continue;
            }
            graphStack.push( tx );
            checkWaitOnRecursive( lockingTx, tx, checkedTransactions,
                graphStack );
            graphStack.pop();
        }

        // ok no deadlock, we can wait on resource
        waitingTxMap.put( tx, resource );
    }

    private synchronized void checkWaitOnRecursive( Transaction lockingTx,
        Transaction waitingTx, List<Transaction> checkedTransactions,
        Stack<Object> graphStack ) throws DeadlockDetectedException
    {
        if ( lockingTx.equals( waitingTx ) )
        {
            StringBuffer circle = null;
            Object resource = null;
            do
            {
                lockingTx = (Transaction) graphStack.pop();
                resource = graphStack.pop();
                if ( circle == null )
                {
                    circle = new StringBuffer();
                    circle.append( lockingTx + " <- " + resource );
                }
                else
                {
                    circle.append( " <- " + lockingTx + " <- " + resource );
                }
            }
            while ( !graphStack.isEmpty() );
            deadlockCount.incrementAndGet();
            throw new DeadlockDetectedException( waitingTx +
                " can't wait on resource " + resource + " since => " + circle );
        }
        checkedTransactions.add( lockingTx );
        Object resource = waitingTxMap.get( lockingTx );
        if ( resource != null )
        {
            graphStack.push( resource );
            // if the resource doesn't exist in resorceMap that means all the
            // locks on the resource has been released
            // it is possible when this tx was in RWLock.acquire and
            // saw it had to wait for the lock the scheduler changes to some
            // other tx that will release the locks on the resource and
            // remove it from the map
            // this is ok since current tx or any other tx will wake
            // in the synchronized block and will be forced to do the deadlock
            // check once more if lock cannot be acquired
            List<Transaction> lockingTxList = resourceMap.get( resource );
            if ( lockingTxList != null )
            {
                Iterator<Transaction> itr = lockingTxList.iterator();
                while ( itr.hasNext() )
                {
                    lockingTx = itr.next();
                    // so we don't
                    if ( !checkedTransactions.contains( lockingTx ) )
                    {
                        graphStack.push( lockingTx );
                        checkWaitOnRecursive( lockingTx, waitingTx,
                            checkedTransactions, graphStack );
                        graphStack.pop();
                    }
                }
            }
            graphStack.pop();
        }
    }

    synchronized void dumpStack()
    {
        System.out.print( "Waiting list: " );
        Iterator<Transaction> transactions = waitingTxMap.keySet().iterator();
        if ( !transactions.hasNext() )
        {
            System.out.println( "No transactions waiting on resources" );
        }
        else
        {
            System.out.println();
        }
        while ( transactions.hasNext() )
        {
            Transaction tx = transactions.next();
            System.out.println( "" + tx + "->" + waitingTxMap.get( tx ) );
        }
        System.out.print( "Resource lock list: " );
        Iterator<?> resources = resourceMap.keySet().iterator();
        if ( !resources.hasNext() )
        {
            System.out.println( "No locked resources found" );
        }
        else
        {
            System.out.println();
        }
        while ( resources.hasNext() )
        {
            Object resource = resources.next();
            System.out.print( "" + resource + "->" );
            Iterator<Transaction> itr = resourceMap.get( resource ).iterator();
            if ( !itr.hasNext() )
            {
                System.out.println( " Error empty list found" );
            }
            while ( itr.hasNext() )
            {
                System.out.print( "" + itr.next() );
                if ( itr.hasNext() )
                {
                    System.out.print( "," );
                }
                else
                {
                    System.out.println();
                }
            }
        }
    }

    Transaction getCurrentTransaction()
    {
        try
        {
            return tm.getTransaction();
        }
        catch ( SystemException e )
        {
            throw new TransactionFailureException(
                "Could not get current transaction.", e );
        }
    }
}