package org.seasar.extension.dbcp.impl;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.sql.XADataSource;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.seasar.extension.dbcp.ConnectionPool;
import org.seasar.extension.dbcp.ConnectionWrapper;
import org.seasar.extension.timer.TimeoutManager;
import org.seasar.extension.timer.TimeoutTarget;
import org.seasar.extension.timer.TimeoutTask;
import org.seasar.framework.log.Logger;
import org.seasar.framework.util.SLinkedList;
import org.seasar.framework.util.TransactionManagerUtil;
import org.seasar.framework.util.TransactionUtil;

public final class ConnectionPoolImpl
	implements ConnectionPool, Synchronization {

	private static Logger logger_ = Logger.getLogger(ConnectionPoolImpl.class);
	private XADataSource xaDataSource_;
	private TransactionManager transactionManager_;
	private int timeout_ = 600;
	private int maxPoolSize_ = 10;
	private Set activePool_ = new HashSet();
	private Map txActivePool_ = new HashMap();
	private SLinkedList freePool_ = new SLinkedList();

	public ConnectionPoolImpl() {
	}

	public XADataSource getXADataSource() {
		return xaDataSource_;
	}

	public void setXADataSource(XADataSource xaDataSource) {
		xaDataSource_ = xaDataSource;
	}

	public TransactionManager getTransactionManager() {
		return transactionManager_;
	}

	public void setTransactionManager(TransactionManager transactionManager) {
		transactionManager_ = transactionManager;
	}

	public int getTimeout() {
		return timeout_;
	}

	public void setTimeout(int timeout) {
		timeout_ = timeout;
	}

	public int getMaxPoolSize() {
		return maxPoolSize_;
	}

	public void setMaxPoolSize(int maxPoolSize) {
		maxPoolSize_ = maxPoolSize;
	}

	public int getActivePoolSize() {
		return activePool_.size();
	}

	public int getTxActivePoolSize() {
		return txActivePool_.size();
	}

	public int getFreePoolSize() {
		return freePool_.size();
	}

	public synchronized ConnectionWrapper checkOut() throws SQLException {
		Transaction tx = getTransaction();
		ConnectionWrapper con = getConnectionTxActivePool(tx);
		if (con != null) {
			con.init();
			return con;
		}
		while (getActivePoolSize() + getTxActivePoolSize()
			>= getMaxPoolSize()) {
			try {
				wait();
			} catch (InterruptedException ignore) {
			}
		}
		con = checkOutFreePool();
		if (con == null) {
			con = createConnection();
		} else {
			con.init();
		}
		if (tx != null) {
			TransactionUtil.enlistResource(tx, con.getXAResource());
			TransactionUtil.registerSynchronization(tx, this);
			setConnectionTxActivePool(tx, con);
		} else {
			setConnectionActivePool(con);
		}
		return con;
	}

	private Transaction getTransaction() {
		return TransactionManagerUtil.getTransaction(transactionManager_);
	}

	private ConnectionWrapper getConnectionTxActivePool(Transaction tx) {
		return (ConnectionWrapper) txActivePool_.get(tx);
	}

	private ConnectionWrapper checkOutFreePool() throws SQLException {
		if (freePool_.isEmpty()) {
			return null;
		}
		FreeItem item = (FreeItem) freePool_.removeLast();
		ConnectionWrapper con = item.getConnection();
		item.destroy(); 
		return con;
	}

	private ConnectionWrapper createConnection() throws SQLException {
		ConnectionWrapper con =
			new ConnectionWrapperImpl(xaDataSource_.getXAConnection(), this);
		logger_.log("DSSR0006", null);
		return con;
	}

	private void setConnectionTxActivePool(
		Transaction tx,
		ConnectionWrapper connection) {

		txActivePool_.put(tx, connection);
	}

	private void setConnectionActivePool(ConnectionWrapper connection) {
		activePool_.add(connection);
	}

	public synchronized void release(ConnectionWrapper connection) {
		activePool_.remove(connection);
		Transaction tx = getTransaction();
		if (tx != null) {
			txActivePool_.remove(tx);
		}
		connection.closeReally();
		notify();
	}

	public synchronized void checkIn(ConnectionWrapper connection) {
		activePool_.remove(connection);
		Transaction tx = getTransaction();
		if (tx == null) {
			checkInFreePool(connection);
		}
		notify();
	}

	private void checkInFreePool(ConnectionWrapper connection) {
		freePool_.addLast(new FreeItem(connection));
	}

	public void afterCompletion(int status) {
		switch (status) {
			case Status.STATUS_COMMITTED :
			case Status.STATUS_ROLLEDBACK :
				checkInTx();
				break;
		}
	}

	private void checkInTx() {
		Transaction tx = getTransaction();
		if (tx == null) {
			return;
		}
		ConnectionWrapper con = checkOutTxPool(tx);
		if (con == null) {
			return;
		}
		checkInFreePool(con);
	}

	private ConnectionWrapper checkOutTxPool(Transaction tx) {
		return (ConnectionWrapper) txActivePool_.remove(tx);
	}

	public final void beforeCompletion() {
	}

	public final synchronized void close() {
		for (SLinkedList.Entry e = freePool_.getFirstEntry();
			e != null;
			e = e.getNext()) {
			FreeItem item = (FreeItem) e.getElement();
			item.getConnection().closeReally();
		}
		freePool_.clear();
		for (Iterator i = txActivePool_.values().iterator(); i.hasNext();) {
			ConnectionWrapper con = (ConnectionWrapper) i.next();
			con.closeReally();
		}
		txActivePool_.clear();
		for (Iterator i = activePool_.iterator(); i.hasNext();) {
			ConnectionWrapper con = (ConnectionWrapper) i.next();
			con.closeReally();
		}
		activePool_.clear();
	}

	private class FreeItem implements TimeoutTarget {

		private ConnectionWrapper connectionWrapper_;
		private TimeoutTask timeoutTask_;

		FreeItem(ConnectionWrapper connectionWrapper) {
			connectionWrapper_ = connectionWrapper;
			timeoutTask_ =
				TimeoutManager.getInstance().addTimeoutTarget(
					this,
					timeout_,
					false);
		}

		public ConnectionWrapper getConnection() {
			return connectionWrapper_;
		}

		public void expired() {
			synchronized (freePool_) {
				freePool_.remove(this);
			}
			timeoutTask_.cancel();
			connectionWrapper_.closeReally();
		}
		
		public void destroy() {
			timeoutTask_.cancel();
			timeoutTask_ = null;
			connectionWrapper_ = null;
		}
	}
}
