package ws.prova;

import ws.prova.reference.IProvaKnowledgeBase;
import ws.prova.util.DefaultLogicFactorySupport;
import ws.prova.jade.jade_exchange;
import ws.prova.esb.ProvaUMO;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

import org.mandarax.kernel.*;
import org.mandarax.reference.TmpClause;
import org.mandarax.reference.Resolution;
import org.mandarax.util.ClauseIterator;
import org.mandarax.util.logging.Logger;
import org.mandarax.util.logging.LoggerFactory;

public class Messenger {
	protected static final Logger logger = LoggerFactory.getDefaultFactory().getLogger("Messenger");

	private reagent prova;
	private IProvaKnowledgeBase kb;
	private String agent;
	private String password;
	private String machine;
	private jade_exchange je;
	private ProvaUMO esb;

	protected Map temporal_rule_count = new HashMap();
	// For each conversation id, store its immediate originator (sender of the corresponding message TO THE CURRENT AGENT)
	protected Map id_sender = new HashMap();
	protected Map id_originator = new HashMap();
	// From conversation-ID to SendData of the end_of_conversation message to be sent
	protected Map id_postponed = new HashMap();

	// For inbound partitions, specify rcvMsgP derived temporal rules
	protected Map inbound_ruleids = new HashMap();

	// For rcvMsgP derived temporal rules specify the outbound partitions
	protected Map ruleid_outbound = new HashMap();

	// For rcvMsgP derived temporal rules specify the inbound partitions
	protected Map ruleid_inbound = new HashMap();

	// For each ruleID specify the initial tid it was generated from
	// If a temporal rule is generated from another temporal rule that tid is inherited
	protected Map id_ruleid = new HashMap();

	public Messenger(reagent reagent, IProvaKnowledgeBase kb, String agent, String password, String machine, jade_exchange je, ProvaUMO esb) {
		this.prova = reagent;
		this.kb = kb;
		this.agent = agent;
		this.password = password;
		this.machine = machine;
		this.je = je;
		this.esb = esb;
	}


	/**
     * Send a message to self (via queue), ESB or JADE to other agents
	 *
	 * @param r        Object[] parameters of the predicate as an array
	 *                 [conversation-id,protocol,recever,performative|context]
	 * @param bindings a list with the input array possibly with instantiated conversation-id
	 * @return Boolean
	 */
	Boolean sendMsg(final Object[] r, final List bindings) {
		 Object xid = r[0];
		 String protocol = (String) r[1];
		 Object obj_receiver = r[2];
		 String perf = (String) r[3];
		 Term ct = r[4] instanceof Term ? (ComplexTerm) r[4] : RListUtils.emptyRListTerm;
		 Object[] retobj = new Object[r.length];
		 Term tt = RListUtils.emptyRListTerm;
		 if (r.length > 5) {
			  // Local context provided
			  for (int i = r.length; i-- > 5;) {
					tt = DefaultLogicFactorySupport.cplx(reagent.flist, tt, r[i]);
					retobj[i] = r[i];
			  }
		 }
		 ct = DefaultLogicFactorySupport.cplx(reagent.flist, tt, ct);
//		System.out.println( term_as_string(ct) );
		 retobj[0] = r[0];
		 retobj[1] = r[1];
		 retobj[2] = r[2];
		 retobj[3] = r[3];
		 retobj[4] = r[4];
		 List objs = new ArrayList();
		 String sMsg = RListUtils.decode_rename(ct, objs);
		 sMsg = sMsg.substring(1, sMsg.length() - 1);
		 String conv_id = prova.calc.get_tid();
		 String ID = null;
		 Object sender = "\"" + agent + "\"";
		 if (protocol.equals("jade")) {
			  sender = "%" + objs.size();
			  objs.add(agent + '@' + machine);
		 }
		 if (protocol.equals("queue")) {
			  sender = "%" + objs.size();
			  objs.add(prova.queue);
		 }
		 if (xid instanceof VariableTerm) {
			  // It is a new conversation
			  ID = agent + '@' + machine + ++prova.queue.id;
			  sMsg = "[[id," + "%" + objs.size() + "]," + sender + "," + perf +
						 "," + sMsg + "]";
			  objs.add(ID);
			  if (xid instanceof VariableTerm) {
					retobj[0] = ID;
			  }
		 } else {
			  // It is a reaction
			  String temptid = null;
			  if (temptid == null) {
					temptid = "%" + objs.size();
//				retobj[0] = temptid;
					objs.add(xid);
			  }
			  sMsg = "[[id," + temptid + "]," + sender + "," + perf + "," + sMsg +
						 "]";
			  ID = conv_id;
		 }
		 RMessage toSend = new RMessage(sMsg, objs.toArray());
		 toSend.setProtocol(protocol);
		 SendData sd = new SendData(this, obj_receiver.toString(), protocol, toSend);

		 if (perf.equals("end_of_transmission") && id_originator.containsValue(ID)) {
			  // Disallow and postpone end_of_transmission if temporal rules pending
			  id_postponed.put(ID, sd);
			  return Boolean.FALSE;
		 }

		 sd.send();
		 bindings.add(retobj);

		 return Boolean.TRUE;
	}

	/**
     * Send the message given SendData with no questions asked.
	 * Called back from SendData.
	 *
	 * @param sd SendData encapsulates receiver, protocol, and RMessage
	 */
	void send(SendData sd) {
		 String protocol = sd.protocol();
		 String receiver = sd.receiver();
		 RMessage toSend = sd.toSend();

		 if (protocol.equals("self") || receiver.equals("0") ||
					receiver.equals(agent)) {
			  // Message to self
			  toSend.setProtocol(protocol);
			  prova.queue.add(toSend);
		 } else if (protocol.equals("jade")) {
			  // Send message via Jade HTTP
			  // receiver here should take the form agent@machine, so what is the machine?
			  String machine = receiver.substring(receiver.indexOf("@") + 1);
			  je.send(receiver + ":1101/JADE",
						 "http://" + machine + ":" + password + "/acc", toSend);
		 } else if( protocol.equals("esb"))
			  try {
					esb.send(agent, receiver, toSend);
			  } catch (Exception e) {
					prova.logger.error(e);
			  }
	}

	/**
     * The ultimate method called for the built-in <code>rcvMsg</code> predicate.
	 * This predicate provides an inline reaction functionality.
	 * This predicate always fails when it is first evaluated, with the side effect of adding
	 *     temporal rules that wait for a message with matching pattern to arrive, whereupon
	 *     all the trail is computed and the temporal rules (and the reaction) are discharged.
	 * is found.
	 * @param r        Object[]
	 * @param bindings List
	 * @param rest TODO
	 * @param goal     Clause
	 *
	 * @return Boolean
	 */
	Boolean rcvMsg(Object[] r, List bindings, Term rest, Clause goal) throws ClauseSetException {
		 // A temporal reaction rule will have to be created
		 Object xid = r[0];
		 Object o_protocol = r[1];
		 Object protocol;
		 if (o_protocol instanceof String) {
			  protocol = DefaultLogicFactorySupport.cons(o_protocol, String.class);
		 } else {
			  protocol = o_protocol;
		 }
		 Object obj_receiver = r[2];
		 Object receiver = null;
		 if (obj_receiver instanceof VariableTerm) {
			  receiver = obj_receiver;
		 } else {
			  receiver = DefaultLogicFactorySupport.cons(obj_receiver, String.class);
		 }
		 Object o_perf = r[3];
		 Object perf;
		 if (o_perf instanceof String) {
			  perf = DefaultLogicFactorySupport.cons(o_perf, String.class);
		 } else {
			  perf = o_perf;
		 }
		 Term ct = (r[4] instanceof Term) ? (Term) r[4] : RListUtils.emptyRListTerm;
		 Term tt = rest; // RListUtils.emptyRListTerm;
//		Object[] retobj = new Object[r.length];
//		for (int i = r.length; i-- > 0;)
//			retobj[i] = r[i];
		 if (r.length > 5) {
			  // Local context provided
			  for (int i = r.length; i-- > 5;) {
					tt = DefaultLogicFactorySupport.cplx(reagent.flist, tt, r[i]);
//				retobj[i] = r[i];
			  }
		 }
		 ct = DefaultLogicFactorySupport.cplx(reagent.flist, tt, ct);

		 String ruleID1 = String.valueOf(prova.unique_id++);
		 Object TID = prova.calc.get_tid();
		 Object ID = null;
		 Object OID = null;
		 if (xid instanceof VariableTerm) {
			  ID = OID = xid;
		 } else {
			  ID = (String) xid;
			  OID = DefaultLogicFactorySupport.cons(ID, String.class);
		 }

		 ComplexTerm pattern = DefaultLogicFactorySupport.cplx(reagent.flist,
					DefaultLogicFactorySupport.cplx(reagent.flist, DefaultLogicFactorySupport.cplx(reagent.flist, ct, perf), receiver),
					protocol);
		 ComplexTerm ct3 = DefaultLogicFactorySupport.cplx(reagent.flist, pattern,
					DefaultLogicFactorySupport.cplx(reagent.flist, DefaultLogicFactorySupport.cplx(reagent.flist, RListUtils.emptyRListTerm, OID),
							  DefaultLogicFactorySupport.cons("id", String.class)));
		 ComplexTerm head = DefaultLogicFactorySupport.cplx(reagent.flist, ct3, DefaultLogicFactorySupport.cons("rcvMsg", String.class));

		 String ruleid = prova.get_temptid();
		 Object temptid = id_ruleid.get(ruleid);
		 Object oOldCount = temporal_rule_count.get(temptid);
		 int oldCount = oOldCount==null ? 0 : ((Integer) oOldCount).intValue();
//		 if (ruleid != null && !ruleid.equals("-1") && false /*oldCount==0*/ ) {
//			  if (ID.equals(TID)) {
//					// Replace a previous temporal rule for rcvMsg with a new one
//					//    if their conversation-ids are the same
//					Fact toRemove = DefaultLogicFactorySupport.fact(reagent.temporal_rule, ruleid, DefaultLogicFactorySupport.variable("ID"),
//							  RListUtils.rlist("X"));
//					// Only rcvMsg (not rcvMult) allowed to be removed
//					Object[] t2 = remove_temporal_rule(toRemove, true);
////					Object xid2 = t2[1];
//					if (t2[0] != null) {
//						 Fact toRemoveEOC = DefaultLogicFactorySupport.fact(reagent.temporal_rule_eoc, ruleid, DefaultLogicFactorySupport.variable("ID"),
//									RListUtils.rlist("X"));
//						 prova.getUpdater().removeall_facts(toRemoveEOC);
//						 id_originator.remove(ruleid);
//						 TID = temptid;
//						 int count = oldCount; // - 1;
//						 temporal_rule_count.put(TID, new Integer(count));
//					// Key correction to enable correct count decrement in remove_temporal_rule
//					//     for the rcvMult originator
//					} else if( t2!=null && !(t2[1] instanceof VariableTerm) ) {
//						 TID = t2[1];
//					}
//			  }
//		 }

		 id_originator.put(ruleID1, TID);
		 id_sender.put(ID, agent);
		 if (temporal_rule_count.get(TID) == null) {
			  temporal_rule_count.put(TID, new Integer(1));
			  // ID was originated by TID
		 } else {
			  int count = ((Integer) temporal_rule_count.get(TID)).intValue() + 1;
			  temporal_rule_count.put(TID, new Integer(count));
		 }

		 // The list of outstanding goals including the current one
		 List rhs = goal.getNegativeLiterals();

		 Fact f = DefaultLogicFactorySupport.fact(reagent.temporal_rule, ruleID1, ID, head);
//		System.out.println("f="+f);
		 List rhsm = new ArrayList();
		 if (temptid == null) {
			  temptid = TID;
		 }
		 // Earmark the temporal rule with the initial tid of the reaction
		 id_ruleid.put(ruleID1, temptid);
		 rhsm.add(DefaultLogicFactorySupport.prereq(kb.pcalc(),
					RListUtils.rlist("[%0,store_temptid,%1]", new Object[]{prova, ruleID1})));
//		 rhsm.add(DefaultLogicFactorySupport.prereq(kb.pcalc(),
//					RListUtils.rlist("[%0,remove_temporal_rule,%1,%2]", new Object[]{prova, ruleID1, ID})));
		 rhsm.addAll(rhs.subList(1, rhs.size() - 1));
		 rhsm.add(rhs.get(rhs.size() - 1));
		 Rule rr = DefaultLogicFactorySupport.rule(rhsm, f);
		 if (xid instanceof VariableTerm && ID instanceof String) {
			  Replacement rp = new Replacement((VariableTerm) xid, DefaultLogicFactorySupport.cons(ID, String.class));
			  rr = (Rule) rr.apply(rp);
		 }
		 kb.add(rr);

		 Fact fc = DefaultLogicFactorySupport.fact(reagent.temporal_rule_eoc, ruleID1, ID, head);
		 kb.add(fc);

		 return Boolean.FALSE;
	}

	/**
     * The ultimate method called for the built-in rcvMult predicate
     *
     * @param r        Object[]
     * @param bindings List
     * @param rest TODO
     * @param goal     Clause
	 * @return Boolean
     */
    Boolean rcvMult(Object[] r, List bindings, Term rest, Clause goal) {
        // A temporal reaction rule will have to be created
        Object xid = r[0];
        Object o_protocol = r[1];
        Object protocol;
        if (o_protocol instanceof String) {
            protocol = DefaultLogicFactorySupport.cons(o_protocol, String.class);
        } else {
            protocol = o_protocol;
        }
        Object obj_receiver = r[2];
        Object receiver = null;
        if (obj_receiver instanceof VariableTerm) {
            receiver = obj_receiver;
        } else {
            receiver = DefaultLogicFactorySupport.cons(obj_receiver, String.class);
        }
        Object o_perf = r[3];
        Object perf;
        if (o_perf instanceof String) {
            perf = DefaultLogicFactorySupport.cons(o_perf, String.class);
        } else {
            perf = o_perf;
        }
        Term ct = (Term) r[4];
        Term tt = rest; // RListUtils.rlist("[]");
//		Object[] retobj = new Object[r.length];
//		for (int i = r.length; i-- > 0;)
//			retobj[i] = r[i];
        if (r.length > 5) {
            // Local context provided
            for (int i = r.length; i-- > 5;) {
                tt = DefaultLogicFactorySupport.cplx(reagent.flist, tt, r[i]);
//				retobj[i] = r[i];
            }
        }
        ct = DefaultLogicFactorySupport.cplx(reagent.flist, tt, ct);

        String ruleID1 = String.valueOf(prova.unique_id++);
        String TID = prova.calc.get_tid();
        Object ID = null;
        Object OID = null;
        if (xid instanceof VariableTerm) {
            ID = OID = xid;
        } else {
            ID = (String) xid;
            OID = DefaultLogicFactorySupport.cons(ID, String.class);
        }

        ComplexTerm pattern = DefaultLogicFactorySupport.cplx(reagent.flist,
                DefaultLogicFactorySupport.cplx(reagent.flist, DefaultLogicFactorySupport.cplx(reagent.flist, ct, perf), receiver),
                protocol);
        ComplexTerm ct3 = DefaultLogicFactorySupport.cplx(reagent.flist, pattern,
                DefaultLogicFactorySupport.cplx(reagent.flist, DefaultLogicFactorySupport.cplx(reagent.flist, RListUtils.emptyRListTerm, OID),
                        DefaultLogicFactorySupport.cons("id", String.class)));
        ComplexTerm head = DefaultLogicFactorySupport.cplx(reagent.flist, ct3, DefaultLogicFactorySupport.cons("rcvMult", String.class));

        id_originator.put(ruleID1, TID);
        id_sender.put(ID, agent);
        if (temporal_rule_count.get(TID) == null) {
            temporal_rule_count.put(TID, new Integer(1));
            // ID was originated by TID
        } else {
            int count = ((Integer) temporal_rule_count.get(TID)).intValue() + 1;
            temporal_rule_count.put(TID, new Integer(count));
        }

        // The list of outstanding goals including the current one
        List rhs = goal.getNegativeLiterals();
        Fact f = DefaultLogicFactorySupport.fact(reagent.temporal_rule, ruleID1, ID, head);
//		System.out.println("f="+f);
        List rhsm = new ArrayList();
        String temptid = (String) id_ruleid.get(prova.get_temptid());
        if (temptid == null) {
            temptid = TID;
        }
        // Earmark the temporal rule with the initial tid of the reaction
        id_ruleid.put(ruleID1, temptid);
        rhsm.add(DefaultLogicFactorySupport.prereq(kb.pcalc(),
                RListUtils.rlist("[%0,store_temptid,%1]", new Object[]{prova, ruleID1})));
        rhsm.addAll(rhs.subList(1, rhs.size() - 1));
        rhsm.add(rhs.get(rhs.size() - 1));
        kb.add(DefaultLogicFactorySupport.rule(rhsm, f));

        if( !(ID instanceof VariableTerm) ) {
            // Key correction: rcvMult with unbound XID are global and cannot be terminated
            //    by end_of_transmission
            Fact fc = DefaultLogicFactorySupport.fact(reagent.temporal_rule_eoc, ruleID1, ID, head);
            kb.add(fc);
        }

        return Boolean.FALSE;
    }

	/**
     * The ultimate method called for the built-in <code>rcvMsgP</code> predicate.
     * This predicate provides an inline reaction functionality.
     * This predicate always fails when it is first evaluated, with the side effect of adding
     *     temporal rules that wait for a message with matching pattern to arrive, whereupon
     *     all the trail is computed and the temporal rules (and the reaction) are discharged.
     * is found.
     * @param r        Object[]
     * @param bindings List
     * @param rest TODO
     * @param goal     Clause
     *
	 * @return Boolean
     */
    Boolean rcvMsgP(Object[] r, List bindings, Term rest, Clause goal) throws ClauseSetException {
        List inbound = new ArrayList();
        if( r[0] instanceof ComplexTerm ) {
            ComplexTerm inboundP = (ComplexTerm) r[0];
            RListUtils.rlist2List(inboundP,inbound,0);
        }
        List outbound = new ArrayList();
        if( r[1] instanceof ComplexTerm ) {
            ComplexTerm outboundP = (ComplexTerm) r[1];
            RListUtils.rlist2List(outboundP,outbound,0);
        }
        ComplexTerm head0 = (ComplexTerm) r[2];
        // A temporal reaction rule will have to be created
        Object reactionCmd = head0.getTerms()[1];
        ComplexTerm rest0 = (ComplexTerm) head0.getTerms()[0];
        Object xid = rest0.getTerms()[1];
        ComplexTerm rest1 = (ComplexTerm) rest0.getTerms()[0];
        Object o_protocol = rest1.getTerms()[1];
        Object protocol;
        if (o_protocol instanceof String) {
            protocol = DefaultLogicFactorySupport.cons(o_protocol, String.class);
        } else {
            protocol = o_protocol;
        }
        ComplexTerm rest2 = (ComplexTerm) rest1.getTerms()[0];
        Object obj_receiver = rest2.getTerms()[1];
        Object receiver = null;
        if (obj_receiver instanceof VariableTerm) {
            receiver = obj_receiver;
        } else {
            receiver = DefaultLogicFactorySupport.cons(obj_receiver, String.class);
        }
        ComplexTerm rest3 = (ComplexTerm) rest2.getTerms()[0];
        Object o_perf = rest3.getTerms()[1];
        Object perf;
        if (o_perf instanceof String) {
            perf = DefaultLogicFactorySupport.cons(o_perf, String.class);
        } else {
            perf = o_perf;
        }
        ComplexTerm rest4 = (ComplexTerm) rest3.getTerms()[0];
        Object content = rest4.getTerms()[1];
        Term ct = (content instanceof Term) ? (Term) content : RListUtils.emptyRListTerm;
        Term tt = rest4.getTerms()[0];
//        if (!tt.equals(RListUtils.emptyRListTerm)) {
//            // Local context provided
//            for (int i = r.length; i-- > 5;) {
//                tt = cplx(flist, tt, r[i]);
//            }
//        }
        ct = DefaultLogicFactorySupport.cplx(reagent.flist, tt, ct);

        String ruleID1 = String.valueOf(prova.unique_id++);
        Object TID = prova.calc.get_tid();
        Object ID = null;
        Object OID = null;
        if (xid instanceof VariableTerm) {
            ID = OID = xid;
        } else {
            ID = ((ConstantTerm) xid).getObject();
            OID = DefaultLogicFactorySupport.cons(ID, String.class);
        }

        ComplexTerm pattern = DefaultLogicFactorySupport.cplx(reagent.flist,
                DefaultLogicFactorySupport.cplx(reagent.flist, DefaultLogicFactorySupport.cplx(reagent.flist, ct, perf), receiver),
                protocol);
        ComplexTerm ct3 = DefaultLogicFactorySupport.cplx(reagent.flist, pattern,
                DefaultLogicFactorySupport.cplx(reagent.flist, DefaultLogicFactorySupport.cplx(reagent.flist, RListUtils.emptyRListTerm, OID),
                        DefaultLogicFactorySupport.cons("id", String.class)));
        ComplexTerm head = DefaultLogicFactorySupport.cplx(reagent.flist, ct3, DefaultLogicFactorySupport.cons("rcvMsg", String.class));

        String ruleid = prova.get_temptid();
        Object temptid = id_ruleid.get(ruleid);
//        if (ruleid != null && !ruleid.equals("-1") && false /*&& temporal_rule_count.get(temptid) == null*/) {
//            if (ID.equals(TID)) {
//                // Replace a previous temporal rule for rcvMsg with a new one
//                //    if their conversation-ids are the same
//                Fact toRemove = DefaultLogicFactorySupport.fact(reagent.temporal_rule, ruleid, DefaultLogicFactorySupport.variable("ID"),
//                        RListUtils.rlist("X"));
//                // Only rcvMsg (not rcvMult) allowed to be removed
//                Object[] t2 = remove_temporal_rule(toRemove, true);
////                Object xid2 = t2[1];
//                if (t2[0] != null) {
//                    Fact toRemoveEOC = DefaultLogicFactorySupport.fact(reagent.temporal_rule_eoc, ruleid, DefaultLogicFactorySupport.variable("ID"),
//                            RListUtils.rlist("X"));
//                    prova.getUpdater().removeall_facts(toRemoveEOC);
//                    id_originator.remove(ruleid);
//                    TID = temptid;
//                    int count = ((Integer) temporal_rule_count.get(TID)).intValue() -
//                            1;
//                    temporal_rule_count.put(TID, new Integer(count));
//                // Key correction to enable correct count decrement in remove_temporal_rule
//                //     for the rcvMult originator
//                } else if( t2!=null && !(t2[1] instanceof VariableTerm) ) {
//                    TID = t2[1];
//                }
//            }
//        }

        id_originator.put(ruleID1, TID);
        id_sender.put(ID, agent);
        if (temporal_rule_count.get(TID) == null) {
            temporal_rule_count.put(TID, new Integer(1));
            // ID was originated by TID
        } else {
            int count = ((Integer) temporal_rule_count.get(TID)).intValue() + 1;
            temporal_rule_count.put(TID, new Integer(count));
        }

        // The list of outstanding goals including the current one
        List rhs = goal.getNegativeLiterals();

        Fact f = DefaultLogicFactorySupport.fact(reagent.temporal_rule, ruleID1, ID, head);
//		System.out.println("f="+f);
        List rhsm = new ArrayList();
        if (temptid == null) {
            temptid = TID;
        }
        // Earmark the temporal rule with the initial tid of the reaction
        id_ruleid.put(ruleID1, temptid);
        // Add this ruleid to all partitions specified as inbound
        for( int i=0; i<inbound.size(); i++ ) {
            List ruleids;
            if( inbound_ruleids.containsKey(inbound.get(i)) ) {
                ruleids = (List) inbound_ruleids.get(inbound.get(i));
            } else {
                ruleids = new ArrayList();
                inbound_ruleids.put(inbound.get(i),ruleids);
            }
            ruleids.add(ruleID1);
        }
        ruleid_outbound.put(ruleID1,outbound);
        ruleid_inbound.put(ruleID1,inbound);
//    	System.out.println("inbound="+inbound_ruleids);
//    	System.out.println("outbound="+ruleid_outbound);
        rhsm.add(DefaultLogicFactorySupport.prereq(kb.pcalc(),
                RListUtils.rlist("[%0,store_temptid,%1]", new Object[]{prova, ruleID1})));
        if( r.length>3 ) {
            ComplexTerm condition = (ComplexTerm) ((ComplexTerm) r[3]).getTerms()[0];
            Term pred = ((ComplexTerm) condition.getTerms()[1]).getTerms()[1];
            rhsm.add( prova.ct2prereq((ComplexTerm) condition.getTerms()[1]) );
            if( tt.equals(RListUtils.emptyRList) )
            rhsm.add(DefaultLogicFactorySupport.prereq(kb.pcalc(),
                    RListUtils.rlist("[%0,remove_temporal_rule,%1,%2,[[id,%2],%3,end_of_transmission]", new Object[]{this, ruleID1, ID, obj_receiver})));
            else {
                Term t = RListUtils.rlist("[%0,remove_temporal_rule,%1,%2,[[id,%2],%3,end_of_transmission|%4]]", new Object[]{this, ruleID1, ID, obj_receiver, tt});
                rhsm.add(DefaultLogicFactorySupport.prereq(kb.pcalc(),t));
            }
        }
        rhsm.addAll(rhs.subList(1, rhs.size() - 1));
        rhsm.add(rhs.get(rhs.size() - 1));
        Rule rr = DefaultLogicFactorySupport.rule(rhsm, f);
        if (xid instanceof VariableTerm && ID instanceof String) {
            Replacement rp = new Replacement((VariableTerm) xid, DefaultLogicFactorySupport.cons(ID, String.class));
            rr = (Rule) rr.apply(rp);
        }
        kb.add(rr);

        if( r.length<=3 ) {
            Fact fc = DefaultLogicFactorySupport.fact(reagent.temporal_rule_eoc, ruleID1, ID, head);
            kb.add(fc);
        }

        return Boolean.FALSE;
    }

	protected Boolean cleanupTemporalRule(Object[] t2, String convID, ComplexTerm ct, String ruleID) throws ClauseSetException {
		 Object xid = (t2 == null) ? convID : t2[1];
		 Object oid = (xid instanceof VariableTerm) ? xid : convID;
		 boolean hasOpenChildren = false; //id_originator.containsValue(oid);
		 if (hasOpenChildren) {
			  // Further temporal rules were generated, so
			  //    set the end_of_transmission message as the one postponed
			  List objs = new ArrayList();
			  String sMsg = RListUtils.decode_rename(ct, objs);
			  RMessage toSendLater = new RMessage(sMsg, objs.toArray());
			  id_postponed.put(oid, new SendData(this, "0", "self", toSendLater));
			  return Boolean.TRUE;
		 }
		 // Remove the temporal rule (b) to record that this nested reaction
		 //    has terminated.
		 Fact toRemoveEOC = DefaultLogicFactorySupport.fact(reagent.temporal_rule_eoc, ruleID, DefaultLogicFactorySupport.variable("ID"),
					RListUtils.rlist("X"));
		 prova.getUpdater().removeall_facts(toRemoveEOC);
		 Object TID = id_originator.get(ruleID);

		 int count = ((Integer) temporal_rule_count.get(TID)).intValue();

		 id_originator.remove(ruleID);

		 if (count == 1) {
			  // All nested exchange complete for this conversation TID.
			  // Now make sure the relevant agent receives a postponed
			  //    end_of_conversation
			  temporal_rule_count.remove(TID);
			  SendData sd = (SendData) id_postponed.get(TID);
			  if (sd != null) {
					sd.send();
			  }
			  return Boolean.TRUE;
		 }
		 temporal_rule_count.put(TID, new Integer(count - 1));

		 return Boolean.TRUE;
	}

	/**
     * Remove all temporal rules matching a specified pattern
	 *
	 * @param f           Fact a pattern that can contain variables
	 * @param only_rcvMsg boolean If true, temporal rules corresponding
	 *                    to rcvMult are left intact
	 * @return Object[]
	 */
	protected Object[] remove_temporal_rule(Fact f, boolean only_rcvMsg) {
//		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 = null;
		try {
			e = kb.clauses(nextClause, null);
			while (e.hasNext()) {
				  Object o = e.next();
				  if (o instanceof Rule) {
						Rule r = (Rule) o;
						if (Resolution.resolve2(r.getHead(), f, prova.unificationAlgorithm,
								  true) != null) {
							 if (only_rcvMsg &&
										((ConstantTerm) ((ComplexTerm) r.getHead().getTerms()[2]).
										getTerms()[1]).getObject().equals("rcvMult")) {
								  return new Object[]{
										  null, r.getHead().getTerms()[1]};
							 }
							 kb.remove(r);
							 return new Object[]{
									 "", r.getHead().getTerms()[1]};
						}
				  }
			 }
		} catch (ClauseSetException ignored) {
			logger.error("Error while processing temporal reaction rules");
		}
		 return null;
	}

	/**
     * Remove a temporary rule pair (generated for each rcvMsg or do actions)
	 * based on its ruleID.
	 * A temporal rule pair includes:
	 * (a) a temporal_rule rule.
	 * (b) a temporal_rule_eoc rule.
	 * When rule (a) is present, it intercepts incoming messages matching
	 * its pattern.
	 * When rule (b) is removed, the count of nested rules for the rule
	 * at the higher level is decremented, and if this is the last nested
	 * rule, the end_of_transmission is sent to a higher
	 * level temporal rule if there is one.
	 * This method is called only from the core rules when either
	 * (1) rcvMsg receives the matching message
	 * (2) or end_of_transmission is received.
	 * If this temporal rule pair has already been replaced by another rule pair,
	 * do nothing.
	 *
	 * @param ruleID the ID of the rule to be retracted
	 * @param convID conversation-ID of the rule
	 * @param ct     the end_of_transmission message as ComplexTerm
	 *
	 * @return - always Boolean.TRUE
	 */
	public Boolean remove_temporal_rule(String ruleID, String convID, ComplexTerm ct) throws ClauseSetException {
		 // Remove the temporal rule (a) responding to messages matching
		 //    the pattern in the inline reaction
		 Fact toRemove = DefaultLogicFactorySupport.fact(reagent.temporal_rule, ruleID, DefaultLogicFactorySupport.variable("ID"), RListUtils.rlist("X"));
		 Object t2[] = remove_temporal_rule(toRemove, false);
		 // Outbound partitions of the rule, if any
		 List outbound = (List) ruleid_outbound.get(ruleID);
		 if( outbound != null ) {
			  for( int i=0; i<outbound.size(); i++ ) {
					Object o = outbound.get(i);
					// Ids of temporal rules to remove
					List inbound = (List) inbound_ruleids.get(o);
					for( int j=0; j<inbound.size(); j++ ) {
						 String ruleIDToRemove = (String) inbound.get(j);
						 Fact moreToRemove = DefaultLogicFactorySupport.fact(reagent.temporal_rule, ruleIDToRemove, DefaultLogicFactorySupport.variable("ID"), RListUtils.rlist("X"));
						 Object t2a[] = remove_temporal_rule(moreToRemove, false);
						 if( t2a!=null )
							 cleanupTemporalRule(t2a, convID, ct, (String) ruleIDToRemove);
					}
					inbound_ruleids.remove(o);
			  }
			  ruleid_outbound.remove(ruleID);
		 }
		 cleanupInboundRuleids(ruleID);
		 return cleanupTemporalRule(t2, convID, ct, ruleID);
	}


	private void cleanupInboundRuleids(String ruleID) {
		// Inbound partitions of the rule, if any
		 List inbound = (List) ruleid_inbound.get(ruleID);
		 if( inbound != null ) {
			 for( int i=0; i<inbound.size(); i++ ) {
				 Object o = inbound.get(i);
				 List list = (List) inbound_ruleids.get(o);
				 if( list!=null ) {
					 list.remove(ruleID);
					 if( list.isEmpty() )
						 inbound_ruleids.remove(o);
				 }
			 }
			 ruleid_inbound.remove(ruleID);
		 }
	}

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