/// SyngoMosaicHandler.cpp
/**
 */

#include "SyngoMosaicHandler.h"

using namespace jcs;

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


///
/**
 */
SeriesHandler::VolListType
SyngoMosaicHandler::ReadVolIds(DicomFile& file) 
{
  VolListType v = SyngoHandler::ReadVolIds(file);

  std::string description;
  file.Find("SeriesDescription", description);
  if ((description.find("EvaSeries") != std::string::npos) ||
      (description.find("Superimposed Data") != std::string::npos)) {

    std::string str;
    int in;
    file.Find("InstanceNumber", in);
    
    int width = 4;
    str = jcs::itos(in, width);
    v.front().ids.push_back(str);
  }

  return v;
}


///
/**
 */
int
SyngoMosaicHandler::GetRows()
{
  std::string pe_direction;
  //Find("haseEncodingDirection", pe_direction);
  Find("InPlanePhaseEncodingDirection", pe_direction);
  double fov_rows;
  if (pe_direction == "ROW") {
    fov_rows = GetRoFov();
  }
  else {
    fov_rows = GetPhaseFov();
  }

  double pixel_size_y = GetVoxelSize()[1];
  return static_cast<int>(floor(fov_rows/pixel_size_y + 0.5));

}


///
/**
 */
int
SyngoMosaicHandler::GetColumns()
{
  std::string pe_direction;
  //Find("PhaseEncodingDirection", pe_direction);
  Find("InPlanePhaseEncodingDirection", pe_direction);
  double fov_cols;
  if (pe_direction == "COL") {
    fov_cols = GetRoFov();
  }
  else {
    fov_cols = GetPhaseFov();
  }

  double pixel_size_x = GetVoxelSize()[0];
  return static_cast<int>(floor(fov_cols/pixel_size_x + 0.5));
}


///
/**
 */
double
SyngoMosaicHandler::GetPhaseFov() const
{
  double phase_fov = 256;
  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.asSlice[0].dPhaseFOV");
    std::stringstream ss(protocol.substr(pos, 256));
    ss.ignore(256, '=');
    ss >> phase_fov;
  }
  return phase_fov;
}


///
/**
 */
double
SyngoMosaicHandler::GetRoFov() const
{
  double ro_fov = 256;
  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.asSlice[0].dReadoutFOV");
    std::stringstream ss(protocol.substr(pos, 256));
    ss.ignore(256, '=');
    ss >> ro_fov;
  }
  return ro_fov;
}


///
/**
 */
int
SyngoMosaicHandler::GetNumberOfSlices() const
{
  std::string slices;
  int n_slices = 1;

  int err = ReadCSAImageHeader("NumberOfImagesInMosaic", slices);
  if (err == 0) {
    wxLogError(_("Unable to find number of slices"));
  }
  else {
    if (err == -1) {
      wxLogError(_("Number of slices not provided"));
    }
    else {
      n_slices = jcs::stoi(slices);
    }
  }
  
  return n_slices;

}


///
/**
 */ 
std::vector<std::string>
SyngoMosaicHandler::GetStringInfo()
{
  std::vector<std::string> info = SyngoHandler::GetStringInfo();
  info.push_back(std::string("Mosaic rows: ") + jcs::itos(GetRows()));
  info.push_back(std::string("Mosaic columns: ") + jcs::itos(GetColumns()));
  
  std::string a_order;
  switch (GetAcquisitionOrder()) {
  case ASCENDING :
    a_order = "Ascending";
    break;

  case DESCENDING :
    a_order = "Descending";
    break;

  case INTERLEAVED_ODD :
    a_order = "Interleaved Odd";
    break;

  case INTERLEAVED_EVEN :
    a_order = "Interleaved Even";
    break;

  case OTHER :
    a_order = "Other";
    break;

  case UNKNOWN :
  default:
    a_order = "Unknown";
    break;
  }
  info.push_back(std::string("Acquisition order: ") + a_order);

//  DicomFile dicomFile((*mFileMap.begin()).first.c_str());
//  std::vector<std::string> temp;
//  dicomFile.ReadCSAImageHeader("MosaicRefAcqTimes", temp);
//  info.insert(info.end(), temp.begin(), temp.end());

  return info;
}


///
/**
 */
template <class T> void
SyngoMosaicHandler::mGetVolumes(std::map<VolId, Volume<T> >& v)
{ 
  v.clear();
  std::string uid = GetSeriesUid();
  wxString message = _("Reading series ") + uid;  //wxString(uid.c_str(), wxConvLocal);
  if (verbose) {
    wxLogStatus(message);
  }

  int image_columns, image_rows;
  //Find(DT_IMAGECOLUMNS, image_columns);
  Find("Columns", image_columns);
  //Find(DT_IMAGEROWS, image_rows);
  Find("Rows", image_rows);

  FileMapType::const_iterator it = mFileMap.begin();
  FileMapType::const_iterator end = mFileMap.end();

  int n_slices = GetNumberOfSlices();
  int mosaic_rows = GetRows();
  int mosaic_columns = GetColumns();

  std::vector<double> voxel_size = GetVoxelSize();
  std::vector<double> offset(2, 0);
  offset[0] = -0.5 * GetColumns() * voxel_size[0];
  offset[1] = -0.5 * GetRows() * voxel_size[1];

  std::map<VolId, double> minDistMap;

  for (;it != end; it++) {
    v[it->second.front()] = Volume<T>(mosaic_rows, mosaic_columns);
    std::vector<T> data;
    DicomFile d_file(it->first.c_str());

    double slope = 1;
    double intercept = 0;
    if (rescale) {
      GetRescaleSlopeAndIntercept(d_file, slope, intercept);
    }

    d_file.PixelData<T>(data, image_rows * image_columns * sizeof(T)/*, slope, intercept*/);

    std::vector<double> rotation = GetRotationMatrix(it->second.front());

    int number_of_columns = image_columns/mosaic_columns;

    std::string str;
    bool BottomUpMosaic = true;   
    if (ReadCSAImageHeader("ProtocolSliceNumber", str)) {
      if (jcs::stoi(str) != 0) {
        // we'll assume top down and not some weird order 
        BottomUpMosaic = false;
      }
    }
      
    std::string protocol;
    int err = ReadCSASeriesHeader("MrProtocol", protocol);
    if (err == -1) {
      err = ReadCSASeriesHeader("MrPhoenixProtocol", protocol);
    }
      
    if (err <= 0) {
      return;
    }

    double sPositionSag, sPositionCor, sPositionTra, sNormalSag, sNormalCor, sNormalTra, dDistFact;
    for (int tile = 0; tile < n_slices; ++tile) {

      int tile_column = tile % number_of_columns;
      int tile_row = tile / number_of_columns;
      typename std::vector<T>::iterator tile_begin = data.begin() + 
        tile_column * mosaic_columns + 
        tile_row * mosaic_rows * image_columns;

      assert(tile_begin < data.end());

      std::vector<T> slice;
      slice.reserve(mosaic_rows * mosaic_columns);
        
      for (int row_no = 0; row_no < mosaic_rows; ++row_no) {
          
        typename std::vector<T>::iterator row_begin = tile_begin + row_no * image_columns;
        typename std::vector<T>::iterator row_end = row_begin + mosaic_columns;
        assert(row_end <= data.end());
        slice.insert(slice.end(), row_begin, row_end);

      }
        
      // get slice distance
      std::string::size_type pos;

      int lSize = 0;
      pos = protocol.find("sSliceArray.lSize");
      if (pos != std::string::npos) {
        std::stringstream ss(protocol.substr(pos, 256));
        ss.ignore(256, '=');
        ss >> lSize;
      }

      std::vector<double> center(3,0);
        
      // ASL from Siemens apparently has only one sSliceArray.asSlice element.
      if (lSize == n_slices) {
        int slice_number = (BottomUpMosaic) ? tile : (n_slices - tile - 1);
        double value;
        wxString searchStr = wxString::Format(_T("sSliceArray.asSlice[%d].sPosition.dSag"), slice_number);
        pos = protocol.find(searchStr.mb_str(wxConvLocal));
        if (pos != std::string::npos) {
          std::stringstream ss(protocol.substr(pos, 256));
          ss.ignore(256, '=');
          ss >> value;
          center[0] = value;
        }
        searchStr = wxString::Format(_T("sSliceArray.asSlice[%d].sPosition.dCor"), slice_number);
        pos = protocol.find(searchStr.mb_str(wxConvLocal));
        if (pos != std::string::npos) {
          std::stringstream ss(protocol.substr(pos, 256));
          ss.ignore(256, '=');
          ss >> value;
          center[1] = value;
        }
        searchStr = wxString::Format(_T("sSliceArray.asSlice[%d].sPosition.dTra"), slice_number);
        pos = protocol.find(searchStr.mb_str(wxConvLocal));
        if (pos != std::string::npos) {
          std::stringstream ss(protocol.substr(pos, 256));
          ss.ignore(256, '=');
          ss >> value;
          center[2] = value;
        }
      }
      else {  // Special case, some series do not have an element for each slice. Base slice spacing on the first element.
        if (tile == 0) {
          if (d_file.Find("SpacingBetweenSlices", dDistFact) == 0) {
            pos = protocol.find("sSliceArray.asSlice[0].dThickness");
            if (pos != std::string::npos) {
              std::stringstream ss(protocol.substr(pos, 256));
              ss.ignore(256, '=');
              ss >> dDistFact;
              dDistFact /= n_slices;
            }
          }

          pos = protocol.find("sSliceArray.asSlice[0].sPosition.dSag");
          if (pos != std::string::npos) {
            std::stringstream ss(protocol.substr(pos, 256));
            ss.ignore(256, '=');
            ss >> sPositionSag;
          }
          center[0] = sPositionSag;
          pos = protocol.find("sSliceArray.asSlice[0].sNormal.dSag");
          if (pos != std::string::npos) {
            std::stringstream ss(protocol.substr(pos, 256));
            ss.ignore(256, '=');
            ss >> sNormalSag;
          }

          pos = protocol.find("sSliceArray.asSlice[0].sPosition.dCor");
          if (pos != std::string::npos) {
            std::stringstream ss(protocol.substr(pos, 256));
            ss.ignore(256, '=');
            ss >> sPositionCor;
          }
          center[1] = sPositionCor;
          pos = protocol.find("sSliceArray.asSlice[0].sNormal.dCor");
          if (pos != std::string::npos) {
            std::stringstream ss(protocol.substr(pos, 256));
            ss.ignore(256, '=');
            ss >> sNormalCor;
          }

          pos = protocol.find("sSliceArray.asSlice[0].sPosition.dTra");
          if (pos != std::string::npos) {
            std::stringstream ss(protocol.substr(pos, 256));
            ss.ignore(256, '=');
            ss >> sPositionTra;
          }
          center[2] = sPositionTra;
          pos = protocol.find("sSliceArray.asSlice[0].sNormal.dTra");
          if (pos != std::string::npos) {
            std::stringstream ss(protocol.substr(pos, 256));
            ss.ignore(256, '=');
            ss >> sNormalTra;
          }
        }
        else {
          // We assume that center varies along the slice normal.
          center[0] = sPositionSag + sNormalSag * sNormalSag * dDistFact * tile;
          center[1] = sPositionCor + sNormalCor * sNormalCor * dDistFact * tile;
          center[2] = sPositionTra + sNormalTra * sNormalTra * dDistFact * tile;
        }
      }

      double dist = 0;
      for (int i = 0; i < 3; ++i) {
        dist += rotation[i+6] * center.at(i);
      }

      v[it->second.front()].AddSlice(dist, slice, it->first);
        
      if (minDistMap.find(it->second.front()) == minDistMap.end()) {

        // Convert center to ipp
        std::vector<double> ipp(3,0);

        ipp[0] = center[0] + rotation[0] * offset[0] + rotation[3] * offset[1];
        ipp[1] = center[1] + rotation[1] * offset[0] + rotation[4] * offset[1];
        ipp[2] = center[2] + rotation[2] * offset[0] + rotation[5] * offset[1];

        minDistMap[it->second.front()] = dist;
        position[it->second.front()] = ipp;
      }

      if (dist < minDistMap[it->second.front()]) {

        // Convert center to ipp
        std::vector<double> ipp(3,0);

        ipp[0] = center[0] + rotation[0] * offset[0] + rotation[3] * offset[1];
        ipp[1] = center[1] + rotation[1] * offset[0] + rotation[4] * offset[1];
        ipp[2] = center[2] + rotation[2] * offset[0] + rotation[5] * offset[1];

        minDistMap[it->second.front()] = dist;
        position[it->second.front()] = ipp;
      }
    }
    assert(v[it->second.front()].size() == n_slices);
  }
}

