/*--- 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 Broadcom Eireann Research.
 * 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.core.Profile;
import jade.mtp.TransportAddress;
import jade.imtp.leap.*;
import java.io.*;
import java.net.*;

/**
 * Class declaration
 * @author Giovanni Caire - TILAB
 * @author Jamie Lawrence - Broadcom Eireann Research
 * @author Ronnie Taib - Motorola
 * @author Nicolas Lhuillier - Motorola
 */
public class JICPPeer implements ICP, ProtocolManager {
	private static final int POOL_SIZE = 50;

	private JICPClient   client;
	private JICPServer   server;
	private Ticker       ticker;

	private String myID;

	private int connectionTimeout;

	public static final String CONNECTION_TIMEOUT = "jade_imtp_leap_JICP_JICPPeer_connectiontimeout";
	public static final String READ_TIMEOUT = "jade_imtp_leap_JICP_JICPPeer_readtimeout";
	/**
	 * Start listening for internal platform messages on the specified port
	 */
	public TransportAddress activate(ICP.Listener l, String peerID, Profile p) throws ICPException {
		myID = peerID;

		connectionTimeout = Integer.parseInt(p.getParameter(CONNECTION_TIMEOUT, "20000"));
		int readTimeout = Integer.parseInt(p.getParameter(READ_TIMEOUT, "30000"));

		// Start the client
		client = new JICPClient(getProtocol(), getConnectionFactory(), POOL_SIZE, readTimeout);

		// Start the server listening for connections
		server = new JICPServer(p, this, l, getConnectionFactory(), POOL_SIZE);
		server.start();

		// Start the Ticker
		ticker = new Ticker(60000);
		ticker.start();

		// Creates the local transport address
		TransportAddress localTA = getProtocol().buildAddress(server.getLocalHost(), String.valueOf(server.getLocalPort()), null, null);

		return localTA;
	} 

	/**
	 * stop listening for internal platform messages
	 */
	public void deactivate() throws ICPException {
		if (server != null) {
			client.shutdown();
			server.shutdown();
			ticker.shutdown();
		} 
		else {
			throw new ICPException("No external listener was activated.");
		} 
	} 

	/**
	 * deliver a serialized command to a given transport address
	 */
	public byte[] deliverCommand(TransportAddress ta, byte[] payload, boolean requireFreshConnection) throws ICPException {
		byte[] respPayload = client.send(ta, JICPProtocol.COMMAND_TYPE, payload, requireFreshConnection);

		return (respPayload);
	} 

	final String getID() {
		return myID;
	}

	/**
	 * Pings the specified transport address in order to obtain
	 * the local hostname or IP address.
	 * @param pingURL The <code>URL</code> to ping (usually the
	 * main container).
	 * @return The local IP address of the local container as a
	 * <code>String</code>.
	 * 
	 * @throws ICPException
	 */
	String getAddress(String pingURL) throws ICPException {
		byte[] respPayload = null;

		try {
			TransportAddress pingAddr = getProtocol().stringToAddr(pingURL);

			respPayload = client.send(pingAddr, JICPProtocol.GET_ADDRESS_TYPE, new byte[0], false);
		} 
		catch (ICPException icpe) {
			throw new ICPException("JICP GET_ADDRESS error. Cannot retrieve local hostname: "
					+icpe.getMessage());
		} 

		return (new String(respPayload));
	} 

	/**
     Subclasses may re-define this method to return their own
     protocol
	 */
	public TransportProtocol getProtocol() {
		return JICPProtocol.getInstance();
	} 

	/**
     Subclasses may re-define this method to return their own
     ConnectionFactory
	 */
	public ConnectionFactory getConnectionFactory() {
		return new ConnectionFactory() {
			public Connection createConnection(Socket s) {
				return new JICPConnection(s);
			}
			public Connection createConnection(TransportAddress ta) throws IOException {
				JICPConnection con = new JICPConnection(ta, connectionTimeout);
				return con;
			}
		};
	}  

	protected ServerSocket getServerSocket(String host, int port, boolean changePortIfBusy) throws ICPException {
		try {
			return new ServerSocket(port, 50, (host != null ? InetAddress.getByName(host) : null));
		} 
		catch (SocketException be) {
			// HACK! We should do this only in case of a BindException. However some implementations 
			// of the JVM (particularly that for Windows 7 64 bits) seem to have a bug and throw a 
			// generic SocketException also in the case that the port is busy.
			if (changePortIfBusy) {
				// The specified port is busy. Let the system find a free one
				try {
					return new ServerSocket(0, 50, (host != null ? InetAddress.getByName(host) : null));
				} catch (IOException ioe) {
					throw new ICPException("Cannot create server socket on a free port. ", ioe);
				}
			} else {
				throw new ICPException("Cannot bind server socket to "+(host != null ? "host "+host : "localhost")+ " port " + port);
			}
		} catch (IOException ioe2) {
			throw new ICPException("Cannot create server socket. ", ioe2);
		}
	}

	/**
     Inner class Ticker
	 */
	private class Ticker extends Thread {
		private long period;
		private boolean active = false;

		private Ticker(long period) {
			super();
			this.period = period;
		}

		public void start() {
			active = true;
			super.start();
		}

		public void run() {
			while (active) {
				try {
					Thread.sleep(period);
					long currentTime = System.currentTimeMillis();
					client.tick(currentTime);
					server.tick(currentTime);
				}
				catch (InterruptedException ie) {
				}
			}
		}

		public void shutdown() {
			active = false;
			interrupt();
		}
	} // END of inner class Ticker
}

