/*
 *
 *  Copyright (C) 1996-2018, 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:  dcmimgle
 *
 *  Author:  Joerg Riesmeier
 *
 *  Purpose: DicomInputPixelTemplate (Header)
 *
 */


#ifndef DIINPXT_H
#define DIINPXT_H

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

#include "dcmtk/ofstd/ofbmanip.h"
#include "dcmtk/ofstd/ofcast.h"

#include "dcmtk/dcmimgle/diinpx.h"
#include "dcmtk/dcmimgle/didocu.h"
#include "dcmtk/dcmimgle/dipxrept.h"


/*--------------------*
 *  helper functions  *
 *--------------------*/

static inline Uint8 expandSign(const Uint8 Value,
                               const Uint8,
                               const Uint8)
{
    return Value;
}


static inline Uint16 expandSign(const Uint16 Value,
                                const Uint16,
                                const Uint16)
{
    return Value;
}


static inline Uint32 expandSign(const Uint32 Value,
                                const Uint32,
                                const Uint32)
{
    return Value;
}


static inline Sint8 expandSign(const Sint8 Value,
                               const Sint8 SignBit,
                               const Sint8 SignMask)
{
    return (Value & SignBit) ? (Value | SignMask) : Value;
}


static inline Sint16 expandSign(const Sint16 Value,
                                const Sint16 SignBit,
                                const Sint16 SignMask)
{
    return (Value & SignBit) ? (Value | SignMask) : Value;
}


static inline Sint32 expandSign(const Sint32 Value,
                                const Sint32 SignBit,
                                const Sint32 SignMask)
{
    return (Value & SignBit) ? (Value | SignMask) : Value;
}


static Uint32 getPixelData(DcmPixelData *PixelData,
                           Uint8 *&pixel)
{
    PixelData->getUint8Array(pixel);
    return PixelData->getLength();
}


static Uint32 getPixelData(DcmPixelData *PixelData,
                           Uint16 *&pixel)
{
    PixelData->getUint16Array(pixel);
    return PixelData->getLength();
}


/*---------------------*
 *  class declaration  *
 *---------------------*/

/** Template class to convert DICOM pixel stream to intermediate representation
 */
template<class T1, class T2>
class DiInputPixelTemplate
  : public DiInputPixel,
    public DiPixelRepresentationTemplate<T2>
{

 public:

    /** constructor
     *
     ** @param  document   pointer to DICOM image object
     *  @param  alloc      number of bits allocated for each pixel
     *  @param  stored     number of bits stored for each pixel
     *  @param  high       position of high bit within bits allocated
     *  @param  first      first frame to be processed
     *  @param  number     number of frames to be processed
     *  @param  fsize      number of pixels per frame (frame size)
     *  @param  fileCache  pointer to file cache object used for partial read
     *  @param  fragment   current pixel item fragment (for encapsulated pixel data)
     */
    DiInputPixelTemplate(const DiDocument *document,
                         const Uint16 alloc,
                         const Uint16 stored,
                         const Uint16 high,
                         const unsigned long first,
                         const unsigned long number,
                         const unsigned long fsize,
                         DcmFileCache *fileCache,
                         Uint32 &fragment)
      : DiInputPixel(stored, first, number, fsize),
        Data(NULL)
    {
        MinValue[0] = 0;
        MinValue[1] = 0;
        MaxValue[0] = 0;
        MaxValue[1] = 0;
        if (this->isSigned())
        {
            AbsMinimum = -OFstatic_cast(double, DicomImageClass::maxval(Bits - 1, 0));
            AbsMaximum = OFstatic_cast(double, DicomImageClass::maxval(Bits - 1));
        } else {
            AbsMinimum = 0;
            AbsMaximum = OFstatic_cast(double, DicomImageClass::maxval(Bits));
        }
        if ((document != NULL) && (document->getPixelData() != NULL))
            convert(document, alloc, stored, high, fileCache, fragment);
        if ((PixelCount == 0) || (PixelStart + PixelCount > Count))         // check for corrupt pixel length
        {
            PixelCount = Count - PixelStart;
            DCMIMGLE_DEBUG("setting number of pixels to be processed (PixelCount) to " << PixelCount);
        }
    }

    /** destructor
     */
    virtual ~DiInputPixelTemplate()
    {
#if defined(HAVE_STD__NOTHROW) && defined(HAVE_NOTHROW_DELETE)
        /* use a non-throwing delete (if available) */
        operator delete[] (Data, std::nothrow);
#else
        delete[] Data;
#endif
    }

    /** determine minimum and maximum pixel value
     *
     ** @return status, true if successful, false otherwise
     */
    int determineMinMax()
    {
        if (Data != NULL)
        {
            DCMIMGLE_DEBUG("determining minimum and maximum pixel values for input data");
            T2 *p = Data;
            unsigned long i;
            const double absrange = getAbsMaxRange();
            const unsigned long ocnt = (absrange <= 10000000.0) ? OFstatic_cast(unsigned long, absrange) : 0 /* no LUT */;
            Uint8 *lut = NULL;
            if ((sizeof(T2) <= 2) && (ocnt > 0) && (Count > 3 * ocnt)) // optimization criteria
            {
                lut = new Uint8[ocnt];
                if (lut != NULL)
                {
                    DCMIMGLE_DEBUG("using optimized routine with additional LUT");
                    OFBitmanipTemplate<Uint8>::zeroMem(lut, ocnt);
                    Uint8 *q = lut - OFstatic_cast(T2, getAbsMinimum());
                    for (i = Count; i != 0; --i)                       // fill lookup table
                        *(q + *(p++)) = 1;
                    q = lut;
                    for (i = 0; i < ocnt; ++i)                         // search for minimum
                    {
                        if (*(q++) != 0)
                        {
                            MinValue[0] = OFstatic_cast(T2, OFstatic_cast(double, i) + getAbsMinimum());
                            break;
                        }
                    }
                    q = lut + ocnt;
                    for (i = ocnt; i != 0; --i)                        // search for maximum
                    {
                        if (*(--q) != 0)
                        {
                            MaxValue[0] = OFstatic_cast(T2, OFstatic_cast(double, i - 1) + getAbsMinimum());
                            break;
                        }
                    }
                    if (Count >= PixelCount)                           // use global min/max value
                    {
                        MinValue[1] = MinValue[0];
                        MaxValue[1] = MaxValue[0];
                    } else {                                           // calculate min/max for selected range
                        OFBitmanipTemplate<Uint8>::zeroMem(lut, ocnt);
                        p = Data + PixelStart;
                        q = lut - OFstatic_cast(T2, getAbsMinimum());
                        for (i = PixelCount; i != 0; --i)                  // fill lookup table
                            *(q + *(p++)) = 1;
                        q = lut;
                        for (i = 0; i < ocnt; ++i)                         // search for minimum
                        {
                            if (*(q++) != 0)
                            {
                                MinValue[1] = OFstatic_cast(T2, OFstatic_cast(double, i) + getAbsMinimum());
                                break;
                            }
                        }
                        q = lut + ocnt;
                        for (i = ocnt; i != 0; --i)                         // search for maximum
                        {
                            if (*(--q) != 0)
                            {
                                MaxValue[1] = OFstatic_cast(T2, OFstatic_cast(double, i - 1) + getAbsMinimum());
                                break;
                            }
                        }
                    }
                }
            }
            if (lut == NULL)                                           // use conventional method
            {
                T2 value = *p;
                MinValue[0] = value;
                MaxValue[0] = value;
                for (i = Count; i > 1; --i)
                {
                    value = *(++p);
                    if (value < MinValue[0])
                        MinValue[0] = value;
                    else if (value > MaxValue[0])
                        MaxValue[0] = value;
                }
                if (Count <= PixelCount)                               // use global min/max value
                {
                    MinValue[1] = MinValue[0];
                    MaxValue[1] = MaxValue[0];
                } else {                                               // calculate min/max for selected range
                    p = Data + PixelStart;
                    value = *p;
                    MinValue[1] = value;
                    MaxValue[1] = value;
                    for (i = PixelCount; i > 1; --i)
                    {
                        value = *(++p);
                        if (value < MinValue[1])
                            MinValue[1] = value;
                        else if (value > MaxValue[1])
                            MaxValue[1] = value;
                    }
                }
            }
            delete[] lut;
            return 1;
        }
        return 0;
    }

    /** get pixel representation
     *
     ** @return pixel representation
     */
    inline EP_Representation getRepresentation() const
    {
        return DiPixelRepresentationTemplate<T2>::getRepresentation();
    }

    /** get pointer to input pixel data
     *
     ** @return pointer to input pixel data
     */
    inline const void *getData() const
    {
        return OFstatic_cast(const void *, Data);
    }

    /** get reference to pointer to input pixel data
     *
     ** @return reference to pointer to input pixel data
     */
    virtual void *getDataPtr()
    {
        return OFstatic_cast(void *, Data);
    }

    /** remove reference to (internally handled) pixel data
     */
    inline void removeDataReference()
    {
        Data = NULL;
    }

    /** get minimum pixel value
     *
     ** @param  idx  specifies whether to return the global minimum (0) or
     *               the minimum of the selected pixel range (1, see PixelStart/Range)
     *
     ** @return minimum pixel value
     */
    inline double getMinValue(const int idx) const
    {
        return (idx == 0) ? OFstatic_cast(double, MinValue[0]) : OFstatic_cast(double, MinValue[1]);
    }

    /** get maximum pixel value
     *
     ** @param  idx  specifies whether to return the global maximum (0) or
     *               the maximum of the selected pixel range (1, see PixelStart/Range)
     *
     ** @return maximum pixel value
     */
    inline double getMaxValue(const int idx) const
    {
        return (idx == 0) ? OFstatic_cast(double, MaxValue[0]) : OFstatic_cast(double, MaxValue[1]);
    }


 private:

    /** convert pixel data from DICOM dataset to input representation
     *
     ** @param  document       pointer to DICOM image object
     *  @param  bitsAllocated  number of bits allocated for each pixel
     *  @param  bitsStored     number of bits stored for each pixel
     *  @param  highBit        position of high bit within bits allocated
     *  @param  fileCache      pointer to file cache object used for partial read
     *  @param  fragment       current pixel item fragment (for encapsulated pixel data)
     */
    void convert(const DiDocument *document,
                 const Uint16 bitsAllocated,
                 const Uint16 bitsStored,
                 const Uint16 highBit,
                 DcmFileCache *fileCache,
                 Uint32 &fragment)
    {
        T1 *pixel = NULL;
        OFBool deletePixel = OFFalse;
        Uint32 lengthBytes = 0;
        DcmPixelData *pixelData = document->getPixelData();
        const Uint16 bitsof_T1 = bitsof(T1);
        const Uint16 bitsof_T2 = bitsof(T2);
        const OFBool uncompressed = pixelData->canWriteXfer(EXS_LittleEndianExplicit, EXS_Unknown);
        /* check whether to use partial read */
        if ((document->getFlags() & CIF_UsePartialAccessToPixelData) && (PixelCount > 0) && (bitsAllocated % 8 == 0))
        {
            /* Bits Allocated is always a multiple of 8 (see above), same for bits of T1 */
            const Uint32 byteFactor = bitsAllocated / 8;
            const Uint32 bytes_T1 = bitsof_T1 / 8;
            const Uint32 count_T1 = (byteFactor == bytes_T1) ? PixelCount : (PixelCount * byteFactor + bytes_T1 - 1) / bytes_T1;
#ifdef DEBUG
            DCMIMGLE_TRACE("PixelCount: " << PixelCount << ", byteFactor: " << byteFactor << ", bytes_T1: " << bytes_T1 << ", count_T1: " << count_T1);
#endif
            /* allocate temporary buffer, even number of bytes required for getUncompressedFrame() */
            const Uint32 extraByte = ((sizeof(T1) == 1) && (count_T1 & 1)) ? 1 : 0;
#ifdef HAVE_STD__NOTHROW
            /* use a non-throwing new here (if available) because the allocated buffer can be huge */
            pixel = new (std::nothrow) T1[count_T1 + extraByte];
#else
            /* make sure that the pointer is set to NULL in case of error */
            try
            {
                pixel = new T1[count_T1 + extraByte];
            }
            catch (STD_NAMESPACE bad_alloc const &)
            {
                pixel = NULL;
            }
#endif
            if (pixel != NULL)
            {
                if (uncompressed)
                {
                    DCMIMGLE_DEBUG("using partial read access to uncompressed pixel data");
                    const Uint32 offset = PixelStart * byteFactor;
                    const Uint32 bufSize = PixelCount * byteFactor;
                    const OFCondition status = pixelData->getPartialValue(pixel, offset, bufSize, fileCache);
                    if (status.good())
                    {
                        PixelStart = 0;
                        lengthBytes = bufSize;
                    } else {
                        DCMIMGLE_ERROR("can't access partial value from byte offset " << offset << " to "
                            << (offset + bufSize - 1) << ": " << status.text());
                    }
                } else {
                    DCMIMGLE_DEBUG("using partial read access to compressed pixel data");
                    OFCondition status = EC_IllegalCall;
                    OFString decompressedColorModel;
                    const Uint32 fsize = FrameSize * byteFactor;
                    for (Uint32 frame = 0; frame < NumberOfFrames; ++frame)
                    {
                        /* make sure that the buffer always has an even number of bytes as required for getUncompressedFrame() */
                        const Uint32 bufSize = (fsize & 1) ? fsize + 1 : fsize;
                        status = pixelData->getUncompressedFrame(document->getDataset(), FirstFrame + frame, fragment,
                            OFreinterpret_cast(Uint8 *, pixel) + lengthBytes, bufSize, decompressedColorModel, fileCache);
                        if (status.good())
                        {
                            DCMIMGLE_TRACE("successfully decompressed frame " << FirstFrame + frame);
                            lengthBytes += fsize;
                        } else {
                            DCMIMGLE_ERROR("can't decompress frame " << FirstFrame + frame << ": " << status.text());
                            break;
                        }
                    }
                    if (status.good())
                        PixelStart = 0;
                    /* check whether color model changed during decompression */
                    if (!decompressedColorModel.empty() && (decompressedColorModel != document->getPhotometricInterpretation()))
                    {
                        DCMIMGLE_WARN("Photometric Interpretation of decompressed pixel data deviates from original image: "
                            << decompressedColorModel);
                    }
                }
                deletePixel = OFTrue;
            } else
                DCMIMGLE_DEBUG("cannot allocate memory buffer for 'pixel' in DiInputPixelTemplate::convert()");
        } else {
            DCMIMGLE_DEBUG("reading uncompressed pixel data completely into memory");
            /* always access complete pixel data */
            lengthBytes = getPixelData(pixelData, pixel);
        }
        if ((pixel != NULL) && (lengthBytes > 0))
        {
            const Uint32 length_T1 = lengthBytes / sizeof(T1);
            /* need to split 'length' in order to avoid integer overflow for large pixel data */
            const Uint32 length_B1 = lengthBytes / bitsAllocated;
            const Uint32 length_B2 = lengthBytes % bitsAllocated;
//          # old code: Count = ((lengthBytes * 8) + bitsAllocated - 1) / bitsAllocated;
            Count = 8 * length_B1 + (8 * length_B2 + bitsAllocated - 1) / bitsAllocated;
            unsigned long i;
#ifdef HAVE_STD__NOTHROW
            /* use a non-throwing new here (if available) because the allocated buffer can be huge */
            Data = new (std::nothrow) T2[Count];
#else
            /* make sure that the pointer is set to NULL in case of error */
            try
            {
                Data = new T2[Count];
            }
            catch (STD_NAMESPACE bad_alloc const &)
            {
                Data = NULL;
            }
#endif
            if (Data != NULL)
            {
                DCMIMGLE_TRACE("Input length: " << lengthBytes << " bytes, Pixel count: " << Count
                    << " (" << PixelCount << "), In: " << bitsof_T1 << " bits, Out: " << bitsof_T2
                    << " bits (" << (this->isSigned() ? "signed" : "unsigned") << ")");
                const T1 *p = pixel;
                T2 *q = Data;
                if (bitsof_T1 == bitsAllocated)                                             // case 1: equal 8/16 bit
                {
                    if (bitsStored == bitsAllocated)
                    {
                        DCMIMGLE_DEBUG("convert input pixel data: case 1a (single copy)");
                        for (i = Count; i != 0; --i)
                            *(q++) = OFstatic_cast(T2, *(p++));
                    }
                    else /* bitsStored < bitsAllocated */
                    {
                        T1 mask = 0;
                        for (i = 0; i < bitsStored; ++i)
                            mask |= OFstatic_cast(T1, 1 << i);
                        const T2 sign = 1 << (bitsStored - 1);
                        T2 smask = 0;
                        for (i = bitsStored; i < bitsof_T2; ++i)
                            smask |= OFstatic_cast(T2, 1 << i);
                        const Uint16 shift = highBit + 1 - bitsStored;
                        if (shift == 0)
                        {
                            DCMIMGLE_DEBUG("convert input pixel data: case 1b (mask & sign)");
                            for (i = length_T1; i != 0; --i)
                                *(q++) = expandSign(OFstatic_cast(T2, *(p++) & mask), sign, smask);
                        }
                        else /* shift > 0 */
                        {
                            DCMIMGLE_DEBUG("convert input pixel data: case 1c (shift & mask & sign)");
                            for (i = length_T1; i != 0; --i)
                                *(q++) = expandSign(OFstatic_cast(T2, (*(p++) >> shift) & mask), sign, smask);
                        }
                    }
                }
                else if ((bitsof_T1 > bitsAllocated) && (bitsof_T1 % bitsAllocated == 0))   // case 2: divisor of 8/16 bit
                {
                    const Uint16 times = bitsof_T1 / bitsAllocated;
                    T1 mask = 0;
                    for (i = 0; i < bitsStored; ++i)
                        mask |= OFstatic_cast(T1, 1 << i);
                    Uint16 j;
                    T1 value;
                    if ((bitsStored == bitsAllocated) && (bitsStored == bitsof_T2))
                    {
                        if (times == 2)
                        {
                            DCMIMGLE_DEBUG("convert input pixel data: case 2a (simple mask)");
                            for (i = length_T1; i != 0; --i, ++p)
                            {
                                *(q++) = OFstatic_cast(T2, *p & mask);
                                *(q++) = OFstatic_cast(T2, *p >> bitsAllocated);
                            }
                            /* check for additional input pixel (in case of odd length when using partial access) */
                            if (length_T1 * 2 /* times */ < lengthBytes)
                            {
                                DCMIMGLE_TRACE("found trailing pixel at the end of odd-length input data ... copying value");
                                *(q++) = OFstatic_cast(T2, *p & mask);
                            }
                        }
                        else
                        {
                            DCMIMGLE_DEBUG("convert input pixel data: case 2b (mask)");
                            for (i = length_T1; i != 0; --i)
                            {
                                value = *(p++);
                                for (j = times; j != 0; --j)
                                {
                                    *(q++) = OFstatic_cast(T2, value & mask);
                                    value >>= bitsAllocated;
                                }
                            }
                        }
                    }
                    else
                    {
                        DCMIMGLE_DEBUG("convert input pixel data: case 2c (shift & mask & sign)");
                        const T2 sign = 1 << (bitsStored - 1);
                        T2 smask = 0;
                        for (i = bitsStored; i < bitsof_T2; ++i)
                            smask |= OFstatic_cast(T2, 1 << i);
                        const Uint16 shift = highBit + 1 - bitsStored;
                        for (i = length_T1; i != 0; --i)
                        {
                            value = *(p++) >> shift;
                            for (j = times; j != 0; --j)
                            {
                                *(q++) = expandSign(OFstatic_cast(T2, value & mask), sign, smask);
                                value >>= bitsAllocated;
                            }
                        }
                    }
                }
                else if ((bitsof_T1 < bitsAllocated) && (bitsAllocated % bitsof_T1 == 0)    // case 3: multiplicand of 8/16
                    && (bitsStored == bitsAllocated))
                {
                    DCMIMGLE_DEBUG("convert input pixel data: case 3 (multi copy)");
                    const Uint16 times = bitsAllocated / bitsof_T1;
                    Uint16 j;
                    Uint16 shift;
                    T2 value;
                    for (i = length_T1; i != 0; --i)
                    {
                        shift = 0;
                        value = OFstatic_cast(T2, *(p++));
                        for (j = times; j > 1; --j, --i)
                        {
                            shift += bitsof_T1;
                            value |= OFstatic_cast(T2, *(p++)) << shift;
                        }
                        *(q++) = value;
                    }
                }
                else                                                                        // case 4: anything else
                {
                    DCMIMGLE_DEBUG("convert input pixel data: case 4 (general)");
                    T2 value = 0;
                    Uint16 bits = 0;
                    Uint32 skip = highBit + 1 - bitsStored;
                    Uint32 times;
                    T1 mask[bitsof_T1];
                    mask[0] = 1;
                    for (i = 1; i < bitsof_T1; ++i)
                        mask[i] = (mask[i - 1] << 1) | 1;
                    T2 smask = 0;
                    for (i = bitsStored; i < bitsof_T2; ++i)
                        smask |= OFstatic_cast(T2, 1 << i);
                    const T2 sign = 1 << (bitsStored - 1);
                    const Uint32 gap = bitsAllocated - bitsStored;
                    i = 0;
                    while (i < length_T1)
                    {
                        if (skip < bitsof_T1)
                        {
                            if (skip + bitsStored - bits < bitsof_T1)       // -++- --++
                            {
                                value |= (OFstatic_cast(T2, (*p >> skip) & mask[bitsStored - bits - 1]) << bits);
                                skip += bitsStored - bits + gap;
                                bits = bitsStored;
                            }
                            else                                            // ++-- ++++
                            {
                                value |= (OFstatic_cast(T2, (*p >> skip) & mask[bitsof_T1 - skip - 1]) << bits);
                                bits += bitsof_T1 - OFstatic_cast(Uint16, skip);
                                skip = (bits == bitsStored) ? gap : 0;
                                ++i;
                                ++p;
                            }
                            if (bits == bitsStored)
                            {
                                *(q++) = expandSign(value, sign, smask);
                                value = 0;
                                bits = 0;
                            }
                        }
                        else
                        {
                            times = skip / bitsof_T1;
                            i += times;
                            p += times;
                            skip -= times * bitsof_T1;
                        }
                    }
                }
            } else
                DCMIMGLE_DEBUG("cannot allocate memory buffer for 'Data' in DiInputPixelTemplate::convert()");
        } else {
            /* in case of error, reset pixel count variable */
            Count = 0;
        }
        if (deletePixel)
        {
            /* delete temporary buffer */
#if defined(HAVE_STD__NOTHROW) && defined(HAVE_NOTHROW_DELETE)
            /* use a non-throwing delete (if available) */
            operator delete[] (pixel, std::nothrow);
#else
            delete[] pixel;
#endif
        }
    }

    /// pointer to pixel data
    T2 *Data;

    /// minimum pixel value ([0] = global, [1] = selected pixel range)
    T2 MinValue[2];
    /// maximum pixel value ([0] = global, [1] = selected pixel range)
    T2 MaxValue[2];

 // --- declarations to avoid compiler warnings

    DiInputPixelTemplate(const DiInputPixelTemplate<T1,T2> &);
    DiInputPixelTemplate<T1,T2> &operator=(const DiInputPixelTemplate<T1,T2> &);
};


#endif
