package modules.codegas;

import simulator.*;
import modules.neighborset.*;
import utils.*;
import java.awt.*;
import java.util.*;

public class CodeGas implements ProcessorModule {
  public Symbol getName() { return Symbol.GetSymbol("CodeGas"); }
  public void link(PortedProcessor p) {
    pp = p;
    pp.addClockListener(this);
    pp.openNetworkPort(this,gasPort);
    ns = (NeighborSet)p.getModule("NeighborSet");
  }

  static final Symbol gasPort = Symbol.GetSymbol("codegas");
  PortedProcessor pp;
  NeighborSet ns;

  public void init() {
  }

  // P(reproduce) = k_r
  // P(die) = k_d * density
  // Expected density for a type = k_r/(k_d*n) for n types
  // Let's have a 2 to 1 density
  double k_r = 0.05; // reproduction constant
  double k_d = 0.025; // death constant
  // temperature and time constant
  double temp = 10;

  Vector particles = new Vector();
  Hashtable counts = new Hashtable(); // per-species local count
  Hashtable fixed = new Hashtable();

  // debuging kludge for recordkeeping...
  public static int globalCount=0;

  // Messages are pairs: (obj, target) where target is the neighbor targetted
  int lastT = 0;
  static boolean desynchronize = false;
  public boolean signalEvent(Symbol name,Object data) {
    boolean dirty = false;
    if(name==PortedProcessor.kClock) {
      int t = ((Integer)data).intValue();
      if(t-lastT>temp) {
        lastT = t;
        if(desynchronize) lastT-=(int)((Math.random()-0.5)*temp);

        // reproduction is allocated as follows:
        // There are n tickets available for n particles in the processor
        // If all particles are equally represented, then each gets an even
        // share of the tickets.
        // a particle with lesser representation gets more tickets.
        // Implementation: sum inverses, then normalize. 
        Iterator i = counts.values().iterator();
        double rtickets = 0;
        while(i.hasNext()) {
          double c = ((Integer)i.next()).doubleValue();
          rtickets += 1/c;
        }
        Vector newp = new Vector();
        Hashtable fixtmp = new Hashtable(fixed);
        i = particles.iterator();
        while(i.hasNext()) {
          GasParticle p = (GasParticle)i.next();
          boolean die = (Math.random() < k_d*(particles.size()-1));
          // if first time on a fixed species, it can't die:
          if(fixtmp.containsKey(p.particle)) {
            die = false; fixtmp.remove(p.particle);
          }
          double c = ((Integer)counts.get(p.particle)).doubleValue();
          double tickets = particles.size()/(rtickets*c*c);
          boolean spawn = (Math.random() < k_r*tickets);
          if(die&&spawn) { // cancel and do nothing
          } else if(die) {
            i.remove(); globalCount--;//System.out.println("Die!");
          } else if(spawn) {
            newp.add(p.clone()); globalCount++;//System.out.println("Spawn!");
          }
        }
        particles.addAll(newp);
        // finally, dispatch to new homes
        fixtmp = new Hashtable(fixed);
        if(ns.size() > 0 && particles.size() > 0) {
          Vector l = new Vector();
          i = particles.iterator();
          while(i.hasNext()) {
            GasParticle p = (GasParticle)i.next();
            // if first time on a fixed, it won't move
            if(fixtmp.containsKey(p.particle)) {
              fixtmp.remove(p.particle); continue;
            }
            double d = ns.getRandomNeighbor(true);
            p.location = d;
            l.add(p);
            i.remove();
          }
          pp.sendMessage(gasPort,l);
        }
        // and last of all... rebuild the count
        counts.clear();
        i = particles.iterator();
        while(i.hasNext()) {
          GasParticle p = (GasParticle)i.next();
          incrementCount(p.particle);
        }
      }
    } else if(name==gasPort) {
      Vector v = (Vector)data;
      Iterator i = v.iterator();
      while(i.hasNext()) {
        GasParticle p = (GasParticle)i.next();
        if(pp.getUID()==p.location) {
          particles.add(p);
          // add to counts
          incrementCount(p.particle);
        }
      }
    }
    return dirty;
  }

  void incrementCount(Object p) {
    if(counts.containsKey(p)) {
      int c = ((Integer)counts.get(p)).intValue();
      counts.put(p,new Integer(c+1));
    } else {
      counts.put(p,new Integer(1));
    }
  }
  
  public void fixParticle(Object data) { fixed.put(data,data); }
  public void unfixParticle(Object data) { fixed.remove(data); }

  public void addParticle(Object data) {
    particles.add(new GasParticle(pp.getUID(),data));
    incrementCount(data);
    globalCount++;
  }
  public int countParticles() { return particles.size(); }
  public Vector getParticles() {
    Vector v = new Vector();
    Iterator i=particles.iterator();
    while(i.hasNext()) { v.add(((GasParticle)i.next()).particle); }
    return v;
  }
  public String toString() { 
    return "CodeGas: "+globalCount+" Fixed:"+fixed.toString()+" Particles:"+particles.toString();
  }
}

class GasParticle {
  double location;
  Object particle;
  public GasParticle(double l, Object p) {
    location=l; particle=p;
  }
  public Object clone() {
    GasParticle p =  new GasParticle(location,particle);
    return p;
  }
  public String toString() {
    return "("+location+", "+particle+")";
  }
}
