/*
 * 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.core.messenger.browser;

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 org.eclipse.swt.browser.Browser} component.
 * 
 * @author dharkness
 * @created Jul 19, 2006
 */
public class BrowserTransactionManager
{
	private ClientMessageContext context;

	// { "type" -> Constructor }
	private Map txnCtors = new HashMap();

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

	private int lastTxnId = 0;

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

	/**
	 * 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 BrowserTransaction cancelTransaction(String type) {
		BrowserTransaction txn = getTransaction(type);
		if (txn != null) {
			txn.cancel();
		}

		return txn;
	}

	/**
	 * 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 BrowserTransaction 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 (BrowserTransaction) 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;
	}

	/**
	 * 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 BrowserTransaction getTransaction(String type) {
		return (BrowserTransaction) txns.get(type);
	}

	/**
	 * 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 BrowserTransaction}
	 * @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 (!BrowserTransaction.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());
		}
	}

	/**
	 * 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(BrowserTransaction txn) {
		if (txn != getTransaction(txn.getType())) {
			return false;
		}

		return txns.remove(txn.getType()) == txn;
	}

	/**
	 * 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 BrowserTransaction startTransaction(String type) {
		cancelTransaction(type);

		BrowserTransaction txn = createTransaction(type);
		if (txn != null) {
			if (!txn.start()) {
				return null;
			}

			txns.put(type, txn);
		}

		return txn;
	}
}
