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

  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 "gdcmImageChangeTransferSyntax.h"
#include "gdcmSequenceOfFragments.h"
#include "gdcmSequenceOfItems.h"
#include "gdcmFragment.h"
#include "gdcmPixmap.h"
#include "gdcmBitmap.h"
#include "gdcmRAWCodec.h"
#include "gdcmJPEGCodec.h"
#include "gdcmJPEGLSCodec.h"
#include "gdcmJPEG2000Codec.h"
#include "gdcmRLECodec.h"

namespace gdcm
{

/*
bool ImageChangeTransferSyntax::TryRAWCodecIcon(const DataElement &pixelde)
{
  unsigned long len = Input->GetIconImage().GetBufferLength();
  //gdcm_assert( len == pixelde.GetByteValue()->GetLength() );
  const TransferSyntax &ts = GetTransferSyntax();

  RAWCodec codec;
  if( codec.CanCode( ts ) )
    {
    codec.SetDimensions( Input->GetIconImage().GetDimensions() );
    codec.SetPlanarConfiguration( Input->GetIconImage().GetPlanarConfiguration() );
    codec.SetPhotometricInterpretation( Input->GetIconImage().GetPhotometricInterpretation() );
    codec.SetPixelFormat( Input->GetIconImage().GetPixelFormat() );
    codec.SetNeedOverlayCleanup( Input->GetIconImage().AreOverlaysInPixelData() );
    DataElement out;
    //bool r = codec.Code(Input->GetDataElement(), out);
    bool r = codec.Code(pixelde, out);

    DataElement &de = Output->GetIconImage().GetDataElement();
    de.SetValue( out.GetValue() );
    if( !r )
      {
      return false;
      }
    return true;
    }
  return false;
}
*/

void UpdatePhotometricInterpretation( Bitmap const &input, Bitmap &output )
{
  // when decompressing J2K, need to revert to proper photo inter in uncompressed TS:
  if( input.GetPhotometricInterpretation() == PhotometricInterpretation::YBR_RCT
    || input.GetPhotometricInterpretation() == PhotometricInterpretation::YBR_ICT )
    {
    output.SetPhotometricInterpretation( PhotometricInterpretation::RGB );
    }
  // when decompressing loss jpeg, need to revert to proper photo inter in uncompressed TS:
  if( input.GetPhotometricInterpretation() == PhotometricInterpretation::YBR_FULL_422 
   || input.GetPhotometricInterpretation() == PhotometricInterpretation::YBR_PARTIAL_422 )
    {
    output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_FULL );
    }
  gdcm_assert( output.GetPhotometricInterpretation() == PhotometricInterpretation::RGB
    || output.GetPhotometricInterpretation() == PhotometricInterpretation::YBR_FULL
    || output.GetPhotometricInterpretation() == PhotometricInterpretation::MONOCHROME1
    || output.GetPhotometricInterpretation() == PhotometricInterpretation::MONOCHROME2
    || output.GetPhotometricInterpretation() == PhotometricInterpretation::ARGB
    || output.GetPhotometricInterpretation() == PhotometricInterpretation::PALETTE_COLOR ); // programmer error
}

bool ImageChangeTransferSyntax::TryRAWCodec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
{
  unsigned long len = input.GetBufferLength(); (void)len;
  //gdcm_assert( len == pixelde.GetByteValue()->GetLength() );
  const TransferSyntax &ts = GetTransferSyntax();

  RAWCodec codec;
  if( codec.CanCode( ts ) )
    {
    codec.SetDimensions( input.GetDimensions() );
    codec.SetPlanarConfiguration( input.GetPlanarConfiguration() );
    codec.SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
    codec.SetPixelFormat( input.GetPixelFormat() );
    codec.SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
    DataElement out;
    //bool r = codec.Code(input.GetDataElement(), out);
    bool r = codec.Code(pixelde, out);

    if( !r )
      {
      return false;
      }
    DataElement &de = output.GetDataElement();
    de.SetValue( out.GetValue() );
    UpdatePhotometricInterpretation( input, output );
    return true;
    }
  return false;
}

bool ImageChangeTransferSyntax::TryRLECodec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
{
  unsigned long len = input.GetBufferLength(); (void)len;
  //gdcm_assert( len == pixelde.GetByteValue()->GetLength() );
  const TransferSyntax &ts = GetTransferSyntax();

  RLECodec codec;
  if( codec.CanCode( ts ) )
    {
    codec.SetDimensions( input.GetDimensions() );
    codec.SetPlanarConfiguration( input.GetPlanarConfiguration() );
    codec.SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
    codec.SetPixelFormat( input.GetPixelFormat() );
    codec.SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
    DataElement out;
    //bool r = codec.Code(input.GetDataElement(), out);
    bool r = codec.Code(pixelde, out);

    if( !r )
      {
      return false;
      }
    DataElement &de = output.GetDataElement();
    de.SetValue( out.GetValue() );
    UpdatePhotometricInterpretation( input, output );
    if( input.GetPixelFormat().GetSamplesPerPixel() == 3 )
    {
      if( input.GetPlanarConfiguration() == 0 )
      {
        // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_G.2.html
        // The use of separate segments implies that the Planar Configuration (0028,0006) will always be 1 for RLE compressed images.
        output.SetPlanarConfiguration(1);
      }
    }
    return true;
    }
  return false;
}

bool ImageChangeTransferSyntax::TryJPEGCodec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
{
  unsigned long len = input.GetBufferLength(); (void)len;
  //gdcm_assert( len == pixelde.GetByteValue()->GetLength() );
  const TransferSyntax &ts = GetTransferSyntax();

  JPEGCodec jpgcodec;
  // pass lossy/lossless flag:
  // JPEGCodec are easier to deal with since there is no dual transfer syntax
  // that can be both lossy and lossless:
  if( ts.IsLossy() )
    {
    //gdcm_assert( !ts.IsLossless() ); // I cannot do since since Try* functions are called with all TS, I could be receiving a JPEGLS TS...
    jpgcodec.SetLossless( false );
    }

  ImageCodec *codec = &jpgcodec;
  JPEGCodec *usercodec = dynamic_cast<JPEGCodec*>(UserCodec);
  if( usercodec && usercodec->CanCode( ts ) )
    {
    codec = usercodec;
    }

  if( codec->CanCode( ts ) )
    {
    codec->SetDimensions( input.GetDimensions() );
    // FIXME: GDCM always apply the planar configuration to 0...
    //if( input.GetPlanarConfiguration() )
    //  {
    //  output.SetPlanarConfiguration( 0 );
    //  }
    codec->SetPlanarConfiguration( input.GetPlanarConfiguration() );
    codec->SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
    codec->SetPixelFormat( input.GetPixelFormat() );
    codec->SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
    // let's check we are not trying to compress 16bits with JPEG/Lossy/8bits
    if( !input.GetPixelFormat().IsCompatible( ts ) )
      {
      gdcmErrorMacro("Pixel Format incompatible with TS" );
      return false;
      }
    DataElement out;
    //bool r = codec.Code(input.GetDataElement(), out);
    bool r = codec->Code(pixelde, out);
    // FIXME: this is not the best place to change the Output image internal type,
    // but since I know IJG is always applying the Planar Configuration, it does make
    // any sense to EVER produce a JPEG image where the Planar Configuration would be one
    // so let's be nice and actually sync JPEG configuration with DICOM Planar Conf.
    output.SetPlanarConfiguration( 0 );
    //output.SetPhotometricInterpretation( PhotometricInterpretation::RGB );

    // Indeed one cannot produce a true lossless RGB image according to DICOM standard
    // when doing lossless jpeg:
    if( output.GetPhotometricInterpretation() == PhotometricInterpretation::RGB )
      {
      gdcmWarningMacro( "Technically this is not defined in the standard. \n"
        "Some validator may complains this image is invalid, but would be wrong.");
      }

    // PHILIPS_Gyroscan-12-MONO2-Jpeg_Lossless.dcm
    if( !r )
      {
      return false;
      }
    DataElement &de = output.GetDataElement();
    de.SetValue( out.GetValue() );
    UpdatePhotometricInterpretation( input, output );
    // When compressing with JPEG I think planar should always be:
    //output.SetPlanarConfiguration(0);
    // FIXME ! This should be done all the time for all codec:
    // Did PI change or not ?
    if ( !output.GetPhotometricInterpretation().IsSameColorSpace( codec->GetPhotometricInterpretation() ) )
      {
      // HACK
      //Image *i = (Image*)this;
      //i->SetPhotometricInterpretation( codec.GetPhotometricInterpretation() );
      gdcm_assert(0);
      }
    return true;
    }
  return false;
}

bool ImageChangeTransferSyntax::TryJPEGLSCodec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
{
  unsigned long len = input.GetBufferLength(); (void)len;
  //gdcm_assert( len == pixelde.GetByteValue()->GetLength() );
  const TransferSyntax &ts = GetTransferSyntax();

  JPEGLSCodec jlscodec;
  ImageCodec *codec = &jlscodec;
  JPEGLSCodec *usercodec = dynamic_cast<JPEGLSCodec*>(UserCodec);
  if( usercodec && usercodec->CanCode( ts ) )
    {
    codec = usercodec;
    }

  if( codec->CanCode( ts ) )
    {
    codec->SetDimensions( input.GetDimensions() );
    codec->SetPixelFormat( input.GetPixelFormat() );
    //codec.SetNumberOfDimensions( input.GetNumberOfDimensions() );
    codec->SetPlanarConfiguration( input.GetPlanarConfiguration() );
    codec->SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
    codec->SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
    DataElement out;
    //bool r = codec.Code(input.GetDataElement(), out);
    bool r;
    if( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() )
      {
      ByteValue *bv = const_cast<ByteValue*>(pixelde.GetByteValue());
      gdcm_assert( bv );
      gdcm::DataElement tmp;
      tmp.SetByteValue( bv->GetPointer(), bv->GetLength());
      bv = const_cast<ByteValue*>(tmp.GetByteValue());
      r = codec->CleanupUnusedBits((char*)bv->GetVoidPointer(), bv->GetLength());
      if(!r) return false;
      r = codec->Code(tmp, out);
      }
    else
      {
      r = codec->Code(pixelde, out);
      }
    if(!r) return false;

    DataElement &de = output.GetDataElement();
    de.SetValue( out.GetValue() );
    UpdatePhotometricInterpretation( input, output );
    if( input.GetPixelFormat().GetSamplesPerPixel() == 3 )
    {
      if( input.GetPlanarConfiguration() == 1 )
      {
        // CP-1843
        // https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_8.2.3.html#para_d1e96f41-db25-4a4b-9009-9fd3796e5b43
        output.SetPlanarConfiguration(0);
      }
    }

    return r;
    }
  return false;
}

bool ImageChangeTransferSyntax::TryJPEG2000Codec(const DataElement &pixelde, Bitmap const &input, Bitmap &output)
{
  unsigned long len = input.GetBufferLength(); (void)len;
  //gdcm_assert( len == pixelde.GetByteValue()->GetLength() );
  const TransferSyntax &ts = GetTransferSyntax();

  JPEG2000Codec j2kcodec;
  ImageCodec *codec = &j2kcodec;
  JPEG2000Codec *usercodec = dynamic_cast<JPEG2000Codec*>(UserCodec);
  if( usercodec && usercodec->CanCode( ts ) )
    {
    codec = usercodec;
    }

  if( codec->CanCode( ts ) )
    {
    codec->SetDimensions( input.GetDimensions() );
    codec->SetPixelFormat( input.GetPixelFormat() );
    codec->SetNumberOfDimensions( input.GetNumberOfDimensions() );
    codec->SetPlanarConfiguration( input.GetPlanarConfiguration() );
    codec->SetPhotometricInterpretation( input.GetPhotometricInterpretation() );
    codec->SetNeedOverlayCleanup( input.AreOverlaysInPixelData() || input.UnusedBitsPresentInPixelData() );
    DataElement out;
    //bool r = codec.Code(input.GetDataElement(), out);
    bool r = codec->Code(pixelde, out);

    // The value of Planar Configuration (0028,0006) is irrelevant since the
    // manner of encoding components is specified in the JPEG 2000 standard,
    // hence it shall be set to 0.
    output.SetPlanarConfiguration( 0 );

    if( input.GetPixelFormat().GetSamplesPerPixel() == 3 )
      {
      if( input.GetPhotometricInterpretation().IsSameColorSpace( PhotometricInterpretation::RGB ) )
        {
        if( ts == TransferSyntax::JPEG2000Lossless )
          {
          output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_RCT );
          }
        else
          {
          gdcm_assert( ts == TransferSyntax::JPEG2000 );
          output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_ICT );
          }
        }
      else
        {
        gdcm_assert( input.GetPhotometricInterpretation().IsSameColorSpace( PhotometricInterpretation::YBR_FULL ) );
        if( ts == TransferSyntax::JPEG2000Lossless )
          {
          output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_FULL );
          // Indeed one cannot produce a true lossless RGB image according to DICOM standard
          gdcmWarningMacro( "Technically this is not defined in the standard. \n"
            "Some validator may complains this image is invalid, but would be wrong.");
          }
        else
          {
          gdcm_assert( ts == TransferSyntax::JPEG2000 );
          //output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_ICT );
          // FIXME: technically when doing lossy we could be standard compliant and first convert to
          // RGB THEN compress to YBR_ICT. For now produce improper j2k image
          output.SetPhotometricInterpretation( PhotometricInterpretation::YBR_FULL );
          }
        }
      }
    else
      {
      gdcm_assert( input.GetPixelFormat().GetSamplesPerPixel() == 1 );
      }

    if( !r ) return false;
    DataElement &de = output.GetDataElement();
    de.SetValue( out.GetValue() );
    UpdatePhotometricInterpretation( input, output );
    return r;
    }
  return false;
}

bool ImageChangeTransferSyntax::Change()
{
  if( TS == TransferSyntax::TS_END )
    {
    if( !Force ) return false;
    // When force option is set but no specific TransferSyntax has been set, only inspect the
    // encapsulated stream...
    // See ImageReader::Read
    if( Input->GetTransferSyntax().IsEncapsulated() && Input->GetTransferSyntax() != TransferSyntax::RLELossless )
      {
      Output = Input;
      return true;
      }
    return false;
    }
  // let's get rid of some easy case:
  if( Input->GetPhotometricInterpretation() == PhotometricInterpretation::PALETTE_COLOR &&
    TS.IsLossy() )
    {
    gdcmErrorMacro( "PALETTE_COLOR and Lossy compression are impossible. Convert to RGB first." );
    return false;
    }

  Output = Input;
//  if( TS.IsLossy() && !TS.IsLossless() )
//    Output->SetLossyFlag( true );

  // Fast path
  if( Input->GetTransferSyntax() == TS && !Force ) return true;

  // FIXME
  // For now only support raw input, otherwise we would need to first decompress them
  if( (Input->GetTransferSyntax() != TransferSyntax::ImplicitVRLittleEndian
    && Input->GetTransferSyntax() != TransferSyntax::ExplicitVRLittleEndian
    && Input->GetTransferSyntax() != TransferSyntax::ExplicitVRBigEndian)
    // YBR_FULL_422 / raw needs to be decompressed:
    || ( (Input->GetTransferSyntax() == TransferSyntax::ImplicitVRLittleEndian
       || Input->GetTransferSyntax() == TransferSyntax::ExplicitVRLittleEndian
       || Input->GetTransferSyntax() == TransferSyntax::ExplicitVRBigEndian)
       && Input->GetPhotometricInterpretation() == PhotometricInterpretation::YBR_FULL_422 )
    || Force )
    {
    // In memory decompression:
    DataElement pixeldata( Tag(0x7fe0,0x0010) );
    ByteValue *bv0 = new ByteValue();
    uint32_t len0 = (uint32_t)Input->GetBufferLength();
    bv0->SetLength( len0 );
    bool b = Input->GetBuffer( (char*)bv0->GetVoidPointer() );
    if( !b )
      {
      gdcmErrorMacro( "Error in getting buffer from input image." );
      delete bv0;
      return false;
      }
    pixeldata.SetValue( *bv0 );

    bool success = false;
    if( !success ) success = TryRAWCodec(pixeldata, *Input, *Output);
    if( !success ) success = TryJPEGCodec(pixeldata, *Input, *Output);
    if( !success ) success = TryJPEGLSCodec(pixeldata, *Input, *Output);
    if( !success ) success = TryJPEG2000Codec(pixeldata, *Input, *Output);
    if( !success ) success = TryRLECodec(pixeldata, *Input, *Output);
    Output->SetTransferSyntax( TS );
    if( !success )
      {
      //gdcm_assert(0);
      return false;
      }

    // same goes for icon
    DataElement iconpixeldata( Tag(0x7fe0,0x0010) );
    Bitmap &bitmap = *Input;
    if( Pixmap *pixmap = dynamic_cast<Pixmap*>( &bitmap ) )
      {
      Bitmap &outbitmap = *Output;
      Pixmap *outpixmap = dynamic_cast<Pixmap*>( &outbitmap );
      gdcm_assert( outpixmap != nullptr );
      if( !pixmap->GetIconImage().IsEmpty() )
        {
        // same goes for icon
        ByteValue *bv = new ByteValue();
        uint32_t len = (uint32_t)pixmap->GetIconImage().GetBufferLength();
        bv->SetLength( len );
        bool bb = pixmap->GetIconImage().GetBuffer( (char*)bv->GetVoidPointer() );
        if( !bb )
          {
          return false;
          }
        iconpixeldata.SetValue( *bv );

        success = false;
        if( !success ) success = TryRAWCodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
        if( !success ) success = TryJPEGCodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
        if( !success ) success = TryJPEGLSCodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
        if( !success ) success = TryJPEG2000Codec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
        if( !success ) success = TryRLECodec(iconpixeldata, pixmap->GetIconImage(), outpixmap->GetIconImage());
        outpixmap->GetIconImage().SetTransferSyntax( TS );
        if( !success )
          {
          //gdcm_assert(0);
          return false;
          }
        gdcm_assert( outpixmap->GetIconImage().GetTransferSyntax() == TS );
        }
      }

    //Output->ComputeLossyFlag();
    gdcm_assert( Output->GetTransferSyntax() == TS );
    //if( TS.IsLossy() ) gdcm_assert( Output->IsLossy() );
    return success;
    }

  // too bad we actually have to do some work...
  bool success = false;
  if( !success ) success = TryRAWCodec(Input->GetDataElement(), *Input, *Output);
  if( !success ) success = TryJPEGCodec(Input->GetDataElement(), *Input, *Output);
  if( !success ) success = TryJPEG2000Codec(Input->GetDataElement(), *Input, *Output);
  if( !success ) success = TryJPEGLSCodec(Input->GetDataElement(), *Input, *Output);
  if( !success ) success = TryRLECodec(Input->GetDataElement(), *Input, *Output);
  Output->SetTransferSyntax( TS );
  if( !success )
    {
    //gdcm_assert(0);
    return false;
    }

  Bitmap &bitmap = *Input;
  if( Pixmap *pixmap = dynamic_cast<Pixmap*>( &bitmap ) )
    {
    if( !pixmap->GetIconImage().IsEmpty() && CompressIconImage )
      {
      Bitmap &outbitmap = *Output;
      Pixmap *outpixmap = dynamic_cast<Pixmap*>( &outbitmap );

      // same goes for icon
      success = false;
      if( !success ) success = TryRAWCodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
      if( !success ) success = TryJPEGCodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
      if( !success ) success = TryJPEGLSCodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
      if( !success ) success = TryJPEG2000Codec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
      if( !success ) success = TryRLECodec(pixmap->GetIconImage().GetDataElement(), pixmap->GetIconImage(), outpixmap->GetIconImage());
      outpixmap->GetIconImage().SetTransferSyntax( TS );
      if( !success )
        {
        //gdcm_assert(0);
        return false;
        }
      gdcm_assert( outpixmap->GetIconImage().GetTransferSyntax() == TS );
      }
    }

  //Output->ComputeLossyFlag();

  gdcm_assert( Output->GetTransferSyntax() == TS );
  return success;
}


} // end namespace gdcm
