/*
 * Decoder.java
 * This class decodes the encoded image data, one frame at a time.
 * No ME and MC considered.
 */

import java.io.*;

class Decoder extends Thread {
  private CircularQueue buf;  //shared buffer
  private BitInputStream bitin;
  private Hcodec hcodec;
  private Dtables decode_tables;
  private Run3D runs[];
  private Run run;
  private Reorder reorder;
  private Quantizer quantizer;
  private RGBImage image;
  private Display display;

  //constructor
  public Decoder ( CircularQueue q, BitInputStream bit_inputs, Display d )
  {
    buf = q;
    bitin = bit_inputs;
    display = d;
    hcodec = new Hcodec();
    hcodec.build_htable();
    decode_tables = new Dtables();       //tables used in decoding process
    hcodec.build_huff_tree ( decode_tables );
    runs = new Run3D[64];
    for ( int i = 0; i < 64; ++i )
      runs[i] = new Run3D();
    run = new Run();
    reorder = new Reorder();
    quantizer = new Quantizer();
    image = new RGBImage ( buf.width, buf.height );
  }

  /*
    Read bit stream from inputs, Huffman-decode the input stream to obtain a
    sample block, then run-level decode it to get an 8x8 sample block, reverse-
    reorder and inverse-quantize it to get the desired DCT block.
    Output: 8x8 DCT sample block in Yi[][].
  */
  boolean get_dct_block ( int Yi[][] )
  {
    short Yr[][] = new short[8][8];
    short Y[][]  = new short[8][8];
    if ( hcodec.huff_decode( bitin, decode_tables, runs ) > 0 ){
      run.run_decode ( runs, Yr );
      reorder.reverse_reorder ( Yr, Y );
      quantizer.inverse_quantize_block ( Y );
      for ( int i = 0; i < 8; i++ )
        for ( int j = 0; j < 8; j++ )
          Yi[i][j] = (int) Y[i][j];
      return true;
    } else
      return false;
  }

  /*
    Get DCT data from get_dct_block(); convert DCT coefficients to YCbcr and put the
    four 8x8 Y sample blocks, one 8x8 Cb sample block and one 8x8 Cr sample block into a
    struct of YCbCr_MACRO.
    Return: number of bytes read from file and put in YCbCr_MACRO class object.
  */
  private int get_yccblocks( YCbCr_MACRO ycbcr_macro )
  {
    int r, row, col, i, j, k, n, b, c;
    byte  abyte;
    int [][] Y = new int[8][8], X = new int[8][8];
    DctVideo dct_idct = new DctVideo();

    n = 0;
    //read data from file and put them in four 8x8 Y sample blocks
    for ( b = 0; b < 4; b++ ) {
      if ( !get_dct_block ( Y ) )
        return 0;
      dct_idct.idct ( Y, X );
      k = 0;
 
      if ( b < 2 )
        k = 8 * b;                //points to beginning of block 
      else
        k = 128 + 8 * ( b - 2 );  //points to beginning of block  
      for ( i = 0; i < 8; i++ ) { //one sample-block
        if ( i > 0 ) k += 16;     //advance by 1 row of macroblock 
        for ( j = 0; j < 8; j++ ){
          ycbcr_macro.Y[k+j] =  X[i][j];
          n++;
        } //for j
      } //for i
    } //for b

    //now do that for 8x8 Cb block
    if ( !get_dct_block( Y ) )    //read in one DCT block
      return 0;
    dct_idct.idct ( Y, X );       //perform IDCT, output in X
    k = 0;
    for ( i = 0; i < 8; ++i ) {
      for ( j = 0; j < 8; ++j ) {
         ycbcr_macro.Cb[k] =  X[i][j]; //put Cb samples in macro block
         k++;
         n++;
      }
    }

    //now do that for 8x8 Cr block
    if ( !get_dct_block( Y ) )    //read in one DCT block
      return 0;
    dct_idct.idct ( Y, X );       //perform IDCT, output in X
    k = 0;
    for ( i = 0; i < 8; ++i ) {
      for ( j = 0; j < 8; ++j ) {
         ycbcr_macro.Cr[k] = X[i][j]; //put Cr samples in macro block
         k++;
         n++;
      }
    }

    return n;                     //number of bytes read
  }

  /*
   * Decode DCT coeffs to a YCbCr frame and then to RGB.  
   */
  private int decode_one_frame ()
  {
    int r, row, col, i, j, k, block;
    int n = 0;
    RGB_MACRO rgb_macro=new RGB_MACRO();         //assume 24-bit for each RGB pixel
    YCbCr_MACRO ycbcr_macro = new YCbCr_MACRO(); //YCbCr macroblock 
    RgbYcc rgbycc = new RgbYcc();
    int tempi = 0;
    Printer printer = new Printer();
    for ( row = 0; row < image.height; row += 16 ) {
      for ( col = 0; col < image.width; col += 16 ) {
        int m = get_yccblocks( ycbcr_macro );
        if ( m <= 0 ) 
          return m;
        n += m;
        rgbycc.ycbcr2macroblock( ycbcr_macro, rgb_macro );
        k = row * image.width + col;
        r = 0;
        for ( i = 0; i < 16; ++i ) {
          for ( j = 0; j < 16; ++j ) {
            //R, B, B data are treated in different order in different applications
            image.ibuf[k].B = rgb_macro.rgb[r].R;
            image.ibuf[k].G = rgb_macro.rgb[r].G;
            image.ibuf[k].R = rgb_macro.rgb[r].B;
            k++;  r++;
          }
          k += (image.width - 16);   //points to next row of macroblock
        }
        ++tempi;
      } //for col
    }  //for row

    return n;
  }

  public void run()  
  { 
     while ( !buf.quit ) {
       buf.waitIfBufferFull();
       //produce data
       int numBytes = decode_one_frame();
       if ( numBytes <= 0 )
         buf.quit = true;
       buf.putRGBSamples( image );
       buf.tailInc();
       display.display_frame();
       buf.headInc();
       try {
         Thread.sleep ( 80 );  //See ThreadedPlayer.java of ../10/ for playing at desired fps
       } catch (InterruptedException e )  {}
     }
  }
}
