package modules.neighborset;

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

public class NeighborSet implements ProcessorModule {
  PortedProcessor pp;

  public Symbol getName() { return Symbol.GetSymbol("NeighborSet"); }
  public void link(PortedProcessor p) {
    pp = p;
    pp.addClockListener(this);
    pp.openNetworkPort(this,port);
    myData = new NbrData(pp.getUID(),0);
  }

  // Note: given the new role added to the neighborset, sends have been
  // increased in frequency
  static final Symbol port = Symbol.GetSymbol("NeighborFinder");
  static final double timeout = 10; // period of sends
  static final int decay = 5;

  public void init() {
    pp.sendMessage(port,myData);
  }

  Hashtable neighbors = new Hashtable();
  NbrData myData;
  boolean send = true;
  boolean sendable = true;
  int lastTime = 0;
  public boolean signalEvent(Symbol name,Object data) {
    boolean changed = false;
    if(name==PortedProcessor.kTransmit) {
      sendable = true; // mark the Q as cleared
    } else if(name==PortedProcessor.kClock) {
      int t = ((Integer)data).intValue();
      if(t - lastTime > timeout) {
        lastTime = t;
        myData.neighbors = neighbors.size();
        // prune things
        Iterator i = getNeighbors();
        while(i.hasNext()) {
          Double key = (Double)i.next();
          NbrData nbr = (NbrData)neighbors.get(key);
          nbr.age++;
          if(nbr.age > decay) {
            i.remove(); changed = true;
          } else {
            neighbors.put(key,nbr);
          }
        }
        send=true;
      }
      if(send && sendable) {
        pp.sendMessage(port,myData); send=false; sendable=false;
      }
    } else if(name==port) {
      NbrData n = (NbrData)data;
      NbrData olddata = (NbrData)neighbors.get(n.id);
      if(olddata != null) {
        olddata.age = 0;
        olddata.data = (Hashtable)n.data.clone();
        if(n.neighbors!=olddata.neighbors) {
          olddata.neighbors = n.neighbors;
          changed = true;
        }
      } else {
        neighbors.put(n.id,n.clone());
        changed = true;
      }
    }
    if(changed) rebuildWeightedMap(); // maintain randomizer
    return changed;
  }

  public Object getSelf() { return myData.id; }
  public Iterator getNeighbors() { return neighbors.keySet().iterator(); }
  public Vector getNeighborList() { return new Vector(neighbors.keySet()); }

  private static Random r = new Random(); // random used by everybody

  TreeMap eqmap = new TreeMap();
  private void rebuildWeightedMap() {
    eqmap.clear();
    double accum = 0;
    Iterator i = getNeighbors();
    while(i.hasNext()) {
      Double key = (Double)i.next();
      NbrData nbr = (NbrData)neighbors.get(key);
      accum += 1/(double)nbr.neighbors;
      eqmap.put(new Double(accum),nbr);
    }
  }
  // if equalize, then weight by inverse connectivity
  public double getRandomNeighbor(boolean equalize) {
    NbrData n;
    if(equalize) {
      double mapsize = ((Double)eqmap.lastKey()).doubleValue();
      Double select = new Double(r.nextDouble()*mapsize);
      Double key = (Double)eqmap.tailMap(select).firstKey();
      n = (NbrData)eqmap.get(key);
    } else {
      Vector v = new Vector(neighbors.values());
      n = (NbrData)v.get(r.nextInt(v.size()));
    }
    return n.id.doubleValue();
  }

  public int size() { return neighbors.size(); }
  static boolean fullDisplay = false;
  public String toString() { 
    String s = "NeighborSet: "+myData;
    if(fullDisplay) s+=" "+eqmap.toString();
    return s;
  }

  // setting things into its data
  public void setData(Object key, Object datum) {
    if(datum!=myData.data.get(key)) send=true; // flag for xmit on change
    if(datum==null) myData.data.remove(key);
    else myData.data.put(key,datum);
  }
  public Object getData(Object key) {
    return myData.data.get(key);
  }
  public Object getData(Object key, Object nbrID) {
    NbrData nd = (NbrData)neighbors.get(nbrID);
    if(nd==null) return null;
    return nd.data.get(key);
  }
}

class NbrData {
  Double id;
  int neighbors;
  int age=0;
  Hashtable data = new Hashtable();
  public NbrData(Double i, int n) { id=i; neighbors=n; }
  public NbrData(double i, int n) { id=new Double(i); neighbors=n; }
  public Object clone() {
    NbrData nd = new NbrData(id,neighbors);
    nd.data = (Hashtable)data.clone();
    return nd;
  }
  public String toString() {
    return "["+id+", "+neighbors+", "+age+", "+data.toString()+"]";
  }
}
