/*
 *
 *  Copyright (C) 1998-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: dcmpstat
 *
 *  Author: Marco Eichelberg
 *
 *  Purpose:
 *    classes: DVPSCurve
 *
 */

#include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
#include "dcmtk/dcmpstat/dvpscu.h"
#include "dcmtk/dcmdata/dctk.h"
#include "dcmtk/ofstd/ofbmanip.h"

DVPSCurve::DVPSCurve()
: curveGroup(0)
, numberOfPoints(0)
, typeOfData(DVPSL_polylineCurve)
, curveData(NULL)
, curveDescription()
, curveLabel()
, axisUnitsX()
, axisUnitsY()
{
}

DVPSCurve::DVPSCurve(const DVPSCurve& copy)
: curveGroup(copy.curveGroup)
, numberOfPoints(copy.numberOfPoints)
, typeOfData(copy.typeOfData)
, curveData(NULL)
, curveDescription(copy.curveDescription)
, curveLabel(copy.curveLabel)
, axisUnitsX(copy.axisUnitsX)
, axisUnitsY(copy.axisUnitsY)
{
  if (copy.curveData)
  {
    curveData = new double[2*numberOfPoints];
    if (curveData)
    {
      OFBitmanipTemplate<double>::copyMem(copy.curveData, curveData, 2*numberOfPoints);
    }
  }
}

DVPSCurve::~DVPSCurve()
{
  if (curveData) delete[] curveData;
}


OFCondition DVPSCurve::read(DcmItem &dset, Uint8 group)
{
  OFCondition result = EC_Normal;
  DcmStack stack;

  curveGroup = group;
  DcmElement *d_curveDimensions = NULL;
  DcmElement *d_numberOfPoints = NULL;
  DcmElement *d_typeOfData = NULL;
  DcmElement *d_dataVR = NULL;
  DcmElement *d_curveData = NULL;
  DcmElement *d_curveDescription = NULL;
  DcmElement *d_axisUnits = NULL;
  DcmElement *d_curveLabel = NULL;

  /* first we look for the Curve Data */
  DcmTagKey key(0x5000 + group,0x3000);
  if (EC_Normal == dset.search(key, stack, ESM_fromHere, OFFalse) && stack.top()->isElement())
  {
    d_curveData = (DcmElement *)(stack.top());
  } else return EC_IllegalCall;

  key.setElement(0x0005); // Curve Dimensions
  stack.clear();
  if (EC_Normal == dset.search(key, stack, ESM_fromHere, OFFalse) && stack.top()->isElement())
  {
    d_curveDimensions = (DcmElement *)(stack.top());
  } else return EC_IllegalCall;

  key.setElement(0x0110); // Curve Data Descriptor
  stack.clear();
  if (EC_Normal == dset.search(key, stack, ESM_fromHere, OFFalse))
  {
    return EC_IllegalCall; // we don't support curves with Curve Data Descriptor!
  }

  key.setElement(0x0010); // Number of Points
  stack.clear();
  if (EC_Normal == dset.search(key, stack, ESM_fromHere, OFFalse) && stack.top()->isElement())
  {
    d_numberOfPoints = (DcmElement *)(stack.top());
  } else return EC_IllegalCall;

  key.setElement(0x0020); // Type of Data
  stack.clear();
  if (EC_Normal == dset.search(key, stack, ESM_fromHere, OFFalse) && stack.top()->isElement())
  {
    d_typeOfData = (DcmElement *)(stack.top());
  } else return EC_IllegalCall;

  key.setElement(0x0103); // Data Value Representation
  stack.clear();
  if (EC_Normal == dset.search(key, stack, ESM_fromHere, OFFalse) && stack.top()->isElement())
  {
    d_dataVR = (DcmElement *)(stack.top());
  } else return EC_IllegalCall;

  key.setElement(0x0022); // Curve Description
  stack.clear();
  if (EC_Normal == dset.search(key, stack, ESM_fromHere, OFFalse) && stack.top()->isElement())
  {
    d_curveDescription = (DcmElement *)(stack.top());
  }

  key.setElement(0x0030); // Axis Units
  stack.clear();
  if (EC_Normal == dset.search(key, stack, ESM_fromHere, OFFalse) && stack.top()->isElement())
  {
    d_axisUnits = (DcmElement *)(stack.top());
  }

  key.setElement(0x2500); // Curve Label
  stack.clear();
  if (EC_Normal == dset.search(key, stack, ESM_fromHere, OFFalse) && stack.top()->isElement())
  {
    d_curveLabel = (DcmElement *)(stack.top());
  }

  Uint16 u=0;
  if ((EC_Normal != d_curveDimensions->getUint16(u,0))||(u != 2))
  {
    // Curve Dimensions unreadable or != 2, bail out
    return EC_IllegalCall;
  }

  u=0;
  if ((EC_Normal != d_numberOfPoints->getUint16(u,0))||(u == 0))
  {
    // Number of Points == 0 or unreadable, bail out.
    return EC_IllegalCall;
  } else numberOfPoints = u;

  OFString aString;
  if (EC_Normal != d_typeOfData->getOFString(aString,0))
  {
    // cannot read type of data, bail out
    return EC_IllegalCall;
  } else {
    if (aString == "ROI") typeOfData=DVPSL_roiCurve;
    else if (aString == "POLY") typeOfData=DVPSL_polylineCurve;
    else return EC_IllegalCall; // unsupported type of data, bail out
  }

  if ((d_curveDescription) &&(EC_Normal == d_curveDescription->getOFString(aString,0)))
  {
    curveDescription = aString;
  }

  if ((d_axisUnits) &&(EC_Normal == d_axisUnits->getOFString(aString,0)))
  {
    axisUnitsX = aString;
  }
  if ((d_axisUnits) &&(EC_Normal == d_axisUnits->getOFString(aString,1)))
  {
    axisUnitsY = aString;
  }

  if ((d_curveLabel) &&(EC_Normal == d_curveLabel->getOFString(aString,0)))
  {
    curveLabel = aString;
  }

  Uint16 dataVR=0;
  if (EC_Normal != d_dataVR->getUint16(dataVR,0))
  {
    // cannot read curve data VR, bail out
    return EC_IllegalCall;
  }

  curveData = new double[numberOfPoints*2];
  if (curveData==NULL) return EC_MemoryExhausted;

  void *rawData=NULL;
  size_t align=0;

  Uint8 *pui8=NULL;
  Uint16 *pui16=NULL;
  Sint16 *psi16=NULL;
  Float32 *pfl32=NULL;
  Float64 *pfl64=NULL;
  Sint32 *psi32=NULL;
  switch (d_curveData->ident())
  {
    case EVR_OB:
    case EVR_OW:
    case EVR_ox:
    case EVR_px:
    case EVR_US:

      if (EC_Normal == d_curveData->getUint8Array(pui8))
      {
        rawData = pui8;
        align = 1;
      }
      else if (EC_Normal == d_curveData->getUint16Array(pui16))
      {
        rawData = pui16;
        align = sizeof(Uint16);
      }
      else result = EC_IllegalCall;
      break;
    case EVR_SS:
      if (EC_Normal == d_curveData->getSint16Array(psi16))
      {
        rawData = psi16;
        align = sizeof(Sint16);
      } else result = EC_IllegalCall;
      break;
    case EVR_FL:
      if (EC_Normal == d_curveData->getFloat32Array(pfl32))
      {
        rawData = pfl32;
        align = sizeof(Float32);
      } else result = EC_IllegalCall;
      break;
    case EVR_FD:
      if (EC_Normal == d_curveData->getFloat64Array(pfl64))
      {
        rawData = pfl64;
        align = sizeof(Float64);
      } else result = EC_IllegalCall;
      break;
    case EVR_SL:
      if (EC_Normal == d_curveData->getSint32Array(psi32))
      {
        rawData = psi32;
        align = sizeof(Sint32);
      } else result = EC_IllegalCall;
      break;
    default:
      result = EC_IllegalCall;
      break;
  }

  size_t byteLength = (size_t)(d_curveData->getLength());
  size_t maxidx = 2*numberOfPoints;
  size_t i;

  switch (dataVR)
  {
    case 0: // VR=US. align can be 1 or 2
      if ((align==1)||(align==sizeof(Uint16)))
      {
        if (align==1) swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, rawData, OFstatic_cast(Uint32, byteLength), sizeof(Uint16));
        if (maxidx*sizeof(Uint16) <= byteLength)
        {
          for (i=0; i<maxidx; i++) curveData[i] = (double)(((Uint16 *)rawData)[i]);
        } else result=EC_IllegalCall;
      } else result=EC_IllegalCall;
      break;
    case 1: // VR=SS. align can be 1 or 2
      if ((align==1)||(align==sizeof(Sint16)))
      {
        if (align==1) swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, rawData, OFstatic_cast(Uint32, byteLength), sizeof(Sint16));
        if (maxidx*sizeof(Sint16) <= byteLength)
        {
          for (i=0; i<maxidx; i++) curveData[i] = (double)(((Sint16 *)rawData)[i]);
        } else result=EC_IllegalCall;
      } else result=EC_IllegalCall;
      break;
    case 2: // VR=FL. align can be 1, 2, 4
      if ((align==1)||(align==sizeof(Uint16))||(align==sizeof(Float32)))
      {
        // if data is word aligned, first swap back to little endian OB
        if (align==sizeof(Uint16)) swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, rawData, OFstatic_cast(Uint32, byteLength), sizeof(Uint16));
        if ((align==1)||(align==sizeof(Uint16)))
          swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, rawData, OFstatic_cast(Uint32, byteLength), sizeof(Float32));
        if (maxidx*sizeof(Float32) <= byteLength)
        {
          for (i=0; i<maxidx; i++) curveData[i] = (double)(((Float32 *)rawData)[i]);
        } else result=EC_IllegalCall;
      } else result=EC_IllegalCall;
      break;
    case 3: // VR=FD. align can be 1, 2, 8
      if ((align==1)||(align==sizeof(Uint16))||(align==sizeof(Float64)))
      {
        // if data is word aligned, first swap back to little endian OB
        if (align==sizeof(Uint16)) swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, rawData, OFstatic_cast(Uint32, byteLength), sizeof(Uint16));
        if ((align==1)||(align==sizeof(Uint16)))
          swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, rawData, OFstatic_cast(Uint32, byteLength), sizeof(Float64));
        if (maxidx*sizeof(Float64) <= byteLength)
        {
          for (i=0; i<maxidx; i++) curveData[i] = (double)(((Float64 *)rawData)[i]);
        } else result=EC_IllegalCall;
      } else result=EC_IllegalCall;
      break;
    case 4: // VR=SL. align can be 1, 2, 4
      if ((align==1)||(align==sizeof(Uint16))||(align==sizeof(Sint32)))
      {
        // if data is word aligned, first swap back to little endian OB
        if (align==sizeof(Uint16)) swapIfNecessary(EBO_LittleEndian, gLocalByteOrder, rawData, OFstatic_cast(Uint32, byteLength), sizeof(Uint16));
        if ((align==1)||(align==sizeof(Uint16)))
          swapIfNecessary(gLocalByteOrder, EBO_LittleEndian, rawData, OFstatic_cast(Uint32, byteLength), sizeof(Sint32));
        if (maxidx*sizeof(Sint32) <= byteLength)
        {
          for (i=0; i<maxidx; i++) curveData[i] = (double)(((Sint32 *)rawData)[i]);
        } else result=EC_IllegalCall;
      } else result=EC_IllegalCall;
      break;
    default:
      result = EC_IllegalCall;
      break;
  }

  return result;
}


OFCondition DVPSCurve::getPoint(size_t idx, double& x, double& y)
{
  x=0.0;
  y=0.0;
  if (idx < numberOfPoints)
  {
    x = curveData[2*idx];
    y = curveData[2*idx+1];
  } else return EC_IllegalCall;
  return EC_Normal;
}
