/*****************************************************************************/
/*                                                                           */
/*                     Producer and Consumer Program                         */
/*               Graphical Version Allowing Multiple Instances               */
/*                                                                           */
/*                     January 1999, Toshimi Minoura                         */
/*                     October 1999, Jacob Lundberg                          */
/*                                                                           */
/*****************************************************************************/

// A producer thread places jobs in a bounded-buffer,
// and these jobs are retrieved by a consumer thread.

import java.awt.*;
import java.util.*;

class BoundedBufferG {
  static final int BufferSize = 3;  // constant defining size of this buffer
  private Object data[];            // storage for data queued
  private int headIdx;              // index to head of queued data
  private int tailIdx;              // index to tail of queued data
  private int nElements;            // number of elements in this buffer

  public BoundedBufferG() {         // constructor
    reset();
  }

  public synchronized void reset() {
    data = new Object[BufferSize];      // create array for Objects
    for (int i = 0; i < BufferSize; i++) {
      data[i] = null;                   // initially, no element
    }
    headIdx = 0;                        // position of data[0]
    tailIdx = BufferSize - 1;           // position preceding data[0]
    nElements = 0;                      // initially, no elements
  }

  public synchronized void enqueue(String name, Object x) {
    while (nElements == BufferSize) {           // buffer is full
//      System.out.println("Thread " + name + " will be blocked");
      try { wait(); }                           // block calling Thread  
      catch (java.lang.InterruptedException e) { };
    }
    if (++tailIdx == BufferSize) tailIdx = 0;   // end of buffer, wrap around
//    System.out.println("Object " + x + " enqueued in data[" + tailIdx + "]"
    data[tailIdx] = x;                          // store argument object
    nElements++;                                // one element added
    notify();                                   // activate blocked consumer
  }

  public synchronized Object dequeue(String name) {
    while (nElements == 0) {                    // buffer is empty
//      System.out.println("Thread " + name + " will be blocked");
      try { wait(); }                           // block calling Thread
      catch (java.lang.InterruptedException e) { };
    }
    Object temp = data[headIdx];                // object to be returned
    data[headIdx] = null;                       // no object in this slot
//    System.out.println("Object " + temp + " dequeued from data["
//                        + headIdx + "]");
    if (++headIdx == BufferSize) headIdx = 0;   // wrap around
    nElements--;                                // one element removed
    notify();                                   // activate blocked producer
    return temp;
  }

  public synchronized void dataPrint() {        // print stack content
     System.out.print("BoundedBuffer: headIdx = " + headIdx +
                                   ", tailIdx = " + tailIdx + ", data = ");
    for (int i = 0; i < BufferSize; i++) {
      System.out.print(data[i]);    // print every value in data[]
      if (i < BufferSize - 1) System.out.print(", ");
    }
    System.out.println("");
  }

  public void drawRightArrow(Graphics g, int x1, int y1, int x2) {
    Polygon poly = new Polygon();     // arrow from (x1, y1) to (x2, y1)
    poly.addPoint(x1, y1 - 2);
    poly.addPoint(x1, y1 + 2); 
    poly.addPoint(x2 - 6, y1 + 2);
    poly.addPoint(x2 - 6, y1 + 6);
    poly.addPoint(x2, y1);             // tip of right arrow
    poly.addPoint(x2 - 6, y1 - 6);
    poly.addPoint(x2 - 6, y1 - 2);
    g.fillPolygon(poly);               // draw arrow
  }

  static final Color BoxColor =  new Color(150, 0, 0);     // for box outline
  static final Color EmptyBox =  new Color(0, 150, 0);     // for empty box
  static final Color FilledBox =  new Color(100, 255, 0);  // for filled box
  static final Color EmptyLetter =  new Color(255,255, 255); // for null box
  static final Color FullLetter =  new Color(100, 100, 0); // for value in box
  static final Color RightArrowColor =  new Color(0, 0, 200);
  static final Color ArrowLabel =  new Color(0, 0, 200); // 

  Font f = new Font("TimesRoman", Font.BOLD, 18);

  public synchronized void paint(Graphics g) {
    int x1 = 280;                   // left position for queue elements
    int y = 80;                     // top position of queue elements
    int Width = 60;                 // width of queue element
    int Height = 35;                // height of queue element
    int PointerXLoc = x1 - Width * 2 / 3;

//    System.out.println("BoundedBufferG.paint() called");

    g.setFont(f);                   // set font for letters

    if (headIdx == tailIdx) {
      int y2 = y + headIdx * Height;   // y position of arrow
      g.setColor (FullLetter);         // set letter color
      g.drawString("head & tail", x1 - Width * 8 / 3, y2 + Height / 2 + 5);
      g.setColor(RightArrowColor);     // set right arrow color
      drawRightArrow(g, PointerXLoc, y2 + Height / 2,    // draw head pointer
                        x1 - 2);
    } else {
      int y2 = y + headIdx * Height;   // y position of arrow
      g.setColor (FullLetter);         // set letter color
      g.drawString("head", x1 - Width * 4 / 3 - 15, y2 + Height / 2 + 5);
      g.setColor(RightArrowColor);     // set right arrow color
      drawRightArrow(g, PointerXLoc, y2 + Height / 2,    // draw head pointer
                        x1 - 2);
      int y3 = y + tailIdx * Height;    // y position of arrow
      g.setColor (FullLetter) ;        // set letter color
      g.drawString("tail", x1 - Width * 4 / 3, y3 + Height / 2 + 5);
      g.setColor(RightArrowColor);     // set right arrow color
      drawRightArrow(g, PointerXLoc, y3 + Height / 2,    // draw tail pointer
                        x1 - 2);
    }
    for (int i = 0; i < data.length; i ++) {  // draw queue elements
      if (data[i] == null) {                  // is queue element empty?
        g.setColor(EmptyBox);                 // yes, use empty box color
        g.fillRect(x1, y, Width, Height);     // fill box
        g.setColor(EmptyLetter);              // set light letter color
      } else {                                // queue element exists
        g.setColor(FilledBox);                // set clor for box with element
        g.fillRect(x1, y, Width, Height);     // draw box
        g.setColor(FullLetter);               // set dark letter color
      }
      if (data[i] == null) {                     // no queue element?
        g.drawString("null", x1 + 12, y + Height / 2 + 5);   // indicate null
      } else {
        g.drawString(String.valueOf(data[i]), x1 + 15, y + Height / 2 + 5);
      }
      g.setColor(BoxColor);                      // set box outline color
      g.drawRect(x1, y, Width - 1, Height);   // draw box outline
      y += Height;               // update y position of next array element
    }
  }
}
 
class ProducerG extends Thread {
  static final int NJobs = 20;    // number of jobs produced
  String name;                    // name of this producer
  BoundedBufferG queue;           // queue this producer is connected to
  StepTestCanvas canvas;          
  String currentJob = null;       // ID of current job being produced

  final int MinTime =  3000;      // minimum time required to prepare a job
  final int MaxTime = 12000;      // maximum time required to prepare a job
  final int DelayTime = 1000;     // interval for updating work progress
  int totalTime;                  // time needed to process current job
  int workTime;                   // time spent so far
  int yloc;                       // y-location of a given producer instance

  public ProducerG(String name, BoundedBufferG queue, StepTestCanvas canvas, int y1) {
    this.name = name;             // copy argument
    this.queue = queue;           // copy argument
    this.canvas = canvas;         // copy argument
    this.yloc = y1;               // copy argument
  }

  void reset() {
    currentJob = null;
  }

  static final Color BoxColor = new Color(150, 0, 0);        // for box outline
  static final Color InsideColor = new Color(200, 200, 255); // for inside box
  static final Color DoneColor = new Color(0, 0, 255);       // for work done
  static final Color LetterColor = new Color(0, 0, 255);     // idle

  synchronized void paint(Graphics g) {
//    System.out.println("ProducerG.paint() called");
    int x1 = 50;              // left position for job progress indicator
    int y1 = yloc;            // top position of job progress indicator
    int MaxWidth = 80;        // maximum width of job progress indicator
    int width;
    int Height = 25;          // height of job progress indicator

    if (y1 <= 0) y1 = 100;    // default for sanity and convenience

    g.setColor(Color.black);
    g.drawString("Producer", x1, y1 - 15);
    if (currentJob == null) {
      return;
    }
    width = MaxWidth * totalTime / MaxTime;
    int doneWidth = width * workTime / totalTime;
//    System.out.println("ProducerG.paint(): doneWidth = " + doneWidth);
    if (workTime < totalTime) {               // is work in progress?
      g.setColor(InsideColor);                // set box inside color
      g.fillRect(x1, y1, width , Height);     // inside background
      g.setColor(DoneColor);                  // set work done color
      g.fillRect(x1, y1, doneWidth, Height);  // work done
      g.setColor(BoxColor);                        // set box outline color
      g.drawRect(x1, y1, width - 1, Height - 1);   // draw box outline
    } else {                                  // producer being blocked
      g.setColor(LetterColor);
      g.drawString("Producer Blocked", x1, y1 + Height /2 +5);
    }
    g.setColor(Color.black);
    g.drawString("Job "+ currentJob, x1, y1 + Height + 22);
  }

  public void run() {    
    // If the whole method is synchronized, when queue.enqueue()
    // is blocked, paint() cannot be executed.

    System.out.println("Producer " + name + " started");
//    queue.dataPrint();                         // print queue content

    for (int i = 1; i <= NJobs; i++) { 
      synchronized(this) {
        totalTime = (int) (Math.random() * (MaxTime - MinTime) + MinTime);
        if (i > NJobs / 2) {   // make producer slow for second-half period
          totalTime = totalTime * 2;
        }
        currentJob = name + '.' + i;      // construct job name
        System.out.println("Producer " + name + " will create " + currentJob +
                          " in " + totalTime + " msecs.");
        canvas.repaintMsg("Producer " + name + " will create " + currentJob +
                          " in " + totalTime + " msecs.");
        for (workTime = 0; workTime < totalTime; ) {
          try { wait(DelayTime);}         // delay execution
          catch (java.lang.InterruptedException e) { };
          workTime += DelayTime;
          canvas.repaint();
        }
      }
      queue.enqueue(name, currentJob);    // insert element into queue
      synchronized(this) {
        currentJob = null;
      }
//      queue.dataPrint();                // print queue content
      canvas.repaint();                   // update screen
    }
    System.out.println("Producer " + name + " completed");
  }  
}
 
class ConsumerG extends Thread {
  static final int NJobs = 20;     // number of jobs consumed
  String name;                     // name of this consumer
  BoundedBufferG queue;            // queue this consumer is connected to
  StepTestCanvas canvas;    
  String currentJob = null;        // job ID of current job

  final int MinTime =  3000;       // minimum time required to consume a job
  final int MaxTime = 12000;       // maximum time required to consume a job
  final int DelayTime = 1000;      // interval for updating work progress
  int totalTime;                   // time required to consume current job
  int workTime;                    // time spent so far
  boolean active = false;          // thread started
  int yloc;                        // y-location of a given consumer instance

  public ConsumerG(String name, BoundedBufferG queue, StepTestCanvas canvas, int y1) {
    this.name = name;              // copy argument
    this.queue = queue;            // copy argument
    this.canvas = canvas;          // copy argument
    this.yloc = y1;                // copy argument
  }

  void reset() {
    currentJob = null;
    active = false;                // to supress "Consumer Idle" message
  }

  static final Color BoxColor = new Color(150, 0, 0);        // for box outline
  static final Color InsideColor = new Color(200, 200, 255); // for inside box
  static final Color DoneColor = new Color(0, 0, 255);       // for work done
  static final Color LetterColor = new Color(0, 0, 255);     // idle

  synchronized void paint(Graphics g) {
//    System.out.println("ProducerG.paint() called");
    int x1 = 400;             // left position for job progress indicator
    int y1 = yloc;            // top position of job progress indicator
    int MaxWidth = 80;        // maximum width of job progress indicator
    int width;
    int Height = 25;          // height of job progress indicator

    if (y1 <= 0) y1 = 100;    // default for sanity and convenience

    g.setColor(Color.black);
    g.drawString("Consumer", x1, y1 - 15);
    if (!active) {                 //  consumer not running
      return;
    }
    if (currentJob == null) {      // consumer waiting for a job
      g.setColor(LetterColor);
      g.drawString("Consumer Idle", x1, y1 + Height /2 +5);
      return;
    }
    width = MaxWidth * totalTime / MaxTime;
    int doneWidth = width * workTime / totalTime;
//    System.out.println("ConsumerG.paint(): doneWidth = " + doneWidth);
    if (workTime < totalTime) {               // is work in progress?
      g.setColor(InsideColor);                // set box inside color
      g.fillRect(x1, y1, width , Height);     // inside background
      g.setColor(DoneColor);                  // set work done color
      g.fillRect(x1, y1, doneWidth, Height);  // work done
    }
    g.setColor(BoxColor);                        // set box outline color
    g.drawRect(x1, y1, width - 1, Height - 1);   // draw box outline
    g.setColor(Color.black);
    g.drawString("Job "+ currentJob, x1, y1 + Height + 22);
  }

  public void run() {          
    // If the whole method is synchronized, when queue.dequeue()
    // is blocked, paint() cannot be executed.

    System.out.println("Consumer " + name + " started");
    active = true;                               // thread started
    for (int i = 1; i <= NJobs; i++) { 
      currentJob = (String) queue.dequeue(name); // get next job
      System.out.println("Consumer " + name + " dequeued " + currentJob);
//      queue.dataPrint();                       // print queue content

      synchronized(this) {
        totalTime = (int) (Math.random() * (MaxTime - MinTime) + MinTime);
        if (i < NJobs / 2) {   // make consumer slow for first-half period
          totalTime = totalTime * 2;
        } 
        System.out.println("Consumer " + name + " will be busy for " +
                           totalTime + " msecs");
        canvas.repaintMsg("Consumer " + name + " will process " + currentJob +
                          " in " + totalTime + " msecs.");
        for (workTime = 0; workTime < totalTime; ) {
          try { wait(DelayTime);}              // delay execution
          catch (java.lang.InterruptedException e) { };
          workTime += DelayTime;
          canvas.repaint();
        }
        currentJob = null;                     // no job being processed
      }
      canvas.repaint();                        // update screen
    }
  System.out.println("Consumer " + name + " completed");
  }  
}
 
public class ProdConsG extends StepTestCanvas {
  BoundedBufferG queue = new BoundedBufferG();
  ProducerG producer1;
  ProducerG producer2;
  ConsumerG consumer1;
  ConsumerG consumer2;

  Font f = new Font("TimesRoman", Font.BOLD, 18);

  public void paint(Graphics g) {    // called when window need be redrawn
//    System.out.println("ProdConsG.paint() called");
    g.setFont(f);                    // set font for letters
    queue.paint(g);                  // draw stack
    if (producer1 != null) producer1.paint(g);   // show state of producer1
    if (producer2 != null) producer2.paint(g);   // show state of producer2
    if (consumer1 != null) consumer1.paint(g);   // show state of consumer1
    if (consumer2 != null) consumer2.paint(g);   // show state of consumer2
  }

  public void runMain() {          
    System.out.println("runMain() started");
    if (producer1 != null) {
      producer1.stop();
      producer1.reset();
    }
    if (producer2 != null) {
      producer2.stop();
      producer2.reset();
    }
    if (consumer1 != null) {
      consumer1.stop();
      consumer1.reset();
    }
    if (consumer2 != null) {
      consumer2.stop();
      consumer2.reset();
    }
    queue.reset();
    repaintStep("Press Step button to start some producers and consumers.");
    producer1 = new ProducerG("p1", queue, this, 50);
    producer1.start();
    producer2 = new ProducerG("p2", queue, this, 175);
    producer2.start();
    consumer1 = new ConsumerG("c1", queue, this, 50);
    consumer1.start();
    consumer2 = new ConsumerG("c2", queue, this, 175);
    consumer2.start();
    repaintMsg("Producers p1, p2 and consumers c1 started.");
    System.out.println("runMain() completed");
  }  

  public static void main(String[] args) {
    StepTestFrame f = new StepTestFrame("Producer and consumer",
                                  new ProdConsG(), 580, 400); 
    f.show();                // display frame
  }
}

