/*
 *
 *  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: RLE compressor
 *
 */

#ifndef DCRLEENC_H
#define DCRLEENC_H

#include "dcmtk/config/osconfig.h"
#include "dcmtk/ofstd/oflist.h"   /* for class OFList<> */
#include "dcmtk/dcmdata/dcdefine.h"
#include <cstring>

#define DcmRLEEncoder_BLOCKSIZE 16384


/** abstract class that defines an interface through which
 *  encoder classes (such as DcmRLEEncoder) may export their
 *  encoded data
 */
class DCMTK_DCMDATA_EXPORT DcmEncoderOutputStream
{
public:
  /** write the given buffer into the output stream
   *  @param buf pointer to buffer
   *  @param bufsize number of bytes in buffer
   */
  virtual void write(const unsigned char *buf, size_t bufsize) =0;

  /** Virtual Destructor
   */
  virtual ~DcmEncoderOutputStream() {}

};


/** this class implements an RLE compressor conforming to the DICOM standard.
 *  The class is loosely based on an implementation by Phil Norman.
 */
class DCMTK_DCMDATA_EXPORT DcmRLEEncoder
{
public:

  /** default constructor
   *  @param doPad if true, RLE codec will pad output data to even number of bytes
   */
  DcmRLEEncoder(int doPad)
  : fail_(0)
  , pad_(doPad)
  , currentBlock_(new unsigned char[DcmRLEEncoder_BLOCKSIZE])
  , offset_(0)
  , blockList_()
  , RLE_buff_(new unsigned char[132])
  , RLE_prev_(-1)
  , RLE_pcount_(0)
  , RLE_bindex_(1)
  {
    if ((! RLE_buff_)||(! currentBlock_)) fail_ = 1;
    else RLE_buff_[0] = 0;
  }

  /// destructor
  ~DcmRLEEncoder()
  {
    delete[] currentBlock_;
    delete[] RLE_buff_;
    OFListIterator(unsigned char *) first = blockList_.begin();
    OFListIterator(unsigned char *) last = blockList_.end();
    while (first != last)
    {
        delete[] *first;
        first = blockList_.erase(first);
    }
  }

  /** this method adds one byte to the byte stream to be compressed
   *  with the RLE compressor.
   *  @param ch byte to be added
   */
  inline void add(unsigned char ch)
  {
    if (! fail_) // if fail_ is true, just ignore input
    {
      // if the current byte equals the last byte read
      // (which is initialized with the "impossible" value -1),
      // just increase the repeat counter
      if (OFstatic_cast(int, ch) == RLE_prev_) RLE_pcount_++;
      else
      {
          // byte is different from last byte read.
          // flush replicate run if necessary
          switch (RLE_pcount_)
          {
            case 0:
              // happens only after construction or flush()
              break;
            case 2:
              // two bytes in repeat buffer. Convert to literal run
              RLE_buff_[RLE_bindex_++] = OFstatic_cast(unsigned char, RLE_prev_);
              // no break. Fall-through into next case statement is intended.
            case 1:
              // one (or two) bytes in repeat buffer. Convert to literal run
              RLE_buff_[RLE_bindex_++] = OFstatic_cast(unsigned char, RLE_prev_);
              break;
            default:
              // more than two bytes in repeat buffer. Convert to replicate run
              if (RLE_bindex_ > 1)
              {
                  // there is a literal run in the buffer that must be flushed
                  // before the replicate run.  Flush literal run now.
                  RLE_buff_[0] = OFstatic_cast(unsigned char, RLE_bindex_-2);
                  move(RLE_bindex_);
              }
              // this is the byte value for the repeat run
              RLE_buff_[1] = OFstatic_cast(unsigned char, RLE_prev_);
              // write as many repeat runs as necessary
              for (; RLE_pcount_>0; RLE_pcount_-=128)
              {
                  // different PackBit schemes exist. The original from which
                  // this code is derived used 0x80 | (RLE_pcount_ - 1)
                  // to represent replicate runs.
                  // DICOM instead uses 257 - RLE_pcount_
                  if (RLE_pcount_ > 128) RLE_buff_[0] = 0x81;
                    else RLE_buff_[0] = OFstatic_cast(unsigned char, 257 - RLE_pcount_);
                  move(2);
              }
              // now the buffer is guaranteed to be empty
              RLE_buff_[0] = 0;
              RLE_bindex_ = 1;
              break;
          }

          // if we have 128 or more bytes in the literal run, flush buffer
          if (RLE_bindex_ > 129)
          {
              RLE_buff_[0] = 127;
              move(129);
              RLE_bindex_ -= 128;
              if (RLE_bindex_ > 1)
                  RLE_buff_[1] = RLE_buff_[129];
              if (RLE_bindex_ > 2)
                  RLE_buff_[2] = RLE_buff_[130];
          }

          // current byte is stored in RLE_prev_, RLE_pcount_ is 1.
          RLE_prev_ = ch;
          RLE_pcount_ = 1;
      }
    }
  }

  /** this method adds a block of bytes to the byte stream to be
   *  compressed with the RLE compressor.
   *  @param buf buffer to be added
   *  @param bufcount number of bytes in buffer
   */
  inline void add(const unsigned char *buf, size_t bufcount)
  {
    if (buf)
    {
      while (bufcount--) add(*buf++);
    }
  }

  /** this method finalizes the compressed RLE stream, i.e. flushes all
   *  pending literal or repeat runs. This method can be called at any
   *  time; however, it must be called before size() or write()
   *  can be used.  Intermediate calls should be avoided since they
   *  possibly decrease the compression ratio.
   */
  inline void flush()
  {
    if (! fail_) // if fail_ is true, do nothing
    {
      // if there are max 1 bytes in the repeat counter, convert to literal run
      if (RLE_pcount_ < 2)
      {
        for (; RLE_pcount_>0; --RLE_pcount_) RLE_buff_[RLE_bindex_++] = OFstatic_cast(unsigned char, RLE_prev_);
      }

      // if we have 128 or more bytes in the literal run, flush buffer
      if (RLE_bindex_ > 129)
      {
          RLE_buff_[0] = 127;
          move(129);
          RLE_bindex_ -= 128;
          if (RLE_bindex_ > 1)
              RLE_buff_[1] = RLE_buff_[129];
          if (RLE_bindex_ > 2)
              RLE_buff_[2] = RLE_buff_[130];
      }

      // if there is still a literal run in the buffer, flush literal run
      if (RLE_bindex_ > 1)
      {
          RLE_buff_[0] = OFstatic_cast(unsigned char, RLE_bindex_-2);
          move(RLE_bindex_);
      }

      // if there is a remaining repeat run, flush this one as well
      if (RLE_pcount_ >= 2)
      {
          RLE_buff_[1] = OFstatic_cast(unsigned char, RLE_prev_);
          // write as many repeat runs as necessary
          for (; RLE_pcount_>0; RLE_pcount_-=128)
          {
            // different PackBit schemes exist. The original from which
            // this code is derived used 0x80 | (RLE_pcount_ - 1)
            // to represent replicate runs.
            // DICOM instead uses 257 - RLE_pcount_
            if (RLE_pcount_ > 128) RLE_buff_[0] = 0x81;
              else RLE_buff_[0] = OFstatic_cast(unsigned char, 257 - RLE_pcount_);
            move(2);
          }
      }

      // now the buffer is guaranteed to be empty, re-initialize
      RLE_buff_[0] = 0;
      RLE_prev_ = -1;
      RLE_pcount_ = 0;
      RLE_bindex_ = 1;
    }
  }

  /** returns the size of compressed RLE stream in bytes.
   *  The size is guaranteed to be an even number of bytes (padded
   *  with a trailing zero byte as required by DICOM if necessary).
   *  This method may only be called after flush() has been executed
   *  to finalize the compressed stream.
   *  @return size of compressed stream, in bytes
   */
  inline size_t size() const
  {
    size_t result = blockList_.size() * DcmRLEEncoder_BLOCKSIZE + offset_;
    if (pad_ && (result & 1)) result++; // enforce even number of bytes
    return result;
  }

  /** returns true if the RLE compressor has run out of memory.  In this case,
   *  no output has been created.
   */
  inline OFBool fail() const
  {
    if (fail_) return OFTrue; else return OFFalse;
  }

  /** copies the compressed RLE byte stream into a target array of at least
   *  size() bytes.
   *  @param target pointer to array of at least size() bytes, must not be NULL.
   */
  inline void write(void *target) const
  {
    if ((!fail_) && target)
    {
      unsigned char *current = NULL;
      unsigned char *target8 = OFstatic_cast(unsigned char *, target);
      OFListConstIterator(unsigned char *) first = blockList_.begin();
      OFListConstIterator(unsigned char *) last = blockList_.end();
      while (first != last)
      {
        current = *first;
        memcpy(target8, current, DcmRLEEncoder_BLOCKSIZE);
        target8 += DcmRLEEncoder_BLOCKSIZE;
        ++first;
      }
      if (offset_ > 0)
      {
        memcpy(target8, currentBlock_, offset_);
      }

      // pad to even number of bytes if necessary
      if (pad_ && ((blockList_.size() * DcmRLEEncoder_BLOCKSIZE + offset_) & 1))
      {
        target8 += offset_;
        *target8 = 0;
      }
    }
  }

  /** copies the compressed RLE byte stream into an
   *  output stream
   *  @param os output stream
   */
  inline void write(DcmEncoderOutputStream& os) const
  {
    if (!fail_)
    {
      OFListConstIterator(unsigned char *) first = blockList_.begin();
      OFListConstIterator(unsigned char *) last = blockList_.end();
      while (first != last)
      {
        os.write(*first, DcmRLEEncoder_BLOCKSIZE);
        ++first;
      }
      if (offset_ > 0)
      {
        os.write(currentBlock_, offset_);
      }

      // pad to even number of bytes if necessary
      if (pad_ && ((blockList_.size() * DcmRLEEncoder_BLOCKSIZE + offset_) & 1))
      {
        unsigned char c = 0;
        os.write(&c, 1);
      }
    }
  }

private:

  /// private undefined copy constructor
  DcmRLEEncoder(const DcmRLEEncoder&);

  /// private undefined copy assignment operator
  DcmRLEEncoder& operator=(const DcmRLEEncoder&);

  /** this method moves the given number of bytes from buff_
   *  to currentBlock_ and "flushes" currentBlock_ to
   *  blockList_ if necessary.
   *  @param numberOfBytes number of bytes to copy
   */
  inline void move(size_t numberOfBytes)
  {
    size_t i=0;
    while (i < numberOfBytes)
    {
      if (offset_ == DcmRLEEncoder_BLOCKSIZE)
      {
        blockList_.push_back(currentBlock_);
        currentBlock_ = new unsigned char[DcmRLEEncoder_BLOCKSIZE];
        offset_ = 0;
        if (! currentBlock_) // out of memory
        {
          fail_ = 1;
          break;    // exit while loop
        }
      }
      currentBlock_[offset_++] = RLE_buff_[i++];
    }
  }

  /* member variables */

  /** this flag indicates a failure of the RLE codec.  Once a failure is
   *  flagged, the codec will consume all input and not produce any more
   *  output.  A failure status can only be caused by an out-of-memory
   *  condition.
   */
  int fail_;

  /** this flag indicates whether the RLE codec must pad encoded
   *  data to an even number of bytes (as required by DICOM).
   *  True if padding is required, false otherwise
   */
  int pad_;

  /** this member points to a block of size DcmRLEEncoder_BLOCKSIZE
   *  (unless fail_ is true). This is the current block of data to
   *  which the RLE stream is written
   */
  unsigned char *currentBlock_;

  /** contains the number of bytes already written the the memory
   *  block pointed to by currentBlock_.  Value is always less than
   *  DcmRLEEncoder_BLOCKSIZE.
   */
  size_t offset_;

  /** this member contains a list of memory blocks of size DcmRLEEncoder_BLOCKSIZE
   *  which already have been filled with encoded RLE data.
   *  The current block (pointed to by currentBlock_) is not contained in this list.
   */
  OFList<unsigned char *> blockList_;

  /** this member points to a buffer of 132 bytes that is used by the RLE
   *  encoding algorithm.
   */
  unsigned char *RLE_buff_;

  /** value of the last byte fed to the RLE compressor.  This byte is not yet
   *  stored in the RLE_buff_ buffer.
   *  Type is int because this allows an "impossible" -1 as default value
   */
  int RLE_prev_;

  /** repeat counter, for RLE compressor
   *  may temporarily become negative, guaranteed to be >= 0 between method calls.
   */
  int RLE_pcount_;

  /** index of next unused byte in RLE_buff_.
   */
  unsigned int RLE_bindex_;

};

#endif
