/// NumarisMosaicHandler.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 "NumarisMosaicHandler.h"

using namespace jcs;

///
/**
*/
std::vector<std::string>
NumarisMosaicHandler::GetStringInfo()
{
  std::vector<std::string> info = SeriesHandler::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";
    break;

  case OTHER :
    a_order = "Other";
    break;

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


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


///
/**
*/
SeriesHandler::VolListType
NumarisMosaicHandler::ReadVolIds(DicomFile& file) 
{
  VolListType v = SeriesHandler::ReadVolIds(file);
  std::string str;
  file.Find("InstanceNumber", str);
  v.front().ids.push_back(str);
  return v;
}


///
/**
*/
AoCode
NumarisMosaicHandler::GetAcquisitionOrder() const
{
  Dictionary* Numaris = Numaris_Dictionary::Instance();
  std::string acquisition_order;
  Find(Numaris->Lookup("ORDER_OF_SLICES"), acquisition_order);
  if (acquisition_order == "ASCENDING") { return ASCENDING; }
  if (acquisition_order == "DECREASING") { return DESCENDING; }
  if (acquisition_order == "INTERLEAVED") { return INTERLEAVED_ODD; }
  return OTHER;
}


///
/** Returns the time interval between volumes,
    in this case, RepititionTime.
    \return The ReptitionTime
*/
double
NumarisMosaicHandler::GetVolumeInterval() const
{
  double tr;
  Find("RepetitionTime", tr);
  double retval = tr/1000.0;
  return retval;
}


///
/**
*/
std::vector<double>
NumarisMosaicHandler::GetSliceOrder()
{
  Dictionary* Numaris = Numaris_Dictionary::Instance();
  int n_slices = GetNumberOfSlices();
  std::vector<double> slice_order;
  slice_order.reserve(n_slices);

  double delta_z = GetVoxelSize().back();

  AoCode acquisition_order = GetAcquisitionOrder();

  switch (acquisition_order) {

  case INTERLEAVED_ODD: 
    for (int i = n_slices; i > 0; i-=2)
    slice_order.push_back(delta_z * i);
    for (int i = (n_slices - 1); i > 0; i-=2)
    slice_order.push_back(delta_z * i);
    break;
    
  case DESCENDING: 
    for (int i = 1; i <= n_slices; ++i) 
    slice_order.push_back(i * delta_z);

    break;

  default: 
    for (int i = n_slices; i > 0; --i)
    slice_order.push_back(i * delta_z);
  }

  return slice_order;
}


///
/**
*/
int
NumarisMosaicHandler::GetRows()
{
  Dictionary* Numaris = Numaris_Dictionary::Instance();
  int mosaic_size = 0;
  Find(Numaris->Lookup("BASE_RAW_MATRIX_SIZE"), mosaic_size);
  return mosaic_size;
}


///
/**
*/
int NumarisMosaicHandler::GetColumns()
{
  return GetRows();
}


///
/**
*/
std::vector<double>
NumarisMosaicHandler::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);
  }
  
  // Pixel spacing is reported incorrectly
  // for mosaic files -- must correct
  int mosaic_size = GetRows();
  double image_size;
  //Find(DT_IMAGECOLUMNS, image_size);
  Find("Columns", image_size);
  voxel_size[0] *= image_size/mosaic_size;
  //Find(DT_IMAGEROWS, image_size);
  Find("Rows", image_size);
  voxel_size[1] *= image_size/mosaic_size;
  
  Find("SliceThickness", voxel_size[2]);
  double slice_spacing = 0;
  //Find("SliceSpacing", slice_spacing);
  Find("SpacingBetweenSlices", slice_spacing);
  voxel_size[2] += slice_spacing;

  return voxel_size;
}


///
/**
*/
int
NumarisMosaicHandler::GetNumberOfSlices() const
{
  Dictionary* Numaris = Numaris_Dictionary::Instance();
  int slices = 0;
  Find(Numaris->Lookup("NUMBER_OF_SLICES_CURRENT"), slices);
  return slices;
}


  ///
  /**
  */
  template <class T> void
  NumarisMosaicHandler::mGetVolumes(std::map<VolId, Volume<T> >& v)
  {
    v.clear();
    std::string uid = GetSeriesUid();
    wxString message = _("Reading series ") + 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> slice_order = GetSliceOrder();

    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*/);

      int number_of_columns = image_columns/mosaic_columns;

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

        }
        v[(*it).second.front()].AddSlice(slice_order[tile], slice, (*it).first);
      }
      assert(v[(*it).second.front()].size() == n_slices);
    }
  }
