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 java.io.BufferedReader;
import java.io.File;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
import org.mandarax.kernel.Clause;
import org.mandarax.kernel.ComplexTerm;
import org.mandarax.kernel.ConstantTerm;
import org.mandarax.kernel.Fact;
import org.mandarax.kernel.Function;
import org.mandarax.kernel.InferenceException;
import org.mandarax.kernel.Predicate;
import org.mandarax.kernel.Prerequisite;
import org.mandarax.kernel.Query;
import org.mandarax.kernel.Replacement;
import org.mandarax.kernel.Result;
import org.mandarax.kernel.Rule;
import org.mandarax.kernel.SelectionPolicy;
import org.mandarax.kernel.SimplePredicate;
import org.mandarax.kernel.Term;
import org.mandarax.kernel.Unification;
import org.mandarax.kernel.UnificationAlgorithm;
import org.mandarax.kernel.VariableTerm;
import org.mandarax.reference.CautiousSemanticEvaluationPolicy;
import org.mandarax.reference.LeftMostSelectionPolicy;
import org.mandarax.reference.Resolution;
import org.mandarax.reference.TmpClause;
import org.mandarax.util.ClauseIterator;

import ws.prova.calc.CalcPredicate;
import ws.prova.jade.jade_exchange;
import ws.prova.kernel.ProvaResultSet;
import ws.prova.parser.AtomicConsult;
import ws.prova.parser.RulebaseParser;
import ws.prova.reference.FactImpl;
import ws.prova.reference.PrerequisiteImpl;
import ws.prova.reference.RBSLAKnowledgeBase;
import ws.prova.reference.ProvaResultSetImpl2;
import ws.prova.reference.RBSLARobinsonsUnificationAlgorithm;
import ws.prova.reference.ResolutionInferenceEngine6;
import ws.prova.sql.SQLExtendedPredicate;
import ws.prova.util.DefaultLogicFactorySupport;
import ws.prova.reactionrules.ReactionRulesUtils;
import ws.prova.esb.ProvaUMO;

/**
 * The main messaging and translation module
 * <p>Title: Prova</p>
 * <p>Description: The main messaging and translation module</p>
 * <p>Copyright: Copyright (C) 2002-2007</p>
 * <p>Company: City University, London, United Kingdom </p>
 *
 * @author Alex Kozlenkov
 * @version 2.0 <25 March 2007>
 */
public final class reagent
		extends DefaultLogicFactorySupport
//	implements IRListFactory
{
	public static final int JAVA_VERSION = 4;
	private jade_exchange je;
	private String agent;
	private String password;
	private String machine;

	// Protocols used for communication
	private List prot = new ArrayList();

//	private Map poly_predicates = new HashMap();

	private RBSLAKnowledgeBase kb = new RBSLAKnowledgeBase();
	final SelectionPolicy selectionPolicy = new LeftMostSelectionPolicy();
	final UnificationAlgorithm unificationAlgorithm = new
			RBSLARobinsonsUnificationAlgorithm();
	final SimplePredicate derivation = HelperPredicates.derivation();
	final static SimplePredicate derive = HelperPredicates.derive();
	final SimplePredicate conversation = HelperPredicates.conversation();
	final SimplePredicate conv_id = HelperPredicates.conv_id();
	final SimplePredicate actions = HelperPredicates.actions();
	final SimplePredicate actions2 = HelperPredicates.actions2();
	//	static SimplePredicate rule_actions = rule_actions();
	final SimplePredicate holds = HelperPredicates.holds();
	final SimplePredicate demo = HelperPredicates.demo();
	final static SimplePredicate state = HelperPredicates.state();
	final SimplePredicate solve_first = HelperPredicates.solve_first();
	final SimplePredicate solve = HelperPredicates.solve();
	final SimplePredicate eval = HelperPredicates.eval();
	final public static SimplePredicate fail = HelperPredicates.fail();
	final public static SimplePredicate restore_exhandler = HelperPredicates.restore_exhandler();
	final SimplePredicate consult = HelperPredicates.consult();
	final static SimplePredicate temporal_rule = HelperPredicates.temporal_rule();
	final static SimplePredicate temporal_rule_eoc = HelperPredicates.temporal_rule_eoc();
	final static SimplePredicate temporal_rule_build = HelperPredicates.temporal_rule_build();
	final static SimplePredicate temporal_rule_remove = HelperPredicates.temporal_rule_remove();
	//	final SimplePredicate append = HelperPredicates.append();
	public final static Function flist = HelperPredicates.list();
	final static Predicate invoke = HelperPredicates.invoke();
	final static Predicate adapt = HelperPredicates.adapt();
	final Predicate accumulate = HelperPredicates.accumulate();
	final Predicate accumulate2 = HelperPredicates.accumulate2();
	final static Predicate acc = HelperPredicates.acc();
	final Predicate acc2 = HelperPredicates.acc2();
	//	final Predicate diff = HelperPredicates.diff();
	public final CalcPredicate bcalc = kb.bcalc();

	Calc calc;

	final ResolutionInferenceEngine6 ie = new ResolutionInferenceEngine6(this);

//	static Random r = new Random();

	final List id_reps = new ArrayList();

	protected int unique_id = 0;
	protected RMessageQueue queue;
	protected TaskQueue tasks;
	protected TaskQueue tasksForRules;

	// From conversation-ID to SendData of the end_of_conversation message to be sent
	//    protected Map id_postponed;
	final private Map map_temptid = new HashMap();

//    // Current message
//    private RMessage current_msg;

	//	final static DataSource ds = null;
	final public SQLExtendedPredicate sql = kb.sqlExtendedPredicate();

//	private int numSolutions = 0;

	final static String placeholderPatternString = "%\\d+";

	Query query;
	Runnable swingThread;
//    final private Object rules;
	
	//ContractLog: add oid
	private int oidCounter = 0;

	final static Logger logger = Logger.getLogger(ws.prova.reagent.class);

	ReactionRulesUtils rrUtils = new ReactionRulesUtils();

	private ProvaUMO esb;

	Messenger messenger;

	Updater updater;

	private int goal_id = 0;
	private Map map_goal = new HashMap();

	private static ThreadLocal messageProcessed = new ThreadLocal();

	/**
	 * Create an instance of Prova runtime
	 *
	 * @param _agent
	 * @param _password
	 * @param _queue
	 * @param _tasks
	 * @param _prot
	 * @param rules
	 * @param async
	 * @param esb
	 */
	public reagent(String _agent, String _password, RMessageQueue _queue,
						TaskQueue _tasks, String[] _prot, Object rules, boolean async, ProvaUMO esb) {
		super();
//		Trace trace = new Trace();
		ie.setSemanticEvaluationPolicy(new CautiousSemanticEvaluationPolicy());
		agent = _agent;
		password = _password;
		try {
			machine = InetAddress.getLocalHost().getHostName().toLowerCase();
		} catch (UnknownHostException ex) {
		}
		tasks = _tasks;
		if (async)
			tasksForRules = new TaskQueue();
		queue = _queue;
		// Copy the names of the requested protocols into the list
		if (_prot != null) {
			for (int i = 0; i < _prot.length; i++) {
				prot.add(_prot[i]);
			}
		}
		if (prot.contains("jade")) {
			// Jade exchange is also in use now
			// . Create a platform and an agent
			// . 1101 is just the logical port (the actual port is given in 'password')
			je = new jade_exchange(machine, password, 1101, agent, this);
			// . Create an agent, but no platform
			//		jade_exchange je2 = new jade_exchange( "agent2", this );

			try {
				// Wait for 5 seconds
				Thread.sleep(5000);
			} catch (Exception e) {
				System.err.println("send: " + e);
				e.printStackTrace();
			}

		}

		kb.addPolyPredicate(accumulate.getName(), accumulate);
		kb.addPolyPredicate(acc.getName(), acc);

		messenger = new Messenger(this, kb, agent, password, machine, je, esb);

		updater = new Updater(this, kb);

		calc = new Calc(this);

		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				cleanUp();
			}
		});

		// Runnable to run Swing events in the Swing events thread
		swingThread = new Runnable() {
			public void run() {
				try {
					org.mandarax.kernel.ResultSet rs = ie.query(query, kb, ie.ALL,
							ie.BUBBLE_EXCEPTIONS);
				} catch (InferenceException exc) {
					exc.printStackTrace(System.out);
				}
			}
		};

		// Configure Prova log4j
//		BasicConfigurator.configure();
		File f = new File("log4j.xml");
		if (f.exists())
			// log4j configuration found
			DOMConfigurator.configure("log4j.xml");

//        this.rules = rules;

		initStandardKB(this, calc, kb, agent);

		if (rules != null && !rules.equals("")) {
			// Import prova rules from .prova file
			try {
				this.consultSync(":-eval(consult(_0)).", "", new Object[]{rules});
//            } catch (ParsingException pe) {
//			System.out.println( pe.errorsToString() );
			} catch (Exception e) {
				throw new RuntimeException(e.getMessage());
			}
		}

	}

	public Calc getCalc() {
		return calc;
	}

	public Object getPoly(final Object key) {
		return kb.getPoly(key);
	}

	/**
	 * Clean up by executing the shutdown predicate if it is in the rule base
	 */
	private void cleanUp() {
		RMessage msg = new RMessage("[0,eval,[shutdown]]");
		msg.setProtocol("self");
		msg.assign_conversation_id(agent);
		Fact queryfact = fact(eval, RListUtils.rlist(msg.content(), msg.objs()));
		Query query = query(queryfact, "eval");
		try {
			org.mandarax.kernel.ResultSet rs = ie.query(query, kb, /*ie.ALL*/ 0,
					ie.BUBBLE_EXCEPTIONS);
		} catch (InferenceException exc) {
			exc.printStackTrace(System.out);
		}
	}

	/**
	 * Generate a new <code>XID</code> (used in conversational <code>consult</code>,
	 * <code>spawn</code>, and <code>sendMsg</code>.
	 *
	 * @return a newly generated <code>XID</code> as a <code>String</code>.
	 */
	public String newXID() {
//		return agent + '@' + machine + ++queue.id;
		return new StringBuffer().append(agent).append('@').append(machine).append(++queue.id).toString();
	}

	/**
	 * Spawn an asynchronous computation, this method corresponds to
	 * the Prova built-in predicate 'spawn'. The method is called from
	 * ws.prova.Calc.spawn().
	 * If something goes wrong (for example, if the method is not found),
	 * an exception occurs, and the spawn predicate fails.
	 *
	 * @param r		  Object[] List of parameters as an array:
	 *                 [XID,Class,Method|Parms].
	 *                 XID is assumed to be an uninstantiated variable.
	 * @param bindings List repeats the input parameters in the first element
	 *                 that is an Object[]. The first element of this array will be the
	 *                 instantiated XID.
	 * @return Boolean always Boolean.TRUE.
	 */
	Boolean spawn(Object[] r, List bindings) {
		Term ct = RListUtils.emptyRListTerm;
		for (int i = r.length; i-- > 1;) {
			ct = cplx(flist, ct, r[i]);
		}
		Object id;
		if( r[0] instanceof String )
			id = r[0];
		else
			id = agent + '@' + machine + ++queue.id;
		ct = cplx(flist, ct, cons(id, String.class));
		Object[] retobj = new Object[r.length];
		start((ComplexTerm) ct);

		retobj[0] = id;
		for (int i = r.length; i-- > 1;) {
			retobj[i] = r[i];
		}
		bindings.add(retobj);
		return Boolean.TRUE;
	}

	/**
	 * Process the built-in solve predicate that gets generated
	 * behind the scenes for the Prova solve construct.
	 * The method is called when a solution is found.
	 * The methid prints the results on standard output, which is a limitation.
	 *
	 * @param r		  Object[]
	 * @param bindings List
	 * @return Boolean
	 */
	public Boolean solve(Object[] r, List bindings) {
		Object o = r[0];
		String sep = (String) r[1];
		if (!(o instanceof ComplexTerm)) {
			return Boolean.FALSE;
		}
		ComplexTerm ct = cplx(flist, RListUtils.emptyRListTerm, (ComplexTerm) o);
		String key = '"' + (String) r[2] + '"';//'"' + conv_id + '"';
		ComplexTerm goal = (ComplexTerm) map_goal.get(key);
		Unification u = unificationAlgorithm.unify2(new Term[]{goal}
				, new Term[]{ct}
				, true);
		Replacement[] reps = new Replacement[u.replacements.size()];
		int i = 0;
		for (Iterator it = u.replacements.iterator(); it.hasNext(); i++) {
			if (i != 0) {
				print(" \t");
			}
			Replacement rep = reps[i] = (Replacement) it.next();
			String original = ((VariableTerm) rep.original).getName();
			original = original.replaceAll("::", ".");
			Term t = rep.replacement;
			String s = "";
			if (t.isConstant()) {
				if (((ConstantTerm) t).getObject() instanceof String) {
					s = '"' + ((ConstantTerm) t).getObject().toString() + '"';
				} else {
					s = ((ConstantTerm) t).getObject().toString();
					if (s.equals("")) {
						s = "[]";
					}
				}
			} else if (t.isVariable()) {
				s = ((VariableTerm) t).getName().replaceAll("::", ".");
			} else {
				s = RListUtils.term_as_string(t);
			}
			print(original + sep + s);
		}
		Result result = new Result(reps);
		ProvaResultSet rs;
		rs = resultSet(key); //(ProvaResultSet) map_result.get(key);
		rs.add(result);

		if (i == 0) {
			println("yes");
		} else {
			println("");
		}
		Object[] retobj = new Object[r.length];
		for (int k = 0; k < r.length; k++)
			retobj[k] = r[k];
		bindings.add(retobj);
//		numSolutions++;
		return Boolean.TRUE;
	}

	synchronized public ProvaResultSet resultSet(Object key) {
		return (ProvaResultSet) map_result.get(key);
	}

	private Map map_result = new HashMap();

	synchronized public void addResultSet(String key, ProvaResultSet rs) {
		map_result.put(key, rs);
	}

	synchronized public void removeResultSet(String key) {
		this.map_result.remove(key);
	}

	/**
	 * Prepare a goal given a conversation-id and the body of the message
	 *
	 * @param xid  conversation-id (an empty string results in its autmatic assignment)
	 * @param body body of the submitted goal ([solve,...])
	 */
	public RMessage prepare_goal(String xid, ComplexTerm body) {
		String xid2 = xid.equals("") ? new StringBuffer().append('"' + agent).append(".goal.").append(goal_id++)
				.append('"').toString() : xid;
		List objs = new ArrayList();
		ComplexTerm ct = cplx(flist,body.getTerms()[1],agent);
		ct = DefaultLogicFactorySupport.cplx(reagent.flist,ct,RListUtils.rlist("[id," + xid2 + "]"));
		objs.add(ct);
		RMessage mes = new RMessage("%0", objs.toArray());
		mes.assign_conversation_id(xid2);
		mes.setProtocol("self");
		// Store the current goal--this is needed to retrieve variable names during solve
//		String conv_id = mes.conv_id();
		synchronized( this) {
			map_goal.put(xid2, body);
		}
		return mes;
	}

	/**
	 * Generate a <code>Prerequisite</code> (a body literal) from a <code>ComplexTerm</code>.
	 *
	 * @param t a <code>ComplexTerm</code> representing the contents of the body literal to be generated
	 * @return the resulting body literal
	 */
	public Prerequisite ct2prereq(ComplexTerm t) {
		ConstantTerm keyterm = (ConstantTerm) t.getTerms()[1];
		String keyrhs = (String) keyterm.getObject();
		if (keyrhs.equals("fail")) {
			return prereq(fail, new Object[0]);
		} else if (keyrhs.equals("rcvMsg")) {
			ComplexTerm ct = cplx(flist, t, calc);
			return prereq(kb.pcalc(), ct);
		} else if (keyrhs.equals("rcvMult")) {
			ComplexTerm ct = cplx(flist, t, calc);
			return prereq(kb.pcalc(), ct);
		} else if (keyrhs.equals("rcvMsgP")) {
			ComplexTerm ct = cplx(flist, t, calc);
			return prereq(kb.pcalc(), ct);
		} else if (keyrhs.equals("bcalc")) {
			return prereq(bcalc, t.getTerms()[0]);
		} else if (keyrhs.equals("pcalc")) {
			return prereq(kb.pcalc(), t.getTerms()[0]);
		} else if (keyrhs.equals("scalc")) {
			return prereq(HelperPredicates.scalc(), t.getTerms()[0]);
		} else if (keyrhs.equals("fcalc")) {
			return prereq(HelperPredicates.fcalc(), t.getTerms()[0]);
		} else if (keyrhs.equals("sfcalc")) {
			return prereq(HelperPredicates.sfcalc(), t.getTerms()[0]);
		} else if (keyrhs.equals("sql_select")) {
			return prereq(sql, t.getTerms()[0]);
		} else if (keyrhs.equals("spec")) {
			return prereq(state, t);
		} else if (keyrhs.equals("meta")) {
			return prereq(state, t);
		} else if (keyrhs.equals("cut")) {
			return cut();
		} else if (keyrhs.equals("derive")) {
			ComplexTerm t3 = (ComplexTerm) ((ComplexTerm) t.getTerms()[0]).getTerms()[1];
			Term t2 = t3.getTerms()[1];
			if (t2 instanceof ConstantTerm) {
				String predicateName = ((ConstantTerm) t2).getObject().toString();
				if (predicateName.startsWith("@")) {
					// Internal predicate
					List args = new ArrayList();
					RListUtils.rlist2List((ComplexTerm) t3.getTerms()[0], args, 0);
					predicateName = predicateName.substring(1);
					if (predicateName.equals("actions")) {
						return prereq(actions, args.toArray());
					}
					if (predicateName.equals("actions2")) {
						return prereq(actions2, args.toArray());
					}
					if (predicateName.equals("temporal_rule")) {
						return prereq(temporal_rule, args.toArray());
					}
					if (predicateName.equals("temporal_rule_eoc")) {
						return prereq(temporal_rule_eoc, args.toArray());
					}
					if (predicateName.equals("temporal_rule_remove")) {
						return prereq(temporal_rule_remove, args.toArray());
					}
				}
			}
			return prereq(derive, t2, t3);
		} else if (keyrhs.equals("findall")) {
			List l = new ArrayList();
			RListUtils.rlist2List(t, l, 0);
			if (l.size() == 4) {
				// The return pattern for findall is specified.
				return prereq(acc, t.getTerms()[0]);
			} else {
				// The return pattern for findall is not specified.
				// Copy the query pattern as a return pattern.
				return prereq(acc, cplx(flist, l.get(2), t.getTerms()[0]));
			}
		} else if (keyrhs.equals("adapt")) {
			List l = new ArrayList();
			RListUtils.rlist2List(t, l, 0);
			return prereq(adapt, l.get(1), l.get(2));
		} else if (keyrhs.equals("exec")) {
			List l = new ArrayList();
			RListUtils.rlist2List(t, l, 0);
			return prereq(invoke, calc, t.getTerms()[0]);
		} else if (!keyrhs.equals("fact") && !keyrhs.equals("subsumed")) {
			return prereq(kb.poly(keyrhs, derive), keyrhs, t);
		} else {
			return prereq(derive, keyrhs, t);
		}
	}

	private boolean checkBodyOnException(final Clause clause) {
		boolean ret = false;
		List negLiterals = clause.getNegativeLiterals();
		for (int i = 0; i < negLiterals.size(); i++) {
			Prerequisite pre = (Prerequisite) negLiterals.get(i);
			final Term[] terms = pre.getTerms();
			if (terms != null && terms.length != 0
					&& terms[0] instanceof ComplexTerm
					&& ((ComplexTerm) terms[0]).getTerms()[0] instanceof ComplexTerm
					&& ((ComplexTerm) ((ComplexTerm) terms[0]).getTerms()[0]).getTerms()[1] instanceof ConstantTerm
					&& ((ConstantTerm) ((ComplexTerm) ((ComplexTerm) terms[0]).getTerms()[0]).getTerms()[1]).getObject().equals("on_exception")) {
				return true;
			}
		}
		return ret;
	}

	/**
	 * Insert the clause input as a regular Mandarax rule
	 *
	 * @param xid
	 * @param src  The rulebase being consulted
	 * @param r	 Parameters as an object array
	 *             <br> 0: Key
	 *             <br> 1: Head
	 *             <br> 2: Body
	 *             <br> 3: variables in the head
	 *             // ContractLog: add rule oid
	 *             <br> 5: oid of the rule
	 * @param sync
	 */
	public void insert_clause_as_rule(String xid, String src, Object[] r, boolean sync) throws Exception {
		List vars = new Vector();
		vars.addAll(((Map) r[3]).values());
		String key = (String) r[0];
		if (r[1] instanceof RList || r[1] instanceof ConstantTerm) {
			if (sync) {
				ProvaResultSet rs = process_message(prepare_goal(xid, (ComplexTerm) r[2]));
				if (rs != null && rs.getException() != null)
					throw rs.getException();
			} else
				queue.add(prepare_goal(xid, (ComplexTerm) r[2]));
			return;
		}
		Term head = (Term) r[1];
		List listbody = new ArrayList();
		Term body = null;
		if (r[2] instanceof RList || r[2] instanceof ConstantTerm) {
			body = RListUtils.emptyRListTerm;
		} else {
			body = (Term) r[2];
			RListUtils.rlist2List((ComplexTerm) body, listbody, 0);
		}
		
		// ContractLog: add rule oid
		String oid = r[5].toString();
		if (oid.equals("0")) {
			oid = (oidCounter+1)+"";
			oidCounter ++;
		}
		//		
		
//		System.out.println("head :" + head + "\nbody: " + listbody);
		// Since version 1.7.2
		// Make functional calls returning new variables return constant terms with null content
		List rhs = new ArrayList();
//		Map vars = (Map) r[4];
//		List reps = new ArrayList();
		for (ListIterator li = listbody.listIterator(); li.hasNext();) {
			final ComplexTerm t = (ComplexTerm) li.next();
			final PrerequisiteImpl f = (PrerequisiteImpl) ct2prereq(t);
			f.setVars(vars);
//			for( Iterator varit = vars.values().iterator(); varit.hasNext(); ) {
//				final VariableTerm var = (VariableTerm)varit.next();
//				if( t.containsVariable(var) ) {
//					varit.remove();
//					if( f.getPredicate().getName().endsWith("fcalc") ) {
//						reps.add( new Replacement(var,cons(null,var.getType())) );
//					}
//				}
//			}
//			if( reps.size()==0 )
			rhs.add(f);
//			else
//				rhs.add( f.applyToFact(reps) );
		}
		// Add the new derivation as a new rule in Mandarax format
		FactImpl f = null;
		if (key.equals("spec") || key.equals("meta")) {
			f = (FactImpl) fact(state, head);
		} else {
			ComplexTerm headct = (ComplexTerm) head;
			if (headct.getTerms()[1].toString().equals("[derive]")) {
				ComplexTerm t3 = (ComplexTerm) ((ComplexTerm) headct.getTerms()[0]).getTerms()[1];
				Term t2 = t3.getTerms()[1];
				String predicateName = ((ConstantTerm) t2).getObject().toString();
				if (predicateName.startsWith("@")) {
					// Internal predicate
					List args = new ArrayList();
					RListUtils.rlist2List((ComplexTerm) t3.getTerms()[0], args, 0);
					predicateName = predicateName.substring(1);
					if (predicateName.equals("actions")) {
						f = (FactImpl) fact(actions, args.toArray());
					}
					if (predicateName.equals("actions2")) {
						f = (FactImpl) fact(actions2, args.toArray());
					}
					if (predicateName.equals("temporal_rule")) {
						f = (FactImpl) fact(temporal_rule, args.toArray());
					}
					if (predicateName.equals("temporal_rule_eoc")) {
						f = (FactImpl) fact(temporal_rule_eoc, args.toArray());
					}
					if (predicateName.equals("temporal_rule_remove")) {
						f = (FactImpl) fact(temporal_rule_remove, args.toArray());
					}
				}
			} else {
				f = (FactImpl) fact(kb.poly(key, derive), key, head);
				if (key.equals("rcvMsg") || key.equals("rcvMult")) {
					// A reaction rule
					ComplexTerm pattern = (ComplexTerm) ((ComplexTerm) head).getTerms()[
							0];
					ComplexTerm ct3a = (ComplexTerm) ((ComplexTerm) ((ComplexTerm)
							pattern.getTerms()[0])).getTerms()[0];
					ComplexTerm ct3 = (ComplexTerm) (ct3a.getTerms()[0]);
					String msg;
					final Term perf = ct3.getTerms()[1];
//				    if( perf.isConstant() ) {
//					    msg = ((ConstantTerm) perf).getObject().
//									        toString();
//				    } else {
					msg = "rcvMsg";
//				    }
					add_conversation_cond(msg,
							pattern,
							RListUtils.emptyRListTerm,
							RListUtils.emptyRListTerm,
							RListUtils.emptyRListTerm,
							rhs);
				}
			}
		}
		f.setVars(vars);
		if (rhs.size() == 0) {
			// Create a fact proper if no rhs
			f.setProperty("src", src);
			
			// ContractLog: add rule oid
			f.setProperty("oid", oid);
			//System.out.println("rules: "+oid);
			
			kb.add(f);
		} else {
			final Rule rule = rule(rhs, f, src);
			rule.setProperty("src", src);
			
			// ContractLog: add rule oid
			rule.setProperty("oid", oid);
			//System.out.println("rules: "+oid);
			
			if (checkBodyOnException(rule)) {
				rule.setProperty("on_exception", "1");
			}
			kb.add(rule);
		}
	}

	/**
	 * Commit changes in the knowledgebase from a temporal store
	 * accumulated during consult in ws.prova.parser.RuleParser
	 *
	 * @param XID
	 * @param src	  The source of the consulted rulebase
	 * @param temp_kb Store for temporal goals and clauses
	 */
	public void commit_consult(String XID, String src, AtomicConsult temp_kb) throws Exception {
		boolean sync = Thread.currentThread().getName().equals("ProvaSync");
		List items = temp_kb.items();
		for (int i = 0; i < items.size(); i++) {
			Object[] r = (Object[]) items.get(i);
			insert_clause_as_rule(XID, src, r, sync);
		}
		if (!XID.equals("")) {
			// It is a conversational consult:
			//     send an end_of_transmission after all possible goals
			List objs = new ArrayList();
			String sender = "\"" + agent + "\"";
			String sMsg = "[[id,%0]," + sender + ",end_of_transmission,[]]";
			objs.add(XID);
			RMessage toSend = new RMessage(sMsg, objs.toArray());
			SendData sd = new SendData(messenger, sender, "self", toSend);
			sd.send();
		}
	}

//	/**
//	 * Dynamically insert a new fact.
//	 * If a fact or a rule with header subsumed by the supplied fact exists in the KB,
//	 * the method does nothing and returns <code>true</code>.
//	 * Both meta/spec state predicates and regular derive predicates are supported.
//	 *
//	 * @param r Parameters including only one: ComplexTerm to be asserted
//	 * @return Always <code>true</code>
//	 */
//	boolean prova_insert(Object[] r) {
//		ComplexTerm ct = (ComplexTerm) r[0];
//		String key = (String) ((ConstantTerm) ct.getTerms()[1]).getObject();
//		Fact f = null;
//		if (key.equals("meta") || key.equals("spec")) {
//			f = fact(state, ct);
//		} else {
//			f = fact(kb.poly(key, derive), key, ct);
//		}
//		List lit = new ArrayList(1);
//		lit.add(f);
//		TmpClause nextClause = new TmpClause();
//		nextClause.negativeLiterals = lit;
//		nextClause.positiveLiterals = new ArrayList();
//		ClauseIterator e = kb.clauses(nextClause, null);
//		while (e.hasNext()) {
//			Object o = e.next();
//			if (o instanceof Fact) {
//				Fact cs = (Fact) o;
//				if (Resolution.subsumes(f, cs, unificationAlgorithm)) {
//					return true;
//				}
//			} else if (o instanceof Rule) {
//				Rule rule = (Rule) o;
//				if (Resolution.subsumes(f, rule.getHead(), unificationAlgorithm)) {
//					return true;
//				}
//			}
//		}
//		kb.add(f);
//		return true;
//	}

//	/**
//	 * Dynamically retract one fact or rule with header subsumed by the specified pattern.
//	 * Both meta/spec state predicates and regular derive predicates are supported.
//	 * If at least one fact is not found, the method returns Boolean.FALSE.
//	 *
//	 * @param r Object[] Parameters including only one: ComplexTerm to be retracted
//	 * @return true if a fact was retracted
//	 */
//	boolean retract(Object[] r) {
//		ComplexTerm ct = (ComplexTerm) r[0];
//		String key = (String) ((ConstantTerm) ct.getTerms()[1]).getObject();
//		boolean removed = false;
//		if (key.equals("meta") || key.equals("spec")) {
//			removed = remove_fact(fact(state, ct));
//		} else {
//			removed = remove_fact(fact(kb.poly(key, derive), key, ct));
//		}
//		return removed;
//	}

//	/**
//	 * Dynamically retract all facts or rules with headers subsumed by the specified pattern.
//	 * Both meta/spec state predicates and regular derive predicates are supported.
//	 *
//	 * @param r The pattern as either
//	 *          <br> a <code>ComplexTerm</code> for the facts to be retracted
//	 *          <br> or a <code>String</code> representing the rulebase source for which all facts and rules are to be retracted
//	 */
//	void retractall(Object r) {
//		if (r instanceof String) {
//			removeall_facts((String) r);
//			return;
//		}
//		ComplexTerm ct = (ComplexTerm) r;
//		String key = (String) ((ConstantTerm) ct.getTerms()[1]).getObject();
//		if (key.equals("meta") || key.equals("spec")) {
//			removeall_facts(fact(state, ct));
//		} else {
//			removeall_facts(fact(kb.poly(key, derive), key, ct));
//		}
//	}

	protected void add_conversation_cond(String pred, Term event, Term effects,
													 Term states, Term cond, List rhs) {
		Predicate p = kb.poly(pred, conversation);
		Fact f = fact(p, pred, event, effects, states, cond);
		kb.add(rule(rhs, f));
	}

	/**
	 * Used for running reaction rules in the Worker thread.
	 * Called only from process_message().
	 *
	 * @param query Query to be executed.
	 * @return Boolean
	 */
	protected Boolean start(Query query) {
		Task task = new Task(this, query);
		tasksForRules.put(task);
		return Boolean.TRUE;
	}

	/**
	 * Submit a task specified as a ComplexTerm with conversation-id in front
	 *
	 * @param ct ComplexTerm a task to be run
	 * @return Boolean always Boolean.TRUE
	 */
	protected Boolean start(ComplexTerm ct) {
		// Conversation id
		String tid = ((ConstantTerm) ct.getTerms()[1]).getObject().toString();
		Task task = new Task(queue, tid, prereq(kb.pcalc(), ct.getTerms()[0]));
		tasks.put(task);
		return Boolean.TRUE;
	}

//	/**
//	 * Retract one fact or a rule with a header that is subsumed by the pattern of the supplied fact.
//	 * This method does not remove any rules.
//	 *
//	 * @param f Fact a fact holding a pattern of a fact to be removed
//	 */
//	protected boolean remove_fact(Fact f) {
////		System.out.println("Pattern to retract: "+f);
//		List lit = new ArrayList(1);
//		lit.add(f);
//		TmpClause nextClause = new TmpClause();
//		nextClause.negativeLiterals = lit;
//		nextClause.positiveLiterals = new ArrayList();
//		ClauseIterator e = kb.clauses(nextClause, null);
//		while (e.hasNext()) {
//			Object o = e.next();
//			if (o instanceof Fact) {
//				Fact cs = (Fact) o;
//				if (Resolution.subsumes(f, cs, unificationAlgorithm)) {
//					kb.remove(cs);
//					return true;
//				}
//			} else if (o instanceof Rule) {
//				Rule r = (Rule) o;
//				if (Resolution.subsumes(f, r.getHead(), unificationAlgorithm)) {
//					kb.remove(r);
//					return true;
//				}
//			}
//		}
//		return false;
//	}

	/**
	 * Print all facts matching a fact serving as a pattern.
	 * Only used to print when logger.isDebugEnabled().
	 *
	 * @param f Fact a fact holding a pattern of facts to be printed
	 */
	public void printall_facts(Fact f) {
		List lit = new ArrayList(1);
		lit.add(f);
		TmpClause nextClause = new TmpClause();
		nextClause.negativeLiterals = lit;
		nextClause.positiveLiterals = new ArrayList();
		ClauseIterator e = kb.clauses(nextClause, null);
		while (e.hasNext()) {
			Object o = e.next();
			if (o instanceof Fact) {
				Fact cs = (Fact) o;
				if (Resolution.subsumes(f, cs, unificationAlgorithm)) {
					logger.info(cs);
				}
			} else if (o instanceof Rule) {
				Rule r = (Rule) o;
				if (Resolution.subsumes(f, r.getHead(), unificationAlgorithm)) {
					logger.info(r.getHead());
				}
			}
		}
	}

//	/**
//	 * Retract all facts or rules subsumed by a fact serving as a pattern.
//	 *
//	 * @param f Fact a fact holding a pattern of facts to be removed
//	 */
//	public void removeall_facts(Fact f) {
////		System.out.println("Pattern to retract: "+f);
//		List lit = new ArrayList(1);
//		lit.add(f);
//		TmpClause nextClause = new TmpClause();
//		nextClause.negativeLiterals = lit;
//		nextClause.positiveLiterals = new ArrayList();
//		ClauseIterator e = kb.clauses(nextClause, null);
//		while (e.hasNext()) {
//			Object o = e.next();
//			if (o instanceof Fact) {
//				Fact cs = (Fact) o;
//				if (Resolution.subsumes(f, cs, unificationAlgorithm)) {
//					kb.remove(cs);
//				}
//			} else if (o instanceof Rule) {
//				Rule r = (Rule) o;
//				if (Resolution.subsumes(f, r.getHead(), unificationAlgorithm)) {
//					kb.remove(r);
//				}
//			}
//		}
//	}

//	/**
//	 * Retract all facts or rules having a src representing the source of the consulted rulebase.
//	 *
//	 * @param src The source of the consulted rulebase
//	 */
//	public void removeall_facts(String src) {
//		List list = kb.getClauseSets();
//		for (Iterator e = list.iterator(); e.hasNext();) {
//			Object o = e.next();
//			if (o instanceof Fact) {
//				Fact f = (Fact) o;
//				if (f.getProperty("src") != null && f.getProperty("src").equals(src)) {
//					kb.remove(f);
//				}
//			} else if (o instanceof Rule) {
//				Rule r = (Rule) o;
//				if (r.getProperty("src") != null && r.getProperty("src").equals(src)) {
//					kb.remove(r);
//				}
//			}
//		}
//	}

	/*
     * Check if the fact is fully ground
     * @param f - the fact to be checked
     * @return - true, if the fact is fully ground
     */
//	public boolean ground(Fact f) {
//		Term[] terms = f.getTerms();
//		for (int i = terms.length; --i >= 0; ) {
//			if (f.getTerms()[i].containsVariables()) {
//				return false;
//			}
//		}
//		return true;
//	}

	void println(String s) {
		calc.println(s);
	}

	void print(String s) {
		calc.print(s);
	}

	/**
	 * The main executable part of a Prova reagent.
	 * It runs a message loop that terminates when
	 * <br> (A) no messages are in the queue
	 * <br> (B) AND either a timeout has occurred
	 * <br> or the program has been externally
	 * terminated (e.g., Ctrl-C), which results in a call to shutdown procedure.
	 * <br> The timeout works as follows:
	 * <br> if this.timeout < 0 (normally, set it to -1), the loop never terminates
	 * unless interrupted;
	 * <br> if this.timeout = 0, the loop terminates once the queue is empty;
	 * <br> if this.timeout > 0, the loop terminates after this.timeout seconds.
	 */
	public void run() {
		// Input message processing loop
		RMessage msg;
		while ((msg = queue.next_message()) != null) {
			if (process_message(msg) == null)
				break;
		}
		tasks.setShutdown();
		tasksForRules.setShutdown();
	}

	/**
	 * The main method for processing each incoming message.
	 *
	 * @param msg RMessage the message to be processed.
	 *            It can be either a goal (solve or eval) or a proper message to be
	 *            processed by reaction rules and inline reactions.
	 */
	private ProvaResultSet process_message(RMessage msg) {
//		System.out.println("\nMessage: " + msg);

//		System.out.println("\nMessage performative: "+msg.performative());
		if (msg.performative().startsWith("solve")) {
			try {
				return runSolve(msg);
			} catch (InferenceException exc) {
				System.out.println(exc.toString());
			}

		} else if (msg.performative().startsWith("eval")) {
			Map vars = new HashMap();
			Fact queryfact;
//			if( map_goal.containsKey(msg.conv_id())) {
//				queryfact = map_goal.get(msg.conv_id());
//			} else
				queryfact = fact(eval, RListUtils.rlist(msg.content(), msg.objs()));
			Query query = query(queryfact, "eval");
			try {
				ProvaResultSet rs = (ProvaResultSet) ie.query(query, kb, /*ie.ALL*/ 0,
						ie.BUBBLE_EXCEPTIONS);
				// 1.8 fix: memory leak for goals recorded in map_goal.
				final String xid = msg.conv_id();
				map_goal.remove(xid);
				if (rs.getException() != null) {
					return rs;
				}
//				return null;
			} catch (InferenceException exc) {
				System.out.println(exc.toString());
			}
		} else {
			messageProcessed.set(new Boolean(false));
			Fact queryfact = fact(actions, variable("ID", Object.class),
					RListUtils.rlist(msg.content(), msg.objs()),
					cons(msg.getProtocol(), String.class),
					RListUtils.rlist("States"), RListUtils.rlist("Conditions"));
			query = query(queryfact, "find actions");
//			System.out.println("\nMessage: " + msg);
//			print_state();
			if (logger.isDebugEnabled()) {
				print_state();
				logger.debug("\nMessage: " + msg);
			}

			if (msg.getProtocol().equals("async") || msg.performative().equals("async")) {
				start(query);
			} else if (msg.performative().equals("swing")) {
				// All Swing events are queued to the Swing events thread
				//    (this is to be conforming to the Swing threads rules)
				try {
					javax.swing.SwingUtilities.invokeAndWait(swingThread);
				} catch (InvocationTargetException ex) {
				} catch (InterruptedException ex) {
				}
			} else {
				try {
					org.mandarax.kernel.ResultSet rs = ie.query(query, kb, ie.ALL,
							ie.BUBBLE_EXCEPTIONS);
				} catch (InferenceException exc) {
					System.out.println(exc.toString());
				}
				if (msg.getProtocol().equals("self") && msg.performative().equals("shutdown")) {
					return null;
				}
			}
		}
		return new ProvaResultSetImpl2();
	}

	/**
	 * Run the <code>solve</code>.
	 * <br> This method is used in process_message locally or from ws.prova.query.SyncQuery.submitQuery.
	 *
	 * @param msg
	 */
	public ProvaResultSet runSolve(RMessage msg) throws InferenceException {
		Term t = RListUtils.rlist(msg.content(), msg.objs());
		String perf = msg.performative();
		Fact queryfact = null;
		Query query = null;
		// Note that we name the query with the conversation-id of the goal in quotes:
		//     it is a unique key for the results
		final String xid = msg.conv_id();
		if (t.containsVariables() && !perf.equals("solve_first")) {
			queryfact = fact(solve, t);
			query = query(queryfact, xid);
		} else {
			queryfact = fact(solve_first, t);
			query = query(queryfact, xid);
		}
		ProvaResultSet rs = (ProvaResultSet) ie.query(query, kb, /*ie.ALL*/ 0, ie.BUBBLE_EXCEPTIONS);
		if (rs.getException() != null && rs.getException() instanceof InferenceException) {
			println(rs.getException().toString());
		} else if (rs.numberOfResults() == 0)
			println("no");
		println("");
		rs.first();
		map_goal.remove(xid);
		// Correction in 1.8 and above for memory leak
		removeResultSet(xid);
		return rs;
	}

	/**
	 * Consult a rulebase from a String and run the goals synchronously
	 *
	 * @param input
	 * @param key
	 * @param objects
	 * @throws Exception
	 */
	public List consultSync(String input, String key, Object[] objects) throws Exception {
		List resultSets = new ArrayList();
		StringReader sr = new StringReader(input);
		BufferedReader in = new BufferedReader(sr);
//		String src = "StringBuffer";
		RulebaseParser parser = new RulebaseParser(calc, key, objects);
		AtomicConsult temp_kb = new AtomicConsult();
		parser.parse(temp_kb, in);

		// Add facts and clauses to the rulebase
		List items = temp_kb.items();
		for (int i = 0; i < items.size(); i++) {
			Object[] r = (Object[]) items.get(i);
			if (r[1] instanceof RList || r[1] instanceof ConstantTerm)
				continue;
			insert_clause_as_rule("", key, r, false);
		}

		// Run goals synchronously
		for (int i = 0; i < items.size(); i++) {
			Object[] r = (Object[]) items.get(i);
			if (r[1] instanceof RList || r[1] instanceof ConstantTerm) {
				final RMessage msg;
//				synchronized(this) {
				msg = prepare_goal("", (ComplexTerm) r[2]);
//					System.out.println("----"+(this.goal_id-1)+"----"+input+"----"+msg);
//				}
				final ProvaResultSet rs = process_message(msg);
				if (rs != null) {
					resultSets.add(rs);
//					if( rs.getException() != null )
//						throw rs.getException();
				}
			}
		}
		return resultSets;
	}

	public void consultAsync(String input, Object[] objects) throws Exception {
//		List resultSets = new ArrayList();
		StringReader sr = new StringReader(input);
		BufferedReader in = new BufferedReader(sr);
		String src = "StringBuffer";
		RulebaseParser parser = new RulebaseParser(calc, src, objects);
		AtomicConsult temp_kb = new AtomicConsult();
		parser.parse(temp_kb, in);

		// Add facts and clauses to the rulebase
		List items = temp_kb.items();
		for (int i = 0; i < items.size(); i++) {
			Object[] r = (Object[]) items.get(i);
			if (r[1] instanceof RList || r[1] instanceof ConstantTerm)
				continue;
			insert_clause_as_rule("", "", r, false);
		}

		// Run goals asynchronously
		for (int i = 0; i < items.size(); i++) {
			Object[] r = (Object[]) items.get(i);
			if (r[1] instanceof RList || r[1] instanceof ConstantTerm)
				queue.add(prepare_goal("", (ComplexTerm) r[2]));
		}
	}

	/**
	 * Called from ws.prova.Task to execute asynchronous queries
	 *
	 * @param query
	 */
	void execute(Query query) {
		try {
			org.mandarax.kernel.ResultSet rs = ie.query(query, kb, ie.ALL,
					ie.BUBBLE_EXCEPTIONS);
		} catch (InferenceException exc) {
			exc.printStackTrace(System.out);
		}
	}

	/**
	 * Test queries for useful print-outs during the execution.
	 */
	void print_state() {
		Fact queryfact;
		Query query;
		/*
					  queryfact = fact(d(),variable("X",String.class),variable("Y",String.class));
						  query = query(queryfact,"enumerate state predicates");

						  try {
					  ResultSet rs = ie.query(query,kb,ie.ALL,ie.BUBBLE_EXCEPTIONS);
					  while( rs.next() ) {
						System.out.println(rs.getResult(String.class,"X")+" "+rs.getResult(String.class,"Y"));
					  }
					  rs.close();
						  }
						  catch (Exception exc) {
					  exc.printStackTrace(System.out);
						  }
					  */
		/*
						  // Database test
		  //		queryfact = fact(sql,rlist("Term2term"));
						  queryfact = fact(sql,rlist("[term2term,[term1_id,%0],[term2_id,Integer.Type]|R]",new Object[]{new Integer(4070)}));
						  query = query(queryfact,"enumerate term2term table");

						  try {
					  org.mandarax.kernel.ResultSet rs = ie.query(query,kb,ie.ALL,ie.BUBBLE_EXCEPTIONS);
					  while( rs.next() ) {
						System.out.println(rs.getResult(RList.class,"R"));
					  }
					  rs.close();
						  }
						  catch (InferenceException exc) {
					  exc.printStackTrace(System.out);
						  }
					  */
		/*
						  queryfact = fact(state,rlist("State"));
						  query = query(queryfact,"enumerate state predicates");

						  try {
							org.mandarax.kernel.ResultSet rs = ie.query(query,kb,ie.ALL,ie.BUBBLE_EXCEPTIONS);
							while( rs.next() ) {
					  System.out.println("State="+term_as_string(rs.getResult(RList.class,"State")));
							}
							rs.close();
						  }
						  catch (InferenceException exc) {
							exc.printStackTrace(System.out);
						  }
					  */
		queryfact = fact(temporal_rule, variable("RuleID", Object.class),
				variable("ID", Object.class), RListUtils.rlist("X"));
		printall_facts(queryfact);
		queryfact = fact(temporal_rule_eoc, variable("RuleID", Object.class),
				variable("ID", Object.class), RListUtils.rlist("X"));
		printall_facts(queryfact);
//		query = query(queryfact,"enumerate temporal rules");
//
//
//		try {
//			org.mandarax.kernel.ResultSet rs = ie.query(query,kb,ie.ALL,ie.BUBBLE_EXCEPTIONS);
//			while( rs.next() ) {
//				System.out.println("["+rs.getResult(Object.class,"RuleID")+","+rs.getResult(Object.class,"ID")+"]="
//										 +term_as_string(rs.getResult(RList.class,"X"))
//										 );
//			}
//			rs.close();
//		}
//		catch (InferenceException exc) {
//			exc.printStackTrace(System.out);
//		}
	}

	final static Pattern placeholderPattern = Pattern.compile("(%\\d+)");

	public String toString() {
		return "reagent";
	}

	/**
	 * Make the events queue available to other classes
	 * It is thread-safe to add to this queue from any class
	 *
	 * @return RMessageQueue the events queue that you can add to messages RMessage
	 */
	public RMessageQueue queue() {
		return queue;
	}

	/**
	 * Send this RMessage to self.
	 * Note that the called method queue.add() is synchronized.
	 *
	 * @param rmsg RMessage the message to be queued.
	 */
	public void add(RMessage rmsg) {
		queue.add(rmsg);
	}

	public String get_temptid() {
		messageProcessed.set(new Boolean(true));
		String thread = Thread.currentThread().getName();
		String ruleid = (String) map_temptid.get(thread);
		return ruleid;
	}

	public Boolean store_temptid(Object[] r, List bindings) {
		String ruleid = (String) r[0];
		String thread = Thread.currentThread().getName();
		// Record an 'open' Statement in the hashmap
		map_temptid.put(thread, ruleid);
		Object[] retobj = new Object[1];
		retobj[0] = ruleid;
		bindings.add(retobj);
		return Boolean.TRUE;
	}

	/**
	 * The main Prova starting point
	 *
	 * @param args String[]
	 */
	public static void main(String[] args) {
		// Create a reagent with a message queue with a specified timeout
		if (args.length < 3) {
			System.out.println("Usage: prova <agent> <password> <rules file> [-t <timeout in seconds>] [-p <prot1,..,protN>]");
			return;
		}

		// Create a reagent with a message queue with timeout of 10 seconds
		String agent = args[0];
//		String password = args[1];
		String rules = args[2];

		// Which protocols to make available for communication
		// If -p is missing altogether, only "local" queries will be possible
		String[] prot = null;

		// Timeout in seconds (0 means no timeout by default)
		int timeout = 0;
		for (int i = 4; i < args.length; i += 2) {
			if (args[i - 1].equals("-p")) {
				// Communication protocols used
				prot = args[i].split(",");
			} else if (args[i - 1].equals("-t")) {
				// Timeout in seconds (0 means no timeout)
				timeout = Integer.parseInt(args[i]);
			}
		}
		RMessageQueue queue = new RMessageQueue(agent, timeout);

		reagent reagent1 = new reagent(args[0], args[1], queue, new ws.prova.TaskQueue(),
				prot, rules, true, null);

		// .. and run it
		reagent1.run();
	}

	public void setESB(ProvaUMO provaUMO) {
		this.esb = provaUMO;
	}

	public Messenger getMessenger() {
		return messenger;
	}

	public void setMessenger(Messenger messenger) {
		this.messenger = messenger;
	}

	public Updater getUpdater() {
		return updater;
	}

	public String getAgent() {
		return agent;
	}
}

