/*--- formatted by Jindent 2.1, (www.c-lab.de/~jindent) ---*/

/**
 * ***************************************************************
 * The LEAP libraries, when combined with certain JADE platform components,
 * provide a run-time environment for enabling FIPA agents to execute on
 * lightweight devices running Java. LEAP and JADE teams have jointly
 * designed the API for ease of integration and hence to take advantage
 * of these dual developments and extensions so that users only see
 * one development platform and a
 * single homogeneous set of APIs. Enabling deployment to a wide range of
 * devices whilst still having access to the full development
 * environment and functionalities that JADE provides.
 * Copyright (C) 2001 Telecom Italia LAB S.p.A.
 * Copyright (C) 2001 Motorola.
 * 
 * GNU Lesser General Public License
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation,
 * version 2.1 of the License.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 * **************************************************************
 */

package jade.imtp.leap.JICP;

//#MIDP_EXCLUDE_FILE

import jade.mtp.TransportAddress;
import java.io.*;
import jade.imtp.leap.*;
import jade.util.leap.*;
import jade.imtp.leap.http.HTTPProtocol;

/**
 * @author Giovanni Caire - TILAB
 */
class ConnectionPool {
	private HashMap connections = new HashMap();
	private TransportProtocol myProtocol;
	private ConnectionFactory myFactory;
	private int maxSize;
	private int size;
	private boolean closed = false;
	
	private long hitCnt = 0;
	private long missCnt = 0;
	
	ConnectionPool(TransportProtocol p, ConnectionFactory f, int ms) {
		myProtocol = p;
		myFactory = f;
		// Temporary hack for HTTP since HTTP connections cannot be re-used
		if (myProtocol instanceof HTTPProtocol) {
			maxSize = 0;
		}
		else {
			maxSize = ms;
		}
		size = 0;
	}
	
	// The actual connection creation operation must NOT be included in the synchronized block. In facts
	// in certain cases it may take a lot of time due to TCP timeouts expiration.
	ConnectionWrapper acquire(TransportAddress ta, boolean requireFreshConnection) throws ICPException {
		ConnectionWrapper cw = null;
		List l = null;
		String url = myProtocol.addrToString(ta);
		synchronized (this) {
			if (closed) {
				throw new ICPException("Pool closed");			
			}
			
			l = (List) connections.get(url);
			if (l == null) {
				l = new ArrayList();
				connections.put(url, l);
			}
			
			if (requireFreshConnection) {
				// We are checking a given destination. This means that this destination may be no longer valid
				// --> In order to avoid keeping invalid connections that can lead to very long waiting times, 
				// close all non-used connections towards this destination.
				closeConnections(l);
			}
			else {
				Iterator it = l.iterator();
				while (it.hasNext()) {
					cw = (ConnectionWrapper) it.next();
					if (cw.lock()) {
						cw.setReused();
						hitCnt++;
						return cw;
					}
				}
			}
		}
		
		// If we get here no connection is available --> create a new one
		try {
			Connection c = myFactory.createConnection(ta);
			synchronized (this) {
				cw = new ConnectionWrapper(c, ta);
				if (size < maxSize) {
					// Reusable connection --> Store it
					l.add(cw);
					size++;
				}
				else {
					// OneShot connection --> don't even store it. 
					cw.setOneShot();
				}
				missCnt++;
				return cw;
			}
		}
		catch (IOException ioe) {
			throw new ICPException("Error creating connection. ", ioe);
		}
		finally {
			// We may have created a new list of connections that end up to be useless (e.g. because the connection is one-shot,
			// or because there was an error creating the connection) --> remove it
			synchronized (this) {
				if (l.isEmpty()) {
					connections.remove(url);
				}
			}
		}
	}
	
	private void closeConnections(List l) {
		List closedConnections = new ArrayList();
		Iterator it = l.iterator();
		while (it.hasNext()) {
			ConnectionWrapper cw = (ConnectionWrapper) it.next();
			if (cw.lock()) {
				cw.close();
				cw.unlock();
				closedConnections.add(cw);
			}
		}
		// Now remove all closed connections
		it = closedConnections.iterator();
		while (it.hasNext()) {
			if (l.remove(it.next())) {
				size--;
			}
		}
	}

	synchronized void release(ConnectionWrapper cw) {
		cw.unlock();
	}
	
	synchronized void remove(ConnectionWrapper cw) {
		try {
			String url = myProtocol.addrToString(cw.getDestAddress());
			List l = (List) connections.get(url);
			if (l != null) {
				if (l.remove(cw)) {
					size--;
					if (l.isEmpty()) {
						connections.remove(url);
					}
				}
			}
			cw.getConnection().close();
		}
		catch (Exception e) {
			// Just ignore it
		}
	}
	
	synchronized void shutdown() {
		Iterator it = connections.values().iterator();
		
		while (it.hasNext()) {
			List l = (List) it.next();
			for (int i = 0; i < l.size(); i++) {
				ConnectionWrapper cw = (ConnectionWrapper) l.get(i);
				cw.close();
			}
			l.clear();
		} 
		connections.clear();
		closed = true;
	}
	
	void clearExpiredConnections(long currentTime) {
		Iterator it = getConnectionsList().iterator();
		while (it.hasNext()) {
			ConnectionWrapper cw = (ConnectionWrapper) it.next();
			if (cw.isExpired(currentTime)) {
				remove(cw);
				cw.unlock();
			}
		}
	}
	
	public String toString() {
		return "[Connection-pool: total-hit="+hitCnt+", total-miss="+missCnt+", current-size="+size+" connections="+connections+"]";
	}
	
	private synchronized List getConnectionsList() {
		List cc = new ArrayList();
		Iterator it = connections.values().iterator();
		while (it.hasNext()) {
			List l = (List) it.next();
			Iterator it1 = l.iterator();
			while (it1.hasNext()) {
				cc.add(it1.next());
			}
		}
		return cc;
	}
}

