/**
  \class CSimpleCodec
  \brief A simple, experimental codec for video conferencing
  
    
  
 */

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

#include "CodecFrame.h"
#include "ImagePanelRGB.h"
#include "ImagePanelYUV.h"
#include "Simple.h"

CSimpleCodec::CSimpleCodec(CVideoDevice *video)
{
   QSize size;
   int i, x, y;
   bool dir;
   
   pVideo = video;
   pVideo->Open();

   pTilesY = pTilesUV = NULL;
   pSorted = NULL;
   pSendAbs = NULL;
   pSendDiff = NULL;

   pRGB = pYUV = NULL;
   pFDCTImage = pIDCTImage = NULL;
   pFDCTDiff = pIDCTDiff = NULL;
   pRecImage = pRecDiff = NULL; // 'receiver' panels
   pReconstruction = NULL;
   pSelection = NULL;
   pRec_YUV = NULL;
   UsedForAbs = UsedForDiff = NULL;
   GraphPos = 0;

   /* Defaults */
   ParamSmear = 64;   // 64/256 = 0.25, a quarter
   ParamThreshold = 16;
   ParamMaxPFrames = 8;
   ParamClearDiff = TRUE;
   ParamBytesPerPacket = 500; // assuming 40Kbit, 10 fps
   
   size = pVideo->GetSize();
   connect(pVideo, SIGNAL(Resized(const QSize &)), this, SLOT(Resize(const QSize &)));

   pRGB = new CImagePanelRGB(pVideo);
   pYUV = new CImagePanelYUV(pVideo);

#if 0
   pFDCTImage = new CDCTPanel(pYUV, FALSE, "simple.image.fdct.yuv", "Forward DCT image");
   pFDCTImage->SetTiles(pTilesY, pTilesUV); // Use our own 'clustering' tiles
   pIDCTImage = new CDCTPanel(pFDCTImage, TRUE, "simple.image.idct.yuv", "Inverse DCT image");
   pIDCTImage->SetTiles(pTilesY, pTilesUV);
   pReconstruction = new CBasicPanel("simple.reconstruction", "Reconstructed image", CCamPanel::YUV420);
   pRec_YUV = new CDiffPanel(pYUV, pReconstruction, 1, "rec.errors", "Reconstruction errors");
   pRecImage = new CBasicPanel("simple.rcv.image.yuv", "Receiver Image panel", YUV420);
   pRecDiff = new CBasicPanel("simple.rcv.diff.yuv", "Receiver Diff panel", YUV420);

   pFDCTDiff = new CDCTPanel(pRec_YUV, FALSE, "simple.diff.fdct.yuv", "Forward DCT of loop");
   pFDCTDiff->SetTiles(pTilesY, pTilesUV);
   pIDCTDiff = new CDCTPanel(pFDCTDiff, TRUE, "simple.diff.idct.yuv", "Inverse DCT of loop");
   pIDCTDiff->SetTiles(pTilesY, pTilesUV);
   
   pSelection = new CBasicPanel("simple.selection", "Show selector", Graph);
#endif

   Resize(size);
   RegisterPanel(pRGB);
   RegisterPanel(pYUV);
#if 0   
   RegisterPanel(pFDCTImage);
   RegisterPanel(pRecImage);
   RegisterPanel(pIDCTImage);
   RegisterPanel(pRec_YUV);
   RegisterPanel(pFDCTDiff);
   RegisterPanel(pRecDiff);
   RegisterPanel(pIDCTDiff);
   RegisterPanel(pReconstruction);
   RegisterPanel(pSelection);
#endif

   Refresh = 10;
   Count = 0;
   
   // Initialize zig-zag pattern
   i = x = y = 0;
   dir = true;
   while (i < 64) { // We should end up in (7,7)
      zigzag[i][0] = x;
      zigzag[i][1] = y;
      
      if (dir) { // north-east
        x++;
        y--;
      }
      else {
        x--;
        y++;
      }
      
      if (y > 7) {
        y = 7;
        x += 2;
        dir = !dir;
      }
      if (x > 7) {
        x = 7;
        y += 2;
        dir = !dir;
      }
      if (y < 0) { // hit top of field, move to right, switch direction to south-west
        y = 0;
        dir = !dir;
      }
      if (x < 0) { // left of field
        x = 0;
        dir = !dir;
      }
      i++;
   }
}

CSimpleCodec::~CSimpleCodec()
{
   if (pRGB != NULL) pRGB->DecrementUse();
   if (pYUV != NULL) pYUV->DecrementUse();
#if 0
   UnregisterPanel(pSelection);
   UnregisterPanel(pReconstruction, TRUE);
   UnregisterPanel(pFDCTDiff, TRUE);
   UnregisterPanel(pRecDiff, TRUE);
   UnregisterPanel(pIDCTDiff, TRUE);
   UnregisterPanel(pRec_YUV, TRUE);
   UnregisterPanel(pIDCTImage, TRUE);
   UnregisterPanel(pRecImage, TRUE);
   UnregisterPanel(pFDCTImage, TRUE);
#endif
   UnregisterPanel(pYUV, TRUE);
   UnregisterPanel(pRGB, TRUE);

   delete pSorted;
   delete pSendAbs;
   delete pSendDiff;
   delete pTilesY;
   delete pTilesUV;
   delete UsedForDiff;
   delete UsedForAbs;

}


// private

/**
 */

void CSimpleCodec::CreateTiles()
{
   int k, l, x, y, t;
   int w8, h8;
   CCamTile *pTile;

   tiles_y = (image_w * image_h) / 64;
   tiles_uv = tiles_y >> 2;

   delete pTilesY; // Clean up old tiles
   delete pTilesUV;
   delete pSorted;
   delete pSendAbs;
   delete pSendDiff;
   delete UsedForDiff;
   delete UsedForAbs;
   pTilesY = new CCamTile[tiles_y];
   pTilesUV = new CCamTile[tiles_uv];
   pSorted = new CCamTile*[tiles_y];
   pSendAbs = new CCamTile*[tiles_y];
   pSendDiff = new CCamTile*[tiles_y];
   UsedForDiff = new int[image_w];
   UsedForAbs = new int[image_w];
   
   if (pTilesY == NULL || pTilesUV == NULL || 
       pSorted == NULL || pSendAbs == NULL || pSendDiff == NULL ||
       UsedForDiff == NULL || UsedForAbs == NULL)
     return;

   pUsed = pTilesY;
   pTile = pTilesY;
   w8 = image_w >> 3; // 1/8th
   h8 = image_h >> 3;
   t = 0;
   for (y = 0; y < h8; y++) {
      for (x = 0; x < w8; x++) {
         // Clear
         memset(pTile, 0, sizeof(struct CCamTile));
         
         /* Initial sort order */
         pSorted[t++] = pTile;

         /* Record location */ 
         pTile->x = x << 3;
         pTile->y = y << 3;

         /* Calculate offsets */
         for (l = 0; l < 8; l++) {
            for (k = 0; k < 8; k++) {
               /* 8x8 blocks */
               pTile->in_offsets[l][k]  = ((y << 3) + l) * image_w + (x << 3) + k;
               /* Scattered pixels */
               pTile->out_offsets[l][k] = ((l * h8) + y) * image_w + (k * w8) + x;
            }
         }

         /* Link to neighbours */
         if (x > 0)	   pTile->Left   = pTile - 1;
         if (x < (w8 - 1)) pTile->Right  = pTile + 1;
         if (y > 0)        pTile->Top    = pTile - w8;
         if (y < (h8 - 1)) pTile->Bottom = pTile + w8;
         
         pTile->pUsedNext = pTile + 1;
         pTile->pUsedPrev = pTile - 1;

         pTile++;
      }
   }
   /* Complete circle */
   pTilesY[0].pUsedPrev = &pTilesY[tiles_y - 1];
   pTilesY[tiles_y - 1].pUsedNext = &pTilesY[0];
   
   /* Repeat for UV tiles, which are only quarter size */
   pTile = pTilesUV;
   w8 = half_w >> 3; // 1/8th
   h8 = half_h >> 3;
   for (y = 0; y < h8; y++) {
      for (x = 0; x < w8; x++) {
         /* Record location */ 
         pTile->x = x << 3;
         pTile->y = y << 3;
         /* Calculate offsets */
         for (l = 0; l < 8; l++) {
            for (k = 0; k < 8; k++) {
               /* 8x8 blocks */
               pTile->in_offsets[l][k]  = ((y << 3) + l) * half_w + (x << 3) + k;
               /* Scattered pixels */
               pTile->out_offsets[l][k] = ((l * h8) + y) * half_w + (k * w8) + x;
            }
         }
         pTile++;
      }
   }
   for (t = 0; t < image_w; t++) {
      UsedForDiff[t] = 0;
      UsedForAbs[t] = 0;
   }
}


/**
  \fn void CSimpleCodec::Send(CCamPanel *p, bool update)
  
  This is the reconstruction part of the encoder; it reconstructs what
  the other end would see (assuming an error free channel); based 
  upon this reconstruction the next loop in the encoder is performed.
  
  Every once in a while a true, fresh image is send (in that case \b update
  is false).
 */
void CSimpleCodec::Send(CCamPanel *p, bool update)
{
   if (update)
     *pReconstruction += *p;
   else 
     pReconstruction->copy(*p);
}


/**
  \fn void CSimpleCodec::MoveTileToEnd(CCamTile *tile)
  \brief Move tile to end of Used chaing
  \param tile The tile to move
  
  The codec maintains a list of the tiles in the order that they have been
  transmitted. When a tile is being used for transmission (either diff or
  absolute), it's moved to the rear of the chain. At each pass a number
  of tiles from the front of the chain will be selected for absolute
  transmission. This way all tiles will be sent eventually.
*/

void CSimpleCodec::MoveTileToEnd(CCamTile *tile)
{
   if (pUsed == tile) { // Oops
     pUsed = pUsed->pUsedNext;
   }
   else {
     /* Move this tile to end of Used chain */
     // chop
     tile->pUsedNext->pUsedPrev = tile->pUsedPrev;
     tile->pUsedPrev->pUsedNext = tile->pUsedNext;

      // insert before head (= pUsed)
     pUsed->pUsedPrev->pUsedNext = tile;
     tile->pUsedNext = pUsed;
     tile->pUsedPrev = pUsed->pUsedPrev;
     pUsed->pUsedPrev = tile;
   }
}

// private slots


void CSimpleCodec::Resize(const QSize &ns)
{
//   CCamPanel::SetImageSize(ns);
//   CCamPanel::SetVisibleSize(QSize(image_w, image_h + half_h));
   image_w = ns.width();
   image_h = ns.height();
   half_w = image_w >> 1;
   half_h = image_h >> 1;
#if 0
//   PixGraph.resize(image_w, image_h + half_h);
   pReconstruction->SetSize(ns);
   pRecImage->SetSize(ns);
   pRecDiff->SetSize(ns);
   pSelection->SetSize(QSize(image_w, image_h + half_h));
#endif   
   CreateTiles();
}



// public

/* This contains the Codec's logic */
void CSimpleCodec::UpdatePanel()
{
#if 0
   QPainter paint(pSelection->GetPixmap());
   QColor bc;

   CCamTile *pTile;
   uchar *src_abs, *src_diff;
   uchar *fdct_abs, *fdct_diff; // Absolute and Diff 
   uchar *rec_abs, *rec_diff;   // Receiver panels
   uchar *idct_abs, *idct_diff; // Inverse DCT
   uchar *dst;
   int t, r, c, pixels;
   int x, y, p, v;
   int CountAbs, CountDiff;
   int BytesForComp, Used;
   
   CAdaptiveHuffman Compr(256);

   if (pRec_YUV == NULL || pYUV == NULL || pTilesY == NULL)
     return;

   // Init pointers   
   pixels = image_w * image_h;
   src_abs   = pYUV->GetImage(0).bits();
   src_diff  = pRec_YUV->GetImage(0).bits();
   fdct_abs  = pFDCTImage->GetImage(0).bits();
   fdct_diff = pFDCTDiff->GetImage(0).bits();
   rec_abs   = pRecImage->GetImage(0).bits();
   rec_diff  = pRecDiff->GetImage(0).bits();
   idct_abs  = pIDCTImage->GetImage(0).bits();
   idct_diff = pIDCTDiff->GetImage(0).bits();
   dst       = pReconstruction->GetImage(0).bits();

   // Calculate difference between current image and the last reconstruction
   pRec_YUV->UpdatePanel();

   // See what has changed most
   pTile = pTilesY;
   for (t = 0; t < tiles_y; t++) {
      if (ParamClearDiff) 
        pTile->abs_value = 0;
      pTile->nb_value = 0; //
      pTile->CalcDiffValue(src_diff);
      pTile++;
   }
   
   pTile = pTilesY;
   for (t = 0; t < tiles_y; t++) {
      /* The edges of a moving object get a relatively high 'score' because
         of the transition from object to background, while the middle 
         doesn't change that much; this leads to unwanted effects, so we
         'spread out' the score of all tiles to their neighbours.
       */
      c = (pTile->abs_value * ParamSmear) >> 8;  // Each neighbour gets a portion
      if (pTile->Left)   pTile->Left->nb_value += c;
      if (pTile->Top)    pTile->Top->nb_value += c;
      if (pTile->Right)  pTile->Right->nb_value += c;
      if (pTile->Bottom) pTile->Bottom->nb_value += c;
      pTile++;
   }

   // Sort tiles, based on abs_value + nb_value; highest score first
   qsort(pSorted, tiles_y, sizeof(CCamTile *), CCamTile::CompareTile);

   /* We remember a which tiles were 'last updated' with a circular linked 
      list; each tile that is being selected is moved just behind the 
      head pointer of this list, so that it will take a full traversal of 
      the list before it's being used again, unless of course it happens 
      to have been selected.
      
      This will guarantee all tiles will be sent every N packets. Neat, huh?
   */

   // paint tiles, and color the highest scores.
   CountAbs = CountDiff = 0;
   for (t = 0; t < tiles_y; t++) {
      pTile = pSorted[t];
      // The absolute maximum is 2 * 64 * 128 = 16384
      c = (pTile->abs_value + pTile->nb_value) >> 4;

      if (c > 255) 
        c = 255;
      if (t >= 32)
        bc.setRgb(c, c, c);
      else if (t >= 16)
        bc.setRgb(0, 0, c);
      else if (t >= 8) 
        bc.setRgb(0, c, 0); 
      else
        bc.setRgb(c, 0, 0);
      
      paint.fillRect(pTile->x, pTile->y, 8, 8, bc);
      
      if (c >= ParamThreshold) { 
//      if (t < ParamMaxCount)

        /* Mark tile, but if it has been used too many times for a 
           differential update, add it to the list of absolute updates.
           With a bit of luck we can transmit it in this packet.
         */
        pTile->PCount++;
        if (pTile->PCount >= ParamMaxPFrames) {
          pSendAbs[CountAbs++] = pTile;
          paint.setPen(magenta);
        }
        else {
          pSendDiff[CountDiff++] = pTile;
          paint.setPen(cyan);
        }
        paint.drawRect(pTile->x, pTile->y, 8, 8);
      }
   }

   /* Traverse the lists of tiles; first the diff-tiles, then the absolute
      tiles. 
      
      Calculate DCTs, then traverse the diff-DCT components zig-zag
      per tile. (that is, first do all DC components, then components at
      (0,1), (1,0), (2,0), (1,1), etc...
    */

   BytesForComp = ParamBytesPerPacket - 24;
   Used = 0; // Bytes used in this round
   memset(rec_diff, 128, pixels);
//   memset(rec_abs, 128, pixels);
   memset(idct_diff, 128, pixels);
   if (CountDiff > 0) {
     /* Init compressor.
        We look at the current number of diff & abs tiles, and use
        the quotient of these to determine how much bytes we're going to
        spend on the diff panels, including a bit of overhead, but never
        use more than 80%.
      */
     r = (BytesForComp * 80) / 100;
     t = (BytesForComp * CountDiff) / (CountDiff + CountAbs + 8);
     if (t > r)
       t = r;

//printf("Allocating %d bytes to Diff panels\n", BytesForComp);
     Compr.InitCompressor(t, FALSE); // Don't use marks

     /* Difference tiles. Step 1: do DCT conversion */
     for (t = 0; t < CountDiff; t++)
        pSendDiff[t]->CalculateForward(fdct_diff, src_diff);

     r = 0;  
     for (c = 0; c < 64 && r == 0; c++) {
        x = zigzag[c][0]; // Follow zig-zag pattern
        y = zigzag[c][1];
        for (t = 0; t < CountDiff; t++) {
           pTile = pSendDiff[t];
           
           /* Get positition in output buffer */
           p = pTile->out_offsets[x][y];
           v = fdct_diff[p];
           r = Compr.AddValue(v);
           // If our buffer appears to be full, bail out
           if (r < 0)
             break;

           // Stuff back in receiver
           rec_diff[p] = v;
           /* After we've done the first component, move to end of chain.
              Should we somehow not be able to do all DC components this tile
              will then stay in its position in the 'lastused' chain
            */
           if (c == 0)
             MoveTileToEnd(pTile);
        }
     }
//printf("Bailed out at c=%d, t=%d, r=%d, bits used=%d\n", c, t, r, Compr.GetBits());

     // Perform IDCT, apply that that to reconstruction
     for (t = 0; t < CountDiff; t++)
        pSendDiff[t]->CalculateInverse(idct_diff, rec_diff);

     *pReconstruction += *pIDCTDiff;

     /* Calc remainder in packet */
     Used = Compr.GetBits() / 8;
   }
   BytesForComp -= Used;
   UsedForDiff[GraphPos] = (Used * half_h) / ParamBytesPerPacket;

//printf("Remaining bytes for Abs tiles: %d\n", BytesForComp);
   Used = 0;
   if (BytesForComp > 48) {
     /* We use marks for the absolute tile, so we send all components of a 
        tile, not just 3 or 4.
      */
     Compr.InitCompressor(BytesForComp, TRUE);
     t = 0;
     r = 0;
     paint.setPen(yellow);
     
     /* First do absolute tiles we marked earlier, then switch to 'last' tiles */
     while (r == 0 && t < tiles_y) {
        Compr.Mark();
        if (t < CountAbs) {
          pTile = pSendAbs[t];
          MoveTileToEnd(pTile);
        }
        else {
          pTile = pUsed;
          pUsed = pUsed->pUsedNext;
        }
        t++;
        pTile->PCount = 0;
        paint.drawRect(pTile->x, pTile->y, 8, 8);
        
        /* Step 1: forward DCT */
        pTile->CalculateForward(fdct_abs, src_abs);
        pTile->ClearOutTile(rec_abs);
        /* Step 2: follow zigzag pattern */
        for (c = 0; c < 64 && r == 0; c++) {
           x = zigzag[c][0]; // Follow zig-zag pattern
           y = zigzag[c][1];
           /* Get positition in output buffer */
           p = pTile->out_offsets[x][y];
           v = fdct_abs[p];
           r = Compr.AddValue(v);
           if (r < 0)
             break;
//           if (c == 20) // Intermediate mark: this should be enough components to avoid annoying blips
//             Compr.Mark();

           /* Step 3: Stuff back in receiver */
           rec_abs[p] = v;
        } // ..for c

        /* Step 4: perform reverse, copy it in the output (reconstructed) image */
        if (r == 0) {
          /* If our receiver did nog get the bytes, so don't we */
          pTile->CalculateInverse(idct_abs, rec_abs);
          pTile->CopyTile(dst, idct_abs);
        }
     } // ..while (r == 0)

//printf("Bailed out at c=%d, t=%d, r=%d, bits used=%d\n", c, t, r, Compr.GetBits());
     Used = Compr.GetBits() / 8;
   }
   UsedForAbs[GraphPos] = (Used * half_h) / ParamBytesPerPacket;; 
   
   /* Paint graphs */
   GraphPos = (GraphPos + 1) % image_w;
   paint.fillRect(0, image_h, image_w, image_h + half_h - 1, black); // clear

   y = image_h + half_h - 1;
   paint.setPen(cyan);
   paint.moveTo(0, y - UsedForDiff[GraphPos]);
   for (t = 1; t < image_w; t++)
      paint.lineTo(t, y - UsedForDiff[(GraphPos + t) % image_w]);

   paint.setPen(yellow);
   paint.moveTo(0, y - UsedForAbs[GraphPos]);
   for (t = 1; t < image_w; t++)
      paint.lineTo(t, y - UsedForAbs[(GraphPos + t) % image_w]);
   
   paint.end();
   emit Updated();
#endif   
}


