#include "wltcompress.h"

wltcompress::wltcompress()
{
}

wltcompress::~wltcompress()
{
  clear();
}

int wltcompress::clear()
{
  cdata.clear();
  return 0;
}

char *wltcompress::compress( wavelet &wlt )
{
  clear();
  std::vector< databuffer* > leveldata;
  wavelet::wavelettree *wltnode;
  wltnode = wlt.getwlt();

  leveldata.push_back( new databuffer );

  compresslowpass( wltnode->hl, leveldata[0] );
  
  wltnode = wltnode->next;
#ifdef __DEBUG__
  std::cout << "level: " 
            << leveldata.size() 
            << " : " << leveldata.back()->size()
            << " bytes " 
            << ( (double)(leveldata.back()->size())) / 
    ( (double)( wltnode->hl->getwidth() * wltnode->hl->getheight() * sizeof(int)) )
            << "\n";
#endif
  while( wltnode != NULL)
    {
      leveldata.push_back( new databuffer );
      compresslevel( wltnode, leveldata.back() );
#ifdef __DEBUG__
      std::cout << "level: " 
                << leveldata.size() 
                << " : " << leveldata.back()->size()
                << " bytes "
                << ( (double)(leveldata.back()->size())) / 
        ( (double)(3*wltnode->hl->getwidth() * wltnode->hl->getheight() * sizeof(int)) )
                << "\n";
#endif
      wltnode = wltnode->next;
    }

  
  unsigned int tint;
  tint = leveldata.size();
  int imh, imw;
  imh = wlt.getheight();
  imw = wlt.getwidth();
  cdata.append( reinterpret_cast<char*>(&imh), sizeof( int ) );
  cdata.append( reinterpret_cast<char*>(&imw), sizeof( int ) );
  cdata.append( reinterpret_cast<char*>(&tint),
                sizeof( unsigned int ) );
  for(unsigned int i = 0; i < leveldata.size(); i++)
    {
      tint = leveldata[i]->size();
      cdata.append( reinterpret_cast<char*>(&tint),
                    sizeof( unsigned int ) );
    }

  for(unsigned int i = 0; i < leveldata.size(); i++)
    {
#ifdef __DEBUG__
      std::cout << "level " << i << " size: "
                << leveldata[i]->size() << "\n";
#endif
      cdata.append( *leveldata[i] );

    }

  for(unsigned int i = 0; i < leveldata.size(); i++)
    delete leveldata[i];

  leveldata.clear();
  cdata.consolidate();

  std::cout << "compressed " << imh*imw*sizeof(int) 
            << " bytes to " << cdata.size() << " bytes "
            << ((double)cdata.size())/(imh*imw*sizeof(int)) << "\n";
  return cdata.getptr();
}

char *wltcompress::compressdata()
{
  return cdata.getptr();
}

int wltcompress::compresssize()
{
  return cdata.size();
}

int wltcompress::compresslowpass( image2d *im, 
                                  databuffer *d )
{
  d->clear();

  int h, w;
  h = im->getheight();
  w = im->getwidth();

  bz_stream bzs;

  bzs.bzalloc = NULL;
  bzs.bzfree = NULL;
  bzs.opaque = NULL;

  char buffer[ 1024*1024 ];
  int bzret;
  
  //std::cout << "wlt level compress:\n";
  bzret = BZ2_bzCompressInit( &bzs, 9, 0, 60);

  bzs.next_out = buffer;
  bzs.avail_out = 1024*1024;

  bzs.next_in = reinterpret_cast<char*>(&h);
  bzs.avail_in = sizeof(int);
  bzret = BZ2_bzCompress( &bzs, BZ_RUN );
  bzs.next_in = reinterpret_cast<char*>(&w);
  bzs.avail_in = sizeof(int);
  bzret = BZ2_bzCompress( &bzs, BZ_RUN );
  int *imd;
  imd = new int[h*w];
  im->getshortint( imd );

  bzs.next_in = reinterpret_cast<char*>( imd );
  bzs.avail_in = sizeof(int)*h*w;

  bzret = BZ2_bzCompress( &bzs, BZ_RUN );
  
  while( bzret != BZ_STREAM_END)
    {
      bzprepout( &bzs, buffer, 1024*1024, *d );
      bzret = BZ2_bzCompress( &bzs, BZ_FINISH );
    }
  bzprepout( &bzs, buffer, 1024*1024, *d );

  BZ2_bzCompressEnd( &bzs );
  delete [] imd;

  return 0;
}

int wltcompress::compresslevel( wavelet::wavelettree *wt,
                                databuffer *d)
{

  d->clear();
  int h, w;
  h = wt->lh->getheight();
  w = wt->lh->getwidth();

  bz_stream bzs;

  bzs.bzalloc = NULL;
  bzs.bzfree = NULL;
  bzs.opaque = NULL;

  char buffer[ 1024*1024 ];
  int bzret;
  
  //std::cout << "wlt level compress:\n";
  bzret = BZ2_bzCompressInit( &bzs, 9, 0, 60);

  bzs.next_out = buffer;
  bzs.avail_out = 1024*1024;
  bzs.next_in = reinterpret_cast<char*>(&h);
  bzs.avail_in = sizeof(int);
  bzret = BZ2_bzCompress( &bzs, BZ_RUN );
  bzs.next_in = reinterpret_cast<char*>(&w);
  bzs.avail_in = sizeof(int);
  bzret = BZ2_bzCompress( &bzs, BZ_RUN );

  int *imd;
  imd = new int[h*w];
  wt->hl->getshortint( imd );

  bzcompdata( &bzs,
              reinterpret_cast<char*>(imd),
              sizeof(int)*h*w,
              buffer, 1024*1024, *d );

  wt->lh->getshortint( imd );
  bzcompdata( &bzs,
              reinterpret_cast<char*>(imd),
              sizeof(int)*h*w,
              buffer, 1024*1024, *d );

  wt->hh->getshortint( imd );
  bzcompdata( &bzs,
              reinterpret_cast<char*>(imd),
              sizeof(int)*h*w,
              buffer, 1024*1024, *d );

  while( bzret != BZ_STREAM_END )
    {
      bzprepout( &bzs, buffer, 1024*1024, *d );
      bzret = BZ2_bzCompress( &bzs, BZ_FINISH );
    }
  bzprepout( &bzs, buffer, 1024*1024, *d );

  bzret = BZ2_bzCompressEnd( &bzs );
  delete [] imd;
  return 0;
}

int wltcompress::bzcompdata( bz_stream *bz, 
                             char *data, int datas,
                             char *tb, int tbs,
                             databuffer &buf )
{
  int bzret;
  bz->next_in = data;
  bz->avail_in = datas;
  bzret = BZ2_bzCompress( bz, BZ_RUN );

  while(bz->avail_in != 0)
    {
      bzprepout( bz, tb, tbs, buf );
      bzret = BZ2_bzCompress( bz, BZ_RUN );
    }
  bzprepout( bz, tb, tbs, buf );
  return 0;
}

int wltcompress::bzprepout( bz_stream *bz, char *b,
                            int bs, databuffer &buf)
{
  unsigned long ws;
  ws = (unsigned long int)bz->next_out -
    (unsigned long int)b;
  buf.append( b, ws );
  bz->next_out = b;
  bz->avail_out = bs;
  return 0;
}

int wltcompress::decompress( wavelet &wlt, char *ptr,
                             int size )
{
  wlt.del();
  int *numlevels, *intptr;
  int imh, imw;
  memcpy( &imh, ptr, sizeof(int) );
  memcpy( &imw, ptr+sizeof(int), sizeof(int) );
  // move pointer pass height width entries
  ptr = ptr + (2*sizeof(int) );
  numlevels = (int*)ptr;
  intptr = (int*)(ptr +sizeof(int) );
#ifdef __DEBUG__
  std::cout << "entry breaks: ";
#endif
  char *data;
  data = ptr+sizeof(int)+sizeof(int)*(*numlevels);
  wavelet::wavelettree *head, *cur;
  head = cur = new wavelet::wavelettree;
  for( int i = 0; i < *numlevels; i++)
    {
      if(i == 0 )
        {
          cur->hl = new image2d;
          cur->lh = cur->hh = NULL;
          cur->next = NULL;
          decompresslowpass( cur->hl, data, *intptr );
        }
      else
        {
          cur->next = new wavelet::wavelettree;
          cur = cur->next;
          cur->hl = new image2d;
          cur->lh = new image2d;
          cur->hh = new image2d;
          cur->next = NULL;
          decompresslevel( cur, data, *intptr );
        }

      data = data + (*intptr);
      intptr = intptr + 1;
    }
#ifdef __DEBUG__
  std::cout << "decompressed " << *numlevels << " levels\n";

  cur = head;
  while(cur != NULL)
    cur = cur->next;
#endif

  wlt.load( head );
  wlt.setheight( imh );
  wlt.setwidth( imw );
  size = 0;
  return 0;
}

int wltcompress::converttodouble( int *si, double *d, int h, int w )
{
  for( int i = 0; i < h*w; i++)
    d[i] = si[i];
  return 0;
} 

int wltcompress::decompresslowpass( image2d *im, 
                                    char *data,
                                    int size)
{
  char buffer[1024*16];
  databuffer d;
  int h, w;

  bz_stream bzs;

  bzs.bzalloc = NULL;
  bzs.bzfree = NULL;
  bzs.opaque = NULL;
  int bzret;
  
  bzret = BZ2_bzDecompressInit( &bzs, 0, 0);

#ifdef __DEBUG__
  std::cout << "input data size: " << size << "\n";
  std::cout << "bzdecompress init: " << bzret << "\n";
#endif
  if(bzret !=  BZ_OK )
    {
      std::cout << "ret not ok\n";
      if( bzret == BZ_CONFIG_ERROR)
        std::cout << "CONFIG ERROR\n";

      if( bzret == BZ_PARAM_ERROR)
        std::cout << "PARAM ERROR\n";

      if( bzret == BZ_MEM_ERROR)
        std::cout << "MEM ERROR\n";
      exit(0);
    }

  bzs.next_in = data;
  bzs.avail_in = size;
  bzs.next_out = buffer;
  bzs.avail_out = 1024*16;
  bzret = BZ2_bzDecompress( &bzs );
  if(bzret !=  BZ_OK && bzret != BZ_STREAM_END)
    {
      if( bzret == BZ_CONFIG_ERROR)
        std::cout << "CONFIG ERROR\n";

      if( bzret == BZ_PARAM_ERROR)
        std::cout << "PARAM ERROR\n";

      if( bzret == BZ_MEM_ERROR)
        std::cout << "MEM ERROR\n";

      if( bzret == BZ_DATA_ERROR)
        std::cout << "ERROR DATA\n";

      if( bzret == BZ_DATA_ERROR_MAGIC)
        std::cout << "ERROR MAGIC\n";
      exit(0);
    }

  //unsigned long ws;
  while( bzret != BZ_STREAM_END )
    {
      bzprepout( &bzs, buffer, 1024*16, d );
      bzret = BZ2_bzDecompress( &bzs );
      if( bzret == BZ_STREAM_END)
        break;
    }
  bzprepout( &bzs, buffer, 1024*16, d );
  BZ2_bzDecompressEnd( &bzs );
  d.consolidate();

  memcpy( &h, d.getptr(), sizeof(int) );
  memcpy( &w, d.getptr()+sizeof(int), sizeof(int) );

  double *si;
  si = new double [h*w];
  converttodouble( (int*)(d.getptr()+(2*sizeof(int)) ), si, h, w );
  im->loadcopy( si, h, w);

  delete [] si;

  return 0;
}

int wltcompress::decompresslevel( wavelet::wavelettree *wt, 
                                  char *data,
                                  int size)
{
  char buffer[1024*16];
  databuffer d;
  int h, w;

  bz_stream bzs;
      
  bzs.bzalloc = NULL;
  bzs.bzfree = NULL;
  bzs.opaque = NULL;
  int bzret;
      
  bzret = BZ2_bzDecompressInit( &bzs, 0, 0);
#ifdef __DEBUG__
  std::cout << "decompress init: ";
  bzretcheck( bzret );
  std::cout << "\n";
#endif
      
  bzs.next_in = data;
  bzs.avail_in = size;
  bzs.next_out = buffer;
  bzs.avail_out = 1024*16;
  //  unsigned long ws;
  bzret = BZ2_bzDecompress( &bzs );
#ifdef __DEBUG__
  std::cout << "decompress run: ";
  bzretcheck( bzret );
  std::cout << "\n";
#endif
  while( bzret != BZ_STREAM_END )
    {
      bzprepout( &bzs, buffer, 1024*16, d );
      bzret = BZ2_bzDecompress( &bzs );
#ifdef __DEBUG__
      bzretcheck( bzret );
#endif
    }
  bzprepout( &bzs, buffer, 1024*16, d );
  BZ2_bzDecompressEnd( &bzs );
  d.consolidate();
      
#ifdef __DEBUG__
  std::cout << "decompressed: " << d.size() << "\n";
#endif

  memcpy( &h, d.getptr(), sizeof(int) );
  memcpy( &w, d.getptr()+sizeof(int), sizeof(int) );
#ifdef __DEBUG__
  std::cout << " expect: " << 2*sizeof(int)+3*sizeof(int)*h*w << "\n";
#endif
  if( (unsigned)d.size() != 2*sizeof(int)+3*sizeof(int)*h*w )
    {
      std::cout << "ERROR expected data size not equal to actural size\n";
      std::cout << " expect: " << 2*sizeof(int)+3*sizeof(int)*h*w << "\n";
      std::cout << " decompressed: " << d.size() << "\n";
      exit(0);
    }
  int imsize;
  char *dptr;
  dptr = d.getptr();
  imsize = sizeof(int)*h*w;

  double *si;
  si = new double [h*w];

  converttodouble( (int*)(dptr+(2*sizeof(int)) ), si, h, w );
  wt->hl->loadcopy( si, h, w);

  converttodouble( (int*)(dptr+(2*sizeof(int)+imsize) ), si, h, w );
  wt->lh->loadcopy(si, h, w);

  converttodouble( (int*)(dptr+(2*sizeof(int)+imsize*2) ), si, h, w );
  wt->hh->loadcopy(si, h, w);
  d.clear();

  delete [] si;
  return 0;
}

int wltcompress::bzretcheck( int bzret )
{
  if(bzret !=  BZ_OK )
    {
      if( bzret == BZ_CONFIG_ERROR)
        std::cout << "CONFIG ERROR\n";

      if( bzret == BZ_PARAM_ERROR)
        std::cout << "PARAM ERROR\n";

      if( bzret == BZ_MEM_ERROR)
        std::cout << "MEM ERROR\n";

      if( bzret == BZ_DATA_ERROR)
        std::cout << "ERROR DATA\n";

      if( bzret == BZ_DATA_ERROR_MAGIC)
        std::cout << "ERROR MAGIC\n";
      if( bzret == BZ_STREAM_END)
        std::cout << "STREAM END\n";
      if( bzret == BZ_RUN_OK)
        std::cout << "RUN OK\n";
    }
  return 0;
}
