/// SeriesHandler.cpp
/** Each handler knows how to interpret the series data
    A SeriesHandler instance represents one or more files associated
    with a single series.  Handles input operations.
*/

#include "SeriesHandler.h"

using namespace jcs;


SeriesHandler::SeriesHandler(const std::string& seriesUid)
: mSeriesUid(seriesUid)
{ 
  seriesInfo.subject_name = "";
  seriesInfo.subject_id = "";
  seriesInfo.study_number = "";
  seriesInfo.series_number = "";
  seriesInfo.StudyDate = "";
  seriesInfo.SeriesDate = "";
  seriesInfo.study_description = "";
  seriesInfo.series_description = "";
  multiecho = false;
}


SeriesHandler::~SeriesHandler()
{
}


///
/**
    \param s String reference, name of DICOM element to find.
    \return Value of DICOM element given in 's' as a string.
  */
std::string
SeriesHandler::Find(const std::string& s) const
{
  std::string retstring;
  //Find(s, retstring));
  //return retstring;
  if (Find(s, retstring)) {
    return retstring;
  }
  else {
    return "";
  }
}


///
/**
    \param s Reference to DicomElement to find.
    \return Value of DICOM element given in 's' as a string.
  */
std::string
SeriesHandler::Find(DicomElement e) const
{
  std::string retstring;
  //Find(e, retstring));
  //return retstring;
  if (Find(e, retstring)) {
    return retstring;
  }
  else {
    return "";
  }
}


///
/**
*/
std::vector<double> 
SeriesHandler::GetIppForFirstSlice(const VolId& id)
{ 
  return position[id]; 
}


///
/** Retrieves voxel dimensions based on PixelSpacing, SpacingBetweenSlices //SliceSpacing and
    SliceThickness.
    \return A three-vector of double representing the voxel dimensions.
*/
std::vector<double>
SeriesHandler::GetVoxelSize()
{
  std::vector<double> voxel_size(3, 0);
  std::string s;
  if (Find("PixelSpacing", s)) {
    voxel_size[0] = jcs::stof(s, 0);
    voxel_size[1] = jcs::stof(s, 1);
  }
  //Find("SliceSpacing", voxel_size[2]);
  Find("SpacingBetweenSlices", voxel_size[2]);
  if (voxel_size[2] == 0) {
    Find("SliceThickness", voxel_size[2]);
  }
  return voxel_size;
}


///
/**
*/
int
SeriesHandler::GetNumberOfSlices() const
{
  int nVolumes = GetNumberOfVolumes();
  int nFiles = GetNumberOfFiles();
  if (nVolumes != 0) {
    return nFiles/nVolumes;
  }
  else {
    return nFiles;
  }
}


///
/** Gets all file names in mFileMap.
  \return A vector of filenames.
*/
std::vector<std::string> 
SeriesHandler::GetFilenames() const
{
  std::vector<std::string> names;
  FileMapType::const_iterator it = mFileMap.begin();
  FileMapType::const_iterator it_end = mFileMap.end();
  for (; it != it_end; ++it) {
    names.push_back(it->first);
  }
  return names;
}


///
/**
*/
bool
SeriesHandler::IsBigEndian() const
{
  if (mFileMap.empty()) {
    return false;
  }
  DicomFile dicomFile((*mFileMap.begin()).first.c_str());
  return dicomFile.IsBigEndian();
}


///
/**
*/
int
SeriesHandler::GetNumberOfVolumes() const
{
  std::set<VolId> volSet = GetVolIds();
  return volSet.size();
}


///
/**
*/
std::string
SeriesHandler::GetImagePositionPatient(DicomFile& dfile, int frame)
{
  std::string ipp;

  if (!dfile.Find("ImagePositionPatient", ipp)) {
    ipp = "0\\0\\" + itos(frame);
  }
  return ipp;
}


///
/**
*/
int
SeriesHandler::GetRescaleSlopeAndIntercept(const VolId& id, double& slope, double& intercept) const
{
  std::string filename;
  if (!GetFirstFilename(id, filename)) {
    return 0;
  }
  DicomFile dicomFile(filename.c_str());
  return GetRescaleSlopeAndIntercept(dicomFile, slope, intercept);
}


///
/** Seeks RescaleSlope and RescaleIntercept attributes. If
    not found, will set slope to 1 and intercept to 0.
*/
int
SeriesHandler::GetRescaleSlopeAndIntercept(DicomFile& dfile, double& slope, double& intercept, int frame) const
{
  if (!dfile.Find("RescaleSlope", slope)) {
    slope = 1;
  }
  if (!dfile.Find("RescaleIntercept", intercept)) {
    intercept = 0;
  }
  return 1;
}


///
/** Calculates time interval between volumes using
    AcquisitionTime.
    \return Difference in Acquisition Time between two adjacent volumes.
*/
double
SeriesHandler::GetVolumeInterval() const
{
  double retval = 0;
  
  FileMapType::const_iterator it = mFileMap.begin();
  FileMapType::const_iterator it_end = mFileMap.end();
  std::map<VolId, std::vector<double> > vtmap;
  for (double time = 0; it != it_end; ++it) { 
    DicomFile file(it->first.c_str());
    file.Find("AcquisitionTime", time);
    vtmap[it->second.front()].push_back(time);
    time = 0;  // Do we trust file.Find to overwrite time?
  }
  retval = CalculateVolIntVal(vtmap, 2);
  return retval;
}


///
/** Given a map structure containing double times, will determine an estimate
    for the volume interval.
    \param vtmap A map of VolId and vector of double associations.
    \param min_num_volumes This is the minimum number of volumes needed to return
    a reasonable estimate for Volume Interval. Indexes the list of interval times
    such that elements at min_num_volumes and min_num_volumes - 1 are used to
    determine the calculated value.
    \return An estimate for volume interval.
*/
double
SeriesHandler::CalculateVolIntVal(std::map<VolId, std::vector<double> >& vtmap, unsigned int min_num_volumes) const
{
  double retval = 0;
  if (vtmap.size() > min_num_volumes) {
    std::map<VolId, std::vector<double> >::iterator vit = vtmap.begin();
    std::map<VolId, std::vector<double> >::iterator vit_end = vtmap.end();
    std::vector<double> vol_times;
    for (;vit != vit_end; ++vit) {
      vol_times.push_back(*min_element(vit->second.begin(), vit->second.end()));
    }
    sort(vol_times.begin(), vol_times.end());
    retval = (vol_times[min_num_volumes] - vol_times[min_num_volumes - 1]);
    CheckIntervalSanity(vol_times, retval);
  }
  return retval;
}


///
/** Compares the given value to the average time between
    volume acquisition times and to RepititionTime. 
    Provides an opinion on the validity of the given value
    to std::cout.
    \param vol_times A vector of acquisitition times.
    \param retval A value to compare against.
*/
void
SeriesHandler::CheckIntervalSanity(std::vector<double>& vol_times, double retval) const
{
  double average = GetMeanVolumeInterval(vol_times);
  std::string str_tr;
  Find("RepetitionTime", str_tr);
  int tr;
  if (!(std::istringstream(str_tr) >> tr)) {
    tr = 0;
  }
  // Convert tr to seconds.
  tr /= 1000;
  
  if (!quiet) {
    double threshold = 0.05;
    if ((std::abs(average - retval) / average) > threshold) {
      std::cout << "For Series: " << GetSeriesUid() << std::endl;
      std::cout << "Volume interval differs from average interval by > " << threshold << "%" << std::endl;
      std::cout << "avg: " << average << " retval: " << retval << std::endl;
    }
    if ((std::abs(tr - retval) / tr) > threshold) {
      std::cout << "For Series: " << GetSeriesUid() << std::endl;
      std::cout << "Volume interval differs from RepetitionTime by > " << threshold << "%" << std::endl;
      std::cout << "TR: " << tr << " retval: " << retval << std::endl;
    }
  }
}


///
/** Calculate the average time between volumes.
    \param vol_times A vector of volume acquisition times,
            type double.
    \return The average difference between consecutive 
            acquisition times.
*/
double
SeriesHandler::GetMeanVolumeInterval(std::vector<double>& vol_times) const
{
  double average = 0;
  std::vector<double>::iterator dit = vol_times.begin();
  std::vector<double>::iterator dit_end = vol_times.end();
  std::vector<double> vol_diffs;
  if (dit < dit_end) {
    for (++dit; dit < dit_end; ++dit) {
      vol_diffs.push_back(*dit - *(dit - 1));
    }
    std::vector<double>::iterator diff = vol_diffs.begin();
    std::vector<double>::iterator diff_end = vol_diffs.end();
    double sum = 0;
    for (; diff < diff_end; ++diff) {
      sum += *diff;
    }
    average = sum / vol_diffs.size();
  }
  return average;
}


///
/**
*/
double
SeriesHandler::GetSliceDuration() const
{
  return 0;
}


///
/**
  \return Number of columns in this series.
*/
int
SeriesHandler::GetColumns()
{
  int columns;
//  Find(DT_IMAGECOLUMNS, columns);
	  Find(DT_IMAGECOLUMNS, columns);
  //Find(DT_IMAGECOLUMNS, columns);
	  Find("Columns", columns);
  return columns;
}


///
/**
  \return Number of rows in this series.
*/
int
SeriesHandler::GetRows()
{
  int rows;

//  Find(DT_IMAGEROWS, rows);
  Find (DT_IMAGEROWS, rows);
  //Find(DT_IMAGEROWS, rows);
  Find("Rows", rows);
  return rows;
}


///
/**
  \return Number of frames in this series.
*/
int
SeriesHandler::GetFrames() const
{
  int frames = 0;
  Find("NumberOfFrames", frames);
  return frames;
}


///
/** Adds a file name to mFileMap.
  Can we make this return true or false?
    \param filename Name of file to add.
    \return 0 if filename already in mFileMap, 1 otherwise.
*/
int
SeriesHandler::AddFile(const char* filename)
{
  if (mFileMap.count(filename)) { return 0; }

  DicomFile dicom_file(filename);

  std::string series_uid;
  //dicom_file.Find("SeriesInstanceUid", series_uid);
  dicom_file.Find("SeriesInstanceUID", series_uid);

  // Fix problems with termination
  std::string temp(series_uid.c_str());
  series_uid.clear();
  series_uid = temp;

  assert(mSeriesUid == series_uid);

  std::string uid;
  //dicom_file.Find("SopInstance", uid);
  dicom_file.Find("SOPInstanceUID", uid);
  if (mInstanceUids.count(uid) == 0 ) {
    mInstanceUids.insert(uid);
    VolListType info = ReadVolIds(dicom_file);
    mFileMap[filename] = info;
  }
  
  if (seriesInfo.SeriesDate == "") {
    //Find("PatientId", seriesInfo.subject_id);
    Find("PatientID", seriesInfo.subject_id);
    Find("PatientName", seriesInfo.subject_name);
    //Find("StudyId", seriesInfo.study_number);
    Find("StudyID", seriesInfo.study_number);
    Find("StudyDescription", seriesInfo.study_description);
    int series_number;
    Find("SeriesNumber", series_number);
    seriesInfo.series_number = itos(series_number, 3);
    Find("StudyDate", seriesInfo.StudyDate);
    Find("SeriesDate", seriesInfo.SeriesDate);
    Find("SeriesDescription", seriesInfo.series_description);
  }

  return 1;
}


///
/** Retrieves a bunch of information about a series.
    \return A vector of strings with series information.
*/
std::vector<std::string>
SeriesHandler::GetStringInfo()
{
  std::vector<std::string> v;

  v.push_back(std::string("Series UID: ") + GetSeriesUid());
  v.push_back(std::string("Study date: ") + Find("StudyDate"));
  v.push_back(std::string("Study time: ") + Find("StudyTime"));
  v.push_back(std::string("Series date: ") + Find("SeriesDate"));
  v.push_back(std::string("Series time: ") + Find("SeriesTime"));
  v.push_back(std::string("Subject: ") + Find("PatientName"));
  v.push_back(std::string("Subject birth date: ") + Find("PatientBirthDate"));
  v.push_back(std::string("Series description: ") + Find("SeriesDescription"));
  v.push_back(std::string("Image type: ") + Find("ImageType"));
 
  std::string manufacturer = Find("Manufacturer");
  v.push_back(std::string("Manufacturer: ") + manufacturer);

  v.push_back(std::string("Model name: ") + Find("ManufacturerModelName"));
  v.push_back(std::string("Software version: ") + Find("SoftwareVersions"));
  v.push_back(std::string("Study id: ") + Find("StudyID"));
  v.push_back(std::string("Series number: ") + Find("SeriesNumber"));
  v.push_back(std::string("Repetition time (ms): ") + Find("RepetitionTime"));

  GetEchoTimes();
  std::map<int, std::string>::iterator echo_it = echo_times.begin();
  while (echo_it != echo_times.end()) {
    v.push_back(std::string("Echo time[" + itos(echo_it->first) + "] (ms): ") + echo_it->second);
	++echo_it;
  }

  std::string found_string;
  found_string = Find("InversionTime");
  if (found_string.size() > 0 )
	  v.push_back(std::string("Inversion time (ms): ") + found_string);
  v.push_back(std::string("Flip angle: ") + Find("FlipAngle"));
  v.push_back(std::string("Number of averages: ") + Find("NumberOfAverages"));
  v.push_back(std::string("Slice thickness (mm): ") + Find("SliceThickness"));
  v.push_back(std::string("Slice spacing (mm): ") + Find("SpacingBetweenSlices"));
  v.push_back(std::string("Image columns: ") + Find("Columns"));
  v.push_back(std::string("Image rows: ") + Find("Rows"));
  v.push_back(std::string("Phase encoding direction: ") + Find("InPlanePhaseEncodingDirection"));

  std::vector<double> voxels = GetVoxelSize();
  v.push_back(std::string("Voxel size x (mm): ") + ftos(voxels[0]));
  v.push_back(std::string("Voxel size y (mm): ") + ftos(voxels[1]));

  int nVolumes = GetNumberOfVolumes();
  v.push_back(std::string("Number of volumes: ") + itos(nVolumes));
  v.push_back(std::string("Number of slices: ") + itos(GetNumberOfSlices()));
  v.push_back(std::string("Number of files: ") + itos(GetNumberOfFiles()));
//  v.push_back(std::string("Number of frames: ") + Find("NumberOfFrames"));
  v.push_back(std::string("Number of frames: ") + itos(GetFrames()));
  v.push_back(std::string("Slice duration (ms) : ") + ftos(GetSliceDuration()));



  if (nVolumes > 1) {
    v.push_back(std::string("Volume interval (s): ") + ftos(GetVolumeInterval()));
  }

  if (!orientations.empty()) {
    if (orientations.front().at(1) > orientations.front().at(0)) { // sagital slices
      v.push_back(std::string("Orientation: sag"));
    }
    else if (orientations.front().at(4) > fabs(orientations.front().at(5))) { // transverse slices
      v.push_back(std::string("Orientation: tra"));
    }
    else { // coronal slices
      v.push_back(std::string("Orientation: cor"));
    }
  }

  return v;
}


///
/** Retrieves echo times for the series. Sets echo_times
    and multiecho.
    Note that echo_times is indexed by the EchoNumbers.
*/
void
SeriesHandler::GetEchoTimes()
{
  std::map<int, std::string>::iterator eterator;
  std::string s;
  int idx;
  echo_times.clear();
  std::set<VolId> volids = GetVolIds();
  std::set<VolId>::iterator vol_it = volids.begin();
  std::set<VolId>::iterator vol_it_end = volids.end();
  for (; vol_it != vol_it_end; vol_it++) {
    //if (Find("EchoTime", s, *vol_it) && Find("EchoNumber", idx, *vol_it)) {
    if (Find("EchoTime", s, *vol_it) && Find("EchoNumbers", idx, *vol_it)) {
      echo_times[idx] = s;
    }
  }
  multiecho = (echo_times.size() > 1);
}


///
/**
*/
SeriesHandler::VolListType
SeriesHandler::ReadVolIds(DicomFile& file)
{
  // should use FrameIncrementPointer, but doing it the easy way for now
  // This fails with RTDOSE data, deb-bug-617342. 20130405cdt 
  // Also may be failure point for PACE series, dropping one volume, 
  // sometimes the first, reference, sometimes another.
  // Note also, there is not a good way to guarantee unique volume
  // identifiers, leading to inappropriate overwriting.
  int nframes;
  if (!file.Find("NumberOfFrames", nframes)) { nframes = 1; }

  wxUint32 fip;
  wxUint16 fiphi, fiplo;
  file.Find("FrameIncrementPointer", fip);
  fiphi = ( fip & 0xFFFF0000 ) >> 16;
  fiplo = ( fip & 0x0000FFFF );
  std::string fip_str;
  VolListType v;

  for (int frameno = 0; frameno < nframes; ++frameno) {

    VolId info;

    info.ids.push_back(GetSeriesUid());
    
    // Make info.ids unique in event of multi-frame series.
    /**
    std::stringstream ss;
    ss << frameno;
    info.ids.push_back(ss.str());
    */
    
    std::string str;
    file.Find("NumberOfTemporalPositions", str);
    int ndigits = str.size();
    
    str.clear();
    int tpid = 0;
    file.Find("TemporalPositionIdentifier", tpid);
    str = itos(tpid, ndigits);
    info.ids.push_back(str);

    str.clear();
    int echonumber = 0;
    file.Find("EchoNumbers", echonumber);
    str = itos(echonumber, 2);
    info.ids.push_back(str);
    
	// added for PET data
	str.clear();
	file.Find("FrameReferenceTime", str);
	info.ids.push_back(str);

    std::string ImageType;
    file.Find("ImageType", ImageType);

    if (ImageType.find("DERIVED") != std::string::npos) {
      info.ids.push_back("D");
    }
    else {
      info.ids.push_back("");
    }
    if (ImageType.find("T2 MAP") != std::string::npos) {
      info.ids.push_back("T2MAP");
    }
    else {
      info.ids.push_back("");
    }
    if (ImageType.find("\\M\\") != std::string::npos) {
      info.ids.push_back("M");
    }
    else {
      if (ImageType.find("\\P\\") != std::string::npos) {
        info.ids.push_back("P");
      }
      else {
        info.ids.push_back("");
      }
    }
    std::vector<double> rotation;
    rotation.resize(9, 0);

    std::string iop;
    if (!file.Find("ImageOrientationPatient", iop)) {
      iop = "1\\0\\0\\0\\1\\0";
    }
    
    for (int i = 0; i < 6; ++i) {
      double f = jcs::stof(iop, i);
      // round off
      rotation[i] = static_cast<double>(static_cast<int>(f * 10000 + 0.5)) / 10000;
    }

    rotation[6] = rotation[1]*rotation[5] - rotation[2]*rotation[4];
    rotation[7] = rotation[2]*rotation[3] - rotation[0]*rotation[5];
    rotation[8] = rotation[0]*rotation[4] - rotation[1]*rotation[3];
    
    std::vector<std::vector <double> >::iterator pos;
    pos = find(orientations.begin(), orientations.end(), rotation);
    if (pos != orientations.end()) {
      info.orientNumber = distance(orientations.begin(), pos);
    }
    else {
      info.orientNumber = orientations.size();
      orientations.push_back(rotation);
    }
    info.ids.push_back(itos(info.orientNumber + 1));

    v.push_back(info);
  }

  return v;
}


///
/**
  \return A set of VolIds from each file in mFileMap.
*/
std::set<VolId>
SeriesHandler::GetVolIds() const
{
  std::set<VolId> volSet;

  FileMapType::const_iterator it = mFileMap.begin();
  FileMapType::const_iterator it_end = mFileMap.end();
  VolListType::const_iterator vit, vit_end;
  for(; it != it_end; ++it) {
    vit = it->second.begin();
    vit_end = it->second.end();
    for (; vit != vit_end; ++vit) { 
      volSet.insert(*vit);
    }
  }
  return volSet;
}


///
/** returns volid + last file with that id
    \return A map relating VolIds and file names.
*/
std::map<VolId, std::string>
SeriesHandler::GetVolIdsAndFiles() const
{
  std::map<VolId, std::string> volMap;

  FileMapType::const_iterator it = mFileMap.begin();
  FileMapType::const_iterator it_end = mFileMap.end();
  VolListType::const_iterator vit, vit_end;
  for (; it != it_end; ++it) {
    vit = it->second.begin();
    vit_end = it->second.end();
    for (; vit != vit_end; ++vit) {
      volMap[*vit] = it->first;
    }
  }
  return volMap;
}


///
/**
  \param id 
  \param str Reference to std::string, contains file name if 'id' matches
  a volume contained in mFileMap.
  \return True if match to str is found.
*/
bool
SeriesHandler::GetFirstFilename(const VolId& id, std::string& str) const
{
  FileMapType::const_iterator it = mFileMap.begin();
  FileMapType::const_iterator it_end = mFileMap.end();
  VolListType::const_iterator vit, vit_end;
  for (; it != it_end; ++it) {
    vit = it->second.begin();
    vit_end = it->second.end();
    for (; vit != vit_end; ++vit) {
      if (*vit == id) {
        str = it->first;
        return true;
      }
    }
  }
  return false;
}


///
/**
*/
std::vector<double>
SeriesHandler::GetRotationMatrix(const VolId& id)
{
  std::vector<double> r(9,0);
  try {
    r = orientations.at(id.orientNumber);
  }
  catch (const std::exception& error) {
    wxLogError(_("orientNumber: %d; number of orientations: %d"), id.orientNumber, orientations.size());
    wxLogError(wxString(error.what(), wxConvLocal));
  }
  return r;
}


///
/**
*/
void 
SeriesHandler::GetImageComments(std::vector<std::string>& comments) const 
{
  std::map<VolId, std::string> volFileMap = GetVolIdsAndFiles();
  std::map<VolId, std::string>::iterator it = volFileMap.begin();
  std::map<VolId, std::string>::iterator it_end = volFileMap.end();
  for (;it != it_end; ++it) {
    std::string comment;
    DicomFile file(it->second.c_str());
    file.Find("ImageComments", comment);
    comments.push_back(comment);
  }
}


///
/**
*/
void 
jcs::Normalize(GradientInfo& info)
{
  std::vector<double> norms;
  for (unsigned int i = 0; i < info.xGrads.size(); ++i) {
    double norm = sqrt( pow(info.xGrads.at(i), 2) +
    pow(info.yGrads.at(i), 2) +
    pow(info.zGrads.at(i), 2) );
    //    norm = sqrt(norm);

    norms.push_back(norm);
  }
  for (unsigned int i = 0; i < info.xGrads.size(); ++i) {
    if ((norms.at(i) != 1.0) && (norms.at(i) != 0)) {
      info.xGrads.at(i) /= norms.at(i);
      info.yGrads.at(i) /= norms.at(i);
      info.zGrads.at(i) /= norms.at(i);
    }
  }
}


///
/**
*/
// nb - inverse(r)*info.xGrads
void
jcs::RotateGradInfo(GradientInfo& info, std::vector<double>& r)
{
  std::vector<double> temp (3,0);
  
  for (unsigned int i = 0; i < info.xGrads.size(); ++i) {
    temp[0] = r[0]*info.xGrads[i] + r[1]*info.yGrads[i] + r[2]*info.zGrads[i];
    temp[1] = r[3]*info.xGrads[i] + r[4]*info.yGrads[i] + r[5]*info.zGrads[i];
    temp[2] = r[6]*info.xGrads[i] + r[7]*info.yGrads[i] + r[8]*info.zGrads[i];

    info.xGrads[i] = temp[0];
    info.yGrads[i] = temp[1];
    info.zGrads[i] = temp[2];
  }

  return;
}
