/// NewMetaOutputter.cpp
/**
*/

#include <string>
#include <vector>

#include "SeriesHandler.h"
#include "NewMetaVolume.h"
#include "NewMetaOutputter.h"

using namespace jcs;

///
/**
*/
NewMetaOutputter::NewMetaOutputter() : 
Basic3DOutputter(CreateOptions())
{
  if (rawExtension == _T("")) {
    saveHeaderOnly = true;
  }
  else {
    saveHeaderOnly = false;
  }
  saveHeaderOnly = (rawExtension == _T("")) ? true : false;
}


/// Sets output options, including MetaImage-specific ones.
/**
*/
Options
NewMetaOutputter::CreateOptions()
{
  Options options = Get3DOptions();
  options.pathname = "MetaImage";
  options.stringOptions["header"] = _T("mhd");
  options.stringOptions["raw"] = _T("raw");

  return options;
}


///
/**
  \param file 
  \return NewMetaVolume object
*/
BasicVolumeFormat* 
NewMetaOutputter::GetOutputVolume(const char* file)
{
  return new NewMetaVolume(file, headerExtension.mb_str(wxConvLocal), rawExtension.mb_str(wxConvLocal));
}


///
/**
  \param handler A pointer to a SeriesHandler object.
*/
void
NewMetaOutputter::UpdateOutputForSeries(SeriesHandler* handler)
{
  Basic3DOutputter::UpdateOutputForSeries(handler);
}


/// 
/** Creates a correctly-typed MetaConversion object and calls Convert.
    \param handler Pointer to SeriesHandler object.
    \return 1
*/
int
NewMetaOutputter::ConvertSeries(SeriesHandler* handler)
{
  int bits_allocated, pixel_rep = 0;
  handler->Find("BitsAllocated", bits_allocated);
  handler->Find("PixelRepresentation", pixel_rep);

  switch (bits_allocated + pixel_rep) {

  case 8 : {
      MetaConversion<wxUint8> conversion(this, handler);
      conversion.Convert();
    }
    break;
  case 9 : {
      MetaConversion<wxInt8> conversion(this, handler);
      conversion.Convert();
    }
    break;
  case 16 : {
      MetaConversion<wxUint16> conversion(this, handler);
      conversion.Convert();
    }
    break;
  case 17 : {
      MetaConversion<wxInt16> conversion(this, handler);
      conversion.Convert();
    }
    break;
  case 32 : {
      MetaConversion<wxUint32> conversion(this, handler);
      conversion.Convert();
    }
    break;
  case 33 : {
      MetaConversion<wxInt32> conversion(this, handler);
      conversion.Convert();
    }
    break;
  case 64 : {
      MetaConversion<wxUint64> conversion(this, handler);
      conversion.Convert();
    }
    break;
  case 65 : {
      MetaConversion<wxInt64> conversion(this, handler);
      conversion.Convert();
    }
    break;
    default : {
      wxLogMessage(_("BitsAllocated and PixelRepresentation values (%d, %d) not supported."), bits_allocated, pixel_rep);
    }
  }
  return 1;
}


///
/**
*/
void
NewMetaOutputter::SetSaveHeader(bool value)
{
  saveHeaderOnly = value;
  if (saveHeaderOnly) {
    rawExtension = _T("");
  }
  else {
    rawExtension = _T("raw");
  }
}


///
/**
*/
void
NewMetaOutputter::SetOption(const std::string& name, bool value)
{
  Basic3DOutputter::SetOption(name, value);
  if (name.find("ho") != std::string::npos) {
    SetSaveHeader(value);
  }
}


///
/**
*/
template <class T>
MetaConversion<T>::MetaConversion(Basic3DOutputter* outputter, SeriesHandler* handler)
: Basic3DConversion<T>(outputter, handler)
{
  mHeader = new NewMetaHeader();
}


///
/**
*/
template <class T>
MetaConversion<T>::~MetaConversion()
{
  delete mHeader;
}


///
/**
*/
template <class T> void
MetaConversion<T>::GetHeaderForSeries()
{
  int nVolumes = this->GetNumberOfVolumes();

  int dimensionality = this->mOutputter->GetDimensionality(this->mHandler->GetSeriesUid());
  bool save4D = (dimensionality == 4 && nVolumes > 1);

  if (save4D) { mHeader->nDims = 4; }
  else { mHeader->nDims = 3; }
  mHeader->nDims = save4D ? 4 : 3;

  mHeader->dimSize[0] = this->mHandler->GetColumns();
  mHeader->dimSize[1] = this->mHandler->GetRows();
  mHeader->dimSize[2] = this->mHandler->GetNumberOfSlices();
  // dimSize[2] recalculated in mConvert 
  if (save4D) {
    mHeader->dimSize[3] = nVolumes;
  }

  std::vector<double>voxel_size = this->mHandler->GetVoxelSize();
  mHeader->elementSpacing[0] = voxel_size[0];
  mHeader->elementSpacing[1] = voxel_size[1];
  mHeader->elementSpacing[2] = voxel_size[2];

  // elementSpacing[2] recalculated in mConvert
  if (save4D) {
    mHeader->elementSpacing[3] = this->mHandler->GetVolumeInterval();
  }

  mHeader->byteOrderMSB = this->mHandler->IsBigEndian();

  this->mHandler->Find("SamplesPerPixel", mHeader->numberOfChannels);
  int bits_allocated, pixel_rep = 0;
  this->mHandler->Find("BitsAllocated", bits_allocated);
  this->mHandler->Find("PixelRepresentation", pixel_rep);
  switch (bits_allocated + pixel_rep) {

    // See metaTypes.h for information on actual, machine-specific, sizes.
  case 8 : mHeader->elementType = MET_UCHAR;
    break;
  case 9 : mHeader->elementType = MET_CHAR;
    break;
  case 16 : mHeader->elementType = MET_USHORT;
    break;
  case 17 : mHeader->elementType = MET_SHORT;
    break;
  case 32 : mHeader->elementType = MET_UINT;
    break;
  case 33 : mHeader->elementType = MET_INT;
    break;
  case 64 : mHeader->elementType = MET_ULONG_LONG;
    break;
  case 65 : mHeader->elementType = MET_LONG_LONG;
    break;
  default : mHeader->elementType = MET_USHORT;
  }

  if (this->mOutputter->rescale == 1) {
	mHeader->elementType = MET_FLOAT;
  }

  mHeader->origin[0] = 0;
  mHeader->origin[1] = 0;
  mHeader->origin[2] = 0;

  NewMetaOutputter* out = dynamic_cast<NewMetaOutputter*>(this->mOutputter);
  std::vector<std::string>::iterator it = out->fields.begin();
  std::vector<std::string>::iterator it_end = out->fields.end();
  for (; it != it_end; ++it) {
    std::string value;
    this->mHandler->Find(*it, value);
    mHeader->extraFields.push_back(*it);
    mHeader->extraFields.back().append(" = ");
    mHeader->extraFields.back().append(value);
  }

  // set to default, may change in ConvertmHeaderOnly.
  mHeader->headerSize = -1;

  if (this->mHandler->IsDti()) {
    Basic3DConversion<T>::WriteGradientFiles();
  }
}


///
/**
*/
template <class T> void
MetaConversion<T>::CompleteHeaderForVolume(std::pair<VolId, Volume<T> > volPair)
{
  int n_slices = volPair.second.size();
  mHeader->dimSize[2] = n_slices;

  if (n_slices > 1) {
    mHeader->elementSpacing[2] = volPair.second.GetSpacing();
  }

  std::vector<double> ipp = this->mHandler->GetIppForFirstSlice(volPair.first);
  for (unsigned int i = 0; i < ipp.size(); ++i) {
    mHeader->origin[i] = ipp.at(i);
  }
  mHeader->origin[3] = 0;

  mHeader->orientation.clear();
  mHeader->orientation = this->mHandler->GetRotationMatrix(volPair.first);
  

  wxFileName name = this->mOutputter->GetFileNameFromVolId(volPair.first);

  if (name.GetName() == _T("error")) {
    wxLogError(_T("File name error"));
    return;
  }

  name.SetExt(this->mOutputter->rawExtension.c_str());
  mHeader->elementFile = name.GetFullName().mb_str(wxConvLocal);
}


///
/**
*/
template <class T> void
MetaConversion<T>::Convert()
{
  NewMetaOutputter* out = dynamic_cast<NewMetaOutputter*>(this->mOutputter);
  if (!out->SaveHeaderOnly()) {
    Basic3DConversion<T>::Convert();
    return;
  }

  std::string series_uid = this->mHandler->GetSeriesUid();
  int dimensionality = this->mOutputter->GetDimensionality(series_uid);

  GetHeaderForSeries();

  typedef std::map <VolId, Volume<T> > vMapType;
  vMapType volumes;
  this->mHandler->GetVolumes(volumes);

  if (dimensionality == 4) {
    
    wxFileName file = this->mOutputter->GetFileName(series_uid);
    if (file.GetName() == _T("error")) {
      wxLogError(_T("File name error"));
      return;
    }
    file.SetExt(_T(""));

    BasicVolumeFormat* outputVolume = this->mOutputter->GetOutputVolume(file.GetFullPath());  //.mb_str(wxConvLocal));

    typename vMapType::iterator it = volumes.begin();
    CompleteHeaderForVolume(*it);
    mHeader->elementFile = "LIST";

    mHeader->sourceFileVector.clear();
    while(it != volumes.end()) {

      std::map<double, std::string>::iterator dicom_names = it->second.dicomFiles.begin();
      while (dicom_names != it->second.dicomFiles.end()) {
        mHeader->sourceFileVector.push_back(dicom_names->second);
        ++dicom_names;
      }

      ++it;
      wxTheApp->Yield();
    }
    outputVolume->WriteHeader(GetHeader());

    delete outputVolume;

  }
  else {

    typename vMapType::iterator it = volumes.begin();
    typename vMapType::iterator it_end = volumes.end();

    for (;it != it_end; it++) {

      CompleteHeaderForVolume(*it);
      mHeader->elementFile = "LIST";

      wxFileName file = this->mOutputter->GetFileNameFromVolId(it->first);
      if (file.GetName() == _T("error")) {
        wxLogError(_T("File name error"));
        break;
      }

      file.SetExt(_T(""));
      BasicVolumeFormat* outputVolume = this->mOutputter->GetOutputVolume(file.GetFullPath());  //.mb_str(wxConvLocal));
      
      mHeader->sourceFileVector.clear();
      
      std::map<double, std::string>::iterator dicom_names = it->second.dicomFiles.begin();
      std::map<double, std::string>::iterator dicom_names_end = it->second.dicomFiles.end();
      for (;dicom_names != dicom_names_end; dicom_names++) {
        mHeader->sourceFileVector.push_back(dicom_names->second);
      }

      outputVolume->WriteHeader(GetHeader());

      delete outputVolume;
      wxTheApp->Yield();
    }
  }
}
