/*
 * CircularQueue.java
 * Implements a circular queue that may have more than
 * one slot.  Each slot may hold the data of an image frame.
 * Because setSamples() and putSamples() are not synchronized,
 * two threads can access the queue data simultaneously as
 * long as they are in different slots.
 * Assume an RGB model for the image.
 */
import java.io.*;
import javax.media.jai.*;
import ij.plugin.AVI_Reader;

class CircularQueue
{
  private long head = 0;
  private long tail = 0;
  private int length;      //length of queue
  public int width;        //image width
  public int height;       //image height
  public byte buffer[][];  //the data queue
  public boolean quit = false; 

  public CircularQueue ( int queueLength, int w, int h )
  {
    if ( queueLength > 0 )
      length = queueLength;
    else
      length = 1;
    width = w;
    height = h;
    int frameSize = 3 * width * height;
    buffer = new byte[length][frameSize];
  }

  public synchronized void headInc()
  {  
    head++;
    notifyAll();   //wake up other threads
  }

  public synchronized void tailInc()
  {
    tail++;
    notifyAll();   //wake up other threads
  }     
  
  public synchronized boolean bufferEmpty()
  {
    if ( tail <= head )
      return true;
    else
      return false;
  }

  public synchronized boolean bufferFull()
  {
     if ( tail >= head + length )
        return true;
     else
        return false;
  }

  public synchronized void waitIfBufferFull()
  {
     if ( tail >= head + length ){
      try {
        //System.out.println( "Buffer full, producer waits." );
        wait();
      } catch ( InterruptedException e ) {
          e.printStackTrace();
      }
    }
  }

  public synchronized void waitIfBufferEmpty()
  {
    if ( tail <= head ){
      try {
        System.out.println( "Buffer empty, consumer waits." );
        wait();
      } catch ( InterruptedException e ) {
          e.printStackTrace();
      }
    }
  }

  public synchronized int getHead()
  {
    return (int) ( head % length );
  }
  
  public synchronized int getTail()
  {
    return (int) ( tail % length );
  }
  
  // consumes data
  // get data from head slot of buffer and put them in the TiledImage object
  public void setSamples ( TiledImage outImage )
  {
    int bands = 3;
    int k = 0;
    int h = (int) ( head % length );     //wrap-around
    for (int y = 0; y < height; y++)
      for (int x = 0; x < width; x++)
         for (int band=0; band < bands; band++)
           outImage.setSample(x, y, band, buffer[h][k++] );
  }

  // produces data
  // put image data of the nFrame frame fo AVI_Reader object into tail slot of buffer
  public int putSamples ( AVI_Reader avi, int nFrame )
  {
    int t = (int) ( tail % length );     //wrap-around
    int size = 3 * width * height;
    int num = 0;

    Object obj = avi.getPixels ( nFrame );
    if ( obj instanceof int[] ) {
      int[] pixels = (int[]) obj;
      int frameSize = width * height;      
      for (int i=0, k = 0; i < frameSize; i++, k += 3) {
        buffer[t][k] =  (byte)( 0x000000ff & (pixels[i]>>16) );
        buffer[t][k+1] =  (byte)( 0x000000ff & (pixels[i]>>8) );
        buffer[t][k+2] =  (byte)( 0x000000ff & pixels[i] );
      }
      num = 3 * frameSize;
    } else {
      System.out.println("Only supports integer pixels\n");
      System.exit ( -1 );
    }
    return num;  //return number of bytes read,-1 for end of data stream
  }

  // produces data
  // put image data at tail slot of buffer
  public boolean putRGBSamples ( RGBImage image )
  {
     if ( width != image.width || height != image.height ){
       System.out.println("Dimensions inconsistent!");
       return false;
     }
     
     int t = getTail();
     int frameSize = width * height;
     for (int i=0, k = 0; i < frameSize; i++, k += 3) {
        buffer[t][k] =  (byte)( image.ibuf[i].R );
        buffer[t][k+1] =  (byte)( image.ibuf[i].G );
        buffer[t][k+2] =  (byte)( image.ibuf[i].B );
     }
     
     return true;
  }
}
