package ws.prova;

/*
 * Copyright (C) 2002-2007 <a href="mailto:a.kozlenkov@city.ac.uk">Alex Kozlenkov</a>
 *
 * 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; either
 * version 2 of the License, or (at your option) any later version.
 *
 * 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
 */

import ws.prova.esb.ProvaUMO;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;

/**
 * Prova Communicator Agent
 * <p>Title: Prova </p>
 * <p>Description: Currently JADE-only  </p>
 * <p>Copyright: Copyright (c) 2005</p>
 * <p>Company: City University</p>
 *
 * @author Alex Kozlenkov
 * @version 2.0 <25 March 2007>
 */

public class Communicator {
	final public static boolean ASYNC = true;
	final public static boolean SYNC = false;
	public static String synch = "";
//	private String port;
//	private Object rules;
	private reagent prova;

	private Messenger messenger;

	private RMessageQueue queue;

	private static List workers = new ArrayList();

	synchronized public static void addWorker(String remote) {
		if (!workers.contains(remote)) {
			workers.add(remote);
		}
	}

	synchronized public static void removeWorker(String remote) {
		if (workers.contains(remote)) {
			workers.remove(remote);
		}
	}

	synchronized public static int numWorkers() {
		return workers.size();
	}

	synchronized public static Object getWorker(int n) {
		return workers.get(n);
	}

	/**
	 * Send a message to an asynchronous Prova agent
	 * (inluding a local <code>Communicator</code> created in asynchronous mode)
	 *
	 * @param xid          Conversation-id for the message. Specifying <code>null</code> starts a new
	 *                     conversation. Otherwise, it is assumed that the message is a follow-up to the conversation
	 *                     with the specified conversation-id.
	 * @param protocol     Currently, "self", "jade", or "jms". For sending messages
	 *                     to the Prova engine in this <code>Communicator</code>, specify "self".
	 * @param obj_receiver The logical name of the target agent. Specifying "0" here results
	 *                     in sending this message to the Prova agent in this <code>Communicator</code> (it is)
	 *                     equivalent to specifying the <code>protocol</code> as "self".
	 * @param perf         Performative, i.e., speech act communicated by the message.
	 *                     Broadly speaking, it is the "type" of the message. Examples include
	 *                     standard FIPA performatives such as <code>query-ref</code>, <code>ask-if</code>, or <code>inform</code>.
	 * @param term         The message contents as a <code>String</code>. It is assumed to be
	 *                     a comma-separated list with various tags and parameters to the message. Java objects
	 *                     may be embedded by using placeholders in the format <code>%N</code> with <code>N</code> corresponding to an index
	 *                     into the array with objects in <code>objs</code>.
	 * @param objs         Array with Java objects to be embedded in the locations specified by
	 *                     placeholders in the message contents <code>term</code>.
	 * @return Conversation-id of the sent message. This id may be useful when a new
	 *         conversation is initiated. In this case, possible follow-up messages must be sent
	 *         with the conversation-id returned here.
	 */
	public Object sendMsg(Object xid, String protocol, Object obj_receiver, String perf, String term, Object[] objs) {
		Object[] r = new Object[5];
		r[0] = (xid == null) ? ws.prova.util.DefaultLogicFactorySupport.variable("_") : xid;
		r[1] = protocol;
		r[2] = obj_receiver;
		r[3] = perf;
		r[4] = RListUtils.rlist('[' + term + ']', objs);
		List bindings = new ArrayList();
		messenger.sendMsg(r, bindings);
		Object newxid = ((Object[]) bindings.get(0))[0];
		return newxid;
	}

    public void rcvMsg() {

    }

    /**
	 * Partition the job represented by a list according to the number of available workers
	 *
	 * @param list
	 * @return the list of lists representing a list of jobs
	 */
	synchronized public static List partitionJob(List list) throws Exception {
		int numWorkers = numWorkers();
		if (numWorkers == 0 || list.size() < 2) {
			throw new Exception();
		}
		int each = list.size() / numWorkers;
		List res = new ArrayList();
		for (int i = 0; i < numWorkers; i++) {
			List one = new ArrayList();
			one.addAll(list.subList(0 + i * each, each + i * each));
			if (i == numWorkers - 1 && each + i * each < list.size()) {
				one.addAll(list.subList(each + i * each, list.size()));
			}
			res.add(one);
		}
		return res;
	}

	public Communicator(String agent, String port, Object rules) {
		this(agent, port, rules, -1, true);
	}

	/**
	 *
	 * @param agent
	 * @param port
	 * @param rules
	 * @param timeout
	 * @param async
	 * @param esb
	 */
	public Communicator(String agent, String port, Object rules, int timeout, boolean async, ProvaUMO esb ) {
		// Create one Connector agent
		String[] prot;
		if (port != null) {
			prot = new String[]{new String("jade")};
		} else {
			prot = null;
		}

		if (async) {
			queue = new RMessageQueue(agent, timeout);
			prova = new reagent(agent, port, queue, new TaskQueue(), prot, rules, async, esb);
			messenger = prova.getMessenger();
			// .. and run it in a thread
			Thread th = new Thread() {
				public void run() {
					prova.run();
					{
					}
				}
			};
			th.setContextClassLoader(Thread.currentThread().getContextClassLoader());
			th.start();
		} else {
			prova = new reagent(agent, port, null, null, prot, rules, async, esb);
//			prova.run();
		}
	}

	/**
	 * A preferred constructor for a Communicator.
	 *
	 * @param agent   A logical name of the Prova agent (must be unique on each machine)
	 * @param port    An external port used for JADE communications. If <code>port</code> is
	 *                <code>null</code>, JADE communication is not used
	 * @param rules   There are three options to use here:
	 *                <br> if the <code>Object</code> is a <code>String</code>, it must be a filename of
	 *                the file to be consulted.
	 *                <br> otherwise, it must be either a <code>StringBuffer</code> or a <code>BufferedReader</code>,
	 *                from which the initital rulebase will be consulted.
	 * @param timeout The Prova agent terminates once this timeout (in seconds) expires after
	 *                the last message. The value of "-1" lets the agent run indefinitely. In the synchronous mode,
	 *                the value of this parameter is ignored.
	 * @param async   If it is <code>Communicator.ASYNC</code>, the <code>Communicator</code> is started in a separate thread.
	 *                <br> If it is <code>Communicator.SYNC</code>, it is started in the current thread.
	 */
	public Communicator(String agent, String port, Object rules, int timeout, boolean async) {
		// Create one Connector agent
//		this.port = port;
//		this.rules = rules;
		String[] prot;
		if (port != null) {
			prot = new String[]{new String("jade")};
		} else {
			prot = null;
		}

		if (async) {
			queue = new RMessageQueue(agent, timeout);
			prova = new reagent(agent, port, queue, new TaskQueue(), prot, rules, async, null);
			messenger = prova.getMessenger();
			// .. and run it in a thread
			Thread th = new Thread() {
				public void run() {
					prova.run();
					{
					}
				}
			};
			th.setContextClassLoader(Thread.currentThread().getContextClassLoader());
			th.start();
		} else {
			prova = new reagent(agent, port, null, null, prot, rules, async, null);
//			prova.run();
		}
	}

	/**
	 * A wrapper for a synchronous query to the rulebase
	 *
	 * @param input   A fragment of Prova code that will be consulted into the Prova rulebase.
	 *                <br> Special constructs _N (e.g., _0) may be embedded in the code to represent Java
	 *                objects used in goals, facts, and rules.
	 * @param key     Unique key identifying the consulted code (useful in interactive environments)
	 * @param objects Java objects embedded into the <code>input</code> parameter.
	 * @return A list of <code>ws.prova.kernel.ProvaResultSet</code> objects,
	 *         where each result set corresponds to one goal in the consulted code.
	 *         For one goal, each resultset contains zero or more solutions with instantiations of all
	 *         free variables in that goal.
	 * @throws Exception
	 */
	public List consultSync(String input, String key, Object[] objects) throws Exception {
		String thname = Thread.currentThread().getName();
		Thread.currentThread().setName("ProvaSync");
		final List list = prova.consultSync(input, key, objects);
		Thread.currentThread().setName(thname);
		return list;
	}

	/**
	 * A wrapper for unconsulting Prova code given a key
	 *
	 * @param key <br> file name if we unconsult the code consulted with consult(Filename)
	 *            <br> the key with which the code was consulted if onsulted with consultSync
	 * @throws Exception
	 */
	public void unconsultSync(String key) throws Exception {
		consultSync(":- eval(retractall(_0)).", "", new Object[]{key});
	}

	/**
	 * @param output
	 */
	public void setPrintWriter(PrintWriter output) {
		prova.getCalc().setPrintWriter(output);
	}

	/**
	 * A wrapper for an asynchronous query to the rulebase
	 *
	 * @param input   A fragment of Prova code that will be consulted into the Prova rulebase.
	 *                <br> Special constructs _N (e.g., _0) may be embedded in the code to represent Java
	 *                objects used in goals, facts, and rules.
	 * @param objects Java objects embedded into the <code>input</code> parameter.
	 * @throws Exception
	 */
	public void consultAsync(String input, Object[] objects) throws Exception {
		prova.consultAsync(input, objects);
	}

    /**
     * Let Communicator know what is a ESB client object.
     * This is needed for messages sent over the "esb" protocol
     * @param client the object that created the Communicator and can send messages to the agent
     */
    public void setESB( ProvaUMO client ) {
        prova.getMessenger().setESB(client);
    }

    /**
     * Allows submitting the remote messages to the agent represented by an async Communicator
     * Normally, such RMessage is deserialized and made available to protocol interchange objects
     * such as Mule Universal Message Objects (UMO)
     * @param rMsg
     */
    public void addMsg( RMessage rMsg ) {
        queue.add(rMsg);
    }
}
