/**
 * 
 * JADE - Java Agent DEvelopment Framework is a framework to develop
 * multi-agent systems in compliance with the FIPA specifications.
 * Copyright (C) 2000 CSELT S.p.A.
 *
 * 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;

//#APIDOC_EXCLUDE_FILE
//#J2ME_EXCLUDE_FILE

import java.net.InetAddress;

import java.util.Enumeration;    // J2ME CLDC OK
import java.util.Vector;         // J2ME CLDC OK
import jade.util.leap.List;
import jade.util.leap.ArrayList;
import jade.util.leap.Iterator;
import jade.util.leap.Properties;
import jade.util.ExtendedProperties;
import jade.util.PropertiesException;

import jade.core.ProfileImpl;
import jade.core.Profile;
import jade.core.Specifier;

/**
 * A profile implementation enhanced to support boot's
 * argument parsing. This class serves as the bridge between
 * boot properties and profile properties. It defines a
 * collection of property keys which correspond to boot
 * argument names. These are used to access the boot properties
 * from the argument properties. The class Profile defines
 * a similar collection of keys which are used to access profile
 * properties.
 * @author Dick Cowan - HP Labs
 * @version $Date: 2011-03-21 14:10:30 +0100(lun, 21 mar 2011) $ $Revision: 6395 $
 */
public class BootProfileImpl extends ProfileImpl {

	public static final String ACLCODEC_KEY = "aclcodec";
	public static final String CONF_KEY = "conf";
	public static final String CONTAINER_KEY = "container";
	public static final String DUMP_KEY = "dump";
	public static final String GUI_KEY = "gui";
	public static final String HELP_KEY = "help";
	public static final String MTP_KEY = "mtp";
	public static final String NOMTP_KEY = "nomtp";
	public static final String NAME_KEY = "name";
	public static final String LOGIN_KEY = "auth";
	public static final String SMHOST_KEY = "smhost";
	public static final String SMPORT_KEY = "smport";
	public static final String VERSION_KEY = "version";
	public static final String NOMOBILITY_KEY = "nomobility";



	ExtendedProperties argProp = null;
	BootHelper helper = new BootHelper();

	/**
	 * Construct default profile with empty argument properties
	 */
	public BootProfileImpl() {
		// creates the default profile
		super(true); 
		argProp = new ExtendedProperties();
	}

	/**
	 * Construct profile with specified arguments
	 * @param args Boot arguments
	 */
	public BootProfileImpl(String[] args) throws PropertiesException {
		this();
		ExtendedProperties properties = new ExtendedProperties(args);
		bootProps = (Properties)properties.clone();
		setArgProperties(properties);
	}        

	/**
	 * Return the properties collection which resulted from the arguments.
	 * This collection is used to create/modify the underlying profile's
	 * properties.
	 * @return ExtendedProperties The argument property collection.
	 */
	public ExtendedProperties getArgProperties() {
		return argProp;
	}

	/**
	 * Copy a collection of argument properties into the existing
	 * argument properties and then into the profile properties.
	 * When moving between the argument properties and profile
	 * properties different keys are required.
	 * @param source A collection of argument properties. The
	 * keys to this collection are from the XXX_KEY strings
	 * defined in this class.
	 */
	public void setArgProperties(ExtendedProperties source) {
		argProp.copyProperties(source);
		String value = null;
		boolean flag = false;


		// Transfer argument properties into profile properties

		ExtendedProperties profileProp = (ExtendedProperties)getProperties();

		boolean isMain = true;
		if (argProp.getProperty(Profile.MAIN) != null) {
			isMain = fetchAndVerifyBoolean(Profile.MAIN);
		}
		else {
			isMain = !fetchAndVerifyBoolean(CONTAINER_KEY);
		}
		if (isMain) {
			profileProp.setProperty(Profile.MAIN, "true");
		}
		else {
			profileProp.setProperty(Profile.MAIN, "false");
			// Since the value is false, we cancel the default done in ProfileImpl's constructor
			setSpecifiers(Profile.MTPS, new ArrayList(0)); // remove default MTP
		}

		String sm = argProp.getProperty(LOCAL_SERVICE_MANAGER);
		if(sm != null) {
			profileProp.setProperty(Profile.LOCAL_SERVICE_MANAGER, sm);
		}

		/* PER IL DF
        value = argProp.getProperty(df.KBDF_MAX_RESULTS);
        if (value != null) {
            profileProp.setProperty(df.KBDF_MAX_RESULTS, value);
        }
        value = argProp.getProperty(df.DBDF_DRIVER);
        if (value != null) {
            profileProp.setProperty(df.DBDF_DRIVER, value);
        }
        value = argProp.getProperty(df.DBDF_URL);
        if (value != null) {
            profileProp.setProperty(df.DBDF_URL, value);
        }*/

		/*
        value = argProp.getProperty(PASSWD_KEY);
        if (value != null) {
            //profileProp.setProperty(Profile.PASSWD_FILE, value);
        }
		 */

		value = argProp.getProperty(IMTP);
		if (value != null) {
			profileProp.setProperty(Profile.IMTP, value);
		}

		String host = argProp.getProperty(MAIN_HOST);
		if (host != null) {
			profileProp.setProperty(Profile.MAIN_HOST, host);
		} else {
			host = profileProp.getProperty(Profile.MAIN_HOST);
			if (host == null) {
				host = getDefaultNetworkName();
				profileProp.setProperty(Profile.MAIN_HOST, host);
			}
		}

		String port = argProp.getProperty(MAIN_PORT);
		if (port == null) {
			// Default for a sole main container: use the local port, or
			// the default port if also the local port is null.
			if(isMasterMain()) {
				port = argProp.getProperty(LOCAL_PORT);
			}
			if(port == null) {
				// All other cases: use the default port.
				port = Integer.toString(DEFAULT_PORT);
			}
		}
		profileProp.setProperty(Profile.MAIN_PORT, port);

		String localHost = argProp.getProperty(LOCAL_HOST);
		if(localHost == null) {
			// Default for a sole main container: use the MAIN_HOST property
			if(isMasterMain()) {
				localHost = host;
			}
			else {
				// Default for a peripheral container or an added main container: use the local host
				localHost = getDefaultNetworkName();
			}
		}
		profileProp.setProperty(Profile.LOCAL_HOST, localHost);

		String localPort = argProp.getProperty(LOCAL_PORT);
		if(localPort == null) {
			// Default for a sole main container: use the MAIN_PORT property
			if(isMasterMain()) {
				localPort = port;
			}
			else {
				// Default for a peripheral container or an added main container: use the default port
				localPort = Integer.toString(DEFAULT_PORT);
			}
		}
		profileProp.setProperty(Profile.LOCAL_PORT, localPort);

		value = argProp.getProperty(REMOTE_SERVICE_MANAGER_ADDRESSES);
		if (value != null) {
			try {
				Vector v = Specifier.parseSpecifierList(value);
				// Convert the Vector into a List
				List l = new ArrayList(v.size());
				Enumeration e = v.elements();
				while (e.hasMoreElements()) {
					l.add(e.nextElement());
				}
				setSpecifiers(Profile.REMOTE_SERVICE_MANAGER_ADDRESSES, l);
			}
			catch(Exception e) {
				e.printStackTrace();
			}
		}

		value = argProp.getProperty(NAME_KEY);
		if (value != null) {
			profileProp.setProperty(Profile.PLATFORM_ID, value);
		}  

		value = argProp.getProperty(LOGIN_KEY);
		if (value != null) {
			profileProp.setProperty(Profile.USERAUTH_KEY, value);
		}  

		value = argProp.getProperty(MTP_KEY);
		if (value != null) {
			setSpecifiers(Profile.MTPS, parseSpecifiers(value));
		}

		//NOMTP
		flag = fetchAndVerifyBoolean(NOMTP_KEY);
		if (flag) {
			// Since the value was set to true, cancel the MTP settings
			setSpecifiers(Profile.MTPS, new ArrayList(0));
		}

		value = argProp.getProperty(ACLCODEC_KEY);
		if (value != null) {
			setSpecifiers(Profile.ACLCODECS, parseSpecifiers(value));
		}

		// Get agent list (if any)
		value = argProp.getProperty(AGENTS);


		flag = fetchAndVerifyBoolean(GUI_KEY);
		if (flag) {
			// need to run RMA agent
			if (value != null) {
				value = "RMA:jade.tools.rma.rma " + value;  // put before other agents
			} else {
				value = "RMA:jade.tools.rma.rma";  // only one
			}
		}

		if (value != null) {
			Vector agentVector = helper.T2(value, false);
			List agents = new ArrayList();

			for (Enumeration e = helper.getCommandLineAgentSpecifiers(agentVector);
			e.hasMoreElements(); ) {
				agents.add((Specifier) e.nextElement());
			}

			setSpecifiers(Profile.AGENTS, agents);
		}

		// Get service list (if any)
		value = argProp.getProperty(SERVICES);
		if(value == null) {
			// Remove mobility service from the list if '-nomobility' was specified
			flag = fetchAndVerifyBoolean(NOMOBILITY_KEY);
			if (flag) {
				value = DEFAULT_SERVICES_NOMOBILITY;
			}
			else {
				value = DEFAULT_SERVICES;
			}
		}

		setSpecifiers(Profile.SERVICES, parseSpecifiers(value));


		// finally, copy into profileProp all the properties that
		// were present in argProp AND were not already present in profileProp.
		// The fact that we do not copy those properties that were already
		// present in profileProp is a sanity check to avoid modification
		// of properties that were set in the code above
		for (Enumeration e=argProp.keys(); e.hasMoreElements(); ) {
			String key = (String)e.nextElement();
			if (getParameter(key,null) == null) 
				setParameter(key, argProp.get(key).toString());
		}

		// The following is for debugging only. Probably should not document the "dumpProfile" attribute.
		// Note All the jade.util.leap.ArrayList structures will only print their type unless a
		// toString() method were added to them. 
		if (argProp.getBooleanProperty("dumpProfile", false)) {
			ArrayList aList = new ArrayList();
			System.out.println("---------- Jade Boot profile property values ----------");
			for (Enumeration e = profileProp.sortedKeys(); e.hasMoreElements(); ) {
				String key = (String) e.nextElement();
				Object o = profileProp.get(key);
				if (o.getClass().isAssignableFrom(aList.getClass())) {
					System.out.print(key + "=");
					ArrayList al = (ArrayList)o;
					Iterator itor = al.iterator();
					if (!itor.hasNext()) {
						System.out.println("<empty>");
					} else {
						StringBuffer sb = new StringBuffer();
						while (itor.hasNext()) {
							sb.append(itor.next());
							if (itor.hasNext()) {
								sb.append(" ");
							}
						}
						System.out.println(sb.toString());
					}
				} else {
					System.out.println(key + "=" + profileProp.getProperty(key));
				}
			}
			System.out.println("-------------------------------------------------------");
		}
	}

	/**
	 * Fetch and verify a boolean attribute.
	 * @param aKey The property key to check.
	 * @return True or false depending on the attributes setting. False if attribute doesn't exist.
	 * @throws PropertiesException if there is a value but its not either "true" or "false".
	 */
	protected boolean fetchAndVerifyBoolean(String aKey) throws PropertiesException {
		String value = argProp.getProperty(aKey);
		if (value != null) {
			if (value.equalsIgnoreCase("true")) {
				return true;
			}
			if (value.equalsIgnoreCase("false")) {
				return false;
			}
			throw new PropertiesException("The value of the attribute " + aKey + " must be either true or false.");
		}
		return false;
	}

	private static final String ARGUMENT_SEPARATOR = ";";

	/**
	 * Parse a String reading for a set of
	 * <code>parameter(arg)</code>
	 * each delimited by a <code>;</code> and no space in between.
	 * <p>
	 * For instance
	 * <code>jade.mtp.iiop(50);http.mtp.http(8080)</code> is a valid
	 * string, while  <code>jade.mtp.iiop(50 80);http.mtp.http(8080)</code>
	 * is not valid
	 * For each object specifier, a new java object <code>Specifier</code>
	 * is added to the passed <code>out</code> List parameter.
	 */
	public List parseSpecifiers(String str) throws PropertiesException {

		List result = new ArrayList();

		// Cursor on the given string: marks the parser position
		int cursor = 0;

		while (cursor < str.length()) {
			int commaPos = str.indexOf(ARGUMENT_SEPARATOR, cursor);

			if (commaPos == -1) {
				commaPos = str.length();
			}

			String arg = str.substring(cursor, commaPos);
			int openBracketPos = arg.indexOf('(');
			int closedBracketPos = arg.indexOf(')');
			Specifier s = new Specifier();

			if ((openBracketPos == -1) && (closedBracketPos == -1)) {

				// No brackets: no argument
				s.setClassName(arg);
			} else {

				// An open bracket, then something, then a closed bracket:
				// the class name is before the open bracket, and the
				// argument is between brackets.
				if ((openBracketPos != -1) && (closedBracketPos != -1)
						&& (openBracketPos < closedBracketPos)) {
					s.setClassName(arg.substring(0, openBracketPos));

					Object a[] = new Object[1];

					a[0] = arg.substring(openBracketPos + 1,
							closedBracketPos);

					s.setArgs(a);
				} else {
					throw new PropertiesException(
							"Ill-formed specifier: mismatched parentheses.");
				}
			}

			cursor = commaPos + 1;

			result.add(s);
		}    // while (cursor)
		return result;
	}
}

