/*
 *
 *  Copyright (C) 1996-2019, 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: DicomOverlay (Source)
 *
 */


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

#include "dcmtk/dcmimgle/diovlay.h"
#include "dcmtk/dcmimgle/diovdat.h"
#include "dcmtk/dcmimgle/diovpln.h"
#include "dcmtk/dcmimgle/discalet.h"
#include "dcmtk/dcmimgle/diflipt.h"
#include "dcmtk/dcmimgle/dirotat.h"
#include "dcmtk/dcmimgle/didocu.h"


/*----------------------------*
 *  constant initializations  *
 *----------------------------*/

const unsigned int DiOverlay::MaxOverlayCount   = 16;
const unsigned int DiOverlay::FirstOverlayGroup = 0x6000;


/*----------------*
 *  constructors  *
 *----------------*/

DiOverlay::DiOverlay(const DiDocument *docu,
                     const Uint16 alloc,
                     const Uint16 stored,
                     const Uint16 high)
  : Left(0),
    Top(0),
    Width(0),
    Height(0),
    Frames(0),
    AdditionalPlanes(docu == NULL),                                                    // planes are added later
    Data(NULL)
{
    Data = new DiOverlayData(MaxOverlayCount);                                         // can't determine number of overlays :-(
    if ((docu != NULL) && (Data != NULL) && (Data->Planes != NULL))
    {
        unsigned int i;
        for (i = 0; i < MaxOverlayCount; ++i)
        {
            Data->Planes[Data->Count] = new DiOverlayPlane(docu, convertToGroupNumber(i), alloc, stored, high);
            if (Data->Planes[Data->Count] != NULL)
            {
                if (checkPlane(Data->Count))
                    ++(Data->Count);
                else {
                    delete Data->Planes[Data->Count];
                    Data->Planes[Data->Count] = NULL;
                }
            }
        }
    }
}


// --- scale/clip overlay

DiOverlay::DiOverlay(const DiOverlay *overlay,
                     const signed long left_pos,
                     const signed long top_pos,
                     const double xfactor,
                     const double yfactor)
  : Left(left_pos),
    Top(top_pos),
    Width(OFstatic_cast(Uint16, xfactor * overlay->Width)),
    Height(OFstatic_cast(Uint16, yfactor * overlay->Height)),
    Frames(overlay->Frames),
    AdditionalPlanes(overlay->AdditionalPlanes),
    Data(NULL)
{
    Uint16 *temp = Init(overlay);
    if (temp != NULL)
    {
        unsigned int i;
        for (i = 0; i < Data->ArrayEntries; ++i)
        {
            if (Data->Planes[i] != NULL)
                Data->Planes[i]->setScaling(xfactor, yfactor);
        }
        DiScaleTemplate<Uint16> scale(1, overlay->Width, overlay->Height, Width, Height, Frames);
        scale.scaleData(OFconst_cast(const Uint16 **, &temp), &(Data->DataBuffer), 0);
        if (temp != overlay->Data->DataBuffer)
            delete[] temp;
    }
}


// --- flip overlay

DiOverlay::DiOverlay(const DiOverlay *overlay,
                     const int horz,
                     const int vert,
                     const Uint16 columns,              // width of surrounding image
                     const Uint16 rows)
  : Left((horz) ? 0 : overlay->Left),
    Top((vert) ? 0 : overlay->Top),
    Width(overlay->Width),
    Height(overlay->Height),
    Frames(overlay->Frames),
    AdditionalPlanes(overlay->AdditionalPlanes),
    Data(NULL)
{
    Uint16 *temp = Init(overlay);
    if (temp != NULL)
    {
        DiFlipTemplate<Uint16> flip(1, Width, Height, Frames);
        flip.flipData(OFconst_cast(const Uint16 **, &temp), &(Data->DataBuffer), horz, vert);
        if (temp != overlay->Data->DataBuffer)
            delete[] temp;
        unsigned int i;
        for (i = 0; i < Data->ArrayEntries; ++i)
        {
            if (Data->Planes[i] != NULL)
            {
                Data->Planes[i]->setFlipping(horz, vert, OFstatic_cast(signed long, columns) + overlay->Left,
                    OFstatic_cast(signed long, rows) + overlay->Top);
            }
        }
    }
}


// --- rotate overlay

DiOverlay::DiOverlay(const DiOverlay *overlay,
                     const int degree,
                     const Uint16 columns,              // width of surrounding image (already rotated)
                     const Uint16 rows)
  : Left(0),
    Top(0),
    Width(((degree == 90) || (degree == 270)) ? overlay->Height : overlay->Width),
    Height(((degree == 90) || (degree == 270)) ? overlay->Width : overlay->Height),
    Frames(overlay->Frames),
    AdditionalPlanes(overlay->AdditionalPlanes),
    Data(NULL)
{
    Uint16 *temp = Init(overlay);
    if (temp != NULL)
    {
        DiRotateTemplate<Uint16> rotate(1, overlay->Width, overlay->Height, Width, Height, Frames);
        rotate.rotateData(OFconst_cast(const Uint16 **, &temp), &(Data->DataBuffer), degree);
        if (temp != overlay->Data->DataBuffer)
            delete[] temp;
        unsigned int i;
        for (i = 0; i < Data->ArrayEntries; ++i)
        {
            if (Data->Planes[i] != NULL)
                Data->Planes[i]->setRotation(degree, overlay->Left, overlay->Top, columns, rows);
        }
    }
}


/*--------------*
 *  destructor  *
 *--------------*/

DiOverlay::~DiOverlay()
{
    if (Data != NULL)
        Data->removeReference();
}


/********************************************************************/


Uint16 *DiOverlay::Init(const DiOverlay *overlay)
{
    if ((overlay != NULL) && (overlay->Data != NULL) && (overlay->Data->Count > 0))
    {
        if (AdditionalPlanes)
            Data = new DiOverlayData(overlay->Data->ArrayEntries);      // use same array size
        else
            Data = new DiOverlayData(overlay->Data->Count);             // shrink array size to minimal size
        const unsigned long count = OFstatic_cast(unsigned long, overlay->Width) *
            OFstatic_cast(unsigned long, overlay->Height) * overlay->Frames;
        if ((Data != NULL) && (Data->Planes != NULL) && (count > 0))
        {
            const unsigned long bufSize = OFstatic_cast(unsigned long, Width) *
                OFstatic_cast(unsigned long, Height) * Frames;
            if (bufSize > 0)                                            // avoid invalid buffer
            {
                Data->DataBuffer = new Uint16[bufSize];
                if (Data->DataBuffer != NULL)
                {
                    unsigned int i;
                    Uint16 *temp = NULL;
                    if (overlay->Data->DataBuffer == NULL)              // no data buffer
                    {
                        temp = new Uint16[count];                       // create temporary buffer
                        if (temp != NULL)
                            OFBitmanipTemplate<Uint16>::zeroMem(temp, count);
                    }
                    for (i = 0; i < Data->ArrayEntries; ++i)
                    {
                        if ((overlay->Data->Planes[i] != NULL) /*&& (overlay->Data->Planes[i]->isValid())*/)
                        {
                            Data->Planes[i] = new DiOverlayPlane(overlay->Data->Planes[i], i, Data->DataBuffer, temp,
                                overlay->Width, overlay->Height, Width, Height);
                            ++(Data->Count);                            // increase number of valid planes
                        }
                    }
                    if (Data->Count != overlay->Data->Count)            // assertion!
                    {
                        DCMIMGLE_WARN("different number of overlay planes for converted and original image");
                    }
                    if (overlay->Data->DataBuffer != NULL)              // existing data buffer
                        temp = overlay->Data->DataBuffer;               // point to input buffer
                    return temp;
                }
            } else {
                DCMIMGLE_DEBUG("skipping overlay planes for converted image ... calculated buffer size is 0");
            }
        }
    }
    return NULL;
}


int DiOverlay::convertToPlaneNumber(unsigned int &plane,
                                    const int mode) const
{
    if ((Data != NULL) && (Data->Planes != NULL))
    {
        if (isValidGroupNumber(plane))
        {
            if (AdditionalPlanes)
            {
                plane = (plane - FirstOverlayGroup) >> 1;                               // plane = (group - 0x6000) / 2
                if (Data->Planes[plane] != NULL)
                    return 2;                                                           // plane already exists
                return 1;                                                               // ... is new
            } else {
                unsigned int i;
                for (i = 0; i < Data->Count; ++i)
                {
                    if ((Data->Planes[i] != NULL) && (Data->Planes[i]->getGroupNumber() == plane))
                    {
                        plane = i;                                                      // plane number
                        return 2;
                    }
                }
            }
        } else if (!mode && (plane < Data->Count) && (Data->Planes[plane] != NULL))     // valid plane number?
            return 3;
    }
    return 0;
}


int DiOverlay::isValidGroupNumber(const unsigned int group) const
{
    return (group >= convertToGroupNumber(0)) && (group <= convertToGroupNumber(MaxOverlayCount - 1)) && !(group & 1);
}


int DiOverlay::checkPlane(const unsigned int plane,
                          const int mode)
{
    if ((Data != NULL) && (Data->Planes != NULL) && (plane < MaxOverlayCount) && (Data->Planes[plane] != NULL))
    {
        if (Data->Planes[plane]->isValid())
        {
            if (Data->Planes[plane]->getWidth() > Width)                              // determine maximum width
                Width = Data->Planes[plane]->getWidth();
            if (Data->Planes[plane]->getHeight() > Height)                            // determine maximum height
                Height = Data->Planes[plane]->getHeight();
            if (mode && (Data->Planes[plane]->getNumberOfFrames() > Frames))          // determine maximum frames
                Frames = Data->Planes[plane]->getNumberOfFrames();
            return 1;
        }
    }
    return 0;
}


int DiOverlay::isPlaneVisible(unsigned int plane)
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
        return Data->Planes[plane]->isVisible();
    return 0;
}


int DiOverlay::showPlane(unsigned int plane)
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
    {
        if (Data->Planes[plane]->isVisible())
            return 2;
        Data->Planes[plane]->show();
        return 1;
    }
    return 0;
}


int DiOverlay::showPlane(unsigned int plane,
                         const double fore,
                         const double tresh,
                         const EM_Overlay mode)
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
    {
        Data->Planes[plane]->show(fore, tresh, mode);
        return 1;
    }
    return 0;
}


int DiOverlay::showPlane(unsigned int plane,
                         const Uint16 pvalue)
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
        return Data->Planes[plane]->show(pvalue);
    return 0;
}


int DiOverlay::showAllPlanes()
{
    if ((Data != NULL) && (Data->Planes != NULL))
    {
        unsigned int i;
        for (i = 0; i < Data->ArrayEntries; ++i)
        {
            if (Data->Planes[i] != NULL)
                Data->Planes[i]->show();
        }
        if (Data->Count > 0)
            return 1;
        return 2;
    }
    return 0;
}


int DiOverlay::showAllPlanes(const double fore,
                             const double tresh,
                             const EM_Overlay mode)
{
    if ((Data != NULL) && (Data->Planes != NULL))
    {
        unsigned int i;
        for (i = 0; i < Data->ArrayEntries; ++i)
        {
            if ((Data->Planes[i] != NULL))
                Data->Planes[i]->show(fore, tresh, mode);
        }
        if (Data->Count > 0)
            return 1;
        return 2;
    }
    return 0;
}


int DiOverlay::hidePlane(unsigned int plane)
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
    {
        if (!Data->Planes[plane]->isVisible())
            return 2;
        Data->Planes[plane]->hide();
        return 1;
    }
    return 0;
}


int DiOverlay::hideAllPlanes()
{
    if ((Data != NULL) && (Data->Planes != NULL))
    {
        unsigned int i;
        for (i = 0; i < Data->ArrayEntries; ++i)
        {
            if (Data->Planes[i] != NULL)
                Data->Planes[i]->hide();
        }
        if (Data->Count > 0)
            return 1;
        return 2;
    }
    return 0;
}


int DiOverlay::placePlane(unsigned int plane,
                          const signed int left_pos,
                          const signed int top_pos)
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
    {
        if ((Data->Planes[plane]->getLeft() == left_pos) && (Data->Planes[plane]->getTop() == top_pos))
            return 2;
        Data->Planes[plane]->place(left_pos, top_pos);
        return 1;
    }
    return 0;
}


unsigned int DiOverlay::getPlaneGroupNumber(unsigned int plane) const
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
        return Data->Planes[plane]->getGroupNumber();
    return 0;
}


const char *DiOverlay::getPlaneLabel(unsigned int plane) const
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
        return Data->Planes[plane]->getLabel();
    return NULL;
}


const char *DiOverlay::getPlaneDescription(unsigned int plane) const
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
        return Data->Planes[plane]->getDescription();
    return NULL;
}


EM_Overlay DiOverlay::getPlaneMode(unsigned int plane) const
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)
        return Data->Planes[plane]->getMode();
    return EMO_Default;
}


int DiOverlay::hasEmbeddedData() const
{
    if ((Data != NULL) && (Data->Planes != NULL))
    {
        unsigned int i;
        for (i = 0; i < Data->ArrayEntries; ++i)
        {
            if ((Data->Planes[i] != NULL) && (Data->Planes[i]->isEmbedded()))
                return 1;
        }
    }
    return 0;
}


int DiOverlay::addPlane(const unsigned int group,
                        const signed int left_pos,
                        const signed int top_pos,
                        const unsigned int columns,
                        const unsigned int rows,
                        const DcmOverlayData &data,
                        const DcmLongString &label,
                        const DcmLongString &description,
                        const EM_Overlay mode)
{
    int status = 0;
    if (AdditionalPlanes && isValidGroupNumber(group))
    {
        unsigned int plane = group;
        status = convertToPlaneNumber(plane, AdditionalPlanes);
        if ((status != 0) && (plane < Data->ArrayEntries))
        {
            if (status == 1)                                                   // add new plane
                ++(Data->Count);
            else if (status == 2)                                              // group number already exists
                delete Data->Planes[plane];
            Data->Planes[plane] = new DiOverlayPlane(group, left_pos, top_pos, columns, rows, data, label, description, mode);
            if (checkPlane(plane, 0))
            {
                if (Data->Planes[plane]->getNumberOfFrames() > Frames)         // set maximum number of frames
                    Frames = Data->Planes[plane]->getNumberOfFrames();
            } else {
                delete Data->Planes[plane];                                    // remove invalid plane
                Data->Planes[plane] = NULL;
                if (status == 1)
                    --(Data->Count);                                           // decrease number of planes
                status = 0;
            }
        }
    }
    return status;
}


int DiOverlay::removePlane(const unsigned int group)
{
    unsigned int plane = group;
    if (AdditionalPlanes && (convertToPlaneNumber(plane, AdditionalPlanes) > 1))
    {
        delete Data->Planes[plane];                                           // remove invalid plane
        Data->Planes[plane] = NULL;
        --(Data->Count);                                                      // decrease number of planes
        return 1;
    }
    return 0;
}


void *DiOverlay::getPlaneData(const unsigned long frame,
                              unsigned int plane,
                              unsigned int &left_pos,
                              unsigned int &top_pos,
                              unsigned int &width,
                              unsigned int &height,
                              EM_Overlay &mode,
                              const Uint16 columns,
                              const Uint16 rows,
                              const int bits,
                              const Uint16 fore,
                              const Uint16 back)
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)                    // plane does exist
    {
        DiOverlayPlane *op = Data->Planes[plane];
        if ((op != NULL) && op->isValid())
        {
            const Uint16 xmin = (op->getLeft(Left) > 0) ? op->getLeft(Left) : 0;
            const Uint16 ymin = (op->getTop(Top) > 0) ? op->getTop(Top) : 0;
            const Uint16 xmax = (op->getRight(Left) < columns) ? op->getRight(Left) : columns;
            const Uint16 ymax = (op->getBottom(Top) < rows) ? op->getBottom(Top) : rows;
            left_pos = xmin;
            top_pos = ymin;
            width = xmax - xmin;
            height = ymax - ymin;
            mode = op->getMode();
            return op->getData(frame, xmin, ymin, xmax, ymax, bits, fore, back);
        }
    }
    return NULL;
}


void *DiOverlay::getFullPlaneData(const unsigned long frame,
                                  unsigned int plane,
                                  unsigned int &width,
                                  unsigned int &height,
                                  const int bits,
                                  const Uint16 fore,
                                  const Uint16 back)
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)                    // plane does exist
    {
        DiOverlayPlane *op = Data->Planes[plane];
        if ((op != NULL) && op->isValid())
        {
            width = op->getWidth();
            height = op->getHeight();
            return op->getData(frame, 0, 0, width, height, bits, fore, back, OFFalse /*useOrigin*/);
        }
    }
    return NULL;
}


unsigned long DiOverlay::create6xxx3000PlaneData(Uint8 *&buffer,
                                                 unsigned int plane,
                                                 unsigned int &width,
                                                 unsigned int &height,
                                                 unsigned long &frames)
{
    if (convertToPlaneNumber(plane, AdditionalPlanes) > 1)                    // plane does exist
    {
        DiOverlayPlane *op = Data->Planes[plane];
        if ((op != NULL) && op->isValid())
            return op->create6xxx3000Data(buffer, width, height, frames);
    }
    return 0;
}
