package simulator;

import utils.*;
import java.util.*;
import java.awt.geom.*;

public class Simulator extends Observable implements Runnable {
  private EmbeddedNetwork net;
  private CommunicationsModel comm;
  private SortedSet events;
  private double absTime;
  public Thread mythread;
  private ProcessorFactory pfac;

  static boolean jiggrid = false; // jiggly grid layout
  static double jigfac = 0.6;
  static public void jiggledGrid(double factor) {
    if(Double.isNaN(factor)) jiggrid=false;
    else { jiggrid=true; jigfac=factor; }
  }

  public Simulator(CommunicationsModel comm,ProcessorFactory pfac, int numProcessors,double nbrRadius) {
    this.pfac=pfac;
    absTime = 0;
    this.comm = comm;
    events = new TreeSet();
    net = new EmbeddedNetwork(nbrRadius);
    // make all the processors
    System.out.print("Creating processors");
    for(int i=0;i<numProcessors;i++) {
      if(i%1000 == 0) System.out.print(".");
      double x, y;
      if(jiggrid) {
        double width = (Math.ceil(Math.sqrt(numProcessors)));
        double unit = 1/width; //System.out.println(unit);
        x = (0.5+(i%width))*unit+(Math.random()-0.5)*(jigfac*unit);
        y = (0.5+(Math.floor(i/width)))*unit+(Math.random()-0.5)*(jigfac*unit);
      } else {
        // to smooth, add loop around choosing of x and y
        x = Math.random();
        y = Math.random();
      }
      Processor p = pfac.MakeProcessor(this,x,y);
      net.addNode(p,x,y);
      events.add(new SimulatorEvent(p,Math.random(),kInit,null));
    }
    comm.setNetwork(net);
    System.out.println();
  }

  // Simulator base substructure: events
  // There are two basic types of events --- timer events and message events
  // timer events are wakeup calls set by a processor for itself.
  // when a processor sends a message, a message event is signalled to its
  // neighbors after some delay.
  // [the communications model can be developed independently]
  double tmem=0;
  public void run() {
    while(!events.isEmpty() && !kludgeterm) { // run while there's activity
      doEvent();
      // while paused, sleep thread
      while(paused) { try{ mythread.sleep(100);}catch(Exception e){} }
    }
    kludgeterm=false;
  }
  public static boolean kludgeterm=false;

  int eventCount=0; // just for fun... count the events
  synchronized private void doEvent() {
    if(events.isEmpty()) return;
    // pull off the first event
    SimulatorEvent s = (SimulatorEvent)events.first();
    events.remove(s);
    eventCount++; // just for fun... count the events
    
    if(Math.floor(s.time)>Math.floor(absTime)) {
      //System.out.println(Math.floor(absTime));
    }
    // update absolute time
    absTime = s.time;
    
    // observable clock tickout
    if(absTime-tmem > 0.1) {
      tmem=absTime; setChanged(); notifyObservers();
    }
    
    // if it's for a suspended processor, then quash the event.
    // (this has the effect of draining out messages delivered to suspended)
    if(suspended.containsKey(s.proc)) return;
    // dispatch event to the processor in question
    if(s.name == kInit) {
      s.proc.init();
    } else {
      Object data = s.data;
      if(s.name == kMessage)
        data = comm.receive(s); // give the comm system a chance to corrupt
      s.proc.signalEvent(s.name,data);
    }
  }

  boolean paused = true;

  public double getTime() { return absTime; }
  public int numEvents() { return eventCount; }
  public EmbeddedNetwork getNet() { return net; }
  public void setPaused(boolean p) { paused = p; }

  // death & life for processors
  Hashtable suspended = new Hashtable();
  // when a processor is suspended, it is placed in the suspended hashtable,
  // along with the simulator timeout events destined for it.
  // these timeout events have their time adjusted, so that when the processor
  // revives again, they continue as though un-interrupted.
  synchronized public void suspendProcessor(Processor p) {
    if(suspended.containsKey(p)) return; // already done

    Vector save = new Vector();
    Iterator i = events.iterator();
    while(i.hasNext()) {
      SimulatorEvent s = (SimulatorEvent)i.next();
      if(s.proc==p) {
        if(s.name != kMessage) // filter out messages
          save.add(new SimulatorEvent(s.proc,s.time-absTime,s.name,s.data));
        i.remove();
      }                                 
    }
    suspended.put(p,save);
    p.suspend();
  }
  // inverse process of suspension
  synchronized public void reviveProcessor(Processor p) {
    if(!suspended.containsKey(p)) return; // already done

    Vector save = (Vector)suspended.get(p);
    Iterator i = save.iterator();
    while(i.hasNext()) {
      SimulatorEvent s = (SimulatorEvent)i.next();
      events.add(new SimulatorEvent(s.proc,s.time+absTime,s.name,s.data));
    }
    suspended.remove(p);
    p.revive();
  }
  // this one is like a combination of revive and suspend...
  // first it revives, then it kills events, then it adds an init event
  synchronized public void resetProcessor(Processor p) {
    suspendProcessor(p); // suspend, with side effect of purging events
    suspended.remove(p);
    Point2D loc = net.getLocation(p);
    net.removeNode(p);

    Processor pnew = pfac.MakeProcessor(this,loc.getX(),loc.getY());
    net.addNode(pnew,loc.getX(),loc.getY());
    Vector v = new Vector();
    v.add(new SimulatorEvent(pnew,0,kInit,null));
    suspended.put(pnew,v); // insert init event
    reviveProcessor(pnew); // restore processor
    setChanged(); notifyObservers(new Pair(p,pnew));
  }

  public static final Symbol kMessage = Symbol.GetSymbol("message");
  public static final Symbol kDelay = Symbol.GetSymbol("delay");
  public static final Symbol kInit = Symbol.GetSymbol("init");

  // processor/sim interface

  // passes the send event to the comm model to produce receive events
  public void processor_broadcast(Processor p, Object message) {
    SimulatorEvent e = new SimulatorEvent(p,absTime,kMessage,message);
    events.addAll(comm.broadcast(e));
  }
  public void processor_pointtopoint(Processor p, double t, Object message) 
  {
    // how can I find the processor with uid t?
    Enumeration en = net.getNeighbors(p);
    while(en.hasMoreElements()) {
      Processor p2 = (Processor)en.nextElement();
      if(p2.getUID()==t) {
        SimulatorEvent e = new SimulatorEvent(p,absTime,kMessage,message);
        events.add(comm.pointtopoint(e,p2));
        return;
      }
    }
  }
  public void processor_timer(Processor p, double delay, Object data) {
    SimulatorEvent e = new SimulatorEvent(p,absTime+delay,kDelay,data);
    events.add(e);
  }
  public boolean processor_carrierSense(Processor p) {
    Point2D loc = net.getLocation(p);
    return comm.carrierSense(absTime,loc.getX(),loc.getY());
  }

  // sends a message to a particular processor --- to be invoked by the
  // user interface.
  public void userSendMessage(Processor p, Object message) {
    SimulatorEvent e = new SimulatorEvent(p,absTime,kMessage,message);
    events.add(e);
  }
}

