/// OutputterBase.cpp
/** Handles naming of output directories and files.
    Handles reading and writing of end-user output options.
*/

#include "OutputterBase.h"

using namespace jcs;

///
/** Retrieves all Options values from the user's configuration file.
*/
void
Options::ReadConfig()
{
#ifndef NO_CONFIG
  wxString oldPath = wxConfig::Get()->GetPath();
  wxConfig::Get()->SetPath("/" + pathname);

  std::map<std::string, bool>::iterator bool_it = boolOptions.begin();
  std::map<std::string, bool>::iterator bool_it_end = boolOptions.end();
  for (; bool_it != bool_it_end; ++bool_it) {
    wxConfig::Get()->Read(bool_it->first, &bool_it->second, bool_it->second);
  }

  std::map<std::string, int>::iterator int_it = intOptions.begin();
  std::map<std::string, int>::iterator int_it_end = intOptions.end();
  for (; int_it != int_it_end; ++int_it) {
    wxConfig::Get()->Read(int_it->first, &int_it->second, int_it->second);
  }

  std::map<std::string, wxString>::iterator string_it = stringOptions.begin();
  std::map<std::string, wxString>::iterator string_it_end = stringOptions.end();
  for (; string_it != string_it_end; ++string_it) {
    wxConfig::Get()->Read(string_it->first, &string_it->second, string_it->second);
  }

  std::map<std::string, double>::iterator double_it = doubleOptions.begin();
  std::map<std::string, double>::iterator double_it_end = doubleOptions.end();
  for (; double_it != double_it_end; ++double_it) {
    wxConfig::Get()->Read(double_it->first, &double_it->second, double_it->second);
  }

  wxConfig::Get()->SetPath(oldPath);
#endif
}


///
/** Stores all Options values to the user's configuration file.
*/
void
Options::WriteConfig()
{
#ifndef NO_CONFIG
  wxString oldPath = wxConfig::Get()->GetPath();
  wxConfig::Get()->SetPath("/" + pathname);

  std::map<std::string, bool>::iterator bool_it = boolOptions.begin();
  std::map<std::string, bool>::iterator bool_it_end = boolOptions.end();
  for (; bool_it != bool_it_end; ++bool_it) {
    wxConfig::Get()->Write(bool_it->first, bool_it->second);
  }

  std::map<std::string, int>::iterator int_it = intOptions.begin();
  std::map<std::string, int>::iterator int_it_end = intOptions.end();
  for (; int_it != int_it_end; ++int_it) {
    wxConfig::Get()->Write(int_it->first, int_it->second);
  }

  std::map<std::string, wxString>::iterator string_it = stringOptions.begin();
  std::map<std::string, wxString>::iterator string_it_end = stringOptions.end();
  for (; string_it != string_it_end; ++string_it) {
    wxConfig::Get()->Write(string_it->first, string_it->second);
  }

  std::map<std::string, double>::iterator double_it = doubleOptions.begin();
  std::map<std::string, double>::iterator double_it_end = doubleOptions.end();
  for (; double_it != double_it_end; ++double_it) {
    wxConfig::Get()->Write(double_it->first, double_it->second);
  }

  wxConfig::Get()->SetPath(oldPath);
#endif
}


///
/** Loads option values from the user's configuration file, if found.
    Populates the 'defaultNameFields' array.
    \param options A reference to an Options object.
*/
OutputterBase::OutputterBase(const Options& options) : mOptions(options)
{
  mOptions.ReadConfig();

  fnFlexFormat = false;

  // These values must match DicomElement names as defined in
  // DicomDictionary.h to enable successful lookups.
  // That is, they must be canonical DICOM element names.
  dfn[PatientName] = "PatientName";
  dfn[PatientId] = "PatientID";
  dfn[SeriesDate] = "SeriesDate";
  dfn[SeriesTime] = "SeriesTime";
  dfn[StudyId] = "StudyID";
  dfn[StudyDescription] = "StudyDescription";
  dfn[SeriesNumber] = "SeriesNumber";
  dfn[SequenceName] = "SequenceName";
  dfn[ProtocolName] = "ProtocolName";
  dfn[SeriesDescription] = "SeriesDescription";
  
  abbr[PatientName] = "PN";
  abbr[PatientId] = "PI";
  abbr[SeriesDate] = "SD";
  abbr[SeriesTime] = "ST";
  abbr[StudyId] = "SI";
  abbr[StudyDescription] = "TD";
  abbr[SeriesNumber] = "SN";
  abbr[SequenceName] = "SQ";
  abbr[ProtocolName] = "PR";
  abbr[SeriesDescription] = "RD";

  defaultNameFields[PatientName] = NameField(dfn[PatientName], mOptions.boolOptions[dfn[PatientName]], abbr[PatientName]);
  defaultNameFields[PatientId] = NameField(dfn[PatientId], mOptions.boolOptions[dfn[PatientId]], abbr[PatientId]);
  defaultNameFields[SeriesDate] = NameField(dfn[SeriesDate], mOptions.boolOptions[dfn[SeriesDate]], abbr[SeriesDate]);
  defaultNameFields[SeriesTime] = NameField(dfn[SeriesTime], mOptions.boolOptions[dfn[SeriesTime]], abbr[SeriesTime]);
  defaultNameFields[StudyId] = NameField(dfn[StudyId], mOptions.boolOptions[dfn[StudyId]], abbr[StudyId]);
  defaultNameFields[StudyDescription] = NameField(dfn[StudyDescription], mOptions.boolOptions[dfn[StudyDescription]], abbr[StudyDescription]);
  defaultNameFields[SeriesNumber] = NameField(dfn[SeriesNumber], mOptions.boolOptions[dfn[SeriesNumber]], abbr[SeriesNumber]);
  defaultNameFields[SequenceName] = NameField(dfn[SequenceName], mOptions.boolOptions[dfn[SequenceName]], abbr[SequenceName]);
  defaultNameFields[ProtocolName] = NameField(dfn[ProtocolName], mOptions.boolOptions[dfn[ProtocolName]], abbr[ProtocolName]);
  defaultNameFields[SeriesDescription] = NameField(dfn[SeriesDescription], mOptions.boolOptions[dfn[SeriesDescription]], abbr[SeriesDescription]);
}


/// The Destructor
/** Gathers option values and writes a configuration file for the user.
*/
OutputterBase::~OutputterBase()
{
  mOptions.boolOptions[dfn[PatientName]] = defaultNameFields[PatientName].value;
  mOptions.boolOptions[dfn[PatientId]] = defaultNameFields[PatientId].value;
  mOptions.boolOptions[dfn[SeriesDate]] = defaultNameFields[SeriesDate].value;
  mOptions.boolOptions[dfn[SeriesTime]] = defaultNameFields[SeriesTime].value;
  mOptions.boolOptions[dfn[StudyId]] = defaultNameFields[StudyId].value;
  mOptions.boolOptions[dfn[StudyDescription]] = defaultNameFields[StudyDescription].value;
  mOptions.boolOptions[dfn[SeriesNumber]] = defaultNameFields[SeriesNumber].value;
  mOptions.boolOptions[dfn[SequenceName]] = defaultNameFields[SequenceName].value;
  mOptions.boolOptions[dfn[ProtocolName]] = defaultNameFields[ProtocolName].value;
  mOptions.boolOptions[dfn[SeriesDescription]] = defaultNameFields[SeriesDescription].value;

  mOptions.WriteConfig();
}


///
/** Create and initialize an Options object with program default values for
    output operations.  Options set here apply to all derivative outputters.
    \return an Options object initialized to program defaults.
*/
Options
OutputterBase::GetBaseOptions()
{
  Options options;

  // Flag whether to save in separate directories.
  options.boolOptions["split_dir"] = true;
  options.boolOptions["split_subj"] = true;

  // Options pertaining to output file naming.
  options.boolOptions["PatientName"] = true;
  options.boolOptions["PatientID"] = false;
  options.boolOptions["SeriesDate"] = true;
  options.boolOptions["SeriesTime"] = false;
  options.boolOptions["StudyID"] = true;
  options.boolOptions["StudyDescription"] = false;
  options.boolOptions["SeriesNumber"] = true;
  options.boolOptions["SequenceName"] = false;
  options.boolOptions["ProtocolName"] = true;
  options.boolOptions["SeriesDescription"] = true;

  return options;
}


///
/** Generate and return a filename prefix according to program rules
    and user selection.
    \param series A pointer to a SeriesHandler
    \return A string to be used as a prefix for output files
*/
std::string
OutputterBase::GenerateDefaultPrefix(SeriesHandler* series)
{
  std::string prefix;

  if (!fnFlexFormat) {
    std::string pn = "";
    bool first = true;
    FieldMap::iterator it = defaultNameFields.begin();
    FieldMap::iterator it_end = defaultNameFields.end();
    for (; it != it_end; ++it) {
      if (it->second.value) {
        std::string str;
        if (series->Find(it->second.name, str)) {
          // If using protocol name AND series description, don't use
          // both if they are the same
          if (it->second.name == dfn[ProtocolName]) {
            pn = str;
          }
          else {
            if (str == pn) {
              continue;
            }
          }
          // Separate filename elements with an underscore character.
          if (!first) {
            prefix.append("_");
          }
          // Any numbers should be padded out to at least 3 digits
          if (jcs::itos(jcs::stoi(str)) == str) {
            str = jcs::itos(jcs::stoi(str), 3);
          }
          prefix.append(str);
          first = false;
        }
      }
    }
    prefix = wxString::From8BitData(prefix.c_str());
  }
  else {
    std::string str;
    FieldMap::iterator it, it_end;
    wxArrayString::iterator arr_it = fnComponents.begin();
    wxArrayString::iterator arr_it_end = fnComponents.end();
    for (; arr_it != arr_it_end; ++arr_it) {
      bool lookup = false;
      it = defaultNameFields.begin();
      it_end = defaultNameFields.end();
      for (; it != it_end; ++it) {
        if (arr_it->IsSameAs(it->second.abbr)) {
          lookup = true;
          if (series->Find(it->second.name, str)) {
            // Any numbers should be padded out to at least 3 digits
            if (jcs::itos(jcs::stoi(str)) == str) {
              str = jcs::itos(jcs::stoi(str), 3);
            }
            prefix.append(wxString::From8BitData(str.c_str()));
          }
          break;
        }
      }
      if (!lookup) {
        prefix.append(*arr_it);
      }
    }
  }

  // If prefix ends up empty, set it to a useful default value.
  if (prefix.empty()) {
    prefix = "output";
  }

  return RemoveInvalidChars(prefix);
}


///
/** Fills in default dirs
    \param name
    \param series
*/
void
OutputterBase::FillInDefaultDirs(ImageFileName& name, SeriesHandler* series)
{
  name.ResetPath();

  if (GetSplitSubj()) {
    // This mess is because subject name in DICOM files is often coded as Unicode.
    // Translation is needed to match filesystem UTF-8.
    std::string subject_dir = wxString::From8BitData(series->seriesInfo.subject_name.c_str()).ToStdString();
    if (subject_dir.empty()) {
      subject_dir = series->seriesInfo.subject_id;
    }
    subject_dir = RemoveInvalidChars(subject_dir);
    if (subject_dir.empty()) {
      subject_dir = "unknown";
    }

    name.AppendDir(subject_dir, subject_dir);
  }

  if (GetSplit()) {
    std::string series_dir = series->seriesInfo.study_number
    + "_" + series->seriesInfo.series_number;

    if (!series->seriesInfo.series_description.empty()) {
      series_dir.append("_");
      series_dir.append(series->seriesInfo.series_description);
    }

    if (!series->seriesInfo.SeriesDate.empty()) {
      series_dir.append("_");
      series_dir.append(series->seriesInfo.SeriesDate);
    }
    series_dir = RemoveInvalidChars(series_dir);
    name.AppendDir(series->GetSeriesUid(), series_dir);
  }
}


///
/** Gets the Image file name.
    \param series_uid
    \param name Name of file to match.
    \return Existing ImageFileName object if found in mOutputList, otherwise a new object.
*/
ImageFileName
OutputterBase::GetImageFileName(const std::string& series_uid, const std::string& name)
{
  ImageFileName retval = ImageFileName();
  OutputList::ListType::iterator pos = mOutputList.fileNameList.lower_bound(series_uid);
  OutputList::ListType::iterator end_pos = mOutputList.fileNameList.end();
  for (; pos != end_pos; ++pos)
  if (pos->second.GetFullName() == name) {
    retval = pos->second;
    break;
  }
  return retval;
}


///
/** Gets the file name for the SeriesUid.
    \param series_uid SeriesUid to act upon.
    \return A wxFileName
*/
wxFileName
OutputterBase::GetFileName(const std::string& series_uid)
{
  wxFileName name(_T("error"));

  OutputList::ListType::iterator pos = mOutputList.fileNameList.lower_bound(series_uid);
  if (pos != mOutputList.fileNameList.end()) {
    name = wxFileName(mOutputList.rootDir + wxFileName::GetPathSeparator() + pos->second.GetFullPath());
  }
  return name;
}


///
/** Changes the file name of the given SeriesUid
    \param series_uid The SeriesUid to act upon.
    \param new_name The new name to bestow.
*/
void
OutputterBase::ChangeFileName(const std::string& series_uid, const std::string& new_name)
{
  OutputList::ListType::iterator pos;
  pos = mOutputList.fileNameList.lower_bound(series_uid);
  while (pos != mOutputList.fileNameList.end() && !pos->first.compare(0, series_uid.size(), series_uid)) {
    if (pos->second.isRenameable) {
      pos->second.SetPrefix(new_name);
    }
    ++pos;
  }
}


///
/** Changes the directory for a vector of SeriesUids
    \param series_uids The vector of SeriesUids to change.
    \param new_name The new directory name
    \param position 
*/
void
OutputterBase::ChangeDirName(const std::vector<std::string>& series_uids, 
const std::string& new_name, int position)
{
  std::vector<std::string>::const_iterator it = series_uids.begin();
  std::vector<std::string>::const_iterator it_end = series_uids.end();
  for (; it != it_end; ++it) {
    OutputList::ListType::iterator pos;
    pos = mOutputList.fileNameList.lower_bound(*it);
    while (pos != mOutputList.fileNameList.end() && !pos->first.compare(0, it->size(), *it)) {
      pos->second.SetDirName(position, new_name);
      ++pos;
    } 
  }
}


/// Sets a boolean option.
/** Two boolean options exist, split by subject ("split_subj"),
    and split by series ("split_dir").
    \param name Name of the option.
    \param value Value of the option.
*/
void
OutputterBase::SetOption(const std::string& name, bool value)
{
  if (name.find("split_dir") != std::string::npos) {
    SetSplit(value);
  }

  if (name.find("split_subj") != std::string::npos) {
    SetSplitSubj(value);
  }
}


/// Seeks an integer override.
/** Discovers whether there is an 'overrides' entry for 'series_uid'. If so, sets 'value'
    to its value and returns true, otherwise returns false.
    \param series_uid String representing a SeriesUid.
    \param seek String representing an Option value.
    \param value Option value is returned in this parameter.
    \return True if an override is found, false otherwise.
*/
bool
OutputterBase::FindIntInOverrides(const std::string& series_uid, const std::string& seek, int& value)
{
  bool retval = false;
  std::map<std::string, Options>::const_iterator pos = overrides.find(series_uid);
  if (pos != overrides.end()) {
    std::map <std::string, int>::const_iterator find_seek = pos->second.intOptions.find(seek);
    if (find_seek != pos->second.intOptions.end()) {
      value = find_seek->second;
      retval = true;
    }
  }
  return retval;
}


/// Seeks a boolean override.
/** Discovers whether there is an 'overrides' entry for 'series_uid'. If so, sets 'value'
    to its value and returns true, otherwise returns false.
    \param series_uid String representing a SeriesUid.
    \param seek String representing an Option value.
    \param value Option value is returned in this parameter.
    \return True if an override is found, false otherwise.
*/
bool
OutputterBase::FindBoolInOverrides(const std::string& series_uid, const std::string& seek, bool& value)
{
  bool retval = false;
  std::map<std::string, Options>::const_iterator pos = overrides.find(series_uid);
  if (pos != overrides.end()) {
    std::map<std::string, bool>::const_iterator find_seek = pos->second.boolOptions.find(seek);
    if (find_seek != pos->second.boolOptions.end()) {
      value = find_seek->second;
      retval = true;
    }
  }
  return retval;
}
