// This code implementation is the intellectual property of
// the GEANT4 collaboration.
//
// By copying, distributing or modifying the Program (or any work
// based on the Program) you indicate your acceptance of this statement,
// and all its terms.
//
// $Id: G4RTJpegCoder.cc,v 1.3 2000/03/09 15:36:37 asaim Exp $
// GEANT4 tag $Name: geant4-02-00 $
//
//
//

#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "G4RTJpeg.hh"
#include "G4RTOutBitStream.hh"
#include "G4RTJpegMaker.hh"
#include "G4RTJpegCoder.hh"
#include "G4RTJpegCoderTables.hh"


G4JpegCoder::G4JpegCoder(u_char* colorR,u_char* colorG,u_char* colorB)
{
  mRgb[0] = colorR;
  mRgb[1] = colorG;
  mRgb[2] = colorB;

  mPreDC[0] = mPreDC[1] = mPreDC[2] = 0;
  mOBSP = 0;

  for(int n=0; n<8; n++)
                for(int m=0; m<8; m++)
                                mCosT[n][m] = cos((2 * m + 1) * n * PaiDiv16);
}

G4JpegCoder::~G4JpegCoder(void)
{
  if(mOBSP != 0)
                delete mOBSP;
}

void
G4JpegCoder::GetJpegData(char** aJpegData, int& size)
{
  if (mOBSP != 0){
    *aJpegData = (char*)mOBSP->GetStreamAddress();
        size = mOBSP->GetStreamSize();
        }
        else{
          *aJpegData = 0;
          size = 0;
        }

}

int
G4JpegCoder::DoCoding(void)
{
  mNumVUnits = (mProperty.nRow / 16) + ((mProperty.nRow % 16) ? 1 : 0);
  mNumHUnits = (mProperty.nColumn / 16) + ((mProperty.nColumn % 16) ? 1 : 0);

  int size = mProperty.nColumn * mProperty.nRow * 3;
  if(size < 10240)
    size = 10240;

  try{
       mOBSP = new G4OutBitStream(size);
           WriteHeader();
           for(int yu=0; yu<mNumVUnits; yu++){
                   for(int xu=0; xu<mNumHUnits; xu++){
                           makeYCC(xu, yu);

  //mRgb->YCrCb
  #ifdef GRAY
  for(int i=0; i<64; i++)
        mCbBlock[i] = mCrBlock[i] = 0;
  #endif
         CodeMCU();
         }
       }
       WriteEOI();
           return M_NoError;
     }

  catch(G4MemoryError &me){
                return M_RuntimeError;
  }
  catch(G4BufferError &be){
                return M_RuntimeError;
  }
  catch(G4IndexError &ie){
                return M_RuntimeError;
  }
}

//MCU
void
G4JpegCoder::CodeMCU(void)
{
  for(int n=0; n<4; n++){
                ForwardDCT(mYBlock[n]);
                Quantization(0);
                CodeHuffman(0);
  }
  ForwardDCT(mCbBlock);
  Quantization(1);
  CodeHuffman(1);

  ForwardDCT(mCrBlock);
  Quantization(2);
  CodeHuffman(2);
}

void
G4JpegCoder::makeYCC(int ux, int uy)
{
  u_char  rv, gv, bv;
  int tCrBlock[4][64];
  int tCbBlock[4][64];

  for(int u=0; u<4; u++){
        int *yp = mYBlock[u];
        int *cbp = tCbBlock[u];
        int *crp = tCrBlock[u];

        int sx = ux * 16 + ((u&1) ? 8 : 0);
        int ex = sx + 8;
        int sy = uy * 16 + ((u>1) ? 8 : 0);
        int ey = sy + 8;

     for(int iv=sy; iv<ey; iv++){
          int ii = iv < mProperty.nRow ? iv : mProperty.nRow - 1;
          for(int ih=sx; ih<ex; ih++){
            int jj = ih < mProperty.nColumn ? ih : mProperty.nColumn - 1;
                int index = ii * mProperty.nColumn + jj;
                rv = mRgb[0][index];
                gv = mRgb[1][index];
                bv = mRgb[2][index];

                *yp++ = int((0.2990 * rv) + (0.5870 * gv) + (0.1140 * bv) - 128)
;
                *cbp++ = int(-(0.1687 * rv) - (0.3313 * gv) + (0.5000 * bv));
                *crp++ = int((0.5000 * rv) - (0.4187 * gv) - (0.0813 * bv));
                                }       // ih
                           }    //iv
  }     //u

 int   n = 0;
  for(int b=0; b<4; b++){
        switch(b){
                case 0:         n=0;    break;
                case 1:         n=4;    break;
                case 2:         n=32;   break;
                case 3:         n=36;
        }
        for(int y=0; y<8; y+=2){
                for(int x=0; x<8; x+=2){
                        int idx = y * 8 + x;
                        mCrBlock[n] = tCrBlock[b][idx];
                        mCbBlock[n] = tCbBlock[b][idx];
                        n++;
                }
                n += 4;
        }
  }
}

void
G4JpegCoder::CodeHuffman(int cs)
{
  const G4HuffmanCodeTable& dcT = cs ? CDcHuffmanT : YDcHuffmanT;
  const G4HuffmanCodeTable& acT = cs ? CAcHuffmanT : YAcHuffmanT;
  const int eobIdx = cs ? CEOBidx : YEOBidx;
  const int zrlIdx = cs ? CZRLidx : YZRLidx;

  int diff = mDCTData[0] - mPreDC[cs];
  mPreDC[cs] = mDCTData[0];
  int absDiff = abs(diff);
  int dIdx = 0;

  while(absDiff > 0){
        absDiff >>= 1;
        dIdx++;
  }
  if(dIdx > dcT.numOfElement)
        throw(G4IndexError(dcT.numOfElement, dIdx, "CodeHuffman:DC"));
  mOBSP->SetBits((dcT.CodeT)[dIdx], (dcT.SizeT)[dIdx]);

  if(dIdx){
        if(diff < 0)
                diff--;
        mOBSP->SetBits(diff, dIdx);
  }

  int run = 0;
  for(int n=1; n<64; n++){
        int absCoefficient = abs( mDCTData[ Zigzag[n] ] );
        if( absCoefficient ){
                while( run > 15 ){
                mOBSP->SetBits((acT.CodeT)[zrlIdx], (acT.SizeT)[zrlIdx]);
                run -= 16;
                }
                int s = 0;
                while( absCoefficient > 0 ){
                        absCoefficient >>= 1;
                        s++;
                }
                int     aIdx = run * 10 + s + (run == 15);
                if( aIdx >= acT.numOfElement ) 
                  throw( G4IndexError( acT.numOfElement, aIdx, "CodeHuffman:AC" )
 );
  mOBSP->SetBits( (acT.CodeT)[aIdx], (acT.SizeT)[aIdx] );
                int     v = mDCTData[ Zigzag[n] ];
                if( v < 0 )
                  v--;
                mOBSP->SetBits( v, s );
                run = 0;
                }
                else{
                  if(n == 63)
                  mOBSP->SetBits( (acT.CodeT)[eobIdx], (acT.SizeT)[eobIdx] );
                else
                run++;
                }
        }
}


void
G4JpegCoder::Quantization(int cs)
{
  int* qt = (int*)(cs ? CQuantumT : YQuantumT);
  for( int i=0; i<64; i++ ){
    mDCTData[i] /= qt[i];
  }
}


void
G4JpegCoder::ForwardDCT(int* picData)
{
  for( int v=0; v<8; v++ ){
    double cv = v ? 1.0 : DisSqrt2;
    for( int u=0; u<8; u++ ){
      double cu = u ? 1.0 : DisSqrt2;
      double sum = 0;

      for( int y=0; y<8; y++ )
        for( int x=0; x<8; x++ )
          sum += picData[ y * 8 + x ] * mCosT[u][x] * mCosT[v][y];
          mDCTData[ v * 8 + u ] = int( sum * cu * cv / 4 );
        }
    }
}


void
G4JpegCoder::WriteHeader( void )
{
  int i = 0;    //counter
  //SOI
  mOBSP->SetByte( M_Marker );   //FF
  mOBSP->SetByte( M_SOI );      //SOI

  //APP0(JFIF Header)
  mOBSP->SetByte( M_Marker );   //FF
  mOBSP->SetByte( M_APP0 );     //APP0
  mOBSP->SetWord( JFIFLength );        //parameter
  mOBSP->CopyByte( (char*)JFIF, 5 );   //"JFIF\0"
  mOBSP->SetWord( JFIFVersion );       //Version
  mOBSP->SetByte( mProperty.Units );
  mOBSP->SetWord( mProperty.HDensity );
  mOBSP->SetWord( mProperty.VDensity );
  mOBSP->SetByte( 0 );
  mOBSP->SetByte( 0 );

 //comment
  if( mProperty.Comment != 0 ){
    mOBSP->SetByte( M_Marker ); //FF
    mOBSP->SetByte( M_COM );    //comment
    int length = strlen( mProperty.Comment ) + 1;
    mOBSP->SetWord( length + 2 );
    mOBSP->CopyByte( mProperty.Comment, length );
  }

  //DQT
  mOBSP->SetByte( M_Marker );
  mOBSP->SetByte( M_DQT );
  mOBSP->SetWord( 67 );
  mOBSP->SetByte( 0 );
  for( i=0; i<64; i++ )
        mOBSP->SetByte( u_char( YQuantumT[Zigzag[i]] ) );
  mOBSP->SetByte( M_Marker );
  mOBSP->SetByte( M_DQT );
  mOBSP->SetWord( 67 );
  mOBSP->SetByte( 1 );
  for( i=0; i<64; i++ )
        mOBSP->SetByte( u_char( CQuantumT[Zigzag[i]] ) );
   // DHT
  mOBSP->CopyByte( (char*)YDcDht, DcDhtLength );
  mOBSP->CopyByte( (char*)CDcDht, DcDhtLength );
  mOBSP->CopyByte( (char*)YAcDht, AcDhtLength );
  mOBSP->CopyByte( (char*)CAcDht, AcDhtLength );

  // Frame Header
  mOBSP->SetByte( M_Marker );   // FF
  mOBSP->SetByte( M_SOF0 );
  mOBSP->SetWord( 3 * mProperty.Dimension + 8 );
  mOBSP->SetByte( mProperty.SamplePrecision );
  mOBSP->SetWord( mProperty.nRow );
  mOBSP->SetWord( mProperty.nColumn );
  mOBSP->SetByte( mProperty.Dimension );

  mOBSP->SetByte( 0 );
  mOBSP->SetByte( YSampleF );
  mOBSP->SetByte( 0 );

  mOBSP->SetByte( 1 );
  mOBSP->SetByte( CSampleF );

  mOBSP->SetByte( 1 );
  mOBSP->SetByte( 2 );
  mOBSP->SetByte( CSampleF );
  mOBSP->SetByte( 1 );

  //Scan Header
  mOBSP->SetByte( M_Marker );
  mOBSP->SetByte( M_SOS );
  mOBSP->SetWord( 2 * mProperty.Dimension + 6 );
  mOBSP->SetByte( mProperty.Dimension );
  for( i=0; i<mProperty.Dimension; i++ ){
        mOBSP->SetByte( i );
        mOBSP->SetByte( i==0 ? 0 : 0x11 );
  }
  mOBSP->SetByte( 0 );  //Ss
  mOBSP->SetByte( 63 ); //Se
  mOBSP->SetByte( 0 );  //Ah,Al
}

//EOI
void
G4JpegCoder::WriteEOI( void )
{
  mOBSP->SetByte( M_Marker );
  mOBSP->SetByte( M_EOI );
}

//SetJpegProperty
void
G4JpegCoder::SetJpegProperty(const G4JpegProperty& aProperty )
{
  mProperty = aProperty;
  mProperty.Dimension = 3;
  mProperty.SamplePrecision = 8;
  mProperty.Format = 1;
  mProperty.MajorRevisions = 1;
  mProperty.MinorRevisions = 2;
  mProperty.HThumbnail = 0;
  mProperty.VThumbnail = 0;
}
