/*
 * Decoder.java
 * DPCM decoder
 * This class decodes the encoded image data using frame differencing, 
 * one frame at a time.
 */

import java.io.*;

class Decoder extends Thread {
  private CircularQueue buf;      //shared buffer, class in ../11/
  private BitInputStream bitin;   //class in ../8/
  private Hcodec hcodec;          //class in ../8/
  private Dtables decode_tables;  //class in ../8/
  private Run3D runs[];           //class in ../7/
  private Run run;                //class in ../7/
  private Reorder reorder;        //class in ../7/
  private RGBImage image;         //class in ../5/
  private Display display;        //class in ../11/
  private RefVector refVector;    

  //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();
    image = new RGBImage ( buf.width, buf.height );
    refVector = new RefVector ( buf );
  }

  /*
    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 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 );
      //inverse quantization has been done in the reconstruction method
      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;
  }

  private void copy_dct_block ( int Y[][], short dctcoefs[][][], int b ) 
  {
    for ( int i = 0; i < 8; i++ )
      for ( int j = 0; j < 8; j++ )
        dctcoefs[b][i][j] = (short) Y[i][j];
  }

  /*
    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 YCbCr_MACRO object.
    Return: Reconstructed YCbCr macroblock in YCbCr_MACRO.
  */
  private int get_yccblocks( YCbCr_MACRO ycbcr_macro, int nm )
  {
    int r, row, col, i, j, k, n, b, c;
    int [][] Y = new int[8][8], X = new int[8][8];
    short dctcoefs[][][] = new short[6][8][8];

    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;
      copy_dct_block ( Y, dctcoefs, b );
    } //for b
    
    //now do that for 8x8 Cb block
    if ( !get_dct_block( Y ) )     //read in one DCT block
      return 0;
    copy_dct_block ( Y, dctcoefs, 4 );
    
    //now do that for 8x8 Cr block
    if ( !get_dct_block( Y ) )     //read in one DCT block
      return 0;
    copy_dct_block ( Y, dctcoefs, 5 );

    //Reconstruct current frame from DCT coefficients
    refVector.reconstruct_macro ( dctcoefs, nm );
    YCbCr_MACRO aMacro;
    aMacro = refVector.get(nm);
    for ( i = 0; i < 256; i++ ){
      ycbcr_macro.Y[i] = aMacro.Y[i];
      if ( i < 64  ) {
        ycbcr_macro.Cb[i] = aMacro.Cb[i];
        ycbcr_macro.Cr[i] = aMacro.Cr[i];
      }
    }

    n = 6 * 64;
    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 nm = 0;

    for ( row = 0; row < image.height; row += 16 ) {
      for ( col = 0; col < image.width; col += 16 ) {
        int m = get_yccblocks( ycbcr_macro, nm++ );
        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 ) {
            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
        }
      } //for col
    }  //for row

    return n;
  }

  public void run()  
  { 
     System.out.printf("\nRunning DPCM Decoder..\n");
     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 ( 50 );    //See ThreadedPlayer.java of ../10/ for playing at desired fps
       } catch (InterruptedException e )  {}

     }
  }

}
