/// Basic3DOutputter.cpp
/**
*/

#include <wx/filename.h>
#include <wx/log.h>
#include <wx/string.h>

#include <string>

#include "OutputList.h"
#include "Basic3DOutputter.h"
#include "SeriesHandler.h"
#include "HandlerFactory.h"
#include "BasicVolumeFormat.h"

using namespace jcs;


///
/**
   \param SetOfVolIdVectors
   \return A vector of true/false values.
*/
std::vector<bool>
GetValuesToUse(std::set<VolId> SetOfVolIdVectors)
{
  std::set< VolId >::iterator viv = SetOfVolIdVectors.begin();
  std::set< VolId >::iterator viv_end = SetOfVolIdVectors.end();
  typedef std::vector< std::set< std::string > > voviv;
  voviv VectorOfVolIdValues;

  for (unsigned int i = 0; i < viv->ids.size(); ++i) {
    VectorOfVolIdValues.push_back(std::set< std::string > ());
  }

  VolId::IdType::const_iterator vid, vid_end;
  voviv::iterator insert_point;
  for (;viv != viv_end; ++viv) {
    vid = viv->ids.begin();
    vid_end = viv->ids.end();
    for (insert_point = VectorOfVolIdValues.begin(); vid != vid_end; ++vid, ++insert_point) {
      insert_point->insert(*vid);
    }
  }

  std::vector<bool> UseValue;
  voviv::iterator vit = VectorOfVolIdValues.begin();
  voviv::iterator vit_end = VectorOfVolIdValues.end();
  for (; vit != vit_end; ++vit) {
    UseValue.push_back(vit->size() > 1);
  }
  
  return UseValue;
}


/// Constructor
/** Populate class variables from 'options'.
 */
Basic3DOutputter::Basic3DOutputter(const Options& options):
OutputterBase(options)
{
  dim_ = mOptions.intOptions["dim"];
  headerExtension = mOptions.stringOptions["header"];
  rawExtension = mOptions.stringOptions["raw"];
  skip_ = mOptions.intOptions["skip"];
  rescale = mOptions.boolOptions["rescale"];
  bvecs_postfix = "_bvecs";
  bvals_postfix = "_bvals";
  moco_postfix = "_moco";
  info_postfix = "_info";
  txt_ext = "txt";
}


/// The Destructor
/** Retrieves Basic3DOutputter-specific option values for
  saving in user's config file.
*/
Basic3DOutputter::~Basic3DOutputter()
{
  mOptions.intOptions["dim"] = dim_;
  mOptions.stringOptions["header"] = headerExtension;
  mOptions.stringOptions["raw"] = rawExtension;
  mOptions.intOptions["skip"] = skip_;
  mOptions.boolOptions["rescale"] = rescale;
}


///
/** Initialize options with program defaults and return Options object.
  \return An Options object.
*/
Options
Basic3DOutputter::Get3DOptions()
{
  Options options = GetBaseOptions();

  // Dimensionality of output, 3D or 4D.
  options.intOptions["dim"] = 3;

  // Number of volumes to skip.
  options.intOptions["skip"] = 0;

  // Save format, hdr/img or nii.
  options.stringOptions["header"] = _T("hdr");
  options.stringOptions["raw"] = _T("img");

  // Apply rescale slope and intercept to data.
  options.boolOptions["rescale"] = false;

  return options;
}


///
/** Updates list of files to write, handles MoCo, DTI and multi-echo series.
    \param handler A pointer to a SeriesHandler object.
*/
void
Basic3DOutputter::UpdateOutputForSeries(SeriesHandler* handler)
{
  std::string series_uid(handler->GetSeriesUid());
  RemoveSeries(series_uid);
  
  ImageFileName name;
  name.seriesUid = series_uid;
  name.SetPrefix(GenerateDefaultPrefix(handler));

  FillInDefaultDirs(name, handler);

  //std::cout << "1 Basic3DOutputter::UpdateOutputForSeries: " << name.GetFullPath() << std::endl;

  int dimensionality = GetDimensionality(series_uid);

  if (dimensionality == 4) {
    name.SetExt(headerExtension.ToStdString());
    mOutputList.fileNameList.insert(make_pair(series_uid, name));
    if (rawExtension != _T("")) {
      name.SetExt(rawExtension.ToStdString());
      mOutputList.fileNameList.insert(make_pair(series_uid, name));
    }
  }
  else { // dimensionality == 3

    std::set<VolId> vol_ids = handler->GetVolIds();

    std::vector<bool> use_id = GetValuesToUse(vol_ids);
    
    std::set<VolId>::iterator it = vol_ids.begin();

    int skip = GetSkip(series_uid);
    int skipVols = (static_cast<int>(vol_ids.size()) > skip) ? skip : 0;
    for (int i = 0; i < skipVols; ++i, ++it);

    for (;it != vol_ids.end(); ++it) {
      std::string postfix;
      for (unsigned int i = 0; i < it->ids.size(); ++i) {
        if (use_id.at(i) && (!it->ids.at(i).empty())) {
          postfix.append("_" + it->ids.at(i));
        }
      }
      name.SetPostfix(postfix);

      std::string output_file_uid = series_uid + postfix;

      volKeyMap[*it] = output_file_uid;
      name.SetExt(headerExtension.ToStdString());
      mOutputList.fileNameList.insert(make_pair(output_file_uid, name));
      if (rawExtension != _T("")) {
        name.SetExt(rawExtension.ToStdString());
        mOutputList.fileNameList.insert(make_pair(output_file_uid, name));
      }
    }
  }

  //std::cout << "2 Basic3DOutputter::UpdateOutputForSeries: " << name.GetFullPath() << std::endl;
  
  // Register MoCo files.
  if (handler->IsMoCo()) {
    RegisterOutputFile(series_uid, txt_ext, moco_postfix, handler);
  }

  // Register DTI files.
  if (handler->IsDti()) {
    RegisterOutputFile(series_uid, "", bvecs_postfix, handler);
    RegisterOutputFile(series_uid, "", bvals_postfix, handler);
  }

  std::vector<std::string> stringInfo = handler->GetStringInfo();
  RegisterOutputFile(series_uid, txt_ext, info_postfix, handler);
}


/// 
 /**
   \param series_uid 
   \param ext Extension for the file.
   \param postfix Postfix for file name.
   \param handler Pointer to SeriesHandler associated with this file.
*/
void
Basic3DOutputter::RegisterOutputFile(std::string series_uid, std::string ext, std::string postfix, SeriesHandler* handler)
{
  ImageFileName name;
  name.seriesUid = series_uid;
  name.SetPrefix(GenerateDefaultPrefix(handler));
  FillInDefaultDirs(name, handler);
  name.SetExt(ext);
  name.SetPostfix(postfix);

  //std::cout << "Basic3DOutputter::RegisterOutputFile: " << name.GetFullPath() << std::endl;

  mOutputList.fileNameList.insert(make_pair(series_uid + name.GetPostfix(), name));
}


/// 
/**
   \param seriesUid a reference to a SeriesUID std::string
*/
void
Basic3DOutputter::RemoveSeries(const std::string& seriesUid)
{
  std::set<OutputList::ListType::key_type> keys;
  OutputList::ListType::iterator it = mOutputList.fileNameList.begin();
  OutputList::ListType::iterator it_end = mOutputList.fileNameList.end();
  for (;it != it_end; ++it) {
    if (it->second.seriesUid == seriesUid) {
      keys.insert(it->first);
    }
  }

  std::set<OutputList::ListType::key_type>::iterator key_it = keys.begin();
  std::set<OutputList::ListType::key_type>::iterator key_it_end = keys.end();
  for (;key_it != key_it_end; ++key_it) {
    mOutputList.fileNameList.erase(*key_it);
  }
}


/// 
/**
   \param vol_id
   \return wxFileName
*/
wxFileName
Basic3DOutputter::GetFileNameFromVolId(const VolId& vol_id)
{
  // first element in vol id must be series uid!
  if (GetDimensionality(vol_id.ids.front()) == 4) {
    return GetFileName(vol_id.ids.front());
  }

  std::string key = volKeyMap[vol_id];
  OutputList::ListType::iterator pos;
  pos = mOutputList.fileNameList.find(key);


  if (pos == mOutputList.fileNameList.end()) {
    wxLogError(_T("Error finding output file name"));
    for (unsigned int i = 0; i < vol_id.ids.size(); ++i) {
      wxLogError(_T("vol_id.ids[%d]: %s"), i, vol_id.ids[i].c_str());
    }

    wxLogError(_T("Key: %s\n"), key.c_str());

    OutputList::ListType::iterator test_it = mOutputList.fileNameList.begin();
    while (test_it != mOutputList.fileNameList.end() ) {
      wxLogMessage(_T("Output list: %s\t%s"), test_it->first.c_str(), test_it->second.GetFullName().c_str());
      ++test_it;
    }

    return wxFileName(_T("error"));
  }

  wxFileName name(mOutputList.rootDir + wxFileName::GetPathSeparator() + pos->second.GetFullPath());
  
  return name;
}


/// Returns the dimension of the given series.
/**
   \param series_uid a reference to a SeriesUID std::string
   \return Value of dimensionality.
*/
int
Basic3DOutputter::GetDimensionality(const std::string& series_uid)
{
  int dimValue = dim_;
  // FindIntInOverrides replaces dimValue only if override value found.
  FindIntInOverrides(series_uid, "dim", dimValue);
  return dimValue;
}


/// Sets dimensionality override for 'series_uid'.
/**
   \param series_uid
   \param dim
*/
void 
Basic3DOutputter::SetDimensionality(const std::string& series_uid, int dim)
{
  overrides[series_uid].intOptions["dim"] = dim;
}


///
/**
   \param series_uid
   \return Value of skip option.
 */
int
Basic3DOutputter::GetSkip(const std::string& series_uid)
{
  int skipValue = skip_;
  FindIntInOverrides(series_uid, "skip", skipValue);
  return skipValue;
}


///
/**
   \param series_uid
   \param skip Integer value of slices to skip.
 */
void 
Basic3DOutputter::SetSkip(const std::string& series_uid, int skip)
{
  overrides[series_uid].intOptions["skip"] = skip;
}


///
/**
   \param series_uid
   \return True/False value of 'rescale' option.
 */
bool
Basic3DOutputter::GetRescale(const std::string& series_uid)
{
  bool boolValue;
  if (FindBoolInOverrides(series_uid, "rescale", boolValue)) {
    return boolValue;
  }
  return mOptions.boolOptions["rescale"];
}


///
/**
   \param series_uid Reference to a series UID std::string.
   \param value 
*/
void 
Basic3DOutputter::SetRescale(const std::string& series_uid, bool value)
{
  overrides[series_uid].boolOptions["rescale"] = value;
}


///
/** Sets an integer option.
  \param name The name of the option.
  \param value The new value for the option.
*/
void
Basic3DOutputter::SetOption(const std::string& name, int value)
{
  OutputterBase::SetOption(name, value);
  if (name.find("skip") != std::string::npos) {
    SetSkip(value);
  }
  if (name.find("dim") != std::string::npos) {
    SetDimensionality(value);
  }
}


///
/** Sets a boolean option.
  \param name The name of the option.
  \param value The new value of the option.
*/
void
Basic3DOutputter::SetOption(const std::string& name, bool value)
{
  OutputterBase::SetOption(name, value);
  if (name.find("rescale") != std::string::npos) {
    rescale = value;
  }
}
