
#include "gdcmDirectoryHelper.h"
#include "gdcmScanner.h"
#include "gdcmIPPSorter.h"
#include "gdcmAttribute.h"
#include "gdcmDataElement.h"
#include "gdcmReader.h"


namespace gdcm{
//given an SOPClassUID, get the number of series that are that SOP Class
//that is, the MR images, the CT Images, whatever.  Just have to be sure to give the proper
//SOP Class UID.
//returns an empty vector if nothing's there or if something goes wrong.
Directory::FilenamesType DirectoryHelper::GetSeriesUIDsBySOPClassUID(const std::string& inDirectory,
                                                                       const std::string& inSOPClassUID)
{
  Scanner theScanner;
  Directory theDir;
  theScanner.AddTag(Tag(0x0008, 0x0016));//SOP Class UID
  theScanner.AddTag(Tag(0x0020, 0x000e));//Series UID
  Directory::FilenamesType theReturn;

  try {
    theDir.Load(inDirectory);
    theScanner.Scan(theDir.GetFilenames());

    //now find all series UIDs
    Directory::FilenamesType theSeriesValues = theScanner.GetOrderedValues(Tag(0x0020,0x000e));

    //now count the number of series that are of that given SOPClassUID
    size_t theNumSeries = theSeriesValues.size();
    for (size_t i = 0; i < theNumSeries; i++){
      std::string theFirstFilename =
      theScanner.GetFilenameFromTagToValue(Tag(0x0020,0x000e), theSeriesValues[i].c_str());
      std::string theSOPClassUID = theScanner.GetValue(theFirstFilename.c_str(), Tag(0x0008,0x0016));
      //dicom strings sometimes have trailing spaces; make sure to avoid those
      size_t endpos = theSOPClassUID.find_last_not_of(' '); // Find the first character position from reverse af
      if( std::string::npos != endpos )
        theSOPClassUID = theSOPClassUID.substr( 0, endpos+1 );
      if (theSOPClassUID == inSOPClassUID){
        theReturn.push_back(theSeriesValues[i]);
      }
    }
    return theReturn;
  } catch (...){
    Directory::FilenamesType theBlank;
    return theBlank;//something broke during scanning
  }
}


//given the name of a directory, return the list of CT Image UIDs
Directory::FilenamesType DirectoryHelper::GetCTImageSeriesUIDs(const std::string& inDirectory)
{
  return GetSeriesUIDsBySOPClassUID(inDirectory, "1.2.840.10008.5.1.4.1.1.2");
}

//given the name of a directory, return the list of CT Image UIDs
Directory::FilenamesType DirectoryHelper::GetMRImageSeriesUIDs(const std::string& inDirectory)
{
  return GetSeriesUIDsBySOPClassUID(inDirectory, "1.2.840.10008.5.1.4.1.1.4");
}

//given the name of a directory, return the list of CT Image UIDs
Directory::FilenamesType DirectoryHelper::GetRTStructSeriesUIDs(const std::string& inDirectory)
{
  return GetSeriesUIDsBySOPClassUID(inDirectory, "1.2.840.10008.5.1.4.1.1.481.3");
}

//given a directory and a series UID, provide all filenames with that series UID.
Directory::FilenamesType DirectoryHelper::GetFilenamesFromSeriesUIDs(const std::string& inDirectory,
                                                                     const std::string& inSeriesUID)
{
  Scanner theScanner;
  Directory theDir;
  theScanner.AddTag(Tag(0x0020, 0x000e));//Series UID
  Directory::FilenamesType theReturn;

  try {
    theDir.Load(inDirectory);
    theScanner.Scan(theDir.GetFilenames());
    //now find all series UIDs
    Directory::FilenamesType theSeriesValues = theScanner.GetOrderedValues(Tag(0x0020,0x000e));
    //now count the number of series that are of that given SOPClassUID
    size_t theNumSeries = theSeriesValues.size();
    for (size_t i = 0; i < theNumSeries; i++)
      {
      std::string theSeriesUID = theSeriesValues[i];
      //dicom strings sometimes have trailing spaces; make sure to avoid those
      size_t endpos = theSeriesUID.find_last_not_of(' '); // Find the first character position from reverse af
      if( std::string::npos != endpos )
        theSeriesUID = theSeriesUID.substr( 0, endpos+1 );
      if (inSeriesUID == theSeriesUID)
      {
	    Directory::FilenamesType theFilenames =
		    theScanner.GetAllFilenamesFromTagToValue(Tag(0x0020, 0x000e), theSeriesValues[i].c_str());
		  Directory::FilenamesType::const_iterator citor;
		  for (citor = theFilenames.begin(); citor < theFilenames.end(); citor++)
        {
		    theReturn.push_back(*citor);
		    }
    //      theReturn.push_back(theScanner.GetFilenameFromTagToValue(Tag(0x0020,0x000e),
    //        theSeriesValues[i].c_str()));
      }
    }
    return theReturn;
  } catch (...){
    Directory::FilenamesType theBlank;
    return theBlank;//something broke during scanning
  }

}
//the code in GetSeriesUIDsBySOPClassUID will enumerate the CT images in a directory
//This code will retrieve an image by its Series UID.
//this code doesn't return pointers or smart pointers because it's intended to
//be easily wrapped by calling languages that don't know pointers (ie, Java)
//this function is a proof of concept
//for it to really work, it needs to also
std::vector<DataSet> DirectoryHelper::LoadImageFromFiles(const std::string& inDirectory,
                                                           const std::string& inSeriesUID)
{
  Scanner theScanner;
  Directory theDir;
  theScanner.AddTag(Tag(0x0020, 0x000e));//Series UID
  std::vector<DataSet> theReturn;
  std::vector<DataSet> blank;//returned in case of an error

  try {
    theDir.Load(inDirectory);
    theScanner.Scan(theDir.GetFilenames());

    //now find all series UIDs
    Directory::FilenamesType theSeriesValues = theScanner.GetOrderedValues(Tag(0x0020,0x000e));

    //now count the number of series that are of that given SOPClassUID
    size_t theNumSeries = theSeriesValues.size();
    for (size_t i = 0; i < theNumSeries; i++){
      if (inSeriesUID == theSeriesValues[i]){
        //find all files that have that series UID, and then load them via
        //the vtkImageReader
        Directory::FilenamesType theFiles =
        theScanner.GetAllFilenamesFromTagToValue(Tag(0x0020, 0x000e), theSeriesValues[i].c_str());
        IPPSorter sorter;
        sorter.SetComputeZSpacing(true);
        sorter.SetZSpacingTolerance(0.000001);
        if (!sorter.Sort(theFiles)){
          gdcmWarningMacro("Unable to sort Image Files.");
          return blank;
        }
        Directory::FilenamesType theSortedFiles = sorter.GetFilenames();
        for (unsigned long j = 0; j < theSortedFiles.size(); ++j){
          Reader theReader;
          theReader.SetFileName(theSortedFiles[j].c_str());
          theReader.Read();
          theReturn.push_back(theReader.GetFile().GetDataSet());
        }
        return theReturn;
      }
    }
    return blank;
  } catch (...){
    gdcmWarningMacro("Something went wrong reading CT images.");
    return blank;
  }
}


//When writing RTStructs, each contour will have z position defined.
//use that z position to determine the SOPInstanceUID for that plane.
std::string DirectoryHelper::RetrieveSOPInstanceUIDFromZPosition(double inZPos,
                                                const std::vector<DataSet>& inDS)
{
  std::string blank;//return only if there's a problem

  //Find z spacing
  const Tag tipp(0x0020,0x0032); // Image Position (Patient)
  const Tag tiop(0x0020,0x0037); // Image Orientation (Patient)
  double normal[3];

  //Compute the normal
  if (inDS.begin() != inDS.end() && inDS.begin()->FindDataElement(tiop))
    {
    DataElement tmpDe = inDS.begin()->GetDataElement(tiop);
    Attribute<0x0020,0x0037> tmpAt;
    tmpAt.SetFromDataElement(tmpDe);
    if (tmpAt.GetNumberOfValues() == 6)
      {
      normal[0] = tmpAt.GetValue(1)*tmpAt.GetValue(5) - tmpAt.GetValue(2)*tmpAt.GetValue(4);
      normal[1] = tmpAt.GetValue(2)*tmpAt.GetValue(3) - tmpAt.GetValue(0)*tmpAt.GetValue(5);
      normal[2] = tmpAt.GetValue(0)*tmpAt.GetValue(4) - tmpAt.GetValue(1)*tmpAt.GetValue(3);
      }
    else
      {
      gdcmDebugMacro( "No 6 values in tiop: " << tmpAt.GetNumberOfValues() );
      return blank;
      }
    }
  else
    {
    gdcmDebugMacro( "No tiop available in the first slice" );
    return blank;
    }

  //Compute dist for the first 2 slices
  double dist1 = 0;
  std::vector<DataSet>::const_iterator itor = inDS.begin();
  if (itor != inDS.end() && itor->FindDataElement(tipp))
    {
    DataElement tmpDe = itor->GetDataElement(tipp);
    Attribute<0x0020,0x0032> tmpAt;
    tmpAt.SetFromDataElement(tmpDe);
    if (tmpAt.GetNumberOfValues() == 3)
      {
      for (int i = 0; i < 3; ++i) dist1 += normal[i]*tmpAt.GetValue(i);
      }
    else
      {
      gdcmDebugMacro( "No 3 values in tipp for the first slice: " << tmpAt.GetNumberOfValues() );
      return blank;
      }
    }
  else
    {
    gdcmDebugMacro( "No tipp available in the first slice" );
    return blank;
    }
  double dist2 = 0;
  itor++;
  if (itor != inDS.end() && itor->FindDataElement(tipp))
    {
    DataElement tmpDe = itor->GetDataElement(tipp);
    Attribute<0x0020,0x0032> tmpAt;
    tmpAt.SetFromDataElement(tmpDe);
    if (tmpAt.GetNumberOfValues() == 3)
      {
      for (int i = 0; i < 3; ++i) dist2 += normal[i]*tmpAt.GetValue(i);
      }
    else
      {
      gdcmDebugMacro( "No 3 values in tipp for the first slice: " << tmpAt.GetNumberOfValues() );
      return blank;
      }
    }
  else
    {
    gdcmDebugMacro( "No tipp available in the first slice" );
    return blank;
    }
  double  interSlice = fabs(dist2 - dist1)/2.0;

  //Find the SOPInstanceUID
  Tag thePosition(0x0020, 0x0032);
  Tag theSOPInstanceUID(0x0008, 0x0018);
  for (itor = inDS.begin(); itor != inDS.end(); itor++)
    {
    if (itor->FindDataElement(thePosition))
      {
      DataElement de = itor->GetDataElement(thePosition);
      Attribute<0x0020,0x0032> at;
      at.SetFromDataElement( de );
      if (fabs(at.GetValue(2) - inZPos)<interSlice)
        {
        DataElement de2 = itor->GetDataElement(theSOPInstanceUID);
        const ByteValue* theVal = de2.GetByteValue();
        size_t theValLen = theVal->GetLength();
        std::string theReturn(de2.GetByteValue()->GetPointer(), theValLen);
        return theReturn;
        }
      }
    }
  return blank;
}

//When writing RTStructs, the frame of reference is done by planes to start with
std::string DirectoryHelper::RetrieveSOPInstanceUIDFromIndex(int inIndex,
                                                               const std::vector<DataSet>& inDS)
{

  Tag theSOPInstanceUID(0x0008, 0x0018);
  std::string blank;//return only if there's a problem
  if (inDS[inIndex].FindDataElement(theSOPInstanceUID)){
    DataElement de = inDS[inIndex].GetDataElement(theSOPInstanceUID);
    const ByteValue* theVal = de.GetByteValue();
    size_t theValLen = theVal->GetLength();
    std::string theReturn(de.GetByteValue()->GetPointer(), theValLen);
    return theReturn;
  }
  return blank;
}

//each plane needs to know the SOPClassUID, and that won't change from image to image
//so, retrieve this once at the start of writing.
std::string DirectoryHelper::GetSOPClassUID(const std::vector<DataSet>& inDS)
{
  Tag theSOPClassUID(0x0008, 0x0016);
  std::string blank;//return only if there's a problem
  if (inDS[0].FindDataElement(theSOPClassUID)){
    DataElement de = inDS[0].GetDataElement(theSOPClassUID);
    const ByteValue* theVal = de.GetByteValue();
    size_t theValLen = theVal->GetLength();
    std::string theReturn(de.GetByteValue()->GetPointer(), theValLen);
    return theReturn;
  }
  return blank;
}

//retrieve the frame of reference from the set of datasets
std::string DirectoryHelper::GetFrameOfReference(const std::vector<DataSet>& inDS){

  Tag theSOPClassUID(0x0020, 0x0052);
  std::string blank;//return only if there's a problem
  if (inDS[0].FindDataElement(theSOPClassUID)){
    DataElement de = inDS[0].GetDataElement(theSOPClassUID);
    const ByteValue* theVal = de.GetByteValue();
    size_t theValLen = theVal->GetLength();
    std::string theReturn(de.GetByteValue()->GetPointer(), theValLen);
    return theReturn;
  }
  return blank;
}


//----------------------------------------------------------------------------
//used by the vtkGDCMImageReader and vtkGDCMPolyDataReader. Could be used elsewhere, I suppose.
std::string DirectoryHelper::GetStringValueFromTag(const Tag& t, const DataSet& ds)
{
  std::string buffer;

  if( ds.FindDataElement( t ) )
    {
    const DataElement& de = ds.GetDataElement( t );
    const ByteValue *bv = de.GetByteValue();
    if( bv ) // Can be Type 2
      {
      buffer = std::string( bv->GetPointer(), bv->GetLength() );
      // Will be padded with at least one \0
      }
    }

  // Since return is a const char* the very first \0 will be considered
  return buffer.c_str(); // Yes, I mean .c_str()
}
} // end namespace gdcm
