 
//package queuesim;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.applet.*;
import java.util.*;

//////////////////////////////////////////////////////////////////////////////
public class QueueSim extends Applet
//////////////////////////////////////////////////////////////////////////////
{
  Button start=new Button("Start");
  Button reset=new Button("Reset");
  MyChoice emitRate=new MyChoice(new String[] {"500  packet/s",
"350 packet/s"}, new double[] {2E-3,3E-3},1);
  MyChoice processorRate=new MyChoice(new String[] {"1000 packet/s",
"500 packet/s","350 packet/s"}, new double[] {1E-3,2E-3,3E-3},3);
  SimTimer myTimer;

  public void init()
  {
    try
    {
      myTimer=new SimTimer(1E-4,1000,this);
      setBackground(Color.white);
      add(new Label("Emission rate",Label.RIGHT));
      add(emitRate);
      add(new Label("Transmission rate",Label.RIGHT));
      add(processorRate);
      start.addActionListener(new ActionListener()
        {
        public void actionPerformed (ActionEvent event)
          {
            emitRate.setEnabled(false);
            processorRate.setEnabled(false);
            start.setEnabled(false);
            myTimer.launchSim(emitRate.getVal(), processorRate.getVal());
          }
        });
      add(start);
      reset.addActionListener(new ActionListener()
        {
        public void actionPerformed (ActionEvent event)
          {
            emitRate.setEnabled(true);
            processorRate.setEnabled(true);
            start.setEnabled(true);
            myTimer=new SimTimer(1E-4,1000,QueueSim.this);
          }
        });
      add(reset);
    }
    catch(Exception e) {}
  }

  public void paint (Graphics g)
  {
    update(g); // eliminate flashing : update is overriden
  }

  public void update (Graphics g)
  {
    //work on a offscreen image
    Dimension offDimension = getSize();
    Image offImage = createImage(offDimension.width, offDimension.height);
    Graphics offGraphics = offImage.getGraphics();
    myTimer.draw(offGraphics);
    g.drawImage(offImage, 0, 0, this);
  }

}

//////////////////////////////////////////////////////////////////////////////
class SimTimer implements Runnable
//////////////////////////////////////////////////////////////////////////////
{
  private double time;
  private double tic;
  private Line line1;
  private Sender sender1;
  private Dropper dropper1;
  private Queue queue1;
  private Line line2;
  private Processor proc1;
  private Applet target;
  private Thread timerThread;

  public SimTimer(double tick, double length, Applet tgt)
  {
    line1=new Line(1E-2,new Rectangle(10,60,200,10));
    sender1=new Sender(1E-3,line1);
    dropper1= new Dropper(.6E-2,new Rectangle(200,70,10,60));
    queue1=new Queue(10,line1,dropper1,new Rectangle(210,40,100,50));
    line2=new Line(1E-2,new Rectangle(340,60,200,10));
    proc1=new Processor(2E-3,queue1,line2,new Rectangle(310,50,30,30));
    target=tgt;
    time=0;
    tic=tick;
    timerThread=new Thread(this);
  }

  public void launchSim(double emitRate, double processorRate)
  {
    sender1.setEmitInterval(emitRate);
    proc1.setWrkLength(processorRate);
    timerThread.start();
  }

  public void run()
  {
    while (true)
    {
      dropper1.setTime(time);
      sender1.setTime(time);
      line1.setTime(time);
      queue1.setTime(time);
      proc1.setTime(time);
      line2.setTime(time);
      target.repaint();
      time+=tic;
      try {timerThread.sleep(50);} catch (Exception e) { };
    }
  }

  public void draw(Graphics g)
  {
    dropper1.draw(g);
    line1.draw(g);
    line2.draw(g);
    queue1.draw(g);
    proc1.draw(g);
    g.drawString(TimedClass.timeToString(time),10,110);
    g.drawString(queue1.getDropStat(),10,125);
  }
}
//////////////////////////////////////////////////////////////////////////////
class Packet
//////////////////////////////////////////////////////////////////////////////
{
  static final double emmissionDelay=1E-3;
  double emmissionTime;
  Color color;

  public Packet(double eT, Color c)
  {
    emmissionTime=eT;
    color=c;
  }
}

//////////////////////////////////////////////////////////////////////////////
class PacketFIFO
//////////////////////////////////////////////////////////////////////////////
{
  private int length;
  private Packet[] content;

  public PacketFIFO()
  {
    length=0;
  }

  public int getLength()
  {
    return length;
  }

  public void add(Packet P)
  {
    if (length==0)
    {
      content=new Packet[1];
      content[0]=P;
    }
    else
    {
        Packet[] temp=new Packet[length+1];
        for (int i=0;i<length;i++) {temp[i]=content[i];}
        temp[length]=P;
        content=temp;
    }
    length++;
  }

  public void rem()
  {
    Packet[] temp=new Packet[length-1];
    for (int i=0;i<length-1;i++) {temp[i]=content[i+1];}
    content=temp;
    length--;
  }

  public Packet getPacket(int index)
  {
    return content[index];
  }

  public Packet getFirstPacket()
  {
    return content[0];
  }
}
//////////////////////////////////////////////////////////////////////////////
class Sender
/////////////////////////////////////////////////////////////////////////////
{
  final Color
packetsColors[]={Color.blue,Color.red,Color.green,Color.gray,Color.magenta,Color.black,Color.pink};
  private double meanInterval;
  private Line outputLine;
  private double time;
  private double nextEmission;
  private int nextColor;
  private Random randomGenerator;

  public Sender(double meanI, Line outputL)
  {
    meanInterval=meanI;
    outputLine=outputL;
    randomGenerator=new Random();
  }

  public void setEmitInterval(double val) {meanInterval=val;}

  public void setTime(double now)
  {
    time=now;
    update();
  }

  private void update()
  {
    if (nextEmission<time)
    {
      outputLine.emitPacket(time,packetsColors[nextColor]);
      nextColor++;
      if (nextColor>=packetsColors.length){nextColor=0;}
      nextEmission=2*randomGenerator.nextDouble()*(meanInterval-Packet.emmissionDelay)+Packet.emmissionDelay;
      nextEmission+=time;
    }
  }
}

//////////////////////////////////////////////////////////////////////////////
class Line extends TimedClass
//////////////////////////////////////////////////////////////////////////////
{
  private boolean availableArrivedPacket;
  private Packet arrivedPacket;
  protected PacketFIFO packets;
  protected double propDelay;

  public Line(double propD,Rectangle r)
  {
    packets=new PacketFIFO();
    propDelay=propD;
    shape=r;
    availableArrivedPacket=false;
  }

  public void emitPacket(double eT, Color c)
  {
    packets.add(new Packet(eT,c));
  }

  public void draw(Graphics g)
  {
    for (int i=0;i<packets.getLength();i++)
    {
      drawPacket(packets.getPacket(i),g);
    }
    g.setColor(Color.black);
    g.drawRect(shape.x,shape.y,shape.width,shape.height);
  }

  private void drawPacket(Packet P, Graphics g)
  {
    double
x1=((double)shape.x)+(time-P.emmissionTime)*((double)shape.width)/propDelay;
    double x2=x1-(P.emmissionDelay)*((double)shape.width)/propDelay;
    if (x1>((double)(shape.x+shape.width))) x1=(double)(shape.x+shape.width);
    if (x2<((double)(shape.x))) x2=(double)(shape.x);
    g.setColor(P.color);
    g.fillRect((int)x2,shape.y,(int)(x1-x2),shape.height);
  }

  protected void update()
  {
    if (packets.getLength()>0)
    {
      Packet fP=packets.getFirstPacket();
      if (fP.emmissionTime+fP.emmissionDelay+propDelay<time)
      {
        packets.rem();
        arrivedPacket=fP;
        availableArrivedPacket=true;
      }
    }
  }
  public boolean getAvailableArrivedPacket() {return availableArrivedPacket;}

  public Packet pickArrivedPacket()
  {
    availableArrivedPacket=false;
    return arrivedPacket;
  }
}

//////////////////////////////////////////////////////////////////////////////
class Queue extends TimedClass
//////////////////////////////////////////////////////////////////////////////
{
  private int maxSize;
  private int size;
  private int receivedCounter;
  private int droppedCounter;
  private PacketFIFO packets;
  private Line inputLine;
  private Dropper dropper;

  public Queue(int mS,Line inputL,Dropper defDropper,Rectangle r)
  {
    shape=r;
    maxSize=mS;
    inputLine=inputL;
    packets=new PacketFIFO();
    dropper=defDropper;
  }

  protected void update()
  {
    //if a packet is at input then add it to the queue
    if (inputLine.getAvailableArrivedPacket())
    {
      addPacket(inputLine.pickArrivedPacket());
    }
  }

  private void addPacket(Packet P)
  {
    receivedCounter++;
    //if the queue is full drop packet else queue it
    if (size==maxSize)
    {
      dropPacket(P);
    }
    else
    {
      size++;
      packets.add(P);
    }
  }
  private void dropPacket(Packet P)
  {
    dropper.emitPacket(time,P.color);
    droppedCounter++;
  }

  public void draw(Graphics g)
  {
    for (int i=0;i<maxSize;i++)
    {
      int xp=shape.x+shape.width-(i+1)*(shape.width/maxSize);
      int wp=(shape.width/maxSize);
      if (i<(packets.getLength()))
      {
        g.setColor(packets.getPacket(i).color);
        g.fillRect(xp,shape.y,wp,shape.height);
      }
      g.setColor(Color.black);
      g.drawRect(xp,shape.y,wp,shape.height);
    }
  }

  public boolean getAvailableArrivedPacket() {return
(packets.getLength()>0);}

  public Packet pickArrivedPacket()
  {
    size--;
    Packet fP=packets.getFirstPacket();
    packets.rem();
    return fP;
  }

  public String getDropStat() {return Integer.toString(droppedCounter)+
" packets dropped out of "+Integer.toString(receivedCounter);}
}

//////////////////////////////////////////////////////////////////////////////
class Processor extends TimedClass
//////////////////////////////////////////////////////////////////////////////
{
  private boolean busy;
  private double workLength;
  private double curWorkStart;
  private Line outputLine;
  private Queue inputQueue;
  private Packet curPacket;

  public Processor(double wrkLength, Queue inputQ, Line outputL,Rectangle r)
  {
    workLength=wrkLength;
    inputQueue=inputQ;
    outputLine=outputL;
    shape=r;
    busy=false;
  }

  public void setWrkLength(double wrkl) {workLength=wrkl;}

  protected void update()
  { //if there is a packet and the job is done then emit it
    if ((busy)&&(workDone()))
    {
      outputLine.emitPacket(time, curPacket.color);
      busy=false;
    }
    if ((!(busy))&&(inputQueue.getAvailableArrivedPacket()))
    {
      busy=true;
      curWorkStart=time;
      curPacket=inputQueue.pickArrivedPacket();
    }
  }

  private boolean workDone(){
    return (curWorkStart+workLength<=time);
  }

  public void draw(Graphics g)
  {
    if (busy)
    {
      g.setColor(curPacket.color);
      g.fillRect(shape.x,shape.y,shape.width,shape.height);
    }
    g.setColor(Color.black);
    g.drawRect(shape.x,shape.y,shape.width,shape.height);
  }
}

class Dropper extends Line
{
  public Dropper(double propD,Rectangle r){super(propD,r);}

  public void draw(Graphics g)
  {
    for (int i=0;i<packets.getLength();i++)
    {
      drawPacket(packets.getPacket(i),g);
    }
  }

  private void drawPacket(Packet P, Graphics g)
  {
    double
y1=((double)shape.y)+(time-P.emmissionTime)*((double)shape.height)/propDelay;
    double y2=y1-(P.emmissionDelay)*((double)shape.height)/propDelay;
    if (y1>((double)(shape.y+shape.height)))
y1=(double)(shape.y+shape.height);
    if (y2<((double)(shape.y))) y2=(double)(shape.y);
    g.setColor(P.color);
    g.fillRect(shape.x,(int)y2,shape.width,(int)(y1-y2));
  }
}

//////////////////////////////////////////////////////////////////////////////
abstract class TimedClass
//////////////////////////////////////////////////////////////////////////////
{
  protected Rectangle shape;
  protected double time;

  public void setTime(double now)
  {
    time=now;
    update();
  }

  protected abstract void update();

  public abstract void draw(Graphics g);

  static String timeToString(double now)
  {
    String res=Double.toString(now*1000);
    int dot=res.indexOf('.');
    String inte=res.substring(0,dot);
    return inte+" msec";
  }
}
//////////////////////////////////////////////////////////////////////////////
class MyChoice extends Choice
//////////////////////////////////////////////////////////////////////////////
{
  private double vals[];

  public MyChoice(String items[], double values[], int defaultValue)
  {
    for (int i=0; i<items.length;i++) {super.addItem(items[i]);}
    vals=values;
    super.select(defaultValue-1);
  }

  public double getVal() {return  vals[super.getSelectedIndex()];}
}
 
 
  
