package modules.dataflow;

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

abstract public class DFNode {
  public static final Symbol call = Symbol.GetSymbol("CALL-EXECUTE");
  public static final Symbol ack = Symbol.GetSymbol("ACK");

  public static final Symbol _activate = Symbol.GetSymbol("*activate*");
  public static final Symbol _return = Symbol.GetSymbol("*return*");
  public static final Symbol _nvl = Symbol.GetSymbol("*no-variable-lambda*");

  Object name;
  Vector ports=new Vector(); // DFNPort objects
 
  public DFNode(double name, DFNPort p) 
  { make(new Double(name),new DFNPort[]{p}); }
  public DFNode(double name, DFNPort[] p) { make(new Double(name),p); }
  public DFNode(Object name, DFNPort p) { make(name,new DFNPort[]{p}); }
  public DFNode(Object name, DFNPort[] p) { make(name,p); }

  private void make(Object name, DFNPort[] p)
  { this.name=name; this.ports.addAll(Arrays.asList(p)); }

  public Object getName() { return name; }
  public Iterator getPorts() { return ports.iterator(); }
  public DFNPort getPort(Object name) { 
    Iterator i = ports.iterator();
    while(i.hasNext()) { 
      DFNPort p = (DFNPort)i.next(); if(p.id().equals(name)) return p; }
    return null;
  }

  // default behavior for isPrimed: check if all port inputs have a callid G
  Object inputName(DFNPort p,Object n,Object cid)
  { return new Pair(new Pair(n,name),cid); }
  Object outputName(DFNPort p,Object n,Object cid)
  { return new Pair(new Pair(name,n),cid); }
  Object returnName(Object gid) { return new Pair(gid,ack); }
  boolean isPrimed(Object callID,ActiveGradient ag) {
    Iterator i = ports.iterator();
    while(i.hasNext()) {
      DFNPort p = (DFNPort)i.next(); 
      Iterator j=p.getIn();
      while(j.hasNext()) {
        Object n = j.next();
        Object gid = inputName(p,n,callID);
        Gradient g = ag.get(gid);
        //System.out.println("testit: "+gid);
        if(g==null || g.isDead()) return false;
      }
    }
    //System.out.println("Primed!");
    // Now we know it's primed.
    // if it's primed, we can also send back the kill messages.
    i = ports.iterator();
    while(i.hasNext()) {
      DFNPort p = (DFNPort)i.next(); 
      Iterator j=p.getIn();
      while(j.hasNext()) {
        Object n = j.next();
        Object gid = inputName(p,n,callID);
        Gradient g = ag.get(gid);
        ag.sendGradient(returnName(gid),ack,ack,new DFLineFollower(n,name));
      }
    }
    return true;
  }
  // execute cannot be standardized
  abstract Hashtable execute(Object callID,ActiveGradient ag);

  // utility functions
  // send to all ports
  Hashtable sendToAll(ActiveGradient ag, Object callID, Object msg) {
    Hashtable t = new Hashtable();
    Iterator i = ports.iterator();
    while(i.hasNext()) {
      t.putAll(sendToPort(ag, callID,msg,(DFNPort)i.next()));
    }
    return t;
  }
  // returns hashtable filled with sends and expected responses
  Hashtable sendToPort(ActiveGradient ag, Object callID, Object msg, DFNPort p)
  {
    Hashtable t = new Hashtable();
    Iterator i=p.getOut();
    while(i.hasNext()) {
      Object n=i.next();
      Object gid = outputName(p,n,callID);
      Gradient g = ag.get(gid);
      if(g==null) {
        ag.sendGradient(gid,call,msg,new DFLineFollower(name,n));
      } else {
        g.data=msg;
        ag.renewGradient(g);
      }
      t.put(gid,returnName(gid));
    }
    return t;
  }
  // get a list of the data on the given port
  Vector getPortValues(ActiveGradient ag,Object callID,DFNPort p) {
    Vector v = new Vector();
    Iterator i=p.getIn();
    while(i.hasNext()) {
      Object n=i.next();
      Object gid = inputName(p,n,callID);
      Gradient g = ag.get(gid);
      if(g==null || g.isDead() || g.data==null) { continue; }
      else { v.add(g.data); }
    }
    return v;
  }
}

// flows through nodes and lines
class DFLineFollower implements PropagationTest {
  static Symbol line = Symbol.GetSymbol("line");
  Object la,lb,na,nb; // names to test for
  DFLineFollower(Object from, Object to) {
    na=from; nb=to; 
    la=new Pair(line,new Pair(from,to)); // line names
    lb=new Pair(line,new Pair(to,from)); // line names
  }
  public boolean propagate(Gradient g, PortedProcessor pp) {
    PersistentNodeB pn = (PersistentNodeB)pp.getModule("PersistentNode");
    PersistentLineB pl = (PersistentLineB)pp.getModule("PersistentLine");
    Vector nodes = pn.getNodes();
    Vector lines = pl.getLines();
    return (lines.contains(la) || lines.contains(lb) || 
            nodes.contains(na) || nodes.contains(nb));
  }
}
