/*
 *
 *  Copyright (C) 2002-2024, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  dcmdata
 *
 *  Author:  Marco Eichelberg
 *
 *  Purpose: encoder codec class for RLE
 *
 */

#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcrlecce.h"

#include "dcmtk/dcmdata/dcrleenc.h"  /* for class DcmRLEEncoder */
#include "dcmtk/dcmdata/dcrlecp.h"   /* for class DcmRLECodecParameter */
#include "dcmtk/dcmdata/dcdeftag.h"  /* for tag constants */
#include "dcmtk/dcmdata/dcpixseq.h"  /* for class DcmPixelSequence */
#include "dcmtk/dcmdata/dcpxitem.h"  /* for class DcmPixelItem */
#include "dcmtk/dcmdata/dcswap.h"    /* for swapIfNecessary */
#include "dcmtk/dcmdata/dcitem.h"
#include "dcmtk/ofstd/ofstd.h"

typedef OFList<DcmRLEEncoder *> DcmRLEEncoderList;
typedef OFListIterator(DcmRLEEncoder *) DcmRLEEncoderListIterator;


// =======================================================================

DcmRLECodecEncoder::DcmRLECodecEncoder()
: DcmCodec()
{
}


DcmRLECodecEncoder::~DcmRLECodecEncoder()
{
}


OFBool DcmRLECodecEncoder::canChangeCoding(
    const E_TransferSyntax oldRepType,
    const E_TransferSyntax newRepType) const
{
  E_TransferSyntax myXfer = EXS_RLELossless;
  DcmXfer newRep(newRepType);
  DcmXfer oldRep(oldRepType);
  if (oldRep.usesNativeFormat() && (newRepType == myXfer)) return OFTrue; // compress requested

  // we don't support re-coding for now.
  return OFFalse;
}


OFCondition DcmRLECodecEncoder::decode(
    const DcmRepresentationParameter * /* fromRepParam */,
    DcmPixelSequence * /* pixSeq */,
    DcmPolymorphOBOW& /* uncompressedPixelData */,
    const DcmCodecParameter * /* cp */,
    const DcmStack& /* objStack */,
    OFBool& /* removeOldRep */ ) const
{
  // we are an encoder only
  return EC_IllegalCall;
}


OFCondition DcmRLECodecEncoder::decodeFrame(
    const DcmRepresentationParameter * /* fromParam */ ,
    DcmPixelSequence * /* fromPixSeq */ ,
    const DcmCodecParameter * /* cp */ ,
    DcmItem * /* dataset */ ,
    Uint32 /* frameNo */ ,
    Uint32& /* startFragment */ ,
    void * /* buffer */ ,
    Uint32 /* bufSize */ ,
    OFString& /* decompressedColorModel */ ) const
{
  // we are an encoder only
  return EC_IllegalCall;
}


OFCondition DcmRLECodecEncoder::encode(
    const E_TransferSyntax /* fromRepType */,
    const DcmRepresentationParameter * /* fromRepParam */,
    DcmPixelSequence * /* fromPixSeq */,
    const DcmRepresentationParameter * /* toRepParam */,
    DcmPixelSequence * & /* toPixSeq */,
    const DcmCodecParameter * /* cp */,
    DcmStack& /* objStack */,
    OFBool& /* removeOldRep */ ) const
{
  // we don't support re-coding for now.
  return EC_IllegalCall;
}


OFCondition DcmRLECodecEncoder::encode(
    const Uint16 *pixelData,
    const Uint32 length,
    const DcmRepresentationParameter * /* toRepParam */ ,
    DcmPixelSequence * & pixSeq,
    const DcmCodecParameter *cp,
    DcmStack& objStack,
    OFBool& /* removeOldRep */ ) const
{
  OFCondition result = EC_Normal;

  // assume we can cast the codec parameter to what we need
  const DcmRLECodecParameter *djcp = OFstatic_cast(const DcmRLECodecParameter *, cp);
  DcmStack localStack(objStack);
  (void)localStack.pop();             // pop pixel data element from stack
  DcmObject *dataset = localStack.pop(); // this is the item in which the pixel data is located
  Uint8 *pixelData8 = OFreinterpret_cast(Uint8 *, OFconst_cast(Uint16 *, pixelData));
  Uint8 *pixelPointer = NULL;
  DcmOffsetList offsetList;
  DcmRLEEncoderList rleEncoderList;
  DcmRLEEncoderListIterator first = rleEncoderList.begin();
  DcmRLEEncoderListIterator last = rleEncoderList.end();
  Uint32 rleHeader[16];
  Uint32 i;
  OFBool byteSwapped = OFFalse;  // true if we have byte-swapped the original pixel data

  if ((!dataset)||((dataset->ident()!= EVR_dataset) && (dataset->ident()!= EVR_item))) result = EC_InvalidTag;
  else
  {
    DcmItem *ditem = OFstatic_cast(DcmItem *, dataset);
    Uint16 bitsAllocated = 0;
    Uint16 bytesAllocated = 0;
    Uint16 samplesPerPixel = 0;
    Uint16 planarConfiguration = 0;
    Uint16 columns = 0;
    Uint16 rows = 0;
    Sint32 numberOfFrames = 1;
    Uint32 numberOfStripes = 0;
    Uint32 compressedSize = 0;

    result = ditem->findAndGetUint16(DCM_BitsAllocated, bitsAllocated);
    if (result.good()) result = ditem->findAndGetUint16(DCM_SamplesPerPixel, samplesPerPixel);
    if (result.good()) result = ditem->findAndGetUint16(DCM_Columns, columns);
    if (result.good()) result = ditem->findAndGetUint16(DCM_Rows, rows);
    if (result.good())
    {
      result = ditem->findAndGetSint32(DCM_NumberOfFrames, numberOfFrames);
      if (result.bad() || numberOfFrames < 1) numberOfFrames = 1;
      result = EC_Normal;
    }
    if (result.good() && (samplesPerPixel > 1))
    {
      result = ditem->findAndGetUint16(DCM_PlanarConfiguration, planarConfiguration);
    }

    if (result.good())
    {
      // check if bitsAllocated is a multiple of 8 - we don't handle anything else
      bytesAllocated = OFstatic_cast(Uint16, bitsAllocated / 8);
      if ((bitsAllocated < 8)||(bitsAllocated % 8 != 0)) result = EC_CannotChangeRepresentation;

      // make sure that all the descriptive attributes have sensible values
      if ((columns < 1)||(rows < 1)||(samplesPerPixel < 1)) result = EC_CannotChangeRepresentation;

      // an RLE stripe set can have at most 15 stripes, i.e. 15 bytes allocated per pixel
      numberOfStripes = bytesAllocated * samplesPerPixel;
      if (numberOfStripes > 15) result = EC_CannotChangeRepresentation;

      // make sure that we have at least as many bytes of pixel data as we expect
      if (numberOfStripes * columns * rows * numberOfFrames > length) result = EC_CannotChangeRepresentation;
    }

    DcmPixelSequence *pixelSequence = NULL;
    DcmPixelItem *offsetTable = NULL;

    // create initial pixel sequence
    if (result.good())
    {
      pixelSequence = new DcmPixelSequence(DCM_PixelSequenceTag);
      if (pixelSequence == NULL) result = EC_MemoryExhausted;
      else
      {
        // create empty offset table
        offsetTable = new DcmPixelItem(DCM_PixelItemTag);
        if (offsetTable == NULL) result = EC_MemoryExhausted;
        else pixelSequence->insert(offsetTable);
      }
    }

    // byte swap pixel data to little endian
    if (gLocalByteOrder == EBO_BigEndian)
    {
      swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, OFstatic_cast(void *, OFconst_cast(Uint16 *, pixelData)), length, sizeof(Uint16));
    }

    // create RLE stripe sets
    if (result.good())
    {
      const Uint32 bytesPerStripe = columns * rows;
      const Uint32 frameSize = columns * rows * samplesPerPixel * bytesAllocated;
      Uint32 frameOffset = 0;
      Uint32 sampleOffset = 0;
      Uint32 offsetBetweenSamples = 0;
      Uint32 sample = 0;
      Uint32 byte = 0;
      Uint32 pixel = 0;
      Uint32 columnCounter = 0;

      DcmRLEEncoder *rleEncoder = NULL;
      Uint32 rleSize = 0;
      Uint8 *rleData = NULL;
      Uint8 *rleData2 = NULL;

      // warn about (possibly) non-standard fragmentation
      if (djcp->getFragmentSize() > 0)
         DCMDATA_WARN("DcmRLECodecEncoder: limiting the fragment size may result in non-standard conformant encoding");

      // compute byte offset between samples
      if (planarConfiguration == 0)
         offsetBetweenSamples = samplesPerPixel * bytesAllocated;
         else offsetBetweenSamples = bytesAllocated;

      // loop through all frames of the image
      for (Uint32 currentFrame = 0; ((currentFrame < OFstatic_cast(Uint32, numberOfFrames)) && result.good()); currentFrame++)
      {
        // offset to start of frame, in bytes
        frameOffset = frameSize * currentFrame;

        // loop through all samples of one frame
        for (sample = 0; sample < samplesPerPixel; sample++)
        {
          // compute byte offset for first sample in frame
          if (planarConfiguration == 0)
             sampleOffset = sample * bytesAllocated;
             else sampleOffset = sample * bytesAllocated * columns * rows;

          // loop through the bytes of one sample
          for (byte = 0; byte < bytesAllocated; byte++)
          {
            pixelPointer = pixelData8 + frameOffset + sampleOffset + bytesAllocated - byte - 1;

            // initialize new RLE codec for this stripe
            rleEncoder = new DcmRLEEncoder(1 /* DICOM padding required */);
            if (rleEncoder)
            {
              rleEncoderList.push_back(rleEncoder);
              columnCounter = columns;

              // loop through all pixels of the frame
              for (pixel = 0; pixel < bytesPerStripe; ++pixel)
              {
                rleEncoder->add(*pixelPointer);

                // enforce DICOM rule that "Each row of the image shall be encoded
                // separately and not cross a row boundary."
                // (see DICOM part 5 section G.3.1)
                if (--columnCounter == 0)
                {
                  rleEncoder->flush();
                  columnCounter = columns;
                }
                pixelPointer += offsetBetweenSamples;
              }

              rleEncoder->flush();
              if (rleEncoder->fail()) result = EC_MemoryExhausted;
            } else result = EC_MemoryExhausted;
          }
        }

        // store frame and erase RLE codec list
        if (result.good() && (rleEncoderList.size() > 0) && (rleEncoderList.size() < 16))
        {
          // compute size of compressed frame including RLE header
          // and populate RLE header
          for (i=0; i<16; i++) rleHeader[i] = 0;
          rleHeader[0] = OFstatic_cast(Uint32, rleEncoderList.size());
          rleSize = 64;
          i = 1;
          first = rleEncoderList.begin();
          while (first != last)
          {
            rleHeader[i++] = rleSize;
            rleSize += OFstatic_cast(Uint32, (*first)->size());
            ++first;
          }

          // allocate buffer for compressed frame
          rleData = new Uint8[rleSize];

          if (rleData)
          {
            // copy RLE header to compressed frame buffer
            swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, rleHeader, OFstatic_cast(Uint32, 16*sizeof(Uint32)), sizeof(Uint32));
            memcpy(rleData, rleHeader, 64);

            // store RLE stripe sets in compressed frame buffer
            rleData2 = rleData + 64;
            first = rleEncoderList.begin();
            while (first != last)
            {
              (*first)->write(rleData2);
              rleData2 += (*first)->size();
              delete *first;
              first = rleEncoderList.erase(first);
            }

            // store compressed frame, breaking into segments if necessary
            result = pixelSequence->storeCompressedFrame(offsetList, rleData, rleSize, djcp->getFragmentSize());
            compressedSize += rleSize;

            // erase buffer for compressed frame
            delete[] rleData;
          } else result = EC_MemoryExhausted;
        }
        else
        {
          // erase RLE codec list
          first = rleEncoderList.begin();
          while (first != last)
          {
            delete *first;
            first = rleEncoderList.erase(first);
          }
          if (result.good()) result = EC_CannotChangeRepresentation;
        }

      }

    }

    // store pixel sequence if everything went well.
    if (result.good()) pixSeq = pixelSequence;
    else
    {
      delete pixelSequence;
      pixSeq = NULL;
    }

    if ((result.good()) && (djcp->getCreateOffsetTable()))
    {
      // create offset table
      result = offsetTable->createOffsetTable(offsetList);
    }

    // the following operations do not affect the Image Pixel Module
    // but other modules such as SOP Common.  We only perform these
    // changes if we're on the main level of the dataset,
    // which should always identify itself as dataset, not as item.
    if (dataset->ident() == EVR_dataset)
    {
        if (result.good())
        {
            // create new UID if mode is true or if we're converting to Secondary Capture
            if (djcp->getConvertToSC() || djcp->getUIDCreation())
            {
                result = DcmCodec::newInstance(OFstatic_cast(DcmItem *, dataset), "DCM", "121320", "Uncompressed predecessor");

                // set image type to DERIVED
                if (result.good()) result = updateImageType(OFstatic_cast(DcmItem *, dataset));

                // update derivation description
                if (result.good())
                {
                  // compute original image size in bytes, ignoring any padding bits.
                  double compressionRatio = 0.0;
                  if (compressedSize > 0) compressionRatio = (OFstatic_cast(double, columns * rows * bitsAllocated * OFstatic_cast(Uint32, numberOfFrames) * samplesPerPixel) / 8.0) / compressedSize;
                  result = updateDerivationDescription(OFstatic_cast(DcmItem *, dataset), compressionRatio);
                }
            }
        }

        // convert to Secondary Capture if requested by user.
        // This method creates a new SOP class UID, so it should be executed
        // after the call to newInstance() which creates a Source Image Sequence.
        if (result.good() && djcp->getConvertToSC()) result = DcmCodec::convertToSecondaryCapture(OFstatic_cast(DcmItem *, dataset));
    }
  }

  // byte swap pixel data back to local endian if necessary
  if (byteSwapped)
  {
    swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, OFstatic_cast(void *, OFconst_cast(Uint16 *, pixelData)), length, sizeof(Uint16));
  }
  return result;
}


OFCondition DcmRLECodecEncoder::updateDerivationDescription(
  DcmItem *dataset,
  double ratio)
{
  char buf[32];

  // create new Derivation Description
  OFString derivationDescription = "Lossless RLE compression, compression ratio ";
  OFStandard::ftoa(buf, sizeof(buf), ratio, OFStandard::ftoa_uppercase, 0, 5);
  derivationDescription += buf;

  // append old Derivation Description, if any
  const char *oldDerivation = NULL;
  if ((dataset->findAndGetString(DCM_DerivationDescription, oldDerivation)).good() && oldDerivation)
  {
    derivationDescription += " [";
    derivationDescription += oldDerivation;
    derivationDescription += "]";
    if (derivationDescription.length() > 1024)
    {
      // ST is limited to 1024 characters, cut off tail
      derivationDescription.erase(1020);
      derivationDescription += "...]";
    }
  }

  return dataset->putAndInsertString(DCM_DerivationDescription, derivationDescription.c_str());
}


OFCondition DcmRLECodecEncoder::determineDecompressedColorModel(
    const DcmRepresentationParameter * /* fromParam */,
    DcmPixelSequence * /* fromPixSeq */,
    const DcmCodecParameter * /* cp */,
    DcmItem * /* dataset */,
    OFString & /* decompressedColorModel */) const
{
    return EC_IllegalCall;
}
