/// EnhancedMrHandler.cpp
/**
*/

#include <wx/filename.h>
#include <wx/stdpaths.h>

#include <algorithm>
#include <set>
#include <math.h>

#include "Dictionary.h"
#include "DicomFile.h"
#include "StringConvert.h"
#include "Volume.h"
#include "SeriesHandler.h"
#include "EnhancedMrHandler.h"

using namespace jcs;

///
/**
*/
EnhancedMrHandler::EnhancedMrHandler(const std::string& seriesUid)
: SeriesHandler(seriesUid)
{
}


///
/**
*/
std::string
EnhancedMrHandler::GetImagePositionPatient(DicomFile& dfile, int frame)
{
  return ipps.at(frame);
}


///
/**
*/
int
EnhancedMrHandler::GetRescaleSlopeAndIntercept(DicomFile& dfile, double& slope, double& intercept, int frame) const
{
  // use a private std::vector
  // if it's empty, fill it on the first call
  // or fill it in getvolids

  slope = slopes.at(frame);
  intercept = intercepts.at(frame);

  return 1;
}


///
/**
*/
std::vector<std::string>
EnhancedMrHandler::GetStringInfo()
{
  std::vector<std::string> info = SeriesHandler::GetStringInfo();
  return info;
}


///
/**
*/
SeriesHandler::VolListType 
EnhancedMrHandler::ReadVolIds(DicomFile& file)
{
  std::string imageType;
  file.Find("ImageType", imageType);
  bool isdti = (imageType.find("DIFFUSION") != std::string::npos);
  VolListType v;
  std::string sequence;
  std::vector<std::string> dips;

  file.Find("DimensionIndexSequence", sequence);
  file.FindInSequence(sequence, "DimensionIndexPointer", dips);

  unsigned int n_indices = dips.size();

  sequence.clear();
  std::vector<std::string> fc_sequence;
  std::vector<std::string> po_sequence;
  std::vector<std::string> pp_sequence;
  std::vector<std::string> diff_sequence;
  std::vector<std::string> pm_sequence;
  std::vector<std::string> pvt_sequence;

  // This is a type 2 element. Could probably comment this out?
  if (file.Find("SharedFunctionalGroupsSequence", sequence)) {
    file.FindInSequence(sequence, "PlaneOrientationSequence", po_sequence);
    file.FindInSequence(sequence, "PlanePositionSequence", pp_sequence);
  }

  sequence.clear();

  if (!file.Find("PerFrameFunctionalGroupsSequence", sequence)) {
    wxLogError(_("Unable to find PerFrameFunctionalGroupsSequence"));
    return v;
  }

  if (!file.FindInSequence(sequence, "FrameContentSequence", fc_sequence)) {
    wxLogError(_("Unable to find FrameContentSequence"));
    return v;
  }

  if (po_sequence.size() == 0) {
    if (!file.FindInSequence(sequence, "PlaneOrientationSequence", po_sequence)) {
      wxLogError(_("Unable to find PlaneOrientationSequence"));
      return v;
    }
  }

  if (pp_sequence.size() == 0) {
    if (!file.FindInSequence(sequence, "PlanePositionSequence", pp_sequence)) {
      wxLogError(_("Unable to find PlanePositionSequence"));
      return v;
    }
  }
  file.FindInSequence(sequence, "PixelMeasuresSequence", pm_sequence);

  if (isdti) {
    file.FindInSequence(sequence, "MRDiffusionSequence", diff_sequence);
  }

  int pvt_found = file.FindInSequence(sequence, "PixelValueTransformationSequence", pvt_sequence);

  std::vector<std::string>::iterator fc_it = fc_sequence.begin();
  std::vector<std::string>::iterator po_it = po_sequence.begin();
  std::vector<std::string>::iterator pp_it = pp_sequence.begin();
  std::vector<std::string>::iterator diff_it = diff_sequence.begin();
  std::vector<std::string>::iterator pvt_it = pvt_sequence.begin();

  std::vector<std::vector <double> > rotations;
  std::vector<std::vector <int> > divs(n_indices);
  std::vector<int> inStackPositions;
  std::vector<std::string> bvecs;
  std::vector<std::string> bvals;

  // Assumes pixel spacing same in every frame -- not, strictly speaking,
  // a safe assumption
  std::vector<std::string> vals;
  if (!pm_sequence.empty()) {
    file.FindInSequence(pm_sequence.front(), "PixelSpacing", vals);
    if (!vals.empty()) {
      pixelSpacing = vals.front();
    }
  }

  while (fc_it != fc_sequence.end()) {

    vals.clear();
    if (file.FindInSequence(*fc_it, "DimensionIndexValues", vals)) {
      for (unsigned int i = 0; i < n_indices; ++i) {
        divs.at(i).push_back(jcs::stoi(vals.front(), i));
      }
      vals.clear();
    }

    if (file.FindInSequence(*fc_it, "InStackPositionNumber", vals)) {
      inStackPositions.push_back(jcs::stoi(vals.front()));
      vals.clear();
    }
    else {
      inStackPositions.push_back(0);
    }

    double rescale_val;
    if (pvt_found && file.FindInSequence(*pvt_it, "RescaleIntercept", vals)) {
      intercepts.push_back(jcs::stof(vals.front()));
      vals.clear();
    }
    else {
      if (file.Find("RescaleIntercept", rescale_val)) {
        intercepts.push_back(rescale_val);
      }
      else {
        intercepts.push_back(0);
      }
    }

    if (pvt_found && file.FindInSequence(*pvt_it, "RescaleSlope", vals)) {
      slopes.push_back(jcs::stof(vals.front()));
      vals.clear();
    }
    else if (file.Find("RescaleSlope", rescale_val)) {
      intercepts.push_back(rescale_val);
    }
    else {
      slopes.push_back(1);
    }

    if (file.FindInSequence(*pp_it, "ImagePositionPatient", vals)) {
      ipps.push_back(vals.front());
    }
    else {
      ipps.push_back("0/0/0");
    }
    vals.clear();

    if (!file.FindInSequence(*po_it, "ImageOrientationPatient", vals)) {
      vals.push_back("1/0/0/0/1/0");
    }
    std::vector<double> rotation;
    rotation.resize(9, 0);

    for (int i = 0; i < 6; ++i) {
      double f = jcs::stof(vals.front(), i);
      // round off
      rotation[i] = static_cast<double>(static_cast<int>(f * 100000 + 0.5)) / 100000;
    }

    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];

    rotations.push_back(rotation);

    if (isdti && diff_sequence.size() != 0) {
      vals.clear();
      file.FindInSequence(*diff_it, "DiffusionBValue", vals);
      if (vals.size() != 0) {
        bvals.push_back(vals.front());
      }
      else bvals.push_back("-1");
      vals.clear();
      file.FindInSequence(*diff_it, "DiffusionDirectionality", vals);
      if (vals.front() == "DIRECTIONAL") {
        vals.clear();
        if (file.FindInSequence(*diff_it, "DiffusionGradientDirectionSequence", vals)) {
          std::vector<std::string> dgo;
          if (file.FindInSequence(vals.front(), "DiffusionGradientOrientation", dgo)) {
            bvecs.push_back(dgo.front());
          }
        }
      }
      else {
        bvecs.push_back("0/0/0");
      }
      ++diff_it;
    };

    ++fc_it;
    if (po_sequence.size() == fc_sequence.size()) {
      ++po_it;
    }
    if (pp_sequence.size() == fc_sequence.size()) {
      ++pp_it;
    }
    wxTheApp->Yield();
  }

  std::vector<bool> use_index;
  for (unsigned int i = 0; i < divs.size(); ++i) {
    // Don't use if indices simply reflect inStackPositions
    bool use = divs.at(i) != inStackPositions;
    // or if they just count up from 1 to nslices
    unsigned int j = 0;
    bool match = use;
    while (match && j < divs.at(i).size()) {
      match = (divs.at(i).at(j) == j+1);
      ++j;
    }
    use &= !match;
    use_index.push_back(use);
  }

  for (unsigned int i = 0; i < rotations.size(); ++i) {
    VolId info;
    info.ids.push_back(GetSeriesUid());

    for (unsigned int j = 0; j < divs.size(); ++j) 
    if (use_index.at(j)) {
      info.ids.push_back(itos(divs.at(j).at(i), 3));
    }

    std::vector<std::vector <double> >::iterator pos;
    pos = find(orientations.begin(), orientations.end(), rotations.at(i));

    if (pos != orientations.end()) {
      info.orientNumber = distance(orientations.begin(), pos);
    }
    else {
      info.orientNumber = orientations.size();
      orientations.push_back(rotations.at(i));
    }

    v.push_back(info);
    if (isdti && diff_sequence.size() != 0) {
      bvalMap[info] = bvals.at(i);
      bvecMap[info] = bvecs.at(i);
    }

    wxTheApp->Yield();
  }
  return v;
}


///
/**
*/
GradientInfo 
EnhancedMrHandler::GetGradientInfo()
{
  GradientInfo info;

  std::set<VolId> vols = GetVolIds();
  std::vector<double> r = GetRotationMatrix(*vols.begin());

  for (std::set<VolId>::iterator it = vols.begin(); it != vols.end(); ++it) {
    info.values.push_back(jcs::stof(bvalMap[*it]));
    info.xGrads.push_back(jcs::stof(bvecMap[*it], 0));
    info.yGrads.push_back(jcs::stof(bvecMap[*it], 1));
    info.zGrads.push_back(jcs::stof(bvecMap[*it], 2));
  };

  wxLogWarning(_("Warning: bvecs have NOT been verified as correct for enhanced MR DICOM files. If you would like to help with this, please email jolinda@uoregon.edu."));

  RotateGradInfo(info, r);

  return info;
}


///
/**
*/
bool 
EnhancedMrHandler::IsDti() const 
{
  std::string imageType;
  Find("ImageType", imageType);
  return (imageType.find("DIFFUSION") != std::string::npos);
}


///
/**
*/
std::vector<double> 
EnhancedMrHandler::GetVoxelSize() 
{
  std::vector<double> voxel_size(3, 0);

  if (pixelSpacing.empty()) {
    Find("PixelSpacing", pixelSpacing);
  }
  if (!pixelSpacing.empty()) {
    voxel_size[0] = jcs::stof(pixelSpacing, 0);
    voxel_size[1] = jcs::stof(pixelSpacing, 1);
  }

  Find("SpacingBetweenSlices", voxel_size[2]);
  if (voxel_size[2] == 0) {
    Find("SliceThickness", voxel_size[2]);
  }
  
  return voxel_size;
}
