/// SyngoHandler.cpp
/**
*/

#include "SyngoHandler.h"

using namespace jcs;

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


///
/**
*/
int
SyngoHandler::ReadCSAImageHeader(const std::string& tag, std::string& value) const
{
  int retval = 0;
  if (!mFileMap.empty()) {
    DicomFile dicomFile((*mFileMap.begin()).first.c_str());
    retval = dicomFile.ReadCSAImageHeader(tag, value);
  }
  return retval;
}


///
/**
*/
int
SyngoHandler::ReadCSASeriesHeader(const std::string& tag, std::string& value) const
{
  int retval = 0;
  if (!mFileMap.empty()) {
    DicomFile dicomFile((*mFileMap.begin()).first.c_str());
    retval = dicomFile.ReadCSASeriesHeader(tag, value);
  }
  return retval;
}


///
/** Returns slice duration as calculated from the MosaicRefAcqTimes
    tag.
    \return Average difference between times in MosaicRefAcqTimes.
*/
double
SyngoHandler::GetSliceDuration() const
{
  double duration = 0;
  std::string str, mos_acq, word;
  mos_acq = Find(DicomTag(0x0019, 0x1029));
  std::stringstream stream(mos_acq);
  std::vector<double> acq_times;
  while(getline(stream, word, '/')) {
    acq_times.push_back(jcs::stof(word));
  }
  sort(acq_times.begin(), acq_times.end());
  duration = SeriesHandler::GetMeanVolumeInterval(acq_times);
  //int err = ReadCSAImageHeader("SliceMeasurementDuration", str);
  //if (err > 0) {
  //  duration = jcs::stof(str);
  //}
  //std::cout << "GetSliceDuration: " << str << std::endl;
  return duration;
}


///
/**
*/
AoCode
SyngoHandler::GetAcquisitionOrder() const
{
  AoCode retval = UNKNOWN;
  std::string protocol;
  int err = ReadCSASeriesHeader("MrProtocol", protocol);
  
  if (err == -1) {
    err = ReadCSASeriesHeader("MrPhoenixProtocol", protocol);
  }

  if (err > 0) {
    std::string::size_type pos = protocol.find("sSliceArray.ucMode");
    pos = protocol.find('=', pos);
    pos = protocol.find_first_not_of(' ', pos+1);
    if (pos != std::string::npos) {
      std::string acquisition = protocol.substr(pos, 3);
      if (acquisition == "0x1") {
        return ASCENDING;
      }
      if (acquisition == "0x2") {
        return DESCENDING;
      }
      if (acquisition == "0x4") {
        if (GetNumberOfSlices() % 2 == 0) {
          return INTERLEAVED_EVEN;
        } else {
          return INTERLEAVED_ODD;
        }
      } else {
        return OTHER;
      }
    }
  }
  return UNKNOWN;
}


///
/**
*/
bool
SyngoHandler::IsMoCo() const
{
  if (!mFileMap.empty()) {
    std::string imageType;
    DicomFile dicomFile((*mFileMap.begin()).first.c_str());
    dicomFile.Find("ImageType", imageType);
    if (imageType.find("MOCO") != std::string::npos) {
      return true;
    }
  }
  return false; 
}


///
/** Add more qualifiers to the unique volume id.
    \param file A DicomFile reference.
    \return A VolListType object.
*/
SeriesHandler::VolListType
SyngoHandler::ReadVolIds(DicomFile& file)
{
  VolListType v = SeriesHandler::ReadVolIds(file);

  // needed for DTI
  std::string str;
  file.Find("SequenceName", str);
  std::string::size_type pos = str.find('#');
  if (pos != std::string::npos) {
    int zeros_to_add = 4 + pos - str.length();
    str.insert(pos+1, zeros_to_add, '0');

    pos = str.find('b') + 1;
    if (pos != std::string::npos) {
      std::string::size_type pos2 = str.find_first_not_of("0123456789", pos);
      zeros_to_add = 4 + pos - pos2;
      str.insert(pos, zeros_to_add, '0');
    }
  }
  str = RemoveInvalidChars(str);
  v.front().ids.push_back(str);

  int an;
  file.Find("AcquisitionNumber", an);
  
  int width = 4;
  str = itos(an, width);
  v.front().ids.push_back(str);

  str.clear();  
  file.ReadCSAImageHeader("UsedChannelMask", str);
  v.front().ids.push_back(RemoveInvalidChars(str));

  return v;
}


///
/**
*/
bool
SyngoHandler::IsDti() const
{
  std::string bValue;
  int err = ReadCSAImageHeader("B_value", bValue);
  return (err > 0);

}


///
/**
*/
GradientInfo
SyngoHandler::GetGradientInfo()
{

  GradientInfo info;

  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();

  DicomFile first_file(it->second.c_str());

  std::string version;
  //first_file.Find("SoftwareVersion", version);
  first_file.Find("SoftwareVersions", version);
  if (version.find("B15") != std::string::npos) {
    wxLogWarning(_("Warning: Polarity of the bvecs may be wrong for VB15 data."));
  }
  if (version.find("B13") != std::string::npos) {
    wxLogWarning(_("Warning: bvecs are sometimes wrong for VB13 data, due to a bug in the algorithm by which Siemen's converted the B_matrix to a principle eigendirection.  Use with caution."));
  }

  for (; it != it_end; ++it) {
    std::string bValue;
    std::string directionality;
    std::vector<std::string> gradDir;

    DicomFile file(it->second.c_str());

    int err = file.ReadCSAImageHeader("B_value", bValue);

    if (err > 0) {
      info.values.push_back(jcs::stof(bValue));
    }

    err = file.ReadCSAImageHeader("DiffusionGradientDirection", gradDir);
    if ((err < 0) || (gradDir.size() != 3)) {
      info.xGrads.push_back(0);
      info.yGrads.push_back(0);
      info.zGrads.push_back(0);
    }
    else {
      info.xGrads.push_back(jcs::stof(gradDir.at(0)));
      info.yGrads.push_back(jcs::stof(gradDir.at(1)));
      info.zGrads.push_back(jcs::stof(gradDir.at(2)));
    }
  }

  std::vector<double> r = GetRotationMatrix(volFileMap.begin()->first);
  RotateGradInfo(info, r);
  
  return info;
}


///
/** Calculates time interval between volumes using
    Siemens shadow header element TimeAfterStart.
    \return Time difference between two consecutive volume 
            or 0 on failure retrieving TimeAfterStart.
*/
double
SyngoHandler::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());
    std::string str;
    if (file.ReadCSAImageHeader("TimeAfterStart", str) <= 0) {
      return retval;
    }
    time = jcs::stof(str);
    vtmap[it->second.front()].push_back(time);
  }
  retval = CalculateVolIntVal(vtmap, 3);
  return retval;
}

std::vector<std::string>
SyngoHandler::GetStringInfo()
{
	std::vector<std::string> v = SeriesHandler::GetStringInfo();
  
	float phaseRes;
	Find("PercentSampling", phaseRes);

	float bandwidth = 1;
	Find("PixelBandwidth", bandwidth);
	v.push_back(std::string("PixelBandwidth: " + ftos(bandwidth)));

	std::string scanSequence = Find("ScanningSequence");

	if (scanSequence.find("EP") != std::string::npos) {


		std::string protocol;
		int err = ReadCSASeriesHeader("MrProtocol", protocol);
		if (err == -1) {
			err = ReadCSASeriesHeader("MrPhoenixProtocol", protocol);
		}

		if (err > 0) {

			int phaseEncodingLines, accelFact;

			std::string::size_type pos = protocol.find("sKSpace.lPhaseEncodingLines");
			if (pos != std::string::npos) {
				std::stringstream ss(protocol.substr(pos, 256));
				ss.ignore(256, '=');
				ss >> phaseEncodingLines;
			}
			pos = protocol.find("sPat.lAccelFactPE");
			if (pos != std::string::npos) {      
				std::stringstream ss(protocol.substr(pos, 256));
				ss.ignore(256, '=');
				ss >> accelFact;
			}
			v.push_back(std::string("Acceleration factor: " + itos(accelFact)));

			std::string phaseEncodeDirection = Find("InPlanePhaseEncodingDirection");
			int reconstructedPhaseLines = 1;
			if (phaseEncodeDirection == "COL") reconstructedPhaseLines = GetRows();
			if (phaseEncodeDirection == "ROW") reconstructedPhaseLines = GetColumns();

			DicomElement BandwidthPerPixelPhaseEncode(DicomTag(0x0019,0x1028));
			float bwPerPixPhasEnc = 1;
			float echoSpacing, effectiveEchoSpacing;
			if (Find(BandwidthPerPixelPhaseEncode, bwPerPixPhasEnc)) {
				echoSpacing = 10*accelFact*phaseRes/(phaseEncodingLines * bwPerPixPhasEnc);
				effectiveEchoSpacing = 1000/(reconstructedPhaseLines * bwPerPixPhasEnc);
				v.push_back(std::string("BandwidthPerPixelPhaseEncode: " + ftos(bwPerPixPhasEnc)));
			}
			else {
				float riseTime;
				pos = protocol.find("sProtConsistencyInfo.flRiseTime");
				if (pos != std::string::npos) {
					std::stringstream ss(protocol.substr(pos, 256));
					ss.ignore(256, '=');
					ss >> riseTime;
					echoSpacing = 1000/bandwidth + 0.02*riseTime;
					effectiveEchoSpacing = 100 * phaseEncodingLines * echoSpacing /(reconstructedPhaseLines * accelFact * phaseRes);
				}
			}
	  

			// These two ways should give the same results but are slightly different. Going with this way.
			// Differences should be too small to matter.
			float totalReadoutTime = (reconstructedPhaseLines - 1) * effectiveEchoSpacing;
			float totalRoTimeSpm = 1000/bwPerPixPhasEnc;

			/*
			int echoTrainLength;
			Find("EchoTrainLength", echoTrainLength);
			float totalReadoutTime = (echoTrainLength - 1) * echoSpacing/1000;
			float totalRoTimeSpm = (echoTrainLength) * echoSpacing/1000;
			*/
			v.push_back(std::string("Echo spacing (ms): " + ftos(echoSpacing,2)));
			v.push_back(std::string("Effective echo spacing (ms): " + ftos(effectiveEchoSpacing,2)));
			v.push_back(std::string("Total readout time (FSL definition) (ms): " + ftos(totalReadoutTime, 2)));
			v.push_back(std::string("Total readout time (SPM definition) (ms): " + ftos(totalRoTimeSpm, 2)));
		}
	}
	return v;
}