/*--- 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

//#J2ME_EXCLUDE_BEGIN
import jade.core.messaging.DeliveryTracing;
//#J2ME_EXCLUDE_END
import jade.imtp.leap.ICPException;
import jade.imtp.leap.TransportProtocol;
import jade.mtp.TransportAddress;
import jade.util.Logger;

import java.io.EOFException;
import java.io.IOException;

/**
 * Class declaration
 * @author Giovanni Caire - TILAB
 * @author Ronnie Taib - Motorola
 * @author Steffen Rusitschka - Siemens
 */
class JICPClient {

	private TransportProtocol protocol;
	private ConnectionFactory connFactory;
	private ConnectionPool pool;
	private int readTimeout;
	private static Logger log = Logger.getMyLogger(JICPClient.class.getName());

	/**
	 * Constructor declaration
	 */
	public JICPClient(TransportProtocol tp, ConnectionFactory f, int max, int rt) {
		protocol = tp;
		connFactory = f;
		pool = new ConnectionPool(protocol, connFactory, max);
		readTimeout = rt;
	} 

	/**
	 * Send a command to this transport address
	 * @param ta the address to send the command to
	 * @param dataType the type of data as defined in the JICPPeer
	 * @param data the command
	 * @return a byte array corresponding to the answer
	 * 
	 * @throws ICPException
	 */
	public byte[] send(TransportAddress ta, byte dataType, byte[] data, boolean requireFreshConnection) throws ICPException {
		ConnectionWrapper cw = null;
		boolean done = false;

		while (true) {
			try {
				// Acquire a connection wrapper from the pool
				cw = pool.acquire(ta, requireFreshConnection);
				manageReadTimeout(cw);
				
				// Prepare JICP information
				byte dataInfo = JICPProtocol.DEFAULT_INFO;
				if (cw.isOneShot()) {
					dataInfo |= JICPProtocol.TERMINATED_INFO;
				}

				// Get the actual connection and send the request
				Connection connection = cw.getConnection();
				JICPPacket request = new JICPPacket(dataType, dataInfo, ta.getFile(), data);
				
				long start = System.currentTimeMillis();
				connection.writePacket(request);

				// Read the reply
				JICPPacket reply = connection.readPacket();
				//#J2ME_EXCLUDE_BEGIN
				DeliveryTracing.setTracingInfo("Network-waiting-time", (System.currentTimeMillis() - start));
				DeliveryTracing.setTracingInfo("Reused-connection", cw.isReused());
				//#J2ME_EXCLUDE_END

				if (reply.getType() == JICPProtocol.ERROR_TYPE) {
					throw new ICPException(new String(reply.getData()));
				} 
				if ((reply.getInfo() & JICPProtocol.TERMINATED_INFO) != 0) {
					// The server cannot keep the connection open --> set it as one-shot
					cw.setOneShot();
				}
				pool.release(cw);

				done = true;
				byte[] bb = reply.getData();
				if (bb == null) {
					throw new ICPException("Null response from server");
				}
				return bb;
			} 
			catch (EOFException eof) {
				if (!cw.isReused()) {
					log.log(Logger.SEVERE, "EOF reached", eof);
					throw new ICPException("EOF reached");
				}
			} 
			catch (IOException ioe) {
				if (!cw.isReused()) {
					throw new ICPException("I/O error sending/receiving data to "+ta.getHost()+":"+ta.getPort(), ioe);
				}
			} 
			catch (ICPException icpe) {
				// Re-throw the exception
				throw icpe;
			} 
			catch (Exception e) {
				throw new ICPException("Problems in communication with "+ta.getHost()+":"+ta.getPort(), e);
			} 
			finally {  
				if (done) {
					if (cw.isOneShot()) {
						// Note that a connection can be marked as one-shot due to the fact that the server is not able to keep it open -->
						// In this case the connection was inserted in the pool at creation time and must now be removed
						pool.remove(cw);
					}
				}
				else {
					// Some error occurred --> The connection (if any) is no longer valid
					if (cw != null) {
						pool.remove(cw);
					}
				}
			}
		}
	} 
	
	private void manageReadTimeout(ConnectionWrapper cw) {
		if (cw.isReused()) {
			Connection c = cw.getConnection();
			if (c instanceof JICPConnection && readTimeout > 0) {
				try {
					((JICPConnection) c).setReadTimeout(readTimeout);
				}
				catch (IOException e) {
					try {
						log.log(Logger.WARNING, "Cannot set read-timeout on reused connection to "+c.getRemoteHost());
					}
					catch (Exception e1) {
						log.log(Logger.WARNING, "Cannot set read-timeout on reused connection");
					}
				}
			}
		}
	}

	public void shutdown() {
		pool.shutdown();
	}

	/**
	 Called by the JICPPeer ticker at each tick
	 */
	public void tick(long currentTime) {
		pool.clearExpiredConnections(currentTime);
	}
}
