/*
 * Created on Jul 19, 2006 10:16:26 PM
 * Copyright (C) 2006 Aelitis, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * AELITIS, SAS au capital de 46,603.30 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 */
package com.aelitis.azureus.ui.swt.browser.txn;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

import com.aelitis.azureus.core.messenger.ClientMessageContext;

/**
 * Manages the context for a single SWT {@link Browser} component.
 * 
 * @author dharkness
 * @created Jul 19, 2006
 */
public class TransactionManager
{
    private ClientMessageContext context;
    
    // { "type" -> Constructor }
    private Map txnCtors = new HashMap();

    // { "type" -> Transaction }
    private Map txns = new HashMap();

    private int lastTxnId = 0;


    public TransactionManager ( ClientMessageContext context ) {
        this.context = context;
    }


    /**
     * Returns the current transaction of the given type.
     * 
     * @param type used to locate the transaction
     * @return the transaction or <code>null</code> if none exists
     */
    public synchronized Transaction getTransaction ( String type ) {
        return (Transaction) txns.get(type);
    }
    
    /**
     * Starts a new transaction of the given type after canceling
     * the current transaction if any.
     * 
     * @param type used to determine the transaction class to create
     * @return a new started transaction
     */
    public synchronized Transaction startTransaction ( String type ) {
        cancelTransaction(type);
        
        Transaction txn = createTransaction(type);
        if ( txn != null ) {
            if ( ! txn.start() ) {
                return null;
            }
            
            txns.put(type, txn);
        }
        
        return txn;
    }

    /**
     * Cancels the current transaction if there is one for this listener 
     * and the browser in the given message.
     * 
     * @param type used to determine the transaction to return
     * @return the current transaction or <code>null</code> if none exists
     */
    public synchronized Transaction cancelTransaction ( String type ) {
        Transaction txn = getTransaction(type);
        if ( txn != null ) {
            txn.cancel();
        }
        
        return txn;
    }

    /**
     * Removes the given transaction from current status if it's current.
     * 
     * @param txn the Transaction to be removed
     * @return <code>true</code> if the transaction was current; <code>false</code> otherwise
     */
    synchronized boolean removeTransaction ( Transaction txn ) {
        if ( txn != getTransaction(txn.getType()) ) {
            return false;
        }
        
        return txns.remove(txn.getType()) == txn;
    }


    /**
     * Registers the given transaction subclass as that to be created
     * for the given type identifier.
     * 
     * @param clazz used when creating transactions
     * 
     * @throws IllegalArgumentException if txnClass is null or not a subclass
     *              of {@link Transaction}
     * @throws IllegalArgumentException if the appropriate constructor 
     *              of txnClass cannot be found or accessed 
     */
    public void registerTransactionType ( String type , Class clazz ) {
        if ( clazz == null ) {
            throw new IllegalArgumentException("Transaction class must be non-null");
        }
        if ( ! Transaction.class.isAssignableFrom(clazz) ) {
            throw new IllegalArgumentException("Transaction class " + clazz.getName() 
                    + " must be a subclass of Transaction");
        }
        
        try {
            Class[] ctorParams = 
                    new Class[] { 
                        Integer.TYPE, 
                        String.class, 
                        ClientMessageContext.class 
                    };
            Constructor ctor = clazz.getConstructor(ctorParams);
            txnCtors.put(type, ctor);
        }
        catch ( Exception e ) {
            throw new IllegalArgumentException(
                    "Cannot access appropriate constructor for " 
                    + clazz.getName());
        }
    }

    /**
     * Creates a new transaction for this listener and the browser 
     * in the given message. If no transaction class was specified
     * in the constructor, subclasses must override this method.
     * 
     * @param message holds a reference to the browser
     * @return a new transaction
     */
    protected Transaction createTransaction ( String type ) {
        Constructor ctor = (Constructor) txnCtors.get(type);
        if ( ctor == null ) {
            throw new IllegalStateException("Unregistered transaction type: " + type);
        }
        
        try {
            Object[] params = 
                    new Object[] { 
                        new Integer(getNextTransactionId()), 
                        type, 
                        context
                    };
            return (Transaction) ctor.newInstance(params);
        }
        catch ( Exception e ) {
            throw new RuntimeException("Exception creating transaction for type " + type, e);
        }
    }

    /**
     * Increments the last transaction ID and returns the new value.
     * 
     * @return a unique ID to use for a new transaction
     */
    private int getNextTransactionId ( ) {
        return ++lastTxnId;
    }
}
