/*
 *
 *  Copyright (C) 2016-2019, Open Connections GmbH
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation are maintained by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  Module:  dcmtract
 *
 *  Author:  Michael Onken
 *
 *  Purpose: Class representing a Track from a Tractography Results IOD
 *
 */
#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcuid.h"
#include "dcmtk/dcmiod/iodutil.h"
#include "dcmtk/dcmtract/trctrack.h"
#include "dcmtk/dcmtract/trctypes.h"

// default constructor (protected, instance creation via create() function)
TrcTrack::TrcTrack()
: IODComponent()
{
  TrcTrack::resetRules();
}


OFString TrcTrack::getName() const
{
  return "TrackSequenceItem";
}


OFCondition TrcTrack::create(const Float32* trackDataPoints,
                             const size_t numPoints,
                             const Uint16* colors,
                             const size_t numColors,
                             TrcTrack*& track /* result */)
{
  track = new TrcTrack();
  if (!track)
    return EC_MemoryExhausted;

  OFCondition result = track->setTrackData(trackDataPoints, numPoints);
  if (result.good())
  {
    result = track->setRecommendedDisplayCIELabValues(colors, numColors);
  }
  if (result.bad())
  {
    delete track;
    track = NULL;
  }
  return result;
}


void TrcTrack::resetRules()
{
  // Set rules for the attributes within this component
  getRules()->addRule(new IODRule(DCM_PointCoordinatesData, "1","1", getName(), DcmIODTypes::IE_INSTANCE), OFTrue);
  getRules()->addRule(new IODRule(DCM_RecommendedDisplayCIELabValueList, "1","1C", getName(), DcmIODTypes::IE_INSTANCE), OFTrue);
  getRules()->addRule(new IODRule(DCM_RecommendedDisplayCIELabValue, "3","1C", getName(), DcmIODTypes::IE_INSTANCE), OFTrue);
}


OFCondition TrcTrack::check(const OFBool quiet)
{
  OFCondition result;
  // Report errors but ignore them
  IODComponent::check(quiet);
  const Float32* data = NULL;
  unsigned long count = 0;
  // If the actual data is missing, return an error, ignore other errors
  if (m_Item->findAndGetFloat32Array(DCM_PointCoordinatesData, data, &count).good())
  {
    if (count != 0)
    {
      if (count % 3 == 0)
      {
        result = EC_Normal;
      }
      else
      {
        DCMTRACT_ERROR("Point Coordinates Data must have x,y,z coordinates for every point but has length: " << count);
        result = IOD_EC_InvalidElementValue;
      }
    }
    else
    {
      DCMTRACT_ERROR("Point Coordinates Data empty");
      result = IOD_EC_InvalidElementValue;
    }
  }
  else
  {
    DCMTRACT_ERROR("Point Coordinates Data element missing");
    result = IOD_EC_MissingAttribute;
  }
  return result;
}


OFCondition TrcTrack::read(DcmItem& source,
                           OFBool clearOldData)
{
  OFCondition result = IODComponent::read(source, clearOldData);
  if (result.good())
  {
    // fix number of values in point coordinates data if wrong
    if (!fixPointCoordinatesDataVM())
    {
      return TRC_EC_InvalidPointCoordinatesData;
    }
  }
  return result;
}


OFCondition TrcTrack::write(DcmItem& destination)
{
  return IODComponent::write(destination);
}


void TrcTrack::clearData()
{
  IODComponent::clearData();
}


TrcTrack::~TrcTrack()
{
  clearData();
}


size_t TrcTrack::getTrackData(const Float32*& data) const
{
  data = NULL;
  unsigned long count;
  if (m_Item->findAndGetFloat32Array(DCM_PointCoordinatesData, data, &count).good())
  {
    return count / 3;
  }
  return 0;
}


size_t TrcTrack::getNumDataPoints()
{
  DcmElement *elem = NULL;
  if (m_Item->findAndGetElement(DCM_PointCoordinatesData, elem).good())
  {
    Uint32 numBytes = elem->getLengthField();
    if (numBytes % 2 == 1)
    {
      DCMTRACT_WARN("Length of Point Coordinates data is odd (" << numBytes << "), assuming " << numBytes - 1);
      numBytes = numBytes -1;
    }
    // single point coordinate has 4 bytes (Float32), and point consists of 3 coordinates
    return numBytes / sizeof(Float32) / 3;
  }
  return 0;
}


OFBool TrcTrack::getRecommendedDisplayCIELabValue(Uint16& L,
                                                  Uint16& a,
                                                  Uint16& b)
{
  const Uint16* colors = NULL;
  unsigned long count = 0;
  if (m_Item->findAndGetUint16Array(DCM_RecommendedDisplayCIELabValue, colors, &count).good())
  {
    if (count < 3)
    {
      L = a = b = 0;
      return OFFalse;
    }
    else
    {
      L = colors[0];
      a = colors[1];
      b = colors[2];
      return OFTrue;
    }
  }
  else
  {
    return OFFalse;
  }
}


size_t TrcTrack::getRecommendedDisplayCIELabValueList(const Uint16*& colors)
{
  unsigned long count = 0;
  if (m_Item->findAndGetUint16Array(DCM_RecommendedDisplayCIELabValueList, colors, &count).good())
  {
    return count / 3;
  }
  return 0;
}


TrcTypes::E_TrackColorMode TrcTrack::getRecommendedDisplayCIELabMode()
{
  DcmElement *elem = NULL;
  if (m_Item->findAndGetElement(DCM_RecommendedDisplayCIELabValue, elem).good())
  {
    if (elem->getNumberOfValues() == 3)
    {
      if (m_Item->findAndGetElement(DCM_RecommendedDisplayCIELabValueList, elem).good())
      {
        DCMTRACT_DEBUG("Recommended Display CIE Lab Value List as well as Recommended Display CIE Lab Value are set, will try per track coloring");
      }
      return TrcTypes::CM_TRACK;
    }
    else
    {
      DCMTRACT_ERROR("Recommended Display CIE Lab Value is set but has more or less than 3 values");
      return TrcTypes::CM_ERROR;
    }
  }
  // RecommendedDisplayCIELabValue is not set or not set correctly,
  // try RecommendedDisplayCIELabValueList instead
  elem = NULL;
  if (m_Item->findAndGetElement(DCM_RecommendedDisplayCIELabValueList, elem).good())
  {
    size_t numBytes = elem->getLength();
    if (numBytes % 2 == 1)
    {
      DCMTRACT_WARN("Recommended Display CIE Lab Value List has odd length " << numBytes << ", assuming " << numBytes -1);
      numBytes = numBytes - 1;
    }
    if (numBytes > 0)
    {
      // divide by 2 to get number of 16 bit entries and divide by 3 since
      // every color has 3 components
      if (getNumDataPoints() == (numBytes / 2 / 3))
      {
        return TrcTypes::CM_POINTS;
      }
      else
      {
        DCMTRACT_ERROR("Number of colors in Recommended Display CIE Lab Value List does not match number of points in Point Coordinates Data");
        return TrcTypes::CM_ERROR;
      }
    }
    else
    {
      DCMTRACT_ERROR("Recommended Display CIE Lab Value List is empty and can not be used");
      return TrcTypes::CM_ERROR;
    }
  }

  return TrcTypes::CM_TRACKSET;
}



OFCondition TrcTrack::setTrackData(const Float32* trackDataPoints,
                                   const size_t numPoints)
{
  return m_Item->putAndInsertFloat32Array(DCM_PointCoordinatesData, trackDataPoints, OFstatic_cast(unsigned long, numPoints*3));
}



OFCondition TrcTrack::setRecommendedDisplayCIELabValues(const Uint16* colors,
                                                        const size_t numColors)
{
  OFCondition result;
  if ( (numColors == 0) && (colors == NULL))
  {
    m_Item->findAndDeleteElement(DCM_RecommendedDisplayCIELabValue);
    m_Item->findAndDeleteElement(DCM_RecommendedDisplayCIELabValueList);
    return EC_Normal;
  }
  else if (colors == NULL)
  {
    return TRC_EC_InvalidColorInformation;
  }
  if (numColors == 1)
  {
    return m_Item->putAndInsertUint16Array(DCM_RecommendedDisplayCIELabValue, colors, OFstatic_cast(unsigned long, numColors * 3));
  }
  else
  {
    return m_Item->putAndInsertUint16Array(DCM_RecommendedDisplayCIELabValueList, colors, OFstatic_cast(unsigned long, numColors * 3));
  }
}



OFBool TrcTrack::fixPointCoordinatesDataVM()
{
  const Float32* values = NULL;
  unsigned long numValues = 0;
  if (m_Item->findAndGetFloat32Array(DCM_PointCoordinatesData, values, &numValues).good())
  {
    if (numValues % 3 == 0)
    {
      return OFTrue;
    }
    else
    {
      if (m_Item->putAndInsertFloat32Array(DCM_PointCoordinatesData, values, numValues - (numValues % 3)).good())
      {
        DCMTRACT_WARN("Wrong number of values in track (" << numValues << "), cutting off " << numValues % 3 << " coordinates at the end");
        return OFTrue;
      }
    }
  } // else error
  return OFFalse;
}
