package modules.gradient;

import utils.*;
import simulator.*;
import java.util.*;

// how active gradients work:
// I, a processor, initiate a gradient.
// Everybody who hears the gradient picks it up to pass on if they can
// when a gradient stops being initiated, it dies from the roots upward
// when decay reaches the leaves, it goes backward again as deletion
public class ActiveGradient implements ProcessorModule {
  PortedProcessor pp;

  public Symbol getName() { return Symbol.GetSymbol("ActiveGradient"); }
  public void link(PortedProcessor p) { 
    gradients.pp = pp = p; 
    pp.addClockListener(this);
    pp.openNetworkPort(this,port);
  }
  public void init() {
  }

  static final Symbol port = Symbol.GetSymbol("activeGradient");
  boolean xmitok = true;
  Set latched = new HashSet();
  public boolean signalEvent(Symbol name,Object data) {
    boolean changed = false;
    if(name==PortedProcessor.kTransmit) {
      xmitok = true;
    } else if(name==PortedProcessor.kClock) {
      int t = ((Integer)data).intValue();
      if(t%2==0) {
        gradients.decay();
        // make a message from the gradients
        if(xmitok && !gradients.isEmpty()) {
          xmitok = false;
          pp.sendMessage(port,gradients.toMessage());
        }
        //changed = true;
      }
      // renew latched gradients
      Iterator i=latched.iterator();
      while(i.hasNext()) {
        Gradient g = get(i.next());
        if(g!=null && g.getHopcount()==0)
          renewGradient(g); 
        else
          i.remove();
      }
    } else if(name==port) {
      gradients.integrate(data);
      //changed = true;
    }
    return changed;
  }

  AGSet gradients = new AGSet();

  // call to initiate a gradient:
  // check if it's in the DB: if so, make it alive. Else, create it
  public void sendGradient(Object gid, Symbol type, Object data) {
    sendGradient(gid,type,data,new UnlimitedPropagation());
  }
  public void sendGradient(Object gid, Symbol type, Object data,
                           PropagationTest limit) {
    sendGradient(new Gradient(gid,type,data,limit));
  }
  public void sendGradient(Gradient newg) {
    Gradient g = gradients.get(newg.gid);
    if(g!=null) gradients.replace(g,newg);
    else gradients.add(newg);
    renewGradient(newg); // make sure it's got hopcount zero, etc.
    // integrate the old gradient's data into the new one... usually
    // will produce a null reaction
    // Actually, wait, this won't work at all yet... come back later
    //newg.integrate(g);
  }
  // maintain a pre-existing gradient
  // Now, with option to start the hops not at zero.
  public void renewGradient(Gradient g) { renewGradient(g,0); }
  public void renewGradient(Gradient g,int hops) {
    Gradient g2 = gradients.get(g.gid);
    if(g2!=null) {
      g2.hopcount=hops;
      g2.resetLife();
    }
  }

  // this will automatically renew a gradient as long as its hopcount is zero
  // when hopcount is no longer zero, the gradient unlatches
  public void latchGradient(Object gid) { latched.add(gid); }
  public void unlatchGradient(Object gid) { latched.remove(gid); }
  public void latchGradient(Gradient g) { latched.add(g.gid); }
  public void unlatchGradient(Gradient g) { latched.remove(g.gid); }

  // returns a list of all gradients
  public Collection getAll() { return gradients.getAll(); }
  // returns the gradient with that gid
  public Gradient get(Object gid) { return gradients.get(gid); }
  // returns the list of ids of gradients with that type
  public Set getType(Symbol type) { return gradients.getType(type); }
  public Set getTypeLive(Symbol type) { 
    Set s2 = new HashSet(2);
    Iterator i = getType(type).iterator();
    while(i.hasNext()) {
      Object id = i.next();
      if(!get(id).isDead()) s2.add(id);
    }
    return s2;
  }

  public String toString() {
    return "ActiveGradient: [Latched: "+latched+"] "+gradients.toMessage().toString();
  }
}

class AGSet {
  PortedProcessor pp;
  Hashtable table = new Hashtable(2);
  Hashtable typesets = new Hashtable(2);

  // simple accessor functions
  public boolean isEmpty() { return table.isEmpty(); }
  public void replace(Gradient oldg, Gradient newg)
  { add(newg); } // added for compatiability...
  public void add(Gradient g)
  { table.put(g.gid,g); typeTable(g.type).add(g.gid); }
  public void remove(Gradient g) 
  { table.remove(g.gid); typeRemove(g); }
  public Collection getAll() { return table.values(); }
  public Gradient get(Object gid) { return (Gradient)table.get(gid); }
  public Set getType(Symbol type) { 
    HashSet h = (HashSet)typesets.get(type);
    if(h==null) return new HashSet(2);
    else return h; // don't mess with me, please!
  }
  // get the right typetable (or create it if it doesn't exist)
  private HashSet typeTable(Object type) {
    HashSet h = (HashSet)typesets.get(type);
    if(h==null) { h=new HashSet(2); typesets.put(type,h); }
    return h;
  }
  private void typeRemove(Gradient g) {
    HashSet h = (HashSet)typesets.get(g.type);
    if(h!=null) {
      h.remove(g.gid); if(h.isEmpty()) typesets.remove(g.type);
    }
  }

  public Object toMessage() {
    Vector v = new Vector();
    Iterator i = table.values().iterator();
    while(i.hasNext()) { v.add(((Gradient)i.next()).clone()); }
    return new Pair(new Double(pp.getUID()),v);
  }
  // for each gradient, if it matches one already here, update.
  // otherwise, add the new one
  public void integrate(Object message) {
    Double nbrID = (Double)((Pair)message).car();
    Iterator i = ((Vector)((Pair)message).cdr()).iterator();
    while(i.hasNext()) {
      Gradient nbr = (Gradient)i.next();
      Gradient g = get(nbr.gid);
      if(g==null) {
        if(!nbr.isDead()) { // only propagate if alive
          g = Gradient.copyFromNeighbor(nbr);
          boolean prop=false;
          if(g.limit instanceof PropagationTestExt)
            prop = ((PropagationTestExt)g.limit).propagate(g,pp,nbrID);
          else prop= g.limit.propagate(g,pp);
          if(prop) add(g);
        }
      } else {
        // first integrate... 
        // (integration step used to go here)
        // ... then allow the reset to go through if it's in limit
        boolean prop=false;
        if(g.limit instanceof PropagationTestExt)
          prop = ((PropagationTestExt)g.limit).propagate(g,pp,nbrID);
        else prop = g.limit.propagate(g,pp);
        if(prop) { 
          g.integrate(nbr); // integration is only allowed on OK props
          g.allowResets();
        }
      }
    }
  }
  
  public void decay() {
    Iterator i = table.values().iterator();
    while(i.hasNext()) {
      Gradient g = (Gradient)i.next();
      g.decay();
      if(g.delete) { i.remove(); typeRemove(g); }
    }
  }
}

