package modules.dataflow;

import utils.*;
import simulator.*;
import java.util.*;
import modules.codegas.*;
import modules.gradient.*;
import modules.line.*;
import modules.node.*;
import java.awt.*;

// will only color itself once per tick
public class DataflowInterpreter implements ProcessorModule {
  PortedProcessor pp; ActiveGradient ag; PersistentNodeB pn; 
  CodeGas cg; PersistentLineB pl; // use type B instead

  public Symbol getName() { return Symbol.GetSymbol("DataflowInterpreter"); }
  public void link(PortedProcessor p) { 
    pp = p; 
    pp.addClockListener(this);
    ag = (ActiveGradient)p.getModule("ActiveGradient");
    cg = (CodeGas)p.getModule("CodeGas");
    pn = (PersistentNodeB)p.getModule("PersistentNode");
    pl = (PersistentLineB)p.getModule("PersistentLine");
  }
  public void init() {} // nothing needs to happen

  public boolean signalEvent(Symbol name,Object data) {
    boolean changed = false;
    if(name==PortedProcessor.kClock) {
      updateInstances(); // run interpreter
    } 
    return changed;
  }

  public String toString() { return "DataflowInterpreter: "+instances; }
  static Symbol blankCall = Symbol.GetSymbol("zombie");
  static boolean firstblank = true;

  public static Hashtable nodetable;
  // process instances
  // an instance is a call-ID and all the port-values its associated with.
  // a variable fires when all of its IO ports are filled
  Object callID(Object gid) { return ((Pair)gid).cdr(); }
  Object nodeIDsrc(Object gid) { return ((Pair)((Pair)gid).car()).car(); }
  Object nodeIDdst(Object gid) { return ((Pair)((Pair)gid).car()).cdr(); }
  public void updateInstances() {
    // collect all gradients of type call
    Set v = ag.getType(DFNode.call);
    Vector nodes = pn.getNodes();
    // first collect all callIDs
    Hashtable t = new Hashtable();
    Iterator ni=nodes.iterator();
    while(ni.hasNext()) {
      DFNode dfn = (DFNode)nodetable.get(ni.next());
      if(dfn==null) continue;
      if(firstblank && dfn.isPrimed(blankCall,ag)) {
        firstblank = false;
        t.put(blankCall,new Instance(dfn,blankCall));
      }
      Iterator i=v.iterator();
      while(i.hasNext()) {
        Gradient g = (Gradient)i.next();
        Object cid = callID(g.gid);
        //if(!dfn.getName().equals(new Double(733233))) 
        //  System.out.println("ig: "+dfn.getName()+", "+g.gid+", "+nodeIDsrc(g.gid)+", "+nodeIDdst(g.gid));
        // add if right node
        if(nodeIDsrc(g.gid).equals(dfn.getName()) || 
           nodeIDdst(g.gid).equals(dfn.getName())) {
          //if(!dfn.getName().equals(new Double(733233))) System.out.println("go");
          if(instances.get(cid)==null) {
            t.put(cid,new Instance(dfn,cid)); 
            //if(!dfn.getName().equals(new Double(733233))) System.out.println("newone");
          } else {
            t.put(cid,instances.get(cid));
          }
        }
      }
    }
    instances=t; // lose the instances that don't pertain any more...
    // then, for each callid, process the instances
    Iterator i=instances.values().iterator();
    while(i.hasNext()) { 
      Instance inst = (Instance)i.next();
      inst.update(ag);
      //if(inst.done()) { System.out.println("kill"); i.remove(); }
    }
  }
  Hashtable instances=new Hashtable();
}

class Instance {
  DFNode dfn;
  Object callID;
  boolean waiting=false;
  boolean dead=false;
  int deadtime=0;
  Hashtable pending=null; // filled with [killid,gid] not constructed by us

  public Instance(DFNode n, Object cid) { dfn=n; callID=cid; }

  // wait for all inputs to be filled, then start execution and add outputs
  // to the pending list. At each step, check for pending gradients.
  // if found, delete from pending list. 
  // When pending list is empty, terminate this
  public void update(ActiveGradient ag) {
    if(dead) { 
      deadtime++; 
    } else if(waiting) {
      Iterator i=pending.keySet().iterator();
      while(i.hasNext()) {
        Object gid = i.next();
        Object killid = pending.get(gid);
        if(ag.get(killid)!=null) { i.remove(); } // kill if we got the return
        else { // else keep the gradient going
          Gradient g = ag.get(gid);
          if(g!=null) ag.renewGradient(g);
        }
      }
      if(pending!=null && pending.isEmpty()) dead=true;
    } else {
      if(dfn.isPrimed(callID,ag)) {
        pending = dfn.execute(callID,ag);
        waiting=true;
      }
    }
  }
  public boolean done() { return deadtime>50; }
  public String toString() {
    return "["+dfn+", "+callID+", "+waiting+", "+pending+"]";
  }
}

// gradient flows, bringing values along with it
// Gradient ID: ((nodesrc, nodedst),(portid, procid)) type: execute data: value
// At node: if all inputs filled with (nodeid), tell backward "done",
// tell forward new thing
// If all ios good, kill kill kill
