/*=========================================================================

  Program: GDCM (Grassroots DICOM). A DICOM library

  Copyright (c) 2006-2011 Mathieu Malaterre
  All rights reserved.
  See Copyright.txt or http://gdcm.sourceforge.net/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "gdcmJPEGLSCodec.h"
#include "gdcmTransferSyntax.h"
#include "gdcmSequenceOfFragments.h"
#include "gdcmDataElement.h"

// CharLS includes
#include "gdcm_charls.h"

namespace gdcm
{

JPEGLSCodec::JPEGLSCodec():BufferLength(0),Lossless(true),LossyError(0)
{
}

JPEGLSCodec::~JPEGLSCodec()
{
}

void JPEGLSCodec::SetLossless(bool l)
{
  Lossless = l;
}

bool JPEGLSCodec::GetLossless() const
{
  return Lossless;
}

bool JPEGLSCodec::GetHeaderInfo(std::istream &is, TransferSyntax &ts)
{
#ifndef GDCM_USE_JPEGLS
  return false;
#else
  is.seekg( 0, std::ios::end);
  std::streampos buf_size = is.tellg();
  //assert(buf_size < INT_MAX);
  char *dummy_buffer = new char[(unsigned int)buf_size];
  is.seekg(0, std::ios::beg);
  is.read( dummy_buffer, buf_size);

#ifdef GDCM_USE_SYSTEM_CHARLS
  JlsParameters metadata;
#else
  JlsParamaters metadata;
#endif
  //assert(buf_size < INT_MAX);
  if (JpegLsReadHeader(dummy_buffer, (unsigned int)buf_size, &metadata) != OK)
    {
    return false;
    }
  delete[] dummy_buffer;

  // $1 = {width = 512, height = 512, bitspersample = 8, components = 1, allowedlossyerror = 0, ilv = ILV_NONE, colorTransform = 0, custom = {MAXVAL = 0, T1 = 0, T2 = 0, T3 = 0, RESET = 0}}

  this->Dimensions[0] = metadata.width;
  this->Dimensions[1] = metadata.height;
  if( metadata.bitspersample <= 8 )
    {
    this->PF = PixelFormat( PixelFormat::UINT8 );
    }
  else if( metadata.bitspersample <= 16 )
    {
    assert( metadata.bitspersample > 8 );
    this->PF = PixelFormat( PixelFormat::UINT16 );
    }
  else
    {
    assert(0);
    }
  this->PF.SetBitsStored( metadata.bitspersample );
  assert( this->PF.IsValid() );
//  switch( metadata.bitspersample )
//    {
//  case 8:
//    this->PF = PixelFormat( PixelFormat::UINT8 );
//    break;
//  case 12:
//    this->PF = PixelFormat( PixelFormat::UINT16 );
//    this->PF.SetBitsStored( 12 );
//    break;
//  case 16:
//    this->PF = PixelFormat( PixelFormat::UINT16 );
//    break;
//  default:
//    assert(0);
//    }
  if( metadata.components == 1 )
    {
    PI = PhotometricInterpretation::MONOCHROME2;
    this->PF.SetSamplesPerPixel( 1 );
    }
  else if( metadata.components == 3 )
    {
    PI = PhotometricInterpretation::RGB;
    this->PF.SetSamplesPerPixel( 3 );
    }
  else assert(0);

  // allowedlossyerror == 0 => Lossless
  LossyFlag = metadata.allowedlossyerror != 0;

  if( metadata.allowedlossyerror == 0 )
    {
    ts = TransferSyntax::JPEGLSLossless;
    }
  else
    {
    ts = TransferSyntax::JPEGLSNearLossless;
    }


  return true;
#endif
}

bool JPEGLSCodec::CanDecode(TransferSyntax const &ts) const
{
#ifndef GDCM_USE_JPEGLS
  return false;
#else
  return ts == TransferSyntax::JPEGLSLossless
      || ts == TransferSyntax::JPEGLSNearLossless;
#endif
}

bool JPEGLSCodec::CanCode(TransferSyntax const &ts) const
{
#ifndef GDCM_USE_JPEGLS
  return false;
#else
  return ts == TransferSyntax::JPEGLSLossless
      || ts == TransferSyntax::JPEGLSNearLossless;
#endif
}

bool JPEGLSCodec::Decode(DataElement const &in, DataElement &out)
{
#ifndef GDCM_USE_JPEGLS
  return false;
#else
  if( NumberOfDimensions == 2 )
    {
    const SequenceOfFragments *sf = in.GetSequenceOfFragments();
    assert( sf );
    std::stringstream is;
    unsigned long totalLen = sf->ComputeByteLength();
    char *buffer = new char[totalLen];
    sf->GetBuffer(buffer, totalLen);
    //is.write(buffer, totalLen);

#ifdef GDCM_USE_SYSTEM_CHARLS
    JlsParameters metadata;
#else
    JlsParamaters metadata;
#endif
    if (JpegLsReadHeader(buffer, totalLen, &metadata) != OK)
      {
      return false;
      }

    // allowedlossyerror == 0 => Lossless
    LossyFlag = metadata.allowedlossyerror!= 0;

    const BYTE* pbyteCompressed = (const BYTE*)buffer;
    int cbyteCompressed = totalLen;

#ifdef GDCM_USE_SYSTEM_CHARLS
    JlsParameters params = {};
#else
    JlsParamaters params = {};
#endif
    JpegLsReadHeader(pbyteCompressed, cbyteCompressed, &params);

    std::vector<BYTE> rgbyteCompressed;
    rgbyteCompressed.resize(params.height *params.width* 4);

    std::vector<BYTE> rgbyteOut;
    rgbyteOut.resize(params.height *params.width * ((params.bitspersample + 7) / 8) * params.components);

#ifdef GDCM_USE_SYSTEM_CHARLS
    JLS_ERROR result = JpegLsDecode(&rgbyteOut[0], rgbyteOut.size(), pbyteCompressed, cbyteCompressed, &params);
#else
    JLS_ERROR result = JpegLsDecode(&rgbyteOut[0], rgbyteOut.size(), pbyteCompressed, cbyteCompressed);
#endif
    ASSERT(result == OK);

    delete[] buffer;

    if (result != OK)
      {
      return false;
      }
    out = in;

    out.SetByteValue( (char*)&rgbyteOut[0], rgbyteOut.size() );
    return true;
    }
  else if( NumberOfDimensions == 3 )
    {
    const SequenceOfFragments *sf = in.GetSequenceOfFragments();
    assert( sf );
    assert( sf->GetNumberOfFragments() == Dimensions[2] );
    std::stringstream os;
    for(unsigned int i = 0; i < sf->GetNumberOfFragments(); ++i)
      {
      const Fragment &frag = sf->GetFragment(i);
      if( frag.IsEmpty() ) return false;
      const ByteValue *bv = frag.GetByteValue();
      assert( bv );
      char *mybuffer = new char[bv->GetLength()];
    unsigned long totalLen = bv->GetLength();

      bv->GetBuffer(mybuffer, bv->GetLength());

    const BYTE* pbyteCompressed = (const BYTE*)mybuffer;
      while( totalLen > 0 && pbyteCompressed[totalLen-1] != 0xd9 )
        {
        totalLen--;
        }
  // what if 0xd9 is never found ?
  assert( totalLen > 0 && pbyteCompressed[totalLen-1] == 0xd9 );

#ifdef GDCM_USE_SYSTEM_CHARLS
    JlsParameters metadata;
#else
    JlsParamaters metadata;
#endif
    if (JpegLsReadHeader(mybuffer, totalLen, &metadata) != OK)
      {
      return false;
      }

    // allowedlossyerror == 0 => Lossless
    LossyFlag = metadata.allowedlossyerror!= 0;


    int cbyteCompressed = totalLen;

#ifdef GDCM_USE_SYSTEM_CHARLS
    JlsParameters params = {};
#else
    JlsParamaters params = {};
#endif
    JpegLsReadHeader(pbyteCompressed, cbyteCompressed, &params);

    std::vector<BYTE> rgbyteCompressed;
    rgbyteCompressed.resize(params.height *params.width* 4);

    std::vector<BYTE> rgbyteOut;
    rgbyteOut.resize(params.height *params.width * ((params.bitspersample + 7) / 8) * params.components);

#ifdef GDCM_USE_SYSTEM_CHARLS
    JLS_ERROR result = JpegLsDecode(&rgbyteOut[0], rgbyteOut.size(), pbyteCompressed, cbyteCompressed, &params);
#else
    JLS_ERROR result = JpegLsDecode(&rgbyteOut[0], rgbyteOut.size(), pbyteCompressed, cbyteCompressed);
#endif
    ASSERT(result == OK);
    bool r = true;


      delete[] mybuffer;
      if (result != OK){
        return false;
      }
    os.write( (char*)&rgbyteOut[0], rgbyteOut.size() );

      if(!r) return false;
      assert( r == true );


      }
    std::string str = os.str();
    assert( str.size() );
    out.SetByteValue( &str[0], str.size() );


    return true;
    }
    return false;

#endif
}

// Compress into JPEG
bool JPEGLSCodec::Code(DataElement const &in, DataElement &out)
{
#ifndef GDCM_USE_JPEGLS
  return false;
#else
  out = in;
  //
  // Create a Sequence Of Fragments:
  SmartPointer<SequenceOfFragments> sq = new SequenceOfFragments;
  const Tag itemStart(0xfffe, 0xe000);
  //sq->GetTable().SetTag( itemStart );

  const unsigned int *dims = this->GetDimensions();
    int image_width = dims[0];
    int image_height = dims[1];
    const PixelFormat &pf = this->GetPixelFormat();
    int sample_pixel = pf.GetSamplesPerPixel();
    int bitsallocated = pf.GetBitsAllocated();
    int bitsstored = pf.GetBitsStored();

  const ByteValue *bv = in.GetByteValue();
  const char *input = bv->GetPointer();
  unsigned long len = bv->GetLength();
  unsigned long image_len = len / dims[2];
  size_t inputlength = image_len;

  for(unsigned int dim = 0; dim < dims[2]; ++dim)
    {
    const char *inputdata = input + dim * image_len; //bv->GetPointer();

#ifdef GDCM_USE_SYSTEM_CHARLS
    JlsParameters params = {};
#else
    JlsParamaters params = {};
#endif
/*
The fields in JlsCustomParameters do not control lossy/lossless. They
provide the possiblity to tune the JPEG-LS internals for better compression
ratios. Expect a lot of work and testing to achieve small improvements.

Lossy/lossless is controlled by the field allowedlossyerror. If you put in
0, encoding is lossless. If it is non-zero, then encoding is lossy. The
value of 3 is often suggested as a default.

The nice part about JPEG-LS encoding is that in lossy encoding, there is a
guarenteed maximum error for each pixel. So a pixel that has value 12,
encoded with a maximum lossy error of 3, may be decoded as a value between 9
and 15, but never anything else. In medical imaging this could be a useful
guarantee.

The not so nice part is that you may see striping artifacts when decoding
"non-natural" images. I haven't seen the effects myself on medical images,
but I suspect screenshots may suffer the most. Also, the bandwidth saving is
not as big as with other lossy schemes.

As for 12 bit, I am about to commit a unit test (with the sample you gave
me) that does a successful round trip encoding of 12 bit color. I did notice
that for 12 bit, the encoder fails if the unused bits are non-zero, but the
sample dit not suffer from that.
*/
    params.allowedlossyerror = Lossless ? 0 : LossyError;
    params.components = sample_pixel;
    // D_CLUNIE_RG3_JPLY.dcm. The famous 16bits allocated / 10 bits stored with the pixel value = 1024
    // CharLS properly encode 1024 considering it as 10bits data, so the output
    // Using bitsstored for the encoder gives a slightly better compression ratio, and is indeed the
    // right way of doing it.

    // gdcmData/PHILIPS_Gyroscan-8-MONO2-Odd_Sequence.dcm
    if( true || pf.GetPixelRepresentation() )
      {
      // gdcmData/CT_16b_signed-UsedBits13.dcm
      params.bitspersample = bitsallocated;
      }
    else
      {
      params.bitspersample = bitsstored;
      }
    params.height = image_height;
    params.width = image_width;

    if (sample_pixel == 4)
{
      params.ilv = ILV_LINE;
}
    else if (sample_pixel == 3)
      {
      params.ilv = ILV_LINE;
      params.colorTransform = COLORXFORM_HP1;
      }

    std::vector<BYTE> rgbyteCompressed;
    rgbyteCompressed.resize(image_width * image_height * 4);
    //rgbyteCompressed.resize(size.cx *size.cy * ccomp * cbit / 4);

    size_t cbyteCompressed;
    JLS_ERROR error = JpegLsEncode(&rgbyteCompressed[0], rgbyteCompressed.size(), &cbyteCompressed, inputdata, inputlength, &params);
    if( error != OK )
      {
      gdcmErrorMacro( "Error compressing: " << error );
      return false;
      }

    assert( cbyteCompressed < rgbyteCompressed.size() );

    Fragment frag;
    frag.SetByteValue( (char*)&rgbyteCompressed[0], cbyteCompressed );
    sq->AddFragment( frag );
    }

  assert( sq->GetNumberOfFragments() == dims[2] );
  out.SetValue( *sq );

  return true;

#endif
}

void JPEGLSCodec::SetLossyError(int error)
{
  LossyError = error;
}

bool JPEGLSCodec::Decode(DataElement const &, char* , uint32_t ,
              uint32_t , uint32_t , uint32_t ,
              uint32_t , uint32_t , uint32_t )
{
 return false;
}

} // end namespace gdcm
